Merge pull request #2697 from Fredkiss3/feat/modify-private-resource-niceid

feat: edit niceid in private resources
This commit is contained in:
Milo Schwartz
2026-03-29 12:18:21 -07:00
committed by GitHub
3 changed files with 74 additions and 38 deletions

View File

@@ -1,5 +1,4 @@
import { Request, Response, NextFunction } from "express"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { z } from "zod";
import { import {
clientSiteResources, clientSiteResources,
clientSiteResourcesAssociationsCache, clientSiteResourcesAssociationsCache,
@@ -8,19 +7,13 @@ import {
orgs, orgs,
roles, roles,
roleSiteResources, roleSiteResources,
SiteResource,
siteResources,
sites, sites,
Transaction, Transaction,
userSiteResources userSiteResources
} from "@server/db"; } from "@server/db";
import { siteResources, SiteResource } from "@server/db"; import { tierMatrix } from "@server/lib/billing/tierMatrix";
import response from "@server/lib/response";
import HttpCode from "@server/types/HttpCode";
import createHttpError from "http-errors";
import { eq, and, ne } from "drizzle-orm";
import { fromError } from "zod-validation-error";
import logger from "@server/logger";
import { OpenAPITags, registry } from "@server/openApi";
import { updatePeerData, updateTargets } from "@server/routers/client/targets";
import { import {
generateAliasConfig, generateAliasConfig,
generateRemoteSubnets, generateRemoteSubnets,
@@ -28,12 +21,17 @@ import {
isIpInCidr, isIpInCidr,
portRangeStringSchema portRangeStringSchema
} from "@server/lib/ip"; } from "@server/lib/ip";
import { import { rebuildClientAssociationsFromSiteResource } from "@server/lib/rebuildClientAssociations";
getClientSiteResourceAccess, import response from "@server/lib/response";
rebuildClientAssociationsFromSiteResource import logger from "@server/logger";
} from "@server/lib/rebuildClientAssociations"; import { OpenAPITags, registry } from "@server/openApi";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { updatePeerData, updateTargets } from "@server/routers/client/targets";
import { tierMatrix } from "@server/lib/billing/tierMatrix"; import HttpCode from "@server/types/HttpCode";
import { and, eq, ne } from "drizzle-orm";
import { NextFunction, Request, Response } from "express";
import createHttpError from "http-errors";
import { z } from "zod";
import { fromError } from "zod-validation-error";
const updateSiteResourceParamsSchema = z.strictObject({ const updateSiteResourceParamsSchema = z.strictObject({
siteResourceId: z.string().transform(Number).pipe(z.int().positive()) siteResourceId: z.string().transform(Number).pipe(z.int().positive())
@@ -43,7 +41,15 @@ const updateSiteResourceSchema = z
.strictObject({ .strictObject({
name: z.string().min(1).max(255).optional(), name: z.string().min(1).max(255).optional(),
siteId: z.int(), siteId: z.int(),
// niceId: z.string().min(1).max(255).regex(/^[a-zA-Z0-9-]+$/, "niceId can only contain letters, numbers, and dashes").optional(), niceId: z
.string()
.min(1)
.max(255)
.regex(
/^[a-zA-Z0-9-]+$/,
"niceId can only contain letters, numbers, and dashes"
)
.optional(),
// mode: z.enum(["host", "cidr", "port"]).optional(), // mode: z.enum(["host", "cidr", "port"]).optional(),
mode: z.enum(["host", "cidr"]).optional(), mode: z.enum(["host", "cidr"]).optional(),
// protocol: z.enum(["tcp", "udp"]).nullish(), // protocol: z.enum(["tcp", "udp"]).nullish(),
@@ -167,6 +173,7 @@ export async function updateSiteResource(
const { const {
name, name,
siteId, // because it can change siteId, // because it can change
niceId,
mode, mode,
destination, destination,
alias, alias,
@@ -321,7 +328,8 @@ export async function updateSiteResource(
const sshPamSet = const sshPamSet =
isLicensedSshPam && isLicensedSshPam &&
(authDaemonPort !== undefined || authDaemonMode !== undefined) (authDaemonPort !== undefined ||
authDaemonMode !== undefined)
? { ? {
...(authDaemonPort !== undefined && { ...(authDaemonPort !== undefined && {
authDaemonPort authDaemonPort
@@ -334,15 +342,16 @@ export async function updateSiteResource(
[updatedSiteResource] = await trx [updatedSiteResource] = await trx
.update(siteResources) .update(siteResources)
.set({ .set({
name: name, name,
siteId: siteId, siteId,
mode: mode, niceId,
destination: destination, mode,
enabled: enabled, destination,
enabled,
alias: alias && alias.trim() ? alias : null, alias: alias && alias.trim() ? alias : null,
tcpPortRangeString: tcpPortRangeString, tcpPortRangeString,
udpPortRangeString: udpPortRangeString, udpPortRangeString,
disableIcmp: disableIcmp, disableIcmp,
...sshPamSet ...sshPamSet
}) })
.where( .where(
@@ -423,7 +432,8 @@ export async function updateSiteResource(
// Update the site resource // Update the site resource
const sshPamSet = const sshPamSet =
isLicensedSshPam && isLicensedSshPam &&
(authDaemonPort !== undefined || authDaemonMode !== undefined) (authDaemonPort !== undefined ||
authDaemonMode !== undefined)
? { ? {
...(authDaemonPort !== undefined && { ...(authDaemonPort !== undefined && {
authDaemonPort authDaemonPort
@@ -617,10 +627,14 @@ export async function handleMessagingForUpdatedSiteResource(
mergedAllClients mergedAllClients
); );
await updateTargets(newt.newtId, { await updateTargets(
oldTargets: oldTarget ? [oldTarget] : [], newt.newtId,
newTargets: newTarget ? [newTarget] : [] {
}, newt.version); oldTargets: oldTarget ? [oldTarget] : [],
newTargets: newTarget ? [newTarget] : []
},
newt.version
);
} }
const olmJobs: Promise<void>[] = []; const olmJobs: Promise<void>[] = [];

View File

@@ -18,7 +18,7 @@ import { resourceQueries } from "@app/lib/queries";
import { ListSitesResponse } from "@server/routers/site"; import { ListSitesResponse } from "@server/routers/site";
import { useQueryClient } from "@tanstack/react-query"; import { useQueryClient } from "@tanstack/react-query";
import { useTranslations } from "next-intl"; import { useTranslations } from "next-intl";
import { useState } from "react"; import { useState, useTransition } from "react";
import { import {
cleanForFQDN, cleanForFQDN,
InternalResourceForm, InternalResourceForm,
@@ -49,10 +49,9 @@ export default function EditInternalResourceDialog({
const t = useTranslations(); const t = useTranslations();
const api = createApiClient(useEnvContext()); const api = createApiClient(useEnvContext());
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const [isSubmitting, setIsSubmitting] = useState(false); const [isSubmitting, startTransition] = useTransition();
async function handleSubmit(values: InternalResourceFormValues) { async function handleSubmit(values: InternalResourceFormValues) {
setIsSubmitting(true);
try { try {
let data = { ...values }; let data = { ...values };
if (data.mode === "host" && isHostname(data.destination)) { if (data.mode === "host" && isHostname(data.destination)) {
@@ -70,6 +69,7 @@ export default function EditInternalResourceDialog({
name: data.name, name: data.name,
siteId: data.siteId, siteId: data.siteId,
mode: data.mode, mode: data.mode,
niceId: data.niceId,
destination: data.destination, destination: data.destination,
alias: alias:
data.alias && data.alias &&
@@ -127,8 +127,6 @@ export default function EditInternalResourceDialog({
), ),
variant: "destructive" variant: "destructive"
}); });
} finally {
setIsSubmitting(false);
} }
} }
@@ -162,7 +160,9 @@ export default function EditInternalResourceDialog({
orgId={orgId} orgId={orgId}
siteResourceId={resource.id} siteResourceId={resource.id}
formId="edit-internal-resource-form" formId="edit-internal-resource-form"
onSubmit={handleSubmit} onSubmit={(values) =>
startTransition(() => handleSubmit(values))
}
/> />
</CredenzaBody> </CredenzaBody>
<CredenzaFooter> <CredenzaFooter>

View File

@@ -132,6 +132,7 @@ export type InternalResourceData = {
siteName: string; siteName: string;
mode: "host" | "cidr"; mode: "host" | "cidr";
siteId: number; siteId: number;
niceId: string;
destination: string; destination: string;
alias?: string | null; alias?: string | null;
tcpPortRangeString?: string | null; tcpPortRangeString?: string | null;
@@ -149,6 +150,7 @@ export type InternalResourceFormValues = {
mode: "host" | "cidr"; mode: "host" | "cidr";
destination: string; destination: string;
alias?: string | null; alias?: string | null;
niceId?: string;
tcpPortRangeString?: string | null; tcpPortRangeString?: string | null;
udpPortRangeString?: string | null; udpPortRangeString?: string | null;
disableIcmp?: boolean; disableIcmp?: boolean;
@@ -243,6 +245,12 @@ export function InternalResourceForm({
: undefined : undefined
), ),
alias: z.string().nullish(), alias: z.string().nullish(),
niceId: z
.string()
.min(1)
.max(255)
.regex(/^[a-zA-Z0-9-]+$/)
.optional(),
tcpPortRangeString: createPortRangeStringSchema(t), tcpPortRangeString: createPortRangeStringSchema(t),
udpPortRangeString: createPortRangeStringSchema(t), udpPortRangeString: createPortRangeStringSchema(t),
disableIcmp: z.boolean().optional(), disableIcmp: z.boolean().optional(),
@@ -387,6 +395,7 @@ export function InternalResourceForm({
disableIcmp: resource.disableIcmp ?? false, disableIcmp: resource.disableIcmp ?? false,
authDaemonMode: resource.authDaemonMode ?? "site", authDaemonMode: resource.authDaemonMode ?? "site",
authDaemonPort: resource.authDaemonPort ?? null, authDaemonPort: resource.authDaemonPort ?? null,
niceId: resource.niceId,
roles: [], roles: [],
users: [], users: [],
clients: [] clients: []
@@ -548,6 +557,19 @@ export function InternalResourceForm({
</FormItem> </FormItem>
)} )}
/> />
<FormField
control={form.control}
name="niceId"
render={({ field }) => (
<FormItem>
<FormLabel>{t("identifier")}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField <FormField
control={form.control} control={form.control}
name="siteId" name="siteId"