From d7e06161a8ac34bb46b8e5e3c66bfe068cef45f0 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Sat, 6 Dec 2025 20:31:09 -0500 Subject: [PATCH] use niceId for client routes --- server/db/names.ts | 66 +++++++++++++---- server/middlewares/verifyClientAccess.ts | 70 +++++++++++------- server/middlewares/verifyOrgAccess.ts | 6 ++ server/middlewares/verifyResourceAccess.ts | 66 +++++++++++------ server/middlewares/verifySiteAccess.ts | 71 ++++++++++++------- server/routers/client/createClient.ts | 4 ++ server/routers/client/getClient.ts | 61 +++++++++++----- server/routers/client/listClients.ts | 3 +- server/routers/client/updateClient.ts | 34 +++++++-- server/routers/external.ts | 10 +++ .../credentials/page.tsx | 0 .../{[clientId] => [niceId]}/general/page.tsx | 33 +++++++-- .../{[clientId] => [niceId]}/layout.tsx | 10 +-- .../machine/{[clientId] => [niceId]}/page.tsx | 4 +- .../settings/clients/machine/create/page.tsx | 2 +- .../[orgId]/settings/clients/machine/page.tsx | 3 +- .../settings/resources/client/page.tsx | 3 +- src/components/ClientInfoCard.tsx | 40 ++++++----- src/components/ClientResourcesTable.tsx | 49 ++++++------- src/components/MachineClientsTable.tsx | 54 ++------------ src/components/ProxyResourcesTable.tsx | 4 +- src/components/SitesTable.tsx | 4 +- 22 files changed, 375 insertions(+), 222 deletions(-) rename src/app/[orgId]/settings/clients/machine/{[clientId] => [niceId]}/credentials/page.tsx (100%) rename src/app/[orgId]/settings/clients/machine/{[clientId] => [niceId]}/general/page.tsx (77%) rename src/app/[orgId]/settings/clients/machine/{[clientId] => [niceId]}/layout.tsx (82%) rename src/app/[orgId]/settings/clients/machine/{[clientId] => [niceId]}/page.tsx (52%) diff --git a/server/db/names.ts b/server/db/names.ts index b9a0d5df..32b0a393 100644 --- a/server/db/names.ts +++ b/server/db/names.ts @@ -1,6 +1,6 @@ import { join } from "path"; import { readFileSync } from "fs"; -import { db, resources, siteResources } from "@server/db"; +import { clients, db, resources, siteResources } from "@server/db"; import { randomInt } from "crypto"; import { exitNodes, sites } from "@server/db"; import { eq, and } from "drizzle-orm"; @@ -16,6 +16,25 @@ if (!dev) { } export const names = JSON.parse(readFileSync(file, "utf-8")); +export async function getUniqueClientName(orgId: string): Promise { + let loops = 0; + while (true) { + if (loops > 100) { + throw new Error("Could not generate a unique name"); + } + + const name = generateName(); + const count = await db + .select({ niceId: clients.niceId, orgId: clients.orgId }) + .from(clients) + .where(and(eq(clients.niceId, name), eq(clients.orgId, orgId))); + if (count.length === 0) { + return name; + } + loops++; + } +} + export async function getUniqueSiteName(orgId: string): Promise { let loops = 0; while (true) { @@ -47,11 +66,21 @@ export async function getUniqueResourceName(orgId: string): Promise { db .select({ niceId: resources.niceId, orgId: resources.orgId }) .from(resources) - .where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))), + .where( + and(eq(resources.niceId, name), eq(resources.orgId, orgId)) + ), db - .select({ niceId: siteResources.niceId, orgId: siteResources.orgId }) + .select({ + niceId: siteResources.niceId, + orgId: siteResources.orgId + }) .from(siteResources) - .where(and(eq(siteResources.niceId, name), eq(siteResources.orgId, orgId))) + .where( + and( + eq(siteResources.niceId, name), + eq(siteResources.orgId, orgId) + ) + ) ]); if (resourceCount.length === 0 && siteResourceCount.length === 0) { return name; @@ -60,7 +89,9 @@ export async function getUniqueResourceName(orgId: string): Promise { } } -export async function getUniqueSiteResourceName(orgId: string): Promise { +export async function getUniqueSiteResourceName( + orgId: string +): Promise { let loops = 0; while (true) { if (loops > 100) { @@ -72,11 +103,21 @@ export async function getUniqueSiteResourceName(orgId: string): Promise db .select({ niceId: resources.niceId, orgId: resources.orgId }) .from(resources) - .where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))), + .where( + and(eq(resources.niceId, name), eq(resources.orgId, orgId)) + ), db - .select({ niceId: siteResources.niceId, orgId: siteResources.orgId }) + .select({ + niceId: siteResources.niceId, + orgId: siteResources.orgId + }) .from(siteResources) - .where(and(eq(siteResources.niceId, name), eq(siteResources.orgId, orgId))) + .where( + and( + eq(siteResources.niceId, name), + eq(siteResources.orgId, orgId) + ) + ) ]); if (resourceCount.length === 0 && siteResourceCount.length === 0) { return name; @@ -87,9 +128,7 @@ export async function getUniqueSiteResourceName(orgId: string): Promise export async function getUniqueExitNodeEndpointName(): Promise { let loops = 0; - const count = await db - .select() - .from(exitNodes); + const count = await db.select().from(exitNodes); while (true) { if (loops > 100) { throw new Error("Could not generate a unique name"); @@ -108,12 +147,9 @@ export async function getUniqueExitNodeEndpointName(): Promise { } } - export function generateName(): string { const name = ( - names.descriptors[ - randomInt(names.descriptors.length) - ] + + names.descriptors[randomInt(names.descriptors.length)] + "-" + names.animals[randomInt(names.animals.length)] ) diff --git a/server/middlewares/verifyClientAccess.ts b/server/middlewares/verifyClientAccess.ts index ab65ba2a..d2df38a4 100644 --- a/server/middlewares/verifyClientAccess.ts +++ b/server/middlewares/verifyClientAccess.ts @@ -1,10 +1,11 @@ import { Request, Response, NextFunction } from "express"; -import { db } from "@server/db"; +import { Client, db } from "@server/db"; import { userOrgs, clients, roleClients, userClients } from "@server/db"; import { and, eq } from "drizzle-orm"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy"; +import logger from "@server/logger"; export async function verifyClientAccess( req: Request, @@ -12,33 +13,51 @@ export async function verifyClientAccess( next: NextFunction ) { const userId = req.user!.userId; // Assuming you have user information in the request - const clientId = parseInt( - req.params.clientId || req.body.clientId || req.query.clientId - ); - - if (!userId) { - return next( - createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") - ); - } - - if (isNaN(clientId)) { - return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid client ID")); - } + const clientIdStr = + req.params?.clientId || req.body?.clientId || req.query?.clientId; + const niceId = req.params?.niceId || req.body?.niceId || req.query?.niceId; + const orgId = req.params?.orgId || req.body?.orgId || req.query?.orgId; try { - // Get the client - const [client] = await db - .select() - .from(clients) - .where(eq(clients.clientId, clientId)) - .limit(1); + if (!userId) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") + ); + } + + let client: Client | null = null; + + if (niceId && orgId) { + const [clientRes] = await db + .select() + .from(clients) + .where( + and(eq(clients.niceId, niceId), eq(clients.orgId, orgId)) + ) + .limit(1); + client = clientRes; + } else { + const clientId = parseInt(clientIdStr); + if (isNaN(clientId)) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid client ID") + ); + } + + // Get the client + const [clientRes] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .limit(1); + client = clientRes; + } if (!client) { return next( createHttpError( HttpCode.NOT_FOUND, - `Client with ID ${clientId} not found` + `Client with ID ${niceId || clientIdStr} not found` ) ); } @@ -47,12 +66,12 @@ export async function verifyClientAccess( return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - `Client with ID ${clientId} does not have an organization ID` + `Client with ID ${niceId || clientIdStr} does not have an organization ID` ) ); } - if (!req.userOrg) { + if (!req.userOrg || req.userOrg?.orgId !== client.orgId) { // Get user's role ID in the organization const userOrgRole = await db .select() @@ -104,7 +123,7 @@ export async function verifyClientAccess( .from(roleClients) .where( and( - eq(roleClients.clientId, clientId), + eq(roleClients.clientId, client.clientId), eq(roleClients.roleId, userOrgRoleId) ) ) @@ -122,7 +141,7 @@ export async function verifyClientAccess( .where( and( eq(userClients.userId, userId), - eq(userClients.clientId, clientId) + eq(userClients.clientId, client.clientId) ) ) .limit(1); @@ -140,6 +159,7 @@ export async function verifyClientAccess( ) ); } catch (error) { + logger.error("Error verifying client access", error); return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, diff --git a/server/middlewares/verifyOrgAccess.ts b/server/middlewares/verifyOrgAccess.ts index c5224ae5..74976553 100644 --- a/server/middlewares/verifyOrgAccess.ts +++ b/server/middlewares/verifyOrgAccess.ts @@ -27,6 +27,8 @@ export async function verifyOrgAccess( ); } + logger.debug(`Verifying access for user ${userId} to organization ${orgId}`); + try { if (!req.userOrg) { const userOrgRes = await db @@ -68,6 +70,10 @@ export async function verifyOrgAccess( // User has access, attach the user's role to the request for potential future use req.userOrgRoleId = req.userOrg.roleId; req.userOrgId = orgId; + + logger.debug( + `User ${userId} has access to organization ${orgId} with role ${req.userOrg.roleId}` + ); return next(); } catch (e) { return next( diff --git a/server/middlewares/verifyResourceAccess.ts b/server/middlewares/verifyResourceAccess.ts index 9b0763ab..2ae591ee 100644 --- a/server/middlewares/verifyResourceAccess.ts +++ b/server/middlewares/verifyResourceAccess.ts @@ -1,5 +1,5 @@ import { Request, Response, NextFunction } from "express"; -import { db } from "@server/db"; +import { db, Resource } from "@server/db"; import { resources, userOrgs, userResources, roleResources } from "@server/db"; import { and, eq } from "drizzle-orm"; import createHttpError from "http-errors"; @@ -12,36 +12,56 @@ export async function verifyResourceAccess( next: NextFunction ) { const userId = req.user!.userId; - const resourceId = - req.params.resourceId || req.body.resourceId || req.query.resourceId; - - if (!userId) { - return next( - createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") - ); - } + const resourceIdStr = + req.params?.resourceId || req.body?.resourceId || req.query?.resourceId; + const niceId = req.params?.niceId || req.body?.niceId || req.query?.niceId; + const orgId = req.params?.orgId || req.body?.orgId || req.query?.orgId; try { - const resource = await db - .select() - .from(resources) - .where(eq(resources.resourceId, resourceId)) - .limit(1); + if (!userId) { + return next( + createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated") + ); + } - if (resource.length === 0) { + let resource: Resource | null = null; + + if (orgId && niceId) { + const [resourceRes] = await db + .select() + .from(resources) + .where( + and( + eq(resources.niceId, niceId), + eq(resources.orgId, orgId) + ) + ) + .limit(1); + resource = resourceRes; + } else { + const resourceId = parseInt(resourceIdStr); + const [resourceRes] = await db + .select() + .from(resources) + .where(eq(resources.resourceId, resourceId)) + .limit(1); + resource = resourceRes; + } + + if (!resource) { return next( createHttpError( HttpCode.NOT_FOUND, - `Resource with ID ${resourceId} not found` + `Resource with ID ${resourceIdStr || niceId} not found` ) ); } - if (!resource[0].orgId) { + if (!resource.orgId) { return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - `Resource with ID ${resourceId} does not have an organization ID` + `Resource with ID ${resourceIdStr || niceId} does not have an organization ID` ) ); } @@ -53,14 +73,14 @@ export async function verifyResourceAccess( .where( and( eq(userOrgs.userId, userId), - eq(userOrgs.orgId, resource[0].orgId) + eq(userOrgs.orgId, resource.orgId) ) ) .limit(1); req.userOrg = userOrgRole[0]; } - if (!req.userOrg) { + if (!req.userOrg || req.userOrg?.orgId !== resource.orgId) { return next( createHttpError( HttpCode.FORBIDDEN, @@ -89,14 +109,14 @@ export async function verifyResourceAccess( const userOrgRoleId = req.userOrg.roleId; req.userOrgRoleId = userOrgRoleId; - req.userOrgId = resource[0].orgId; + req.userOrgId = resource.orgId; const roleResourceAccess = await db .select() .from(roleResources) .where( and( - eq(roleResources.resourceId, resourceId), + eq(roleResources.resourceId, resource.resourceId), eq(roleResources.roleId, userOrgRoleId) ) ) @@ -112,7 +132,7 @@ export async function verifyResourceAccess( .where( and( eq(userResources.userId, userId), - eq(userResources.resourceId, resourceId) + eq(userResources.resourceId, resource.resourceId) ) ) .limit(1); diff --git a/server/middlewares/verifySiteAccess.ts b/server/middlewares/verifySiteAccess.ts index 06f06a93..175dc565 100644 --- a/server/middlewares/verifySiteAccess.ts +++ b/server/middlewares/verifySiteAccess.ts @@ -1,10 +1,9 @@ import { Request, Response, NextFunction } from "express"; import { db } from "@server/db"; -import { sites, userOrgs, userSites, roleSites, roles } from "@server/db"; +import { sites, Site, userOrgs, userSites, roleSites, roles } from "@server/db"; import { and, eq, or } from "drizzle-orm"; import createHttpError from "http-errors"; import HttpCode from "@server/types/HttpCode"; -import logger from "@server/logger"; import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy"; export async function verifySiteAccess( @@ -13,9 +12,10 @@ export async function verifySiteAccess( next: NextFunction ) { const userId = req.user!.userId; // Assuming you have user information in the request - const siteId = parseInt( - req.params.siteId || req.body.siteId || req.query.siteId - ); + const siteIdStr = + req.params?.siteId || req.body?.siteId || req.query?.siteId; + const niceId = req.params?.niceId || req.body?.niceId || req.query?.niceId; + const orgId = req.params?.orgId || req.body?.orgId || req.query?.orgId; if (!userId) { return next( @@ -23,32 +23,49 @@ export async function verifySiteAccess( ); } - if (isNaN(siteId)) { - return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID")); - } - try { - // Get the site - const site = await db - .select() - .from(sites) - .where(eq(sites.siteId, siteId)) - .limit(1); + let site: Site | null = null; - if (site.length === 0) { + if (niceId && orgId) { + const [siteRes] = await db + .select() + .from(sites) + .where(and(eq(sites.niceId, niceId), eq(sites.orgId, orgId))) + .limit(1); + + site = siteRes; + } else { + const siteId = parseInt(siteIdStr); + if (isNaN(siteId)) { + return next( + createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID") + ); + } + + // Get the site + const [siteRes] = await db + .select() + .from(sites) + .where(eq(sites.siteId, siteId)) + .limit(1); + + site = siteRes; + } + + if (!site) { return next( createHttpError( HttpCode.NOT_FOUND, - `Site with ID ${siteId} not found` + `Site with ID ${siteIdStr || niceId} not found` ) ); } - if (!site[0].orgId) { + if (!site.orgId) { return next( createHttpError( HttpCode.INTERNAL_SERVER_ERROR, - `Site with ID ${siteId} does not have an organization ID` + `Site with ID ${siteIdStr} does not have an organization ID` ) ); } @@ -59,16 +76,13 @@ export async function verifySiteAccess( .select() .from(userOrgs) .where( - and( - eq(userOrgs.userId, userId), - eq(userOrgs.orgId, site[0].orgId) - ) + and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId)) ) .limit(1); req.userOrg = userOrgRole[0]; } - if (!req.userOrg) { + if (!req.userOrg || req.userOrg?.orgId !== site.orgId) { return next( createHttpError( HttpCode.FORBIDDEN, @@ -97,7 +111,7 @@ export async function verifySiteAccess( const userOrgRoleId = req.userOrg.roleId; req.userOrgRoleId = userOrgRoleId; - req.userOrgId = site[0].orgId; + req.userOrgId = site.orgId; // Check role-based site access first const roleSiteAccess = await db @@ -105,7 +119,7 @@ export async function verifySiteAccess( .from(roleSites) .where( and( - eq(roleSites.siteId, siteId), + eq(roleSites.siteId, site.siteId), eq(roleSites.roleId, userOrgRoleId) ) ) @@ -121,7 +135,10 @@ export async function verifySiteAccess( .select() .from(userSites) .where( - and(eq(userSites.userId, userId), eq(userSites.siteId, siteId)) + and( + eq(userSites.userId, userId), + eq(userSites.siteId, site.siteId) + ) ) .limit(1); diff --git a/server/routers/client/createClient.ts b/server/routers/client/createClient.ts index 160006e1..ea3a371d 100644 --- a/server/routers/client/createClient.ts +++ b/server/routers/client/createClient.ts @@ -25,6 +25,7 @@ import { listExitNodes } from "#dynamic/lib/exitNodes"; import { generateId } from "@server/auth/sessions/app"; import { OpenAPITags, registry } from "@server/openApi"; import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations"; +import { getUniqueClientName } from "@server/db/names"; const createClientParamsSchema = z.strictObject({ orgId: z.string() @@ -206,9 +207,12 @@ export async function createClient( ); } + const niceId = await getUniqueClientName(orgId); + [newClient] = await trx .insert(clients) .values({ + niceId, exitNodeId: randomExitNode.exitNodeId, orgId, name, diff --git a/server/routers/client/getClient.ts b/server/routers/client/getClient.ts index bbe320cb..cfb2652b 100644 --- a/server/routers/client/getClient.ts +++ b/server/routers/client/getClient.ts @@ -12,22 +12,34 @@ import { fromError } from "zod-validation-error"; import { OpenAPITags, registry } from "@server/openApi"; const getClientSchema = z.strictObject({ - clientId: z.string().transform(stoi).pipe(z.int().positive()) + clientId: z + .string() + .optional() + .transform(stoi) + .pipe(z.int().positive().optional()) + .optional(), + niceId: z.string().optional(), + orgId: z.string().optional() }); -async function query(clientId: number) { - // Get the client - const [client] = await db - .select() - .from(clients) - .where(and(eq(clients.clientId, clientId))) - .leftJoin(olms, eq(clients.olmId, olms.olmId)) - .limit(1); - - if (!client) { - return null; +async function query(clientId?: number, niceId?: string, orgId?: string) { + if (clientId) { + const [res] = await db + .select() + .from(clients) + .where(eq(clients.clientId, clientId)) + .leftJoin(olms, eq(clients.clientId, olms.clientId)) + .limit(1); + return res; + } else if (niceId && orgId) { + const [res] = await db + .select() + .from(clients) + .where(and(eq(clients.niceId, niceId), eq(clients.orgId, orgId))) + .leftJoin(olms, eq(olms.clientId, olms.clientId)) + .limit(1); + return res; } - return client; } export type GetClientResponse = NonNullable< @@ -36,13 +48,30 @@ export type GetClientResponse = NonNullable< olmId: string | null; }; +registry.registerPath({ + method: "get", + path: "/org/{orgId}/client/{niceId}", + description: + "Get a client by orgId and niceId. NiceId is a readable ID for the site and unique on a per org basis.", + tags: [OpenAPITags.Org, OpenAPITags.Site], + request: { + params: z.object({ + orgId: z.string(), + niceId: z.string() + }) + }, + responses: {} +}); + registry.registerPath({ method: "get", path: "/client/{clientId}", description: "Get a client by its client ID.", tags: [OpenAPITags.Client], request: { - params: getClientSchema + params: z.object({ + clientId: z.number() + }) }, responses: {} }); @@ -66,9 +95,9 @@ export async function getClient( ); } - const { clientId } = parsedParams.data; + const { clientId, niceId, orgId } = parsedParams.data; - const client = await query(clientId); + const client = await query(clientId, niceId, orgId); if (!client) { return next( diff --git a/server/routers/client/listClients.ts b/server/routers/client/listClients.ts index ff1050ef..58144967 100644 --- a/server/routers/client/listClients.ts +++ b/server/routers/client/listClients.ts @@ -128,7 +128,8 @@ function queryClients(orgId: string, accessibleClientIds: number[], filter?: "us olmVersion: olms.version, userId: clients.userId, username: users.username, - userEmail: users.email + userEmail: users.email, + niceId: clients.niceId }) .from(clients) .leftJoin(orgs, eq(clients.orgId, orgs.orgId)) diff --git a/server/routers/client/updateClient.ts b/server/routers/client/updateClient.ts index e2f11a7c..12d0a199 100644 --- a/server/routers/client/updateClient.ts +++ b/server/routers/client/updateClient.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from "express"; import { z } from "zod"; -import { Client, db, exitNodes, olms, sites } from "@server/db"; -import { clients, clientSitesAssociationsCache } from "@server/db"; +import { db } from "@server/db"; +import { clients } from "@server/db"; import response from "@server/lib/response"; import HttpCode from "@server/types/HttpCode"; import createHttpError from "http-errors"; @@ -15,7 +15,8 @@ const updateClientParamsSchema = z.strictObject({ }); const updateClientSchema = z.strictObject({ - name: z.string().min(1).max(255).optional() + name: z.string().min(1).max(255).optional(), + niceId: z.string().min(1).max(255).optional() }); export type UpdateClientBody = z.infer; @@ -54,7 +55,7 @@ export async function updateClient( ); } - const { name } = parsedBody.data; + const { name, niceId } = parsedBody.data; const parsedParams = updateClientParamsSchema.safeParse(req.params); if (!parsedParams.success) { @@ -84,9 +85,32 @@ export async function updateClient( ); } + // if niceId is provided, check if it's already in use by another client + if (niceId) { + const [existingClient] = await db + .select() + .from(clients) + .where( + and( + eq(clients.niceId, niceId), + eq(clients.orgId, clients.orgId) + ) + ) + .limit(1); + + if (existingClient) { + return next( + createHttpError( + HttpCode.CONFLICT, + `A client with niceId "${niceId}" already exists` + ) + ); + } + } + const updatedClient = await db .update(clients) - .set({ name }) + .set({ name, niceId }) .where(eq(clients.clientId, clientId)) .returning(); diff --git a/server/routers/external.ts b/server/routers/external.ts index 188654bc..54e84e2e 100644 --- a/server/routers/external.ts +++ b/server/routers/external.ts @@ -111,6 +111,7 @@ authenticated.get( authenticated.get( "/org/:orgId/site/:niceId", verifyOrgAccess, + verifySiteAccess, verifyUserHasAction(ActionsEnum.getSite), site.getSite ); @@ -149,6 +150,14 @@ authenticated.get( client.getClient ); +authenticated.get( + "/org/:orgId/client/:niceId", + verifyOrgAccess, + verifyClientAccess, + verifyUserHasAction(ActionsEnum.getClient), + client.getClient +); + authenticated.put( "/org/:orgId/client", verifyOrgAccess, @@ -458,6 +467,7 @@ authenticated.get( authenticated.get( "/org/:orgId/resource/:niceId", verifyOrgAccess, + verifyResourceAccess, verifyUserHasAction(ActionsEnum.getResource), resource.getResource ); diff --git a/src/app/[orgId]/settings/clients/machine/[clientId]/credentials/page.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx similarity index 100% rename from src/app/[orgId]/settings/clients/machine/[clientId]/credentials/page.tsx rename to src/app/[orgId]/settings/clients/machine/[niceId]/credentials/page.tsx diff --git a/src/app/[orgId]/settings/clients/machine/[clientId]/general/page.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/general/page.tsx similarity index 77% rename from src/app/[orgId]/settings/clients/machine/[clientId]/general/page.tsx rename to src/app/[orgId]/settings/clients/machine/[niceId]/general/page.tsx index 63d88593..c2ef26e4 100644 --- a/src/app/[orgId]/settings/clients/machine/[clientId]/general/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/[niceId]/general/page.tsx @@ -34,7 +34,8 @@ import { useForm } from "react-hook-form"; import { z } from "zod"; const GeneralFormSchema = z.object({ - name: z.string().nonempty("Name is required") + name: z.string().nonempty("Name is required"), + niceId: z.string().min(1).max(255).optional() }); type GeneralFormValues = z.infer; @@ -49,7 +50,8 @@ export default function GeneralPage() { const form = useForm({ resolver: zodResolver(GeneralFormSchema), defaultValues: { - name: client?.name + name: client?.name, + niceId: client?.niceId || "" }, mode: "onChange" }); @@ -84,10 +86,11 @@ export default function GeneralPage() { try { await api.post(`/client/${client?.clientId}`, { - name: data.name + name: data.name, + niceId: data.niceId }); - updateClient({ name: data.name }); + updateClient({ name: data.name, niceId: data.niceId }); toast({ title: t("clientUpdated"), @@ -139,6 +142,28 @@ export default function GeneralPage() { )} /> + + ( + + + {t("identifier")} + + + + + + + )} + /> diff --git a/src/app/[orgId]/settings/clients/machine/[clientId]/layout.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx similarity index 82% rename from src/app/[orgId]/settings/clients/machine/[clientId]/layout.tsx rename to src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx index 54ddc24c..f8a9610e 100644 --- a/src/app/[orgId]/settings/clients/machine/[clientId]/layout.tsx +++ b/src/app/[orgId]/settings/clients/machine/[niceId]/layout.tsx @@ -4,7 +4,6 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle"; import { internal } from "@app/lib/api"; import { authCookieHeader } from "@app/lib/api/cookies"; import ClientProvider from "@app/providers/ClientProvider"; -import { build } from "@server/build"; import { GetClientResponse } from "@server/routers/client"; import { AxiosResponse } from "axios"; import { getTranslations } from "next-intl/server"; @@ -12,7 +11,7 @@ import { redirect } from "next/navigation"; type SettingsLayoutProps = { children: React.ReactNode; - params: Promise<{ clientId: number | string; orgId: string }>; + params: Promise<{ niceId: number | string; orgId: string }>; }; export default async function SettingsLayout(props: SettingsLayoutProps) { @@ -22,8 +21,9 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { let client = null; try { + console.log("making request to ", `/org/${params.orgId}/client/${params.niceId}`); const res = await internal.get>( - `/client/${params.clientId}`, + `/org/${params.orgId}/client/${params.niceId}`, await authCookieHeader() ); client = res.data.data; @@ -37,11 +37,11 @@ export default async function SettingsLayout(props: SettingsLayoutProps) { const navItems = [ { title: t("general"), - href: `/{orgId}/settings/clients/machine/{clientId}/general` + href: `/{orgId}/settings/clients/machine/{niceId}/general` }, { title: t("credentials"), - href: `/{orgId}/settings/clients/machine/{clientId}/credentials` + href: `/{orgId}/settings/clients/machine/{niceId}/credentials` } ]; diff --git a/src/app/[orgId]/settings/clients/machine/[clientId]/page.tsx b/src/app/[orgId]/settings/clients/machine/[niceId]/page.tsx similarity index 52% rename from src/app/[orgId]/settings/clients/machine/[clientId]/page.tsx rename to src/app/[orgId]/settings/clients/machine/[niceId]/page.tsx index c59f6920..3aa4a2c4 100644 --- a/src/app/[orgId]/settings/clients/machine/[clientId]/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/[niceId]/page.tsx @@ -1,10 +1,10 @@ import { redirect } from "next/navigation"; export default async function ClientPage(props: { - params: Promise<{ orgId: string; clientId: number | string }>; + params: Promise<{ orgId: string; niceId: number | string }>; }) { const params = await props.params; redirect( - `/${params.orgId}/settings/clients/machine/${params.clientId}/general` + `/${params.orgId}/settings/clients/machine/${params.niceId}/general` ); } diff --git a/src/app/[orgId]/settings/clients/machine/create/page.tsx b/src/app/[orgId]/settings/clients/machine/create/page.tsx index a0489f30..efad8ffc 100644 --- a/src/app/[orgId]/settings/clients/machine/create/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/create/page.tsx @@ -276,7 +276,7 @@ export default function Page() { if (res && res.status === 201) { const data = res.data.data; - router.push(`/${orgId}/settings/clients/machine/${data.clientId}`); + router.push(`/${orgId}/settings/clients/machine/${data.niceId}`); } setCreateLoading(false); diff --git a/src/app/[orgId]/settings/clients/machine/page.tsx b/src/app/[orgId]/settings/clients/machine/page.tsx index b450b09f..d55498b1 100644 --- a/src/app/[orgId]/settings/clients/machine/page.tsx +++ b/src/app/[orgId]/settings/clients/machine/page.tsx @@ -56,7 +56,8 @@ export default async function ClientsPage(props: ClientsPageProps) { olmUpdateAvailable: client.olmUpdateAvailable || false, userId: client.userId, username: client.username, - userEmail: client.userEmail + userEmail: client.userEmail, + niceId: client.niceId }; }; diff --git a/src/app/[orgId]/settings/resources/client/page.tsx b/src/app/[orgId]/settings/resources/client/page.tsx index 6688a6ef..49ccb97f 100644 --- a/src/app/[orgId]/settings/resources/client/page.tsx +++ b/src/app/[orgId]/settings/resources/client/page.tsx @@ -66,7 +66,8 @@ export default async function ClientResourcesPage( destination: siteResource.destination, // destinationPort: siteResource.destinationPort, alias: siteResource.alias || null, - siteNiceId: siteResource.siteNiceId + siteNiceId: siteResource.siteNiceId, + niceId: siteResource.niceId }; } ); diff --git a/src/components/ClientInfoCard.tsx b/src/components/ClientInfoCard.tsx index f8d96158..8e7fa5e7 100644 --- a/src/components/ClientInfoCard.tsx +++ b/src/components/ClientInfoCard.tsx @@ -19,25 +19,27 @@ export default function SiteInfoCard({}: ClientInfoCardProps) { return ( - - <> - - {t("status")} - - {client.online ? ( -
-
- {t("online")} -
- ) : ( -
-
- {t("offline")} -
- )} -
-
- + + + {t("identifier")} + {client.niceId} + + + {t("status")} + + {client.online ? ( +
+
+ {t("online")} +
+ ) : ( +
+
+ {t("offline")} +
+ )} +
+
{t("address")} diff --git a/src/components/ClientResourcesTable.tsx b/src/components/ClientResourcesTable.tsx index e813cb57..a5e257c7 100644 --- a/src/components/ClientResourcesTable.tsx +++ b/src/components/ClientResourcesTable.tsx @@ -25,32 +25,6 @@ import EditInternalResourceDialog from "@app/components/EditInternalResourceDial import { orgQueries } from "@app/lib/queries"; import { useQuery } from "@tanstack/react-query"; -export type TargetHealth = { - targetId: number; - ip: string; - port: number; - enabled: boolean; - healthStatus?: "healthy" | "unhealthy" | "unknown"; -}; - -export type ResourceRow = { - id: number; - nice: string | null; - name: string; - orgId: string; - domain: string; - authState: string; - http: boolean; - protocol: string; - proxyPort: number | null; - enabled: boolean; - domainId?: string; - ssl: boolean; - targetHost?: string; - targetPort?: number; - targets?: TargetHealth[]; -}; - export type InternalResourceRow = { id: number; name: string; @@ -66,6 +40,7 @@ export type InternalResourceRow = { destination: string; // destinationPort: number | null; alias: string | null; + niceId: string; }; type ClientResourcesTableProps = { @@ -158,6 +133,28 @@ export default function ClientResourcesTable({ ); } }, + { + id: "niceId", + accessorKey: "niceId", + friendlyName: t("identifier"), + enableHiding: true, + header: ({ column }) => { + return ( + + ); + }, + cell: ({ row }) => { + return {row.original.niceId || "-"}; + } + }, { accessorKey: "siteName", friendlyName: t("site"), diff --git a/src/components/MachineClientsTable.tsx b/src/components/MachineClientsTable.tsx index 4695844e..ea30b52e 100644 --- a/src/components/MachineClientsTable.tsx +++ b/src/components/MachineClientsTable.tsx @@ -40,6 +40,7 @@ export type ClientRow = { userId: string | null; username: string | null; userEmail: string | null; + niceId: string; }; type ClientTableProps = { @@ -66,7 +67,8 @@ export default function MachineClientsTable({ const defaultMachineColumnVisibility = { client: false, subnet: false, - userId: false + userId: false, + niceId: false }; const refreshData = () => { @@ -129,8 +131,8 @@ export default function MachineClientsTable({ } }, { - accessorKey: "userId", - friendlyName: "User", + accessorKey: "niceId", + friendlyName: "Identifier", header: ({ column }) => { return ( ); - }, - cell: ({ row }) => { - const r = row.original; - return r.userId ? ( - - - - ) : ( - "-" - ); } }, - // { - // accessorKey: "siteName", - // header: ({ column }) => { - // return ( - // - // ); - // }, - // cell: ({ row }) => { - // const r = row.original; - // return ( - // - // - // - // ); - // } - // }, { accessorKey: "online", friendlyName: "Connectivity", @@ -369,7 +329,7 @@ export default function MachineClientsTable({ ); diff --git a/src/components/SitesTable.tsx b/src/components/SitesTable.tsx index 03881669..6982f46a 100644 --- a/src/components/SitesTable.tsx +++ b/src/components/SitesTable.tsx @@ -128,7 +128,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { { id: "niceId", accessorKey: "nice", - friendlyName: t("niceId"), + friendlyName: t("identifier"), enableHiding: true, header: ({ column }) => { return ( @@ -138,7 +138,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) { column.toggleSorting(column.getIsSorted() === "asc") } > - {t("niceId")} + {t("identifier")} );