mirror of
https://github.com/fosrl/pangolin.git
synced 2026-01-28 22:00:51 +00:00
use niceId for client routes
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
import { join } from "path";
|
import { join } from "path";
|
||||||
import { readFileSync } from "fs";
|
import { readFileSync } from "fs";
|
||||||
import { db, resources, siteResources } from "@server/db";
|
import { clients, db, resources, siteResources } from "@server/db";
|
||||||
import { randomInt } from "crypto";
|
import { randomInt } from "crypto";
|
||||||
import { exitNodes, sites } from "@server/db";
|
import { exitNodes, sites } from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
@@ -16,6 +16,25 @@ if (!dev) {
|
|||||||
}
|
}
|
||||||
export const names = JSON.parse(readFileSync(file, "utf-8"));
|
export const names = JSON.parse(readFileSync(file, "utf-8"));
|
||||||
|
|
||||||
|
export async function getUniqueClientName(orgId: string): Promise<string> {
|
||||||
|
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<string> {
|
export async function getUniqueSiteName(orgId: string): Promise<string> {
|
||||||
let loops = 0;
|
let loops = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
@@ -47,11 +66,21 @@ export async function getUniqueResourceName(orgId: string): Promise<string> {
|
|||||||
db
|
db
|
||||||
.select({ niceId: resources.niceId, orgId: resources.orgId })
|
.select({ niceId: resources.niceId, orgId: resources.orgId })
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))),
|
.where(
|
||||||
|
and(eq(resources.niceId, name), eq(resources.orgId, orgId))
|
||||||
|
),
|
||||||
db
|
db
|
||||||
.select({ niceId: siteResources.niceId, orgId: siteResources.orgId })
|
.select({
|
||||||
|
niceId: siteResources.niceId,
|
||||||
|
orgId: siteResources.orgId
|
||||||
|
})
|
||||||
.from(siteResources)
|
.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) {
|
if (resourceCount.length === 0 && siteResourceCount.length === 0) {
|
||||||
return name;
|
return name;
|
||||||
@@ -60,7 +89,9 @@ export async function getUniqueResourceName(orgId: string): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getUniqueSiteResourceName(orgId: string): Promise<string> {
|
export async function getUniqueSiteResourceName(
|
||||||
|
orgId: string
|
||||||
|
): Promise<string> {
|
||||||
let loops = 0;
|
let loops = 0;
|
||||||
while (true) {
|
while (true) {
|
||||||
if (loops > 100) {
|
if (loops > 100) {
|
||||||
@@ -72,11 +103,21 @@ export async function getUniqueSiteResourceName(orgId: string): Promise<string>
|
|||||||
db
|
db
|
||||||
.select({ niceId: resources.niceId, orgId: resources.orgId })
|
.select({ niceId: resources.niceId, orgId: resources.orgId })
|
||||||
.from(resources)
|
.from(resources)
|
||||||
.where(and(eq(resources.niceId, name), eq(resources.orgId, orgId))),
|
.where(
|
||||||
|
and(eq(resources.niceId, name), eq(resources.orgId, orgId))
|
||||||
|
),
|
||||||
db
|
db
|
||||||
.select({ niceId: siteResources.niceId, orgId: siteResources.orgId })
|
.select({
|
||||||
|
niceId: siteResources.niceId,
|
||||||
|
orgId: siteResources.orgId
|
||||||
|
})
|
||||||
.from(siteResources)
|
.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) {
|
if (resourceCount.length === 0 && siteResourceCount.length === 0) {
|
||||||
return name;
|
return name;
|
||||||
@@ -87,9 +128,7 @@ export async function getUniqueSiteResourceName(orgId: string): Promise<string>
|
|||||||
|
|
||||||
export async function getUniqueExitNodeEndpointName(): Promise<string> {
|
export async function getUniqueExitNodeEndpointName(): Promise<string> {
|
||||||
let loops = 0;
|
let loops = 0;
|
||||||
const count = await db
|
const count = await db.select().from(exitNodes);
|
||||||
.select()
|
|
||||||
.from(exitNodes);
|
|
||||||
while (true) {
|
while (true) {
|
||||||
if (loops > 100) {
|
if (loops > 100) {
|
||||||
throw new Error("Could not generate a unique name");
|
throw new Error("Could not generate a unique name");
|
||||||
@@ -108,12 +147,9 @@ export async function getUniqueExitNodeEndpointName(): Promise<string> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function generateName(): string {
|
export function generateName(): string {
|
||||||
const name = (
|
const name = (
|
||||||
names.descriptors[
|
names.descriptors[randomInt(names.descriptors.length)] +
|
||||||
randomInt(names.descriptors.length)
|
|
||||||
] +
|
|
||||||
"-" +
|
"-" +
|
||||||
names.animals[randomInt(names.animals.length)]
|
names.animals[randomInt(names.animals.length)]
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
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 { userOrgs, clients, roleClients, userClients } from "@server/db";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
|
||||||
export async function verifyClientAccess(
|
export async function verifyClientAccess(
|
||||||
req: Request,
|
req: Request,
|
||||||
@@ -12,33 +13,51 @@ export async function verifyClientAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId; // Assuming you have user information in the request
|
const userId = req.user!.userId; // Assuming you have user information in the request
|
||||||
const clientId = parseInt(
|
const clientIdStr =
|
||||||
req.params.clientId || req.body.clientId || req.query.clientId
|
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;
|
||||||
if (!userId) {
|
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isNaN(clientId)) {
|
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid client ID"));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the client
|
if (!userId) {
|
||||||
const [client] = await db
|
return next(
|
||||||
.select()
|
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
||||||
.from(clients)
|
);
|
||||||
.where(eq(clients.clientId, clientId))
|
}
|
||||||
.limit(1);
|
|
||||||
|
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) {
|
if (!client) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
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
|
// Get user's role ID in the organization
|
||||||
const userOrgRole = await db
|
const userOrgRole = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -104,7 +123,7 @@ export async function verifyClientAccess(
|
|||||||
.from(roleClients)
|
.from(roleClients)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(roleClients.clientId, clientId),
|
eq(roleClients.clientId, client.clientId),
|
||||||
eq(roleClients.roleId, userOrgRoleId)
|
eq(roleClients.roleId, userOrgRoleId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -122,7 +141,7 @@ export async function verifyClientAccess(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(userClients.userId, userId),
|
eq(userClients.userId, userId),
|
||||||
eq(userClients.clientId, clientId)
|
eq(userClients.clientId, client.clientId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
@@ -140,6 +159,7 @@ export async function verifyClientAccess(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
logger.error("Error verifying client access", error);
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
HttpCode.INTERNAL_SERVER_ERROR,
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ export async function verifyOrgAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logger.debug(`Verifying access for user ${userId} to organization ${orgId}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!req.userOrg) {
|
if (!req.userOrg) {
|
||||||
const userOrgRes = await db
|
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
|
// User has access, attach the user's role to the request for potential future use
|
||||||
req.userOrgRoleId = req.userOrg.roleId;
|
req.userOrgRoleId = req.userOrg.roleId;
|
||||||
req.userOrgId = orgId;
|
req.userOrgId = orgId;
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
`User ${userId} has access to organization ${orgId} with role ${req.userOrg.roleId}`
|
||||||
|
);
|
||||||
return next();
|
return next();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
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 { resources, userOrgs, userResources, roleResources } from "@server/db";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -12,36 +12,56 @@ export async function verifyResourceAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId;
|
const userId = req.user!.userId;
|
||||||
const resourceId =
|
const resourceIdStr =
|
||||||
req.params.resourceId || req.body.resourceId || req.query.resourceId;
|
req.params?.resourceId || req.body?.resourceId || req.query?.resourceId;
|
||||||
|
const niceId = req.params?.niceId || req.body?.niceId || req.query?.niceId;
|
||||||
if (!userId) {
|
const orgId = req.params?.orgId || req.body?.orgId || req.query?.orgId;
|
||||||
return next(
|
|
||||||
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const resource = await db
|
if (!userId) {
|
||||||
.select()
|
return next(
|
||||||
.from(resources)
|
createHttpError(HttpCode.UNAUTHORIZED, "User not authenticated")
|
||||||
.where(eq(resources.resourceId, resourceId))
|
);
|
||||||
.limit(1);
|
}
|
||||||
|
|
||||||
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
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(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(userOrgs.userId, userId),
|
eq(userOrgs.userId, userId),
|
||||||
eq(userOrgs.orgId, resource[0].orgId)
|
eq(userOrgs.orgId, resource.orgId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
req.userOrg = userOrgRole[0];
|
req.userOrg = userOrgRole[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.userOrg) {
|
if (!req.userOrg || req.userOrg?.orgId !== resource.orgId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
@@ -89,14 +109,14 @@ export async function verifyResourceAccess(
|
|||||||
|
|
||||||
const userOrgRoleId = req.userOrg.roleId;
|
const userOrgRoleId = req.userOrg.roleId;
|
||||||
req.userOrgRoleId = userOrgRoleId;
|
req.userOrgRoleId = userOrgRoleId;
|
||||||
req.userOrgId = resource[0].orgId;
|
req.userOrgId = resource.orgId;
|
||||||
|
|
||||||
const roleResourceAccess = await db
|
const roleResourceAccess = await db
|
||||||
.select()
|
.select()
|
||||||
.from(roleResources)
|
.from(roleResources)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(roleResources.resourceId, resourceId),
|
eq(roleResources.resourceId, resource.resourceId),
|
||||||
eq(roleResources.roleId, userOrgRoleId)
|
eq(roleResources.roleId, userOrgRoleId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -112,7 +132,7 @@ export async function verifyResourceAccess(
|
|||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(userResources.userId, userId),
|
eq(userResources.userId, userId),
|
||||||
eq(userResources.resourceId, resourceId)
|
eq(userResources.resourceId, resource.resourceId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { db } from "@server/db";
|
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 { and, eq, or } from "drizzle-orm";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import logger from "@server/logger";
|
|
||||||
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
import { checkOrgAccessPolicy } from "#dynamic/lib/checkOrgAccessPolicy";
|
||||||
|
|
||||||
export async function verifySiteAccess(
|
export async function verifySiteAccess(
|
||||||
@@ -13,9 +12,10 @@ export async function verifySiteAccess(
|
|||||||
next: NextFunction
|
next: NextFunction
|
||||||
) {
|
) {
|
||||||
const userId = req.user!.userId; // Assuming you have user information in the request
|
const userId = req.user!.userId; // Assuming you have user information in the request
|
||||||
const siteId = parseInt(
|
const siteIdStr =
|
||||||
req.params.siteId || req.body.siteId || req.query.siteId
|
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) {
|
if (!userId) {
|
||||||
return next(
|
return next(
|
||||||
@@ -23,32 +23,49 @@ export async function verifySiteAccess(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isNaN(siteId)) {
|
|
||||||
return next(createHttpError(HttpCode.BAD_REQUEST, "Invalid site ID"));
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get the site
|
let site: Site | null = null;
|
||||||
const site = await db
|
|
||||||
.select()
|
|
||||||
.from(sites)
|
|
||||||
.where(eq(sites.siteId, siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.NOT_FOUND,
|
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(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.INTERNAL_SERVER_ERROR,
|
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()
|
.select()
|
||||||
.from(userOrgs)
|
.from(userOrgs)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(eq(userOrgs.userId, userId), eq(userOrgs.orgId, orgId))
|
||||||
eq(userOrgs.userId, userId),
|
|
||||||
eq(userOrgs.orgId, site[0].orgId)
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
req.userOrg = userOrgRole[0];
|
req.userOrg = userOrgRole[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!req.userOrg) {
|
if (!req.userOrg || req.userOrg?.orgId !== site.orgId) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
HttpCode.FORBIDDEN,
|
HttpCode.FORBIDDEN,
|
||||||
@@ -97,7 +111,7 @@ export async function verifySiteAccess(
|
|||||||
|
|
||||||
const userOrgRoleId = req.userOrg.roleId;
|
const userOrgRoleId = req.userOrg.roleId;
|
||||||
req.userOrgRoleId = userOrgRoleId;
|
req.userOrgRoleId = userOrgRoleId;
|
||||||
req.userOrgId = site[0].orgId;
|
req.userOrgId = site.orgId;
|
||||||
|
|
||||||
// Check role-based site access first
|
// Check role-based site access first
|
||||||
const roleSiteAccess = await db
|
const roleSiteAccess = await db
|
||||||
@@ -105,7 +119,7 @@ export async function verifySiteAccess(
|
|||||||
.from(roleSites)
|
.from(roleSites)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(roleSites.siteId, siteId),
|
eq(roleSites.siteId, site.siteId),
|
||||||
eq(roleSites.roleId, userOrgRoleId)
|
eq(roleSites.roleId, userOrgRoleId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@@ -121,7 +135,10 @@ export async function verifySiteAccess(
|
|||||||
.select()
|
.select()
|
||||||
.from(userSites)
|
.from(userSites)
|
||||||
.where(
|
.where(
|
||||||
and(eq(userSites.userId, userId), eq(userSites.siteId, siteId))
|
and(
|
||||||
|
eq(userSites.userId, userId),
|
||||||
|
eq(userSites.siteId, site.siteId)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import { listExitNodes } from "#dynamic/lib/exitNodes";
|
|||||||
import { generateId } from "@server/auth/sessions/app";
|
import { generateId } from "@server/auth/sessions/app";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
import { rebuildClientAssociationsFromClient } from "@server/lib/rebuildClientAssociations";
|
||||||
|
import { getUniqueClientName } from "@server/db/names";
|
||||||
|
|
||||||
const createClientParamsSchema = z.strictObject({
|
const createClientParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -206,9 +207,12 @@ export async function createClient(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const niceId = await getUniqueClientName(orgId);
|
||||||
|
|
||||||
[newClient] = await trx
|
[newClient] = await trx
|
||||||
.insert(clients)
|
.insert(clients)
|
||||||
.values({
|
.values({
|
||||||
|
niceId,
|
||||||
exitNodeId: randomExitNode.exitNodeId,
|
exitNodeId: randomExitNode.exitNodeId,
|
||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -12,22 +12,34 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
|
||||||
const getClientSchema = z.strictObject({
|
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) {
|
async function query(clientId?: number, niceId?: string, orgId?: string) {
|
||||||
// Get the client
|
if (clientId) {
|
||||||
const [client] = await db
|
const [res] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.where(and(eq(clients.clientId, clientId)))
|
.where(eq(clients.clientId, clientId))
|
||||||
.leftJoin(olms, eq(clients.olmId, olms.olmId))
|
.leftJoin(olms, eq(clients.clientId, olms.clientId))
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
return res;
|
||||||
if (!client) {
|
} else if (niceId && orgId) {
|
||||||
return null;
|
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<
|
export type GetClientResponse = NonNullable<
|
||||||
@@ -36,13 +48,30 @@ export type GetClientResponse = NonNullable<
|
|||||||
olmId: string | null;
|
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({
|
registry.registerPath({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: "/client/{clientId}",
|
path: "/client/{clientId}",
|
||||||
description: "Get a client by its client ID.",
|
description: "Get a client by its client ID.",
|
||||||
tags: [OpenAPITags.Client],
|
tags: [OpenAPITags.Client],
|
||||||
request: {
|
request: {
|
||||||
params: getClientSchema
|
params: z.object({
|
||||||
|
clientId: z.number()
|
||||||
|
})
|
||||||
},
|
},
|
||||||
responses: {}
|
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) {
|
if (!client) {
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -128,7 +128,8 @@ function queryClients(orgId: string, accessibleClientIds: number[], filter?: "us
|
|||||||
olmVersion: olms.version,
|
olmVersion: olms.version,
|
||||||
userId: clients.userId,
|
userId: clients.userId,
|
||||||
username: users.username,
|
username: users.username,
|
||||||
userEmail: users.email
|
userEmail: users.email,
|
||||||
|
niceId: clients.niceId
|
||||||
})
|
})
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
.leftJoin(orgs, eq(clients.orgId, orgs.orgId))
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { Client, db, exitNodes, olms, sites } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { clients, clientSitesAssociationsCache } from "@server/db";
|
import { clients } from "@server/db";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -15,7 +15,8 @@ const updateClientParamsSchema = z.strictObject({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateClientSchema = 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<typeof updateClientSchema>;
|
export type UpdateClientBody = z.infer<typeof updateClientSchema>;
|
||||||
@@ -54,7 +55,7 @@ export async function updateClient(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name } = parsedBody.data;
|
const { name, niceId } = parsedBody.data;
|
||||||
|
|
||||||
const parsedParams = updateClientParamsSchema.safeParse(req.params);
|
const parsedParams = updateClientParamsSchema.safeParse(req.params);
|
||||||
if (!parsedParams.success) {
|
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
|
const updatedClient = await db
|
||||||
.update(clients)
|
.update(clients)
|
||||||
.set({ name })
|
.set({ name, niceId })
|
||||||
.where(eq(clients.clientId, clientId))
|
.where(eq(clients.clientId, clientId))
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,7 @@ authenticated.get(
|
|||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/site/:niceId",
|
"/org/:orgId/site/:niceId",
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
|
verifySiteAccess,
|
||||||
verifyUserHasAction(ActionsEnum.getSite),
|
verifyUserHasAction(ActionsEnum.getSite),
|
||||||
site.getSite
|
site.getSite
|
||||||
);
|
);
|
||||||
@@ -149,6 +150,14 @@ authenticated.get(
|
|||||||
client.getClient
|
client.getClient
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.get(
|
||||||
|
"/org/:orgId/client/:niceId",
|
||||||
|
verifyOrgAccess,
|
||||||
|
verifyClientAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.getClient),
|
||||||
|
client.getClient
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.put(
|
authenticated.put(
|
||||||
"/org/:orgId/client",
|
"/org/:orgId/client",
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
@@ -458,6 +467,7 @@ authenticated.get(
|
|||||||
authenticated.get(
|
authenticated.get(
|
||||||
"/org/:orgId/resource/:niceId",
|
"/org/:orgId/resource/:niceId",
|
||||||
verifyOrgAccess,
|
verifyOrgAccess,
|
||||||
|
verifyResourceAccess,
|
||||||
verifyUserHasAction(ActionsEnum.getResource),
|
verifyUserHasAction(ActionsEnum.getResource),
|
||||||
resource.getResource
|
resource.getResource
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ import { useForm } from "react-hook-form";
|
|||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
const GeneralFormSchema = z.object({
|
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<typeof GeneralFormSchema>;
|
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||||
@@ -49,7 +50,8 @@ export default function GeneralPage() {
|
|||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: client?.name
|
name: client?.name,
|
||||||
|
niceId: client?.niceId || ""
|
||||||
},
|
},
|
||||||
mode: "onChange"
|
mode: "onChange"
|
||||||
});
|
});
|
||||||
@@ -84,10 +86,11 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post(`/client/${client?.clientId}`, {
|
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({
|
toast({
|
||||||
title: t("clientUpdated"),
|
title: t("clientUpdated"),
|
||||||
@@ -139,6 +142,28 @@ export default function GeneralPage() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="niceId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>
|
||||||
|
{t("identifier")}
|
||||||
|
</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input
|
||||||
|
{...field}
|
||||||
|
placeholder={t(
|
||||||
|
"enterIdentifier"
|
||||||
|
)}
|
||||||
|
className="flex-1"
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
</form>
|
</form>
|
||||||
</Form>
|
</Form>
|
||||||
</SettingsSectionForm>
|
</SettingsSectionForm>
|
||||||
@@ -4,7 +4,6 @@ import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
|||||||
import { internal } from "@app/lib/api";
|
import { internal } from "@app/lib/api";
|
||||||
import { authCookieHeader } from "@app/lib/api/cookies";
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
import ClientProvider from "@app/providers/ClientProvider";
|
import ClientProvider from "@app/providers/ClientProvider";
|
||||||
import { build } from "@server/build";
|
|
||||||
import { GetClientResponse } from "@server/routers/client";
|
import { GetClientResponse } from "@server/routers/client";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
@@ -12,7 +11,7 @@ import { redirect } from "next/navigation";
|
|||||||
|
|
||||||
type SettingsLayoutProps = {
|
type SettingsLayoutProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
params: Promise<{ clientId: number | string; orgId: string }>;
|
params: Promise<{ niceId: number | string; orgId: string }>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default async function SettingsLayout(props: SettingsLayoutProps) {
|
export default async function SettingsLayout(props: SettingsLayoutProps) {
|
||||||
@@ -22,8 +21,9 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||||||
|
|
||||||
let client = null;
|
let client = null;
|
||||||
try {
|
try {
|
||||||
|
console.log("making request to ", `/org/${params.orgId}/client/${params.niceId}`);
|
||||||
const res = await internal.get<AxiosResponse<GetClientResponse>>(
|
const res = await internal.get<AxiosResponse<GetClientResponse>>(
|
||||||
`/client/${params.clientId}`,
|
`/org/${params.orgId}/client/${params.niceId}`,
|
||||||
await authCookieHeader()
|
await authCookieHeader()
|
||||||
);
|
);
|
||||||
client = res.data.data;
|
client = res.data.data;
|
||||||
@@ -37,11 +37,11 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||||||
const navItems = [
|
const navItems = [
|
||||||
{
|
{
|
||||||
title: t("general"),
|
title: t("general"),
|
||||||
href: `/{orgId}/settings/clients/machine/{clientId}/general`
|
href: `/{orgId}/settings/clients/machine/{niceId}/general`
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t("credentials"),
|
title: t("credentials"),
|
||||||
href: `/{orgId}/settings/clients/machine/{clientId}/credentials`
|
href: `/{orgId}/settings/clients/machine/{niceId}/credentials`
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import { redirect } from "next/navigation";
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export default async function ClientPage(props: {
|
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;
|
const params = await props.params;
|
||||||
redirect(
|
redirect(
|
||||||
`/${params.orgId}/settings/clients/machine/${params.clientId}/general`
|
`/${params.orgId}/settings/clients/machine/${params.niceId}/general`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -276,7 +276,7 @@ export default function Page() {
|
|||||||
|
|
||||||
if (res && res.status === 201) {
|
if (res && res.status === 201) {
|
||||||
const data = res.data.data;
|
const data = res.data.data;
|
||||||
router.push(`/${orgId}/settings/clients/machine/${data.clientId}`);
|
router.push(`/${orgId}/settings/clients/machine/${data.niceId}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
setCreateLoading(false);
|
setCreateLoading(false);
|
||||||
|
|||||||
@@ -56,7 +56,8 @@ export default async function ClientsPage(props: ClientsPageProps) {
|
|||||||
olmUpdateAvailable: client.olmUpdateAvailable || false,
|
olmUpdateAvailable: client.olmUpdateAvailable || false,
|
||||||
userId: client.userId,
|
userId: client.userId,
|
||||||
username: client.username,
|
username: client.username,
|
||||||
userEmail: client.userEmail
|
userEmail: client.userEmail,
|
||||||
|
niceId: client.niceId
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -66,7 +66,8 @@ export default async function ClientResourcesPage(
|
|||||||
destination: siteResource.destination,
|
destination: siteResource.destination,
|
||||||
// destinationPort: siteResource.destinationPort,
|
// destinationPort: siteResource.destinationPort,
|
||||||
alias: siteResource.alias || null,
|
alias: siteResource.alias || null,
|
||||||
siteNiceId: siteResource.siteNiceId
|
siteNiceId: siteResource.siteNiceId,
|
||||||
|
niceId: siteResource.niceId
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,25 +19,27 @@ export default function SiteInfoCard({}: ClientInfoCardProps) {
|
|||||||
return (
|
return (
|
||||||
<Alert>
|
<Alert>
|
||||||
<AlertDescription>
|
<AlertDescription>
|
||||||
<InfoSections cols={2}>
|
<InfoSections cols={3}>
|
||||||
<>
|
<InfoSection>
|
||||||
<InfoSection>
|
<InfoSectionTitle>{t("identifier")}</InfoSectionTitle>
|
||||||
<InfoSectionTitle>{t("status")}</InfoSectionTitle>
|
<InfoSectionContent>{client.niceId}</InfoSectionContent>
|
||||||
<InfoSectionContent>
|
</InfoSection>
|
||||||
{client.online ? (
|
<InfoSection>
|
||||||
<div className="text-green-500 flex items-center space-x-2">
|
<InfoSectionTitle>{t("status")}</InfoSectionTitle>
|
||||||
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
<InfoSectionContent>
|
||||||
<span>{t("online")}</span>
|
{client.online ? (
|
||||||
</div>
|
<div className="text-green-500 flex items-center space-x-2">
|
||||||
) : (
|
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
||||||
<div className="text-neutral-500 flex items-center space-x-2">
|
<span>{t("online")}</span>
|
||||||
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
</div>
|
||||||
<span>{t("offline")}</span>
|
) : (
|
||||||
</div>
|
<div className="text-neutral-500 flex items-center space-x-2">
|
||||||
)}
|
<div className="w-2 h-2 bg-gray-500 rounded-full"></div>
|
||||||
</InfoSectionContent>
|
<span>{t("offline")}</span>
|
||||||
</InfoSection>
|
</div>
|
||||||
</>
|
)}
|
||||||
|
</InfoSectionContent>
|
||||||
|
</InfoSection>
|
||||||
<InfoSection>
|
<InfoSection>
|
||||||
<InfoSectionTitle>{t("address")}</InfoSectionTitle>
|
<InfoSectionTitle>{t("address")}</InfoSectionTitle>
|
||||||
<InfoSectionContent>
|
<InfoSectionContent>
|
||||||
|
|||||||
@@ -25,32 +25,6 @@ import EditInternalResourceDialog from "@app/components/EditInternalResourceDial
|
|||||||
import { orgQueries } from "@app/lib/queries";
|
import { orgQueries } from "@app/lib/queries";
|
||||||
import { useQuery } from "@tanstack/react-query";
|
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 = {
|
export type InternalResourceRow = {
|
||||||
id: number;
|
id: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -66,6 +40,7 @@ export type InternalResourceRow = {
|
|||||||
destination: string;
|
destination: string;
|
||||||
// destinationPort: number | null;
|
// destinationPort: number | null;
|
||||||
alias: string | null;
|
alias: string | null;
|
||||||
|
niceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClientResourcesTableProps = {
|
type ClientResourcesTableProps = {
|
||||||
@@ -158,6 +133,28 @@ export default function ClientResourcesTable({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: "niceId",
|
||||||
|
accessorKey: "niceId",
|
||||||
|
friendlyName: t("identifier"),
|
||||||
|
enableHiding: true,
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() =>
|
||||||
|
column.toggleSorting(column.getIsSorted() === "asc")
|
||||||
|
}
|
||||||
|
>
|
||||||
|
{t("identifier")}
|
||||||
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <span>{row.original.niceId || "-"}</span>;
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "siteName",
|
accessorKey: "siteName",
|
||||||
friendlyName: t("site"),
|
friendlyName: t("site"),
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ export type ClientRow = {
|
|||||||
userId: string | null;
|
userId: string | null;
|
||||||
username: string | null;
|
username: string | null;
|
||||||
userEmail: string | null;
|
userEmail: string | null;
|
||||||
|
niceId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
type ClientTableProps = {
|
type ClientTableProps = {
|
||||||
@@ -66,7 +67,8 @@ export default function MachineClientsTable({
|
|||||||
const defaultMachineColumnVisibility = {
|
const defaultMachineColumnVisibility = {
|
||||||
client: false,
|
client: false,
|
||||||
subnet: false,
|
subnet: false,
|
||||||
userId: false
|
userId: false,
|
||||||
|
niceId: false
|
||||||
};
|
};
|
||||||
|
|
||||||
const refreshData = () => {
|
const refreshData = () => {
|
||||||
@@ -129,8 +131,8 @@ export default function MachineClientsTable({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
accessorKey: "userId",
|
accessorKey: "niceId",
|
||||||
friendlyName: "User",
|
friendlyName: "Identifier",
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return (
|
||||||
<Button
|
<Button
|
||||||
@@ -141,54 +143,12 @@ export default function MachineClientsTable({
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
User
|
{t("identifier")}
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
},
|
|
||||||
cell: ({ row }) => {
|
|
||||||
const r = row.original;
|
|
||||||
return r.userId ? (
|
|
||||||
<Link
|
|
||||||
href={`/${r.orgId}/settings/access/users/${r.userId}`}
|
|
||||||
>
|
|
||||||
<Button variant="outline">
|
|
||||||
{r.userEmail || r.username || r.userId}
|
|
||||||
<ArrowUpRight className="ml-2 h-4 w-4" />
|
|
||||||
</Button>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
"-"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// {
|
|
||||||
// accessorKey: "siteName",
|
|
||||||
// header: ({ column }) => {
|
|
||||||
// return (
|
|
||||||
// <Button
|
|
||||||
// variant="ghost"
|
|
||||||
// onClick={() =>
|
|
||||||
// column.toggleSorting(column.getIsSorted() === "asc")
|
|
||||||
// }
|
|
||||||
// >
|
|
||||||
// Site
|
|
||||||
// <ArrowUpDown className="ml-2 h-4 w-4" />
|
|
||||||
// </Button>
|
|
||||||
// );
|
|
||||||
// },
|
|
||||||
// cell: ({ row }) => {
|
|
||||||
// const r = row.original;
|
|
||||||
// return (
|
|
||||||
// <Link href={`/${r.orgId}/settings/sites/${r.siteId}`}>
|
|
||||||
// <Button variant="outline">
|
|
||||||
// {r.siteName}
|
|
||||||
// <ArrowUpRight className="ml-2 h-4 w-4" />
|
|
||||||
// </Button>
|
|
||||||
// </Link>
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
// },
|
|
||||||
{
|
{
|
||||||
accessorKey: "online",
|
accessorKey: "online",
|
||||||
friendlyName: "Connectivity",
|
friendlyName: "Connectivity",
|
||||||
@@ -369,7 +329,7 @@ export default function MachineClientsTable({
|
|||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</DropdownMenu>
|
</DropdownMenu>
|
||||||
<Link
|
<Link
|
||||||
href={`/${clientRow.orgId}/settings/clients/machine/${clientRow.id}`}
|
href={`/${clientRow.orgId}/settings/clients/machine/${clientRow.niceId}`}
|
||||||
>
|
>
|
||||||
<Button variant={"outline"}>
|
<Button variant={"outline"}>
|
||||||
{t("edit")}
|
{t("edit")}
|
||||||
|
|||||||
@@ -319,7 +319,7 @@ export default function ProxyResourcesTable({
|
|||||||
{
|
{
|
||||||
id: "niceId",
|
id: "niceId",
|
||||||
accessorKey: "nice",
|
accessorKey: "nice",
|
||||||
friendlyName: t("niceId"),
|
friendlyName: t("identifier"),
|
||||||
enableHiding: true,
|
enableHiding: true,
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return (
|
||||||
@@ -329,7 +329,7 @@ export default function ProxyResourcesTable({
|
|||||||
column.toggleSorting(column.getIsSorted() === "asc")
|
column.toggleSorting(column.getIsSorted() === "asc")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("niceId")}
|
{t("identifier")}
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
|||||||
{
|
{
|
||||||
id: "niceId",
|
id: "niceId",
|
||||||
accessorKey: "nice",
|
accessorKey: "nice",
|
||||||
friendlyName: t("niceId"),
|
friendlyName: t("identifier"),
|
||||||
enableHiding: true,
|
enableHiding: true,
|
||||||
header: ({ column }) => {
|
header: ({ column }) => {
|
||||||
return (
|
return (
|
||||||
@@ -138,7 +138,7 @@ export default function SitesTable({ sites, orgId }: SitesTableProps) {
|
|||||||
column.toggleSorting(column.getIsSorted() === "asc")
|
column.toggleSorting(column.getIsSorted() === "asc")
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{t("niceId")}
|
{t("identifier")}
|
||||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user