Compare commits

...

8 Commits

Author SHA1 Message Date
miloschwartz 591cb9cdc1 dont use strict query params in list alises 2026-07-10 18:05:16 -04:00
miloschwartz 34b18bdb53 only show approved resources in launcher 2026-07-10 17:24:10 -04:00
miloschwartz 7d475f5e91 filter out pending resources 2026-07-10 17:18:33 -04:00
miloschwartz 39c35fa539 reorganize components nad hooks for consistency 2026-07-10 17:05:45 -04:00
Owen e1bc0b7efd Make sure the enabled gets set to false 2026-07-10 15:36:05 -04:00
Owen 5ef068c8dc Set the resource from the site 2026-07-10 15:31:37 -04:00
Owen 94c01a23a9 Merge branch 'private-resource-enable' into provisioning-resources 2026-07-10 15:17:06 -04:00
Owen cf9a17cc2e Add status filter 2026-07-10 15:04:28 -04:00
28 changed files with 236 additions and 85 deletions
+8 -2
View File
@@ -200,7 +200,10 @@ export const resources = pgTable(
authDaemonMode: varchar("authDaemonMode", { length: 32 })
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123)
authDaemonPort: integer("authDaemonPort").default(22123),
status: varchar("status")
.$type<"pending" | "approved">()
.default("approved")
},
(t) => [
index("idx_resources_fulldomain")
@@ -451,7 +454,10 @@ export const siteResources = pgTable(
onDelete: "set null"
}),
subdomain: varchar("subdomain"),
fullDomain: varchar("fullDomain")
fullDomain: varchar("fullDomain"),
status: varchar("status")
.$type<"pending" | "approved">()
.default("approved")
},
(t) => [index("idx_siteresources_orgid_niceid").on(t.orgId, t.niceId)]
);
+4 -2
View File
@@ -209,7 +209,8 @@ export const resources = sqliteTable("resources", {
authDaemonMode: text("authDaemonMode")
.$type<"site" | "remote" | "native">()
.default("site"),
authDaemonPort: integer("authDaemonPort").default(22123)
authDaemonPort: integer("authDaemonPort").default(22123),
status: text("status").$type<"pending" | "approved">().default("approved")
});
export const labels = sqliteTable("labels", {
@@ -447,7 +448,8 @@ export const siteResources = sqliteTable("siteResources", {
onDelete: "set null"
}),
subdomain: text("subdomain"),
fullDomain: text("fullDomain")
fullDomain: text("fullDomain"),
status: text("status").$type<"pending" | "approved">().default("approved")
});
export const networks = sqliteTable("networks", {
+16 -5
View File
@@ -198,17 +198,19 @@ export async function updatePrivateResources(
}
}
let resourceStatusFromSite: "approved" | "pending" = "approved";
if (siteId && allSites.length === 0) {
// only add if there are not provided sites
// Use the provided siteId directly, but verify it belongs to the org
const [siteSingle] = await trx
.select({ siteId: sites.siteId })
.select({ siteId: sites.siteId, status: sites.status })
.from(sites)
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
.limit(1);
if (siteSingle) {
allSites.push(siteSingle);
}
resourceStatusFromSite = siteSingle.status ?? "approved";
}
if (allSites.length === 0) {
@@ -217,6 +219,13 @@ export async function updatePrivateResources(
);
}
const resourceEnabled =
resourceData.enabled == undefined || resourceData.enabled == null
? true
: resourceStatusFromSite === "pending"
? false
: resourceData.enabled;
if (existingResource) {
let domainInfo:
| { subdomain: string | null; domainId: string }
@@ -240,7 +249,7 @@ export async function updatePrivateResources(
scheme: resourceData.scheme,
destination: resourceData.destination,
destinationPort: resourceData["destination-port"],
enabled: resourceData.enabled ?? true,
enabled: resourceEnabled,
alias: resourceData.alias || null,
disableIcmp:
resourceData["disable-icmp"] ||
@@ -259,7 +268,8 @@ export async function updatePrivateResources(
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
authDaemonMode:
resourceData["auth-daemon"]?.mode || "native",
authDaemonPort: resourceData["auth-daemon"]?.port || 22123
authDaemonPort: resourceData["auth-daemon"]?.port || 22123,
status: resourceStatusFromSite
})
.where(
eq(
@@ -492,7 +502,7 @@ export async function updatePrivateResources(
scheme: resourceData.scheme,
destination: resourceData.destination,
destinationPort: resourceData["destination-port"],
enabled: resourceData.enabled ?? true,
enabled: resourceEnabled,
alias: resourceData.alias || null,
aliasAddress: aliasAddress,
disableIcmp:
@@ -512,7 +522,8 @@ export async function updatePrivateResources(
pamMode: resourceData["auth-daemon"]?.pam || "passthrough",
authDaemonMode:
resourceData["auth-daemon"]?.mode || "native",
authDaemonPort: resourceData["auth-daemon"]?.port || 22123
authDaemonPort: resourceData["auth-daemon"]?.port || 22123,
status: resourceStatusFromSite
})
.returning();
+37 -15
View File
@@ -25,6 +25,7 @@ import {
rolePolicies,
roleResources,
roles,
Site,
sites,
Target,
TargetHealthCheck,
@@ -74,19 +75,40 @@ export async function updatePublicResources(
)) {
const targetsToUpdate: Target[] = [];
const healthchecksToUpdate: TargetHealthCheck[] = [];
let resource: Resource;
let resourceStatusFromSite: "approved" | "pending" = "approved";
let providedSite: Partial<Site> | undefined;
if (siteId) {
// Use the provided siteId directly, but verify it belongs to the org
[providedSite] = await trx
.select({
siteId: sites.siteId,
type: sites.type,
status: sites.status
})
.from(sites)
.where(and(eq(sites.siteId, siteId), eq(sites.orgId, orgId)))
.limit(1);
resourceStatusFromSite = providedSite?.status ?? "approved";
}
async function createTarget( // reusable function to create a target
resourceId: number,
targetData: TargetData
) {
const targetSiteId = targetData.site;
let site;
let site: Partial<Site> | undefined;
if (targetSiteId) {
// Look up site by niceId
[site] = await trx
.select({ siteId: sites.siteId, type: sites.type })
.select({
siteId: sites.siteId,
type: sites.type,
status: sites.status
})
.from(sites)
.where(
and(
@@ -95,15 +117,9 @@ export async function updatePublicResources(
)
)
.limit(1);
} else if (siteId) {
} else if (siteId && providedSite) {
// Use the provided siteId directly, but verify it belongs to the org
[site] = await trx
.select({ siteId: sites.siteId, type: sites.type })
.from(sites)
.where(
and(eq(sites.siteId, siteId), eq(sites.orgId, orgId))
)
.limit(1);
site = providedSite;
} else {
throw new Error(`Target site is required`);
}
@@ -139,7 +155,7 @@ export async function updatePublicResources(
.insert(targets)
.values({
resourceId: resourceId,
siteId: site.siteId,
siteId: site.siteId!,
ip: targetData.hostname,
mode: resourceData.mode as Target["mode"],
method: targetData.method,
@@ -172,7 +188,7 @@ export async function updatePublicResources(
.insert(targetHealthCheck)
.values({
name: `${targetData.hostname}:${targetData.port}`,
siteId: site.siteId,
siteId: site.siteId!,
targetId: newTarget.targetId,
orgId: orgId,
hcEnabled: healthcheckData?.enabled || false,
@@ -230,7 +246,10 @@ export async function updatePublicResources(
const resourceEnabled =
resourceData.enabled == undefined || resourceData.enabled == null
? true
: resourceData.enabled;
: resourceStatusFromSite === "pending"
? false
: resourceData.enabled;
const resourceSsl =
resourceData.ssl == undefined || resourceData.ssl == null
? true
@@ -406,7 +425,8 @@ export async function updatePublicResources(
? (resourceData["proxy-protocol-version"] ??
1)
: 1,
resourcePolicyId: sharedPolicy.resourcePolicyId
resourcePolicyId: sharedPolicy.resourcePolicyId,
status: resourceStatusFromSite
})
.where(
eq(
@@ -590,7 +610,8 @@ export async function updatePublicResources(
authDaemonPort:
resourceData["auth-daemon"]?.port || 22123,
resourcePolicyId: null,
defaultResourcePolicyId: inlinePolicyId
defaultResourcePolicyId: inlinePolicyId,
status: resourceStatusFromSite
})
.where(
eq(
@@ -1131,6 +1152,7 @@ export async function updatePublicResources(
.values({
orgId,
niceId: resourceNiceId,
status: resourceStatusFromSite,
name: resourceData.name || "Unnamed Resource",
mode: resourceData.mode,
proxyPort: ["http", "ssh", "rdp", "vnc"].includes(
@@ -157,7 +157,8 @@ async function resolveAccessibleIdsUncached(
.where(
and(
eq(userResources.userId, userId),
eq(resources.orgId, orgId)
eq(resources.orgId, orgId),
eq(resources.status, "approved")
)
),
userRoleIds.length > 0
@@ -171,7 +172,8 @@ async function resolveAccessibleIdsUncached(
.where(
and(
inArray(roleResources.roleId, userRoleIds),
eq(resources.orgId, orgId)
eq(resources.orgId, orgId),
eq(resources.status, "approved")
)
)
: Promise.resolve([]),
@@ -183,7 +185,11 @@ async function resolveAccessibleIdsUncached(
eq(effectiveResourcePolicyId, userPolicies.resourcePolicyId)
)
.where(
and(eq(userPolicies.userId, userId), eq(resources.orgId, orgId))
and(
eq(userPolicies.userId, userId),
eq(resources.orgId, orgId),
eq(resources.status, "approved")
)
),
userRoleIds.length > 0
? db
@@ -199,21 +205,48 @@ async function resolveAccessibleIdsUncached(
.where(
and(
inArray(rolePolicies.roleId, userRoleIds),
eq(resources.orgId, orgId)
eq(resources.orgId, orgId),
eq(resources.status, "approved")
)
)
: Promise.resolve([]),
db
.select({ siteResourceId: userSiteResources.siteResourceId })
.from(userSiteResources)
.where(eq(userSiteResources.userId, userId)),
.innerJoin(
siteResources,
eq(
userSiteResources.siteResourceId,
siteResources.siteResourceId
)
)
.where(
and(
eq(userSiteResources.userId, userId),
eq(siteResources.orgId, orgId),
eq(siteResources.status, "approved")
)
),
userRoleIds.length > 0
? db
.select({
siteResourceId: roleSiteResources.siteResourceId
})
.from(roleSiteResources)
.where(inArray(roleSiteResources.roleId, userRoleIds))
.innerJoin(
siteResources,
eq(
roleSiteResources.siteResourceId,
siteResources.siteResourceId
)
)
.where(
and(
inArray(roleSiteResources.roleId, userRoleIds),
eq(siteResources.orgId, orgId),
eq(siteResources.status, "approved")
)
)
: Promise.resolve([])
]);
@@ -365,6 +398,7 @@ async function filterPublicResourceIdsByTextSearch(
inArray(resources.resourceId, resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true),
eq(resources.status, "approved"),
textMatch
)
);
@@ -402,6 +436,7 @@ async function filterSiteResourceIdsByTextSearch(
inArray(siteResources.siteResourceId, siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true),
eq(siteResources.status, "approved"),
textMatch
)
);
@@ -503,7 +538,8 @@ async function listSiteGroups(
const publicConditions = [
inArray(resources.resourceId, accessible.resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true)
eq(resources.enabled, true),
eq(resources.status, "approved")
];
if (searchPublic) {
publicConditions.push(searchPublic);
@@ -558,7 +594,8 @@ async function listSiteGroups(
const siteConditions = [
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true)
eq(siteResources.enabled, true),
eq(siteResources.status, "approved")
];
if (searchSite) {
siteConditions.push(searchSite);
@@ -621,7 +658,8 @@ async function listSiteGroups(
const noSitePublicConditions = [
inArray(resources.resourceId, accessible.resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true)
eq(resources.enabled, true),
eq(resources.status, "approved")
];
if (searchPublic) {
noSitePublicConditions.push(searchPublic);
@@ -655,7 +693,8 @@ async function listSiteGroups(
const noSiteSiteConditions = [
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true)
eq(siteResources.enabled, true),
eq(siteResources.status, "approved")
];
if (searchSite) {
noSiteSiteConditions.push(searchSite);
@@ -746,7 +785,8 @@ async function listLabelGroups(
const publicConditions = [
inArray(resources.resourceId, accessible.resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true)
eq(resources.enabled, true),
eq(resources.status, "approved")
];
const searchPublic = buildSearchConditionForPublic(query.query);
if (searchPublic) {
@@ -810,7 +850,8 @@ async function listLabelGroups(
const siteConditions = [
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true)
eq(siteResources.enabled, true),
eq(siteResources.status, "approved")
];
const searchSite = buildSearchConditionForSiteResource(query.query);
if (searchSite) {
@@ -997,6 +1038,7 @@ async function mapPublicResources(
inArray(resources.resourceId, resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true),
eq(resources.status, "approved"),
siteIdFilter != null
? eq(sites.siteId, siteIdFilter)
: undefined
@@ -1088,6 +1130,7 @@ async function mapSiteResources(
inArray(siteResources.siteResourceId, siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true),
eq(siteResources.status, "approved"),
siteIdFilter != null
? eq(sites.siteId, siteIdFilter)
: undefined
@@ -1382,7 +1425,8 @@ async function collectAccessibleSites(
const publicConditions = [
inArray(resources.resourceId, accessible.resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true)
eq(resources.enabled, true),
eq(resources.status, "approved")
];
if (siteNameSearch) {
publicConditions.push(siteNameSearch);
@@ -1422,7 +1466,8 @@ async function collectAccessibleSites(
const siteConditions = [
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true)
eq(siteResources.enabled, true),
eq(siteResources.status, "approved")
];
if (siteNameSearch) {
siteConditions.push(siteNameSearch);
@@ -1476,6 +1521,7 @@ async function collectAccessibleLabels(
inArray(resources.resourceId, accessible.resourceIds),
eq(resources.orgId, orgId),
eq(resources.enabled, true),
eq(resources.status, "approved"),
eq(labels.orgId, orgId)
];
if (labelNameSearch) {
@@ -1511,6 +1557,7 @@ async function collectAccessibleLabels(
inArray(siteResources.siteResourceId, accessible.siteResourceIds),
eq(siteResources.orgId, orgId),
eq(siteResources.enabled, true),
eq(siteResources.status, "approved"),
eq(labels.orgId, orgId)
];
if (labelNameSearch) {
+14
View File
@@ -138,6 +138,15 @@ const listResourcesSchema = z.strictObject({
description:
"When set, only resources that have at least one target on this site are returned"
}),
status: z
.enum(["pending", "approved"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["pending", "approved"],
description: "Filter by resource status"
}),
labels: z
.preprocess((val) => {
if (val === undefined || val === null || val === "") {
@@ -451,6 +460,7 @@ export async function listResources(
sort_by,
order,
siteId,
status,
labels: labelFilter
} = parsedQuery.data;
@@ -660,6 +670,10 @@ export async function listResources(
}
}
if (typeof status !== "undefined") {
conditions.push(eq(resources.status, status));
}
if (siteId != null) {
const resourcesWithSite = db
.select({ resourceId: targets.resourceId })
@@ -45,18 +45,19 @@ function userResourceAliasesCacheKey(
page: number,
pageSize: number,
includeLabels: boolean,
labelFilter: string[]
labelFilter: string[],
status?: "pending" | "approved"
) {
const labelsKey =
labelFilter.length > 0 ? labelFilter.slice().sort().join(",") : "all";
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}`;
return `userResourceAliases:${orgId}:${userId}:${page}:${pageSize}:${includeLabels ? "labels" : "plain"}:${labelsKey}:${status ?? "all"}`;
}
const listUserResourceAliasesParamsSchema = z.strictObject({
orgId: z.string()
});
const listUserResourceAliasesQuerySchema = z.strictObject({
const listUserResourceAliasesQuerySchema = z.object({
pageSize: z.coerce
.number<string>()
.int()
@@ -96,7 +97,16 @@ const listUserResourceAliasesQuerySchema = z.strictObject({
type: "array",
description:
"Filter by resource labels. A resource matches when it has any of the given labels (OR)."
})
}),
status: z
.enum(["pending", "approved"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["pending", "approved"],
description: "Filter by site resource status"
})
});
export type UserResourceAliasItem = {
@@ -130,7 +140,8 @@ export async function listUserResourceAliases(
page,
pageSize,
includeLabels,
labels: labelFilter
labels: labelFilter,
status
} = parsedQuery.data;
const parsedParams = listUserResourceAliasesParamsSchema.safeParse(
@@ -172,7 +183,8 @@ export async function listUserResourceAliases(
page,
pageSize,
includeLabels,
labelFilter ?? []
labelFilter ?? [],
status
);
const cachedData: ListUserResourceAliasesResponse | undefined =
await cache.get(cacheKey);
@@ -257,6 +269,10 @@ export async function listUserResourceAliases(
inArray(siteResources.siteResourceId, accessibleSiteResourceIds)
];
if (typeof status !== "undefined") {
whereConditions.push(eq(siteResources.status, status));
}
if (labelFilter && labelFilter.length > 0) {
whereConditions.push(
inArray(
@@ -86,6 +86,15 @@ const listAllSiteResourcesByOrgQuerySchema = z.strictObject({
description:
"When set, only site resources associated with this site (via network) are returned"
}),
status: z
.enum(["pending", "approved"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["pending", "approved"],
description: "Filter by site resource status"
}),
labels: z
.preprocess((val) => {
if (val === undefined || val === null || val === "") {
@@ -283,6 +292,7 @@ export async function listAllSiteResourcesByOrg(
sort_by,
order,
siteId,
status,
labels: labelFilter
} = parsedQuery.data;
@@ -315,6 +325,10 @@ export async function listAllSiteResourcesByOrg(
conditions.push(eq(siteResources.mode, mode));
}
if (typeof status !== "undefined") {
conditions.push(eq(siteResources.status, status));
}
if (labelFilter && labelFilter.length > 0) {
conditions.push(
inArray(
@@ -47,6 +47,15 @@ const listSiteResourcesQuerySchema = z.strictObject({
enum: ["asc", "desc"],
default: "asc",
description: "Sort order"
}),
status: z
.enum(["pending", "approved"])
.optional()
.catch(undefined)
.openapi({
type: "string",
enum: ["pending", "approved"],
description: "Filter by site resource status"
})
});
@@ -110,7 +119,7 @@ export async function listSiteResources(
}
const { siteId, orgId } = parsedParams.data;
const { limit, offset, sort_by, order } = parsedQuery.data;
const { limit, offset, sort_by, order, status } = parsedQuery.data;
// Verify the site exists and belongs to the org
const site = await db
@@ -124,6 +133,15 @@ export async function listSiteResources(
}
// Get site resources by joining networks to siteResources via siteNetworks
const conditions = [
eq(siteNetworks.siteId, siteId),
eq(siteResources.orgId, orgId)
];
if (typeof status !== "undefined") {
conditions.push(eq(siteResources.status, status));
}
const siteResourcesList = await db
.select()
.from(siteNetworks)
@@ -132,12 +150,7 @@ export async function listSiteResources(
siteResources,
eq(siteResources.networkId, networks.networkId)
)
.where(
and(
eq(siteNetworks.siteId, siteId),
eq(siteResources.orgId, orgId)
)
)
.where(and(...conditions))
.orderBy(
sort_by
? order === "asc"
@@ -29,7 +29,7 @@ import { useQuery, useQueryClient } from "@tanstack/react-query";
import { useTranslations } from "next-intl";
import { useActionState, useEffect } from "react";
import { useForm } from "react-hook-form";
import { PrivateResourceAccessFields } from "../../PrivateResourceAccessFields";
import { PrivateResourceAccessFields } from "@app/components/PrivateResourceAccessFields";
export default function PrivateResourceAccessPage() {
const t = useTranslations();
@@ -20,12 +20,12 @@ import { useTranslations } from "next-intl";
import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { PrivateResourceSitesField } from "../../PrivateResourceSitesField";
import { PrivateResourceCidrDestinationField } from "../../PrivateResourceDestinationFields";
import { PrivateResourcePortRanges } from "../../PrivateResourcePortRanges";
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
import { asAnyControl, asAnySetValue } from "../../formControlUtils";
import { useSaveSiteResource } from "../../useSaveSiteResource";
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
import { PrivateResourceCidrDestinationField } from "@app/components/PrivateResourceDestinationFields";
import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges";
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
import { asAnyControl, asAnySetValue } from "@app/lib/formControlUtils";
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
export default function PrivateResourceCidrPage() {
const t = useTranslations();
@@ -30,7 +30,7 @@ import { useTranslations } from "next-intl";
import { useActionState, useMemo } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { useSaveSiteResource } from "../../useSaveSiteResource";
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
export default function PrivateResourceGeneralPage() {
const t = useTranslations();
@@ -20,16 +20,16 @@ import { useTranslations } from "next-intl";
import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { PrivateResourceSitesField } from "../../PrivateResourceSitesField";
import { PrivateResourceHostDestinationFields } from "../../PrivateResourceDestinationFields";
import { PrivateResourcePortRanges } from "../../PrivateResourcePortRanges";
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
import { PrivateResourceHostDestinationFields } from "@app/components/PrivateResourceDestinationFields";
import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges";
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
import {
asAnyControl,
asAnySetValue,
asAnyWatch
} from "../../formControlUtils";
import { useSaveSiteResource } from "../../useSaveSiteResource";
} from "@app/lib/formControlUtils";
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
export default function PrivateResourceHostPage() {
const t = useTranslations();
@@ -22,15 +22,15 @@ import { useTranslations } from "next-intl";
import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { PrivateResourceSitesField } from "../../PrivateResourceSitesField";
import { PrivateResourceHttpFields } from "../../PrivateResourceHttpFields";
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
import { PrivateResourceHttpFields } from "@app/components/PrivateResourceHttpFields";
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
import {
asAnyControl,
asAnySetValue,
asAnyWatch
} from "../../formControlUtils";
import { useSaveSiteResource } from "../../useSaveSiteResource";
} from "@app/lib/formControlUtils";
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
export default function PrivateResourceHttpPage() {
const t = useTranslations();
@@ -26,15 +26,15 @@ import { useTranslations } from "next-intl";
import { useActionState, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { PrivateResourceSshFields } from "../../PrivateResourceSshFields";
import { buildSelectedSitesForResource } from "../../privateResourceUtils";
import { PrivateResourceSshFields } from "@app/components/PrivateResourceSshFields";
import type { Selectedsite } from "@app/components/site-selector";
import { useSaveSiteResource } from "@app/hooks/useSaveSiteResource";
import {
asAnyControl,
asAnySetValue,
asAnyWatch
} from "../../formControlUtils";
import { useSaveSiteResource } from "../../useSaveSiteResource";
import type { Selectedsite } from "@app/components/site-selector";
} from "@app/lib/formControlUtils";
import { buildSelectedSitesForResource } from "@app/lib/privateResourceUtils";
export default function PrivateResourceSshPage() {
const t = useTranslations();
@@ -50,16 +50,20 @@ import { useParams, useRouter, useSearchParams } from "next/navigation";
import { useEffect, useMemo, useState, useTransition } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { PrivateResourceSitesField } from "../PrivateResourceSitesField";
import { PrivateResourceHttpFields } from "../PrivateResourceHttpFields";
import { PrivateResourceSshFields } from "../PrivateResourceSshFields";
import { PrivateResourcePortRanges } from "../PrivateResourcePortRanges";
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
import { PrivateResourceHttpFields } from "@app/components/PrivateResourceHttpFields";
import { PrivateResourceSshFields } from "@app/components/PrivateResourceSshFields";
import { PrivateResourcePortRanges } from "@app/components/PrivateResourcePortRanges";
import {
PrivateResourceAliasField,
PrivateResourceCidrDestinationField,
PrivateResourceHostDestinationFields
} from "../PrivateResourceDestinationFields";
import { asAnyControl, asAnySetValue, asAnyWatch } from "../formControlUtils";
} from "@app/components/PrivateResourceDestinationFields";
import {
asAnyControl,
asAnySetValue,
asAnyWatch
} from "@app/lib/formControlUtils";
export default function CreatePrivateResourcePage() {
const params = useParams();
@@ -27,6 +27,7 @@ export default async function ClientResourcesPage(
const params = await props.params;
const t = await getTranslations();
const searchParams = new URLSearchParams(await props.searchParams);
searchParams.set("status", "approved");
let siteResources: ListAllSiteResourcesByOrgResponse["siteResources"] = [];
let pagination: ListAllSiteResourcesByOrgResponse["pagination"] = {
@@ -38,6 +38,7 @@ export default async function ProxyResourcesPage(
const params = await props.params;
const t = await getTranslations();
const searchParams = new URLSearchParams(await props.searchParams);
searchParams.set("status", "approved");
let resources: ListResourcesResponse["resources"] = [];
let pagination: ListResourcesResponse["pagination"] = {
@@ -23,7 +23,7 @@ import {
import { ChevronsUpDown } from "lucide-react";
import { useTranslations } from "next-intl";
import type { Control, FieldPath, FieldValues } from "react-hook-form";
import { PrivateResourceMultiSiteRoutingHelp } from "./PrivateResourceMultiSiteRoutingHelp";
import { PrivateResourceMultiSiteRoutingHelp } from "@app/components/PrivateResourceMultiSiteRoutingHelp";
type PrivateResourceSitesFieldProps<T extends FieldValues> = {
control: Control<T>;
@@ -9,10 +9,10 @@ import {
} from "@app/components/Settings";
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
import { SshServerSettingsFields } from "@app/components/SshServerSettingsFields";
import { PrivateResourceAliasField } from "./PrivateResourceDestinationFields";
import { PrivateResourceSitesField } from "./PrivateResourceSitesField";
import { getSshUseMultiSiteTargetForm } from "./privateResourceUtils";
import { PrivateResourceAliasField } from "@app/components/PrivateResourceDestinationFields";
import { PrivateResourceSitesField } from "@app/components/PrivateResourceSitesField";
import { inferSshPamMode } from "@app/lib/privateResourceForm";
import { getSshUseMultiSiteTargetForm } from "@app/lib/privateResourceUtils";
import {
FormControl,
FormField,