mirror of
https://github.com/fosrl/pangolin.git
synced 2026-03-06 18:56:39 +00:00
Build client site resource associations and send messages
This commit is contained in:
@@ -654,7 +654,7 @@ export const clients = pgTable("clients", {
|
|||||||
maxConnections: integer("maxConnections")
|
maxConnections: integer("maxConnections")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const clientSites = pgTable("clientSites", {
|
export const clientSitesAssociationsCache = pgTable("clientSitesAssociationsCache", {
|
||||||
clientId: integer("clientId")
|
clientId: integer("clientId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => clients.clientId, { onDelete: "cascade" }),
|
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||||
@@ -665,6 +665,15 @@ export const clientSites = pgTable("clientSites", {
|
|||||||
endpoint: varchar("endpoint")
|
endpoint: varchar("endpoint")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const clientSiteResourcesAssociationsCache = pgTable("clientSiteResourcesAssociationsCache", {
|
||||||
|
clientId: integer("clientId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||||
|
siteResourceId: integer("siteResourceId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => siteResources.siteResourceId, { onDelete: "cascade" })
|
||||||
|
});
|
||||||
|
|
||||||
export const olms = pgTable("olms", {
|
export const olms = pgTable("olms", {
|
||||||
olmId: varchar("id").primaryKey(),
|
olmId: varchar("id").primaryKey(),
|
||||||
secretHash: varchar("secretHash").notNull(),
|
secretHash: varchar("secretHash").notNull(),
|
||||||
@@ -847,7 +856,7 @@ export type ApiKey = InferSelectModel<typeof apiKeys>;
|
|||||||
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
export type ApiKeyAction = InferSelectModel<typeof apiKeyActions>;
|
||||||
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
export type ApiKeyOrg = InferSelectModel<typeof apiKeyOrg>;
|
||||||
export type Client = InferSelectModel<typeof clients>;
|
export type Client = InferSelectModel<typeof clients>;
|
||||||
export type ClientSite = InferSelectModel<typeof clientSites>;
|
export type ClientSite = InferSelectModel<typeof clientSitesAssociationsCache>;
|
||||||
export type Olm = InferSelectModel<typeof olms>;
|
export type Olm = InferSelectModel<typeof olms>;
|
||||||
export type OlmSession = InferSelectModel<typeof olmSessions>;
|
export type OlmSession = InferSelectModel<typeof olmSessions>;
|
||||||
export type UserClient = InferSelectModel<typeof userClients>;
|
export type UserClient = InferSelectModel<typeof userClients>;
|
||||||
|
|||||||
@@ -361,7 +361,7 @@ export const clients = sqliteTable("clients", {
|
|||||||
lastHolePunch: integer("lastHolePunch")
|
lastHolePunch: integer("lastHolePunch")
|
||||||
});
|
});
|
||||||
|
|
||||||
export const clientSites = sqliteTable("clientSites", {
|
export const clientSitesAssociationsCache = sqliteTable("clientSitesAssociationsCache", {
|
||||||
clientId: integer("clientId")
|
clientId: integer("clientId")
|
||||||
.notNull()
|
.notNull()
|
||||||
.references(() => clients.clientId, { onDelete: "cascade" }),
|
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||||
@@ -374,6 +374,15 @@ export const clientSites = sqliteTable("clientSites", {
|
|||||||
endpoint: text("endpoint")
|
endpoint: text("endpoint")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const clientSiteResourcesAssociationsCache = sqliteTable("clientSiteResourcesAssociationsCache", {
|
||||||
|
clientId: integer("clientId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => clients.clientId, { onDelete: "cascade" }),
|
||||||
|
siteResourceId: integer("siteResourceId")
|
||||||
|
.notNull()
|
||||||
|
.references(() => siteResources.siteResourceId, { onDelete: "cascade" })
|
||||||
|
});
|
||||||
|
|
||||||
export const olms = sqliteTable("olms", {
|
export const olms = sqliteTable("olms", {
|
||||||
olmId: text("id").primaryKey(),
|
olmId: text("id").primaryKey(),
|
||||||
secretHash: text("secretHash").notNull(),
|
secretHash: text("secretHash").notNull(),
|
||||||
@@ -894,7 +903,7 @@ export type ResourceRule = InferSelectModel<typeof resourceRules>;
|
|||||||
export type Domain = InferSelectModel<typeof domains>;
|
export type Domain = InferSelectModel<typeof domains>;
|
||||||
export type DnsRecord = InferSelectModel<typeof dnsRecords>;
|
export type DnsRecord = InferSelectModel<typeof dnsRecords>;
|
||||||
export type Client = InferSelectModel<typeof clients>;
|
export type Client = InferSelectModel<typeof clients>;
|
||||||
export type ClientSite = InferSelectModel<typeof clientSites>;
|
export type ClientSite = InferSelectModel<typeof clientSitesAssociationsCache>;
|
||||||
export type RoleClient = InferSelectModel<typeof roleClients>;
|
export type RoleClient = InferSelectModel<typeof roleClients>;
|
||||||
export type UserClient = InferSelectModel<typeof userClients>;
|
export type UserClient = InferSelectModel<typeof userClients>;
|
||||||
export type SupporterKey = InferSelectModel<typeof supporterKey>;
|
export type SupporterKey = InferSelectModel<typeof supporterKey>;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { clients, clientSites, db, olms, orgs, roleClients, roles, userClients, userOrgs, Transaction } from "@server/db";
|
import { clients, clientSitesAssociationsCache, db, olms, orgs, roleClients, roles, userClients, userOrgs, Transaction } from "@server/db";
|
||||||
import { eq, and, notInArray } from "drizzle-orm";
|
import { eq, and, notInArray } from "drizzle-orm";
|
||||||
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
import { listExitNodes } from "#dynamic/lib/exitNodes";
|
||||||
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
import { getNextAvailableClientSubnet } from "@server/lib/ip";
|
||||||
@@ -228,8 +228,8 @@ async function cleanupOrphanedClients(
|
|||||||
// Delete client-site associations first, then delete the clients
|
// Delete client-site associations first, then delete the clients
|
||||||
for (const client of clientsToDelete) {
|
for (const client of clientsToDelete) {
|
||||||
await trx
|
await trx
|
||||||
.delete(clientSites)
|
.delete(clientSitesAssociationsCache)
|
||||||
.where(eq(clientSites.clientId, client.clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (clientsToDelete.length > 0) {
|
if (clientsToDelete.length > 0) {
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { clientSites, db, SiteResource, Transaction } from "@server/db";
|
import { clientSitesAssociationsCache, db, SiteResource, Transaction } from "@server/db";
|
||||||
import { clients, orgs, sites } from "@server/db";
|
import { clients, orgs, sites } from "@server/db";
|
||||||
import { and, eq, isNotNull } from "drizzle-orm";
|
import { and, eq, isNotNull } from "drizzle-orm";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import z from "zod";
|
import z from "zod";
|
||||||
import { getClientSiteResourceAccess } from "./rebuildSiteClientAssociations";
|
import { getClientSiteResourceAccess } from "./rebuildClientAssociations";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
|
|
||||||
interface IPRange {
|
interface IPRange {
|
||||||
@@ -338,41 +338,54 @@ export type SubnetProxyTarget = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export async function generateSubnetProxyTargets(
|
export function generateSingleSubnetProxyTargets(
|
||||||
allSiteResources: SiteResource[],
|
siteResource: SiteResource,
|
||||||
trx: Transaction | typeof db = db
|
clients: {
|
||||||
): Promise<SubnetProxyTarget[]> {
|
clientId: number;
|
||||||
|
pubKey: string | null;
|
||||||
|
subnet: string | null;
|
||||||
|
}[]
|
||||||
|
): SubnetProxyTarget[] {
|
||||||
let targets: SubnetProxyTarget[] = [];
|
let targets: SubnetProxyTarget[] = [];
|
||||||
|
|
||||||
for (const siteResource of allSiteResources) {
|
if (clients.length === 0) {
|
||||||
const { mergedAllClients } =
|
logger.debug(
|
||||||
await getClientSiteResourceAccess(siteResource, trx);
|
`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`
|
||||||
|
);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
if (mergedAllClients.length === 0) {
|
for (const clientSite of clients) {
|
||||||
logger.debug(`No clients have access to site resource ${siteResource.siteResourceId}, skipping target generation.`);
|
if (!clientSite.subnet) {
|
||||||
|
logger.debug(
|
||||||
|
`Client ${clientSite.clientId} has no subnet, skipping for site resource ${siteResource.siteResourceId}.`
|
||||||
|
);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const clientSite of mergedAllClients) {
|
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
|
||||||
const clientPrefix = `${clientSite.subnet.split("/")[0]}/32`;
|
|
||||||
|
|
||||||
if (siteResource.mode == "host") {
|
if (siteResource.mode == "host") {
|
||||||
// check if this is a valid ip
|
// check if this is a valid ip
|
||||||
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
const ipSchema = z.union([z.ipv4(), z.ipv6()]);
|
||||||
if (ipSchema.safeParse(siteResource.destination).success) {
|
if (ipSchema.safeParse(siteResource.destination).success) {
|
||||||
targets.push({
|
|
||||||
sourcePrefix: clientPrefix,
|
|
||||||
destPrefix: `${siteResource.destination}/32`
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (siteResource.mode == "cidr") {
|
|
||||||
targets.push({
|
targets.push({
|
||||||
sourcePrefix: clientPrefix,
|
sourcePrefix: clientPrefix,
|
||||||
destPrefix: siteResource.destination
|
destPrefix: `${siteResource.destination}/32`
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
} else if (siteResource.mode == "cidr") {
|
||||||
|
targets.push({
|
||||||
|
sourcePrefix: clientPrefix,
|
||||||
|
destPrefix: siteResource.destination
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// print a nice representation of the targets
|
||||||
|
logger.debug(
|
||||||
|
`Generated subnet proxy targets for: ${JSON.stringify(targets, null, 2)}`
|
||||||
|
);
|
||||||
|
|
||||||
return targets;
|
return targets;
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
Client,
|
Client,
|
||||||
clients,
|
clients,
|
||||||
clientSiteResources,
|
clientSiteResources,
|
||||||
clientSites,
|
clientSitesAssociationsCache,
|
||||||
db,
|
db,
|
||||||
exitNodes,
|
exitNodes,
|
||||||
newts,
|
newts,
|
||||||
@@ -29,7 +29,15 @@ import {
|
|||||||
} from "@server/routers/olm/peers";
|
} from "@server/routers/olm/peers";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { generateRemoteSubnetsStr } from "@server/lib/ip";
|
import {
|
||||||
|
generateRemoteSubnetsStr,
|
||||||
|
generateSingleSubnetProxyTargets,
|
||||||
|
SubnetProxyTarget
|
||||||
|
} from "@server/lib/ip";
|
||||||
|
import {
|
||||||
|
addTargets as addSubnetProxyTargets,
|
||||||
|
removeTargets as removeSubnetProxyTargets
|
||||||
|
} from "@server/routers/client/targets";
|
||||||
|
|
||||||
export async function getClientSiteResourceAccess(
|
export async function getClientSiteResourceAccess(
|
||||||
siteResource: SiteResource,
|
siteResource: SiteResource,
|
||||||
@@ -117,21 +125,29 @@ export async function getClientSiteResourceAccess(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function rebuildSiteClientAssociations(
|
export async function rebuildClientAssociations(
|
||||||
siteResource: SiteResource,
|
siteResource: SiteResource,
|
||||||
trx: Transaction | typeof db = db
|
trx: Transaction | typeof db = db
|
||||||
): Promise<void> {
|
): Promise<{
|
||||||
|
mergedAllClients: {
|
||||||
|
clientId: number;
|
||||||
|
pubKey: string | null;
|
||||||
|
subnet: string | null;
|
||||||
|
}[];
|
||||||
|
}> {
|
||||||
const siteId = siteResource.siteId;
|
const siteId = siteResource.siteId;
|
||||||
|
|
||||||
const { site, mergedAllClients, mergedAllClientIds } =
|
const { site, mergedAllClients, mergedAllClientIds } =
|
||||||
await getClientSiteResourceAccess(siteResource, trx);
|
await getClientSiteResourceAccess(siteResource, trx);
|
||||||
|
|
||||||
|
/////////// process the client-site associations ///////////
|
||||||
|
|
||||||
const existingClientSites = await trx
|
const existingClientSites = await trx
|
||||||
.select({
|
.select({
|
||||||
clientId: clientSites.clientId
|
clientId: clientSitesAssociationsCache.clientId
|
||||||
})
|
})
|
||||||
.from(clientSites)
|
.from(clientSitesAssociationsCache)
|
||||||
.where(eq(clientSites.siteId, siteResource.siteId));
|
.where(eq(clientSitesAssociationsCache.siteId, siteResource.siteId));
|
||||||
|
|
||||||
const existingClientSiteIds = existingClientSites.map(
|
const existingClientSiteIds = existingClientSites.map(
|
||||||
(row) => row.clientId
|
(row) => row.clientId
|
||||||
@@ -153,15 +169,16 @@ export async function rebuildSiteClientAssociations(
|
|||||||
(clientId) => !existingClientSiteIds.includes(clientId)
|
(clientId) => !existingClientSiteIds.includes(clientId)
|
||||||
);
|
);
|
||||||
|
|
||||||
const clientSitesToInsert = mergedAllClientIds
|
const clientSitesToInsert = clientSitesToAdd.map((clientId) => ({
|
||||||
.filter((clientId) => !existingClientSiteIds.includes(clientId))
|
clientId,
|
||||||
.map((clientId) => ({
|
siteId
|
||||||
clientId,
|
}));
|
||||||
siteId
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (clientSitesToInsert.length > 0) {
|
if (clientSitesToInsert.length > 0) {
|
||||||
await trx.insert(clientSites).values(clientSitesToInsert);
|
await trx
|
||||||
|
.insert(clientSitesAssociationsCache)
|
||||||
|
.values(clientSitesToInsert)
|
||||||
|
.returning();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now remove any client-site associations that should no longer exist
|
// Now remove any client-site associations that should no longer exist
|
||||||
@@ -171,11 +188,68 @@ export async function rebuildSiteClientAssociations(
|
|||||||
|
|
||||||
if (clientSitesToRemove.length > 0) {
|
if (clientSitesToRemove.length > 0) {
|
||||||
await trx
|
await trx
|
||||||
.delete(clientSites)
|
.delete(clientSitesAssociationsCache)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(clientSites.siteId, siteId),
|
eq(clientSitesAssociationsCache.siteId, siteId),
|
||||||
inArray(clientSites.clientId, clientSitesToRemove)
|
inArray(
|
||||||
|
clientSitesAssociationsCache.clientId,
|
||||||
|
clientSitesToRemove
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/////////// process the client-siteResource associations ///////////
|
||||||
|
|
||||||
|
const existingClientSiteResources = await trx
|
||||||
|
.select({
|
||||||
|
clientId: clientSiteResources.clientId
|
||||||
|
})
|
||||||
|
.from(clientSiteResources)
|
||||||
|
.where(
|
||||||
|
eq(clientSiteResources.siteResourceId, siteResource.siteResourceId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const existingClientSiteResourceIds = existingClientSiteResources.map(
|
||||||
|
(row) => row.clientId
|
||||||
|
);
|
||||||
|
|
||||||
|
const clientSiteResourcesToAdd = mergedAllClientIds.filter(
|
||||||
|
(clientId) => !existingClientSiteResourceIds.includes(clientId)
|
||||||
|
);
|
||||||
|
|
||||||
|
const clientSiteResourcesToInsert = clientSiteResourcesToAdd.map(
|
||||||
|
(clientId) => ({
|
||||||
|
clientId,
|
||||||
|
siteResourceId: siteResource.siteResourceId
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
if (clientSiteResourcesToInsert.length > 0) {
|
||||||
|
await trx
|
||||||
|
.insert(clientSiteResources)
|
||||||
|
.values(clientSiteResourcesToInsert)
|
||||||
|
.returning();
|
||||||
|
}
|
||||||
|
|
||||||
|
const clientSiteResourcesToRemove = existingClientSiteResourceIds.filter(
|
||||||
|
(clientId) => !mergedAllClientIds.includes(clientId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (clientSiteResourcesToRemove.length > 0) {
|
||||||
|
await trx
|
||||||
|
.delete(clientSiteResources)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(
|
||||||
|
clientSiteResources.siteResourceId,
|
||||||
|
siteResource.siteResourceId
|
||||||
|
),
|
||||||
|
inArray(
|
||||||
|
clientSiteResources.clientId,
|
||||||
|
clientSiteResourcesToRemove
|
||||||
|
)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -190,6 +264,20 @@ export async function rebuildSiteClientAssociations(
|
|||||||
clientSitesToRemove,
|
clientSitesToRemove,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Handle subnet proxy target updates for the resource associations
|
||||||
|
await handleSubnetProxyTargetUpdates(
|
||||||
|
siteResource,
|
||||||
|
mergedAllClients,
|
||||||
|
existingClients,
|
||||||
|
clientSiteResourcesToAdd,
|
||||||
|
clientSiteResourcesToRemove,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
mergedAllClients
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleMessagesForSiteClients(
|
async function handleMessagesForSiteClients(
|
||||||
@@ -401,9 +489,12 @@ export async function updateClientSiteDestinations(
|
|||||||
const sitesData = await trx
|
const sitesData = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.innerJoin(clientSites, eq(sites.siteId, clientSites.siteId))
|
.innerJoin(
|
||||||
|
clientSitesAssociationsCache,
|
||||||
|
eq(sites.siteId, clientSitesAssociationsCache.siteId)
|
||||||
|
)
|
||||||
.leftJoin(exitNodes, eq(sites.exitNodeId, exitNodes.exitNodeId))
|
.leftJoin(exitNodes, eq(sites.exitNodeId, exitNodes.exitNodeId))
|
||||||
.where(eq(clientSites.clientId, client.clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||||
|
|
||||||
for (const site of sitesData) {
|
for (const site of sitesData) {
|
||||||
if (!site.sites.subnet) {
|
if (!site.sites.subnet) {
|
||||||
@@ -411,7 +502,7 @@ export async function updateClientSiteDestinations(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!site.clientSites.endpoint) {
|
if (!site.clientSitesAssociationsCache.endpoint) {
|
||||||
logger.warn(`Site ${site.sites.siteId} has no endpoint, skipping`); // if this is a new association the endpoint is not set yet // TODO: FIX THIS
|
logger.warn(`Site ${site.sites.siteId} has no endpoint, skipping`); // if this is a new association the endpoint is not set yet // TODO: FIX THIS
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@@ -427,9 +518,9 @@ export async function updateClientSiteDestinations(
|
|||||||
exitNodeId: site.exitNodes?.exitNodeId || 0,
|
exitNodeId: site.exitNodes?.exitNodeId || 0,
|
||||||
type: site.exitNodes?.type || "",
|
type: site.exitNodes?.type || "",
|
||||||
name: site.exitNodes?.name || "",
|
name: site.exitNodes?.name || "",
|
||||||
sourceIp: site.clientSites.endpoint.split(":")[0] || "",
|
sourceIp: site.clientSitesAssociationsCache.endpoint.split(":")[0] || "",
|
||||||
sourcePort:
|
sourcePort:
|
||||||
parseInt(site.clientSites.endpoint.split(":")[1]) || 0,
|
parseInt(site.clientSitesAssociationsCache.endpoint.split(":")[1]) || 0,
|
||||||
destinations: [
|
destinations: [
|
||||||
{
|
{
|
||||||
destinationIP: site.sites.subnet.split("/")[0],
|
destinationIP: site.sites.subnet.split("/")[0],
|
||||||
@@ -481,3 +572,76 @@ export async function updateClientSiteDestinations(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSubnetProxyTargetUpdates(
|
||||||
|
siteResource: SiteResource,
|
||||||
|
allClients: {
|
||||||
|
clientId: number;
|
||||||
|
pubKey: string | null;
|
||||||
|
subnet: string | null;
|
||||||
|
}[],
|
||||||
|
existingClients: {
|
||||||
|
clientId: number;
|
||||||
|
pubKey: string | null;
|
||||||
|
subnet: string | null;
|
||||||
|
}[],
|
||||||
|
clientSiteResourcesToAdd: number[],
|
||||||
|
clientSiteResourcesToRemove: number[],
|
||||||
|
trx: Transaction | typeof db = db
|
||||||
|
): Promise<void> {
|
||||||
|
// Get the newt for this site
|
||||||
|
const [newt] = await trx
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, siteResource.siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!newt) {
|
||||||
|
logger.warn(
|
||||||
|
`Newt not found for site ${siteResource.siteId}, skipping subnet proxy target updates`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate targets for added associations
|
||||||
|
if (clientSiteResourcesToAdd.length > 0) {
|
||||||
|
const addedClients = allClients.filter((client) =>
|
||||||
|
clientSiteResourcesToAdd.includes(client.clientId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (addedClients.length > 0) {
|
||||||
|
const targetsToAdd = generateSingleSubnetProxyTargets(
|
||||||
|
siteResource,
|
||||||
|
addedClients
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetsToAdd.length > 0) {
|
||||||
|
logger.info(
|
||||||
|
`Adding ${targetsToAdd.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
||||||
|
);
|
||||||
|
await addSubnetProxyTargets(newt.newtId, targetsToAdd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate targets for removed associations
|
||||||
|
if (clientSiteResourcesToRemove.length > 0) {
|
||||||
|
const removedClients = existingClients.filter((client) =>
|
||||||
|
clientSiteResourcesToRemove.includes(client.clientId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (removedClients.length > 0) {
|
||||||
|
const targetsToRemove = generateSingleSubnetProxyTargets(
|
||||||
|
siteResource,
|
||||||
|
removedClients
|
||||||
|
);
|
||||||
|
|
||||||
|
if (targetsToRemove.length > 0) {
|
||||||
|
logger.info(
|
||||||
|
`Removing ${targetsToRemove.length} subnet proxy targets for siteResource ${siteResource.siteResourceId}`
|
||||||
|
);
|
||||||
|
await removeSubnetProxyTargets(newt.newtId, targetsToRemove);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -345,9 +345,9 @@ export async function getTraefikConfig(
|
|||||||
routerMiddlewares.push(rewriteMiddlewareName);
|
routerMiddlewares.push(rewriteMiddlewareName);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(
|
// logger.debug(
|
||||||
`Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
// `Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
||||||
);
|
// );
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to create path rewrite middleware for resource ${resource.resourceId}: ${error}`
|
`Failed to create path rewrite middleware for resource ${resource.resourceId}: ${error}`
|
||||||
|
|||||||
@@ -434,9 +434,9 @@ export async function getTraefikConfig(
|
|||||||
routerMiddlewares.push(rewriteMiddlewareName);
|
routerMiddlewares.push(rewriteMiddlewareName);
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.debug(
|
// logger.debug(
|
||||||
`Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
// `Created path rewrite middleware ${rewriteMiddlewareName}: ${resource.pathMatchType}(${resource.path}) -> ${resource.rewritePathType}(${resource.rewritePath})`
|
||||||
);
|
// );
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Failed to create path rewrite middleware for resource ${resource.resourceId}: ${error}`
|
`Failed to create path rewrite middleware for resource ${resource.resourceId}: ${error}`
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ export async function cleanUpOldLogs(orgId: string, retentionDays: number) {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
logger.debug(
|
// logger.debug(
|
||||||
`Cleaned up request audit logs older than ${retentionDays} days`
|
// `Cleaned up request audit logs older than ${retentionDays} days`
|
||||||
);
|
// );
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error("Error cleaning up old request audit logs:", error);
|
logger.error("Error cleaning up old request audit logs:", error);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { clients, clientSites } from "@server/db";
|
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -70,8 +70,8 @@ export async function deleteClient(
|
|||||||
await db.transaction(async (trx) => {
|
await db.transaction(async (trx) => {
|
||||||
// Delete the client-site associations first
|
// Delete the client-site associations first
|
||||||
await trx
|
await trx
|
||||||
.delete(clientSites)
|
.delete(clientSitesAssociationsCache)
|
||||||
.where(eq(clientSites.clientId, clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, clientId));
|
||||||
|
|
||||||
// Then delete the client itself
|
// Then delete the client itself
|
||||||
await trx.delete(clients).where(eq(clients.clientId, clientId));
|
await trx.delete(clients).where(eq(clients.clientId, clientId));
|
||||||
|
|||||||
@@ -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 { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { clients, clientSites } from "@server/db";
|
import { clients, clientSitesAssociationsCache } from "@server/db";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import response from "@server/lib/response";
|
import response from "@server/lib/response";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -29,9 +29,9 @@ async function query(clientId: number) {
|
|||||||
|
|
||||||
// Get the siteIds associated with this client
|
// Get the siteIds associated with this client
|
||||||
const sites = await db
|
const sites = await db
|
||||||
.select({ siteId: clientSites.siteId })
|
.select({ siteId: clientSitesAssociationsCache.siteId })
|
||||||
.from(clientSites)
|
.from(clientSitesAssociationsCache)
|
||||||
.where(eq(clientSites.clientId, clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, clientId));
|
||||||
|
|
||||||
// Add the siteIds to the client object
|
// Add the siteIds to the client object
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import {
|
|||||||
roleClients,
|
roleClients,
|
||||||
sites,
|
sites,
|
||||||
userClients,
|
userClients,
|
||||||
clientSites
|
clientSitesAssociationsCache
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
@@ -142,14 +142,14 @@ async function getSiteAssociations(clientIds: number[]) {
|
|||||||
|
|
||||||
return db
|
return db
|
||||||
.select({
|
.select({
|
||||||
clientId: clientSites.clientId,
|
clientId: clientSitesAssociationsCache.clientId,
|
||||||
siteId: clientSites.siteId,
|
siteId: clientSitesAssociationsCache.siteId,
|
||||||
siteName: sites.name,
|
siteName: sites.name,
|
||||||
siteNiceId: sites.niceId
|
siteNiceId: sites.niceId
|
||||||
})
|
})
|
||||||
.from(clientSites)
|
.from(clientSitesAssociationsCache)
|
||||||
.leftJoin(sites, eq(clientSites.siteId, sites.siteId))
|
.leftJoin(sites, eq(clientSitesAssociationsCache.siteId, sites.siteId))
|
||||||
.where(inArray(clientSites.clientId, clientIds));
|
.where(inArray(clientSitesAssociationsCache.clientId, clientIds));
|
||||||
}
|
}
|
||||||
|
|
||||||
type OlmWithUpdateAvailable = Awaited<ReturnType<typeof queryClients>>[0] & {
|
type OlmWithUpdateAvailable = Awaited<ReturnType<typeof queryClients>>[0] & {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { SubnetProxyTarget } from "@server/lib/ip";
|
|||||||
|
|
||||||
export async function addTargets(newtId: string, targets: SubnetProxyTarget[]) {
|
export async function addTargets(newtId: string, targets: SubnetProxyTarget[]) {
|
||||||
await sendToClient(newtId, {
|
await sendToClient(newtId, {
|
||||||
type: `newt/wg/target/add`,
|
type: `newt/wg/targets/add`,
|
||||||
data: targets
|
data: targets
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -13,7 +13,7 @@ export async function removeTargets(
|
|||||||
targets: SubnetProxyTarget[]
|
targets: SubnetProxyTarget[]
|
||||||
) {
|
) {
|
||||||
await sendToClient(newtId, {
|
await sendToClient(newtId, {
|
||||||
type: `newt/wg/target/remove`,
|
type: `newt/wg/targets/remove`,
|
||||||
data: targets
|
data: targets
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -26,7 +26,7 @@ export async function updateTargets(
|
|||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
await sendToClient(newtId, {
|
await sendToClient(newtId, {
|
||||||
type: `newt/wg/target/update`,
|
type: `newt/wg/targets/update`,
|
||||||
data: targets
|
data: targets
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 { Client, db, exitNodes, olms, sites } from "@server/db";
|
||||||
import { clients, clientSites } from "@server/db";
|
import { clients, clientSitesAssociationsCache } 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";
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import {
|
|||||||
olms,
|
olms,
|
||||||
Site,
|
Site,
|
||||||
sites,
|
sites,
|
||||||
clientSites,
|
clientSitesAssociationsCache,
|
||||||
ExitNode
|
ExitNode
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
@@ -109,8 +109,8 @@ export async function generateRelayMappings(exitNode: ExitNode) {
|
|||||||
// Find all clients associated with this site through clientSites
|
// Find all clients associated with this site through clientSites
|
||||||
const clientSitesRes = await db
|
const clientSitesRes = await db
|
||||||
.select()
|
.select()
|
||||||
.from(clientSites)
|
.from(clientSitesAssociationsCache)
|
||||||
.where(eq(clientSites.siteId, site.siteId));
|
.where(eq(clientSitesAssociationsCache.siteId, site.siteId));
|
||||||
|
|
||||||
for (const clientSite of clientSitesRes) {
|
for (const clientSite of clientSitesRes) {
|
||||||
if (!clientSite.endpoint) {
|
if (!clientSite.endpoint) {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import {
|
|||||||
olms,
|
olms,
|
||||||
Site,
|
Site,
|
||||||
sites,
|
sites,
|
||||||
clientSites,
|
clientSitesAssociationsCache,
|
||||||
exitNodes,
|
exitNodes,
|
||||||
ExitNode
|
ExitNode
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
@@ -174,11 +174,11 @@ export async function updateAndGenerateEndpointDestinations(
|
|||||||
listenPort: sites.listenPort
|
listenPort: sites.listenPort
|
||||||
})
|
})
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.innerJoin(clientSites, eq(sites.siteId, clientSites.siteId))
|
.innerJoin(clientSitesAssociationsCache, eq(sites.siteId, clientSitesAssociationsCache.siteId))
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(sites.exitNodeId, exitNode.exitNodeId),
|
eq(sites.exitNodeId, exitNode.exitNodeId),
|
||||||
eq(clientSites.clientId, olm.clientId)
|
eq(clientSitesAssociationsCache.clientId, olm.clientId)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -189,14 +189,14 @@ export async function updateAndGenerateEndpointDestinations(
|
|||||||
);
|
);
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.update(clientSites)
|
.update(clientSitesAssociationsCache)
|
||||||
.set({
|
.set({
|
||||||
endpoint: `${ip}:${port}`
|
endpoint: `${ip}:${port}`
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(clientSites.clientId, olm.clientId),
|
eq(clientSitesAssociationsCache.clientId, olm.clientId),
|
||||||
eq(clientSites.siteId, site.siteId)
|
eq(clientSitesAssociationsCache.siteId, site.siteId)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import {
|
|||||||
Target,
|
Target,
|
||||||
targets
|
targets
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { clients, clientSites, Newt, sites } from "@server/db";
|
import { clients, clientSitesAssociationsCache, Newt, sites } from "@server/db";
|
||||||
import { eq, and, inArray } from "drizzle-orm";
|
import { eq, and, inArray } from "drizzle-orm";
|
||||||
import { updatePeer } from "../olm/peers";
|
import { updatePeer } from "../olm/peers";
|
||||||
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
import { sendToExitNode } from "#dynamic/lib/exitNodes";
|
||||||
@@ -138,8 +138,8 @@ export const handleGetConfigMessage: MessageHandler = async (context) => {
|
|||||||
const clientsRes = await db
|
const clientsRes = await db
|
||||||
.select()
|
.select()
|
||||||
.from(clients)
|
.from(clients)
|
||||||
.innerJoin(clientSites, eq(clients.clientId, clientSites.clientId))
|
.innerJoin(clientSitesAssociationsCache, eq(clients.clientId, clientSitesAssociationsCache.clientId))
|
||||||
.where(eq(clientSites.siteId, siteId));
|
.where(eq(clientSitesAssociationsCache.siteId, siteId));
|
||||||
|
|
||||||
// Prepare peers data for the response
|
// Prepare peers data for the response
|
||||||
const peers = await Promise.all(
|
const peers = await Promise.all(
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { NextFunction, Request, Response } from "express";
|
import { NextFunction, Request, Response } from "express";
|
||||||
import { db } from "@server/db";
|
import { db } from "@server/db";
|
||||||
import { olms, clients, clientSites } from "@server/db";
|
import { olms, clients, clientSitesAssociationsCache } from "@server/db";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import HttpCode from "@server/types/HttpCode";
|
import HttpCode from "@server/types/HttpCode";
|
||||||
import createHttpError from "http-errors";
|
import createHttpError from "http-errors";
|
||||||
@@ -57,8 +57,8 @@ export async function deleteUserOlm(
|
|||||||
// Delete client-site associations for each associated client
|
// Delete client-site associations for each associated client
|
||||||
for (const client of associatedClients) {
|
for (const client of associatedClients) {
|
||||||
await trx
|
await trx
|
||||||
.delete(clientSites)
|
.delete(clientSitesAssociationsCache)
|
||||||
.where(eq(clientSites.clientId, client.clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Delete all associated clients
|
// Delete all associated clients
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
users
|
users
|
||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
import { MessageHandler } from "@server/routers/ws";
|
||||||
import { clients, clientSites, exitNodes, Olm, olms, sites } from "@server/db";
|
import { clients, clientSitesAssociationsCache, exitNodes, Olm, olms, sites } from "@server/db";
|
||||||
import { and, eq, inArray, isNull } from "drizzle-orm";
|
import { and, eq, inArray, isNull } from "drizzle-orm";
|
||||||
import { addPeer, deletePeer } from "../newt/peers";
|
import { addPeer, deletePeer } from "../newt/peers";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
@@ -159,19 +159,19 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
|
|
||||||
// set isRelay to false for all of the client's sites to reset the connection metadata
|
// set isRelay to false for all of the client's sites to reset the connection metadata
|
||||||
await db
|
await db
|
||||||
.update(clientSites)
|
.update(clientSitesAssociationsCache)
|
||||||
.set({
|
.set({
|
||||||
isRelayed: relay == true
|
isRelayed: relay == true
|
||||||
})
|
})
|
||||||
.where(eq(clientSites.clientId, client.clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all sites data
|
// Get all sites data
|
||||||
const sitesData = await db
|
const sitesData = await db
|
||||||
.select()
|
.select()
|
||||||
.from(sites)
|
.from(sites)
|
||||||
.innerJoin(clientSites, eq(sites.siteId, clientSites.siteId))
|
.innerJoin(clientSitesAssociationsCache, eq(sites.siteId, clientSitesAssociationsCache.siteId))
|
||||||
.where(eq(clientSites.clientId, client.clientId));
|
.where(eq(clientSitesAssociationsCache.clientId, client.clientId));
|
||||||
|
|
||||||
// Prepare an array to store site configurations
|
// Prepare an array to store site configurations
|
||||||
const siteConfigurations = [];
|
const siteConfigurations = [];
|
||||||
@@ -225,11 +225,11 @@ export const handleOlmRegisterMessage: MessageHandler = async (context) => {
|
|||||||
|
|
||||||
const [clientSite] = await db
|
const [clientSite] = await db
|
||||||
.select()
|
.select()
|
||||||
.from(clientSites)
|
.from(clientSitesAssociationsCache)
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(clientSites.clientId, client.clientId),
|
eq(clientSitesAssociationsCache.clientId, client.clientId),
|
||||||
eq(clientSites.siteId, site.siteId)
|
eq(clientSitesAssociationsCache.siteId, site.siteId)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.limit(1);
|
.limit(1);
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { db, exitNodes, sites } from "@server/db";
|
import { db, exitNodes, sites } from "@server/db";
|
||||||
import { MessageHandler } from "@server/routers/ws";
|
import { MessageHandler } from "@server/routers/ws";
|
||||||
import { clients, clientSites, Olm } from "@server/db";
|
import { clients, clientSitesAssociationsCache, Olm } from "@server/db";
|
||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { updatePeer } from "../newt/peers";
|
import { updatePeer } from "../newt/peers";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
@@ -67,14 +67,14 @@ export const handleOlmRelayMessage: MessageHandler = async (context) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await db
|
await db
|
||||||
.update(clientSites)
|
.update(clientSitesAssociationsCache)
|
||||||
.set({
|
.set({
|
||||||
isRelayed: true
|
isRelayed: true
|
||||||
})
|
})
|
||||||
.where(
|
.where(
|
||||||
and(
|
and(
|
||||||
eq(clientSites.clientId, olm.clientId),
|
eq(clientSitesAssociationsCache.clientId, olm.clientId),
|
||||||
eq(clientSites.siteId, siteId)
|
eq(clientSitesAssociationsCache.siteId, siteId)
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const addClientToSiteResourceBodySchema = z
|
const addClientToSiteResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -136,7 +136,7 @@ export async function addClientToSiteResource(
|
|||||||
siteResourceId
|
siteResourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const addRoleToSiteResourceBodySchema = z
|
const addRoleToSiteResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -146,7 +146,7 @@ export async function addRoleToSiteResource(
|
|||||||
siteResourceId
|
siteResourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const addUserToSiteResourceBodySchema = z
|
const addUserToSiteResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -115,7 +115,7 @@ export async function addUserToSiteResource(
|
|||||||
siteResourceId
|
siteResourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, newts, roleResources, roles, roleSiteResources } from "@server/db";
|
import {
|
||||||
import { siteResources, sites, orgs, SiteResource } from "@server/db";
|
clientSiteResources,
|
||||||
|
db,
|
||||||
|
newts,
|
||||||
|
roles,
|
||||||
|
roleSiteResources,
|
||||||
|
userSiteResources
|
||||||
|
} from "@server/db";
|
||||||
|
import { siteResources, sites, SiteResource } 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";
|
||||||
@@ -9,10 +16,8 @@ import { eq, and } from "drizzle-orm";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { addTargets } from "../client/targets";
|
|
||||||
import { getUniqueSiteResourceName } from "@server/db/names";
|
import { getUniqueSiteResourceName } from "@server/db/names";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
import { generateSubnetProxyTargets } from "@server/lib/ip";
|
|
||||||
|
|
||||||
const createSiteResourceParamsSchema = z.strictObject({
|
const createSiteResourceParamsSchema = z.strictObject({
|
||||||
siteId: z.string().transform(Number).pipe(z.int().positive()),
|
siteId: z.string().transform(Number).pipe(z.int().positive()),
|
||||||
@@ -23,12 +28,15 @@ const createSiteResourceSchema = z
|
|||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().min(1).max(255),
|
name: z.string().min(1).max(255),
|
||||||
mode: z.enum(["host", "cidr", "port"]),
|
mode: z.enum(["host", "cidr", "port"]),
|
||||||
protocol: z.enum(["tcp", "udp"]).optional(),
|
// protocol: z.enum(["tcp", "udp"]).optional(),
|
||||||
// proxyPort: z.int().positive().optional(),
|
// proxyPort: z.int().positive().optional(),
|
||||||
// destinationPort: z.int().positive().optional(),
|
// destinationPort: z.int().positive().optional(),
|
||||||
destination: z.string().min(1),
|
destination: z.string().min(1),
|
||||||
enabled: z.boolean().default(true),
|
enabled: z.boolean().default(true),
|
||||||
alias: z.string().optional()
|
alias: z.string().optional(),
|
||||||
|
userIds: z.array(z.string()),
|
||||||
|
roleIds: z.array(z.int()),
|
||||||
|
clientIds: z.array(z.int())
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
// .refine(
|
// .refine(
|
||||||
@@ -138,12 +146,15 @@ export async function createSiteResource(
|
|||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
mode,
|
mode,
|
||||||
protocol,
|
// protocol,
|
||||||
// proxyPort,
|
// proxyPort,
|
||||||
// destinationPort,
|
// destinationPort,
|
||||||
destination,
|
destination,
|
||||||
enabled,
|
enabled,
|
||||||
alias
|
alias,
|
||||||
|
userIds,
|
||||||
|
roleIds,
|
||||||
|
clientIds
|
||||||
} = parsedBody.data;
|
} = parsedBody.data;
|
||||||
|
|
||||||
// Verify the site exists and belongs to the org
|
// Verify the site exists and belongs to the org
|
||||||
@@ -194,7 +205,7 @@ export async function createSiteResource(
|
|||||||
orgId,
|
orgId,
|
||||||
name,
|
name,
|
||||||
mode,
|
mode,
|
||||||
protocol: mode === "port" ? protocol : null,
|
// protocol: mode === "port" ? protocol : null,
|
||||||
// proxyPort: mode === "port" ? proxyPort : null,
|
// proxyPort: mode === "port" ? proxyPort : null,
|
||||||
// destinationPort: mode === "port" ? destinationPort : null,
|
// destinationPort: mode === "port" ? destinationPort : null,
|
||||||
destination,
|
destination,
|
||||||
@@ -203,6 +214,10 @@ export async function createSiteResource(
|
|||||||
})
|
})
|
||||||
.returning();
|
.returning();
|
||||||
|
|
||||||
|
const siteResourceId = newSiteResource.siteResourceId;
|
||||||
|
|
||||||
|
//////////////////// update the associations ////////////////////
|
||||||
|
|
||||||
const [adminRole] = await trx
|
const [adminRole] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(roles)
|
.from(roles)
|
||||||
@@ -217,9 +232,34 @@ export async function createSiteResource(
|
|||||||
|
|
||||||
await trx.insert(roleSiteResources).values({
|
await trx.insert(roleSiteResources).values({
|
||||||
roleId: adminRole.roleId,
|
roleId: adminRole.roleId,
|
||||||
siteResourceId: newSiteResource.siteResourceId
|
siteResourceId: siteResourceId
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (roleIds.length > 0) {
|
||||||
|
await trx
|
||||||
|
.insert(roleSiteResources)
|
||||||
|
.values(
|
||||||
|
roleIds.map((roleId) => ({ roleId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userIds.length > 0) {
|
||||||
|
await trx
|
||||||
|
.insert(userSiteResources)
|
||||||
|
.values(
|
||||||
|
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientIds.length > 0) {
|
||||||
|
await trx.insert(clientSiteResources).values(
|
||||||
|
clientIds.map((clientId) => ({
|
||||||
|
clientId,
|
||||||
|
siteResourceId
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const [newt] = await trx
|
const [newt] = await trx
|
||||||
.select()
|
.select()
|
||||||
.from(newts)
|
.from(newts)
|
||||||
@@ -232,10 +272,10 @@ export async function createSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const targets = await generateSubnetProxyTargets([newSiteResource], trx);
|
// const targets = await generateSubnetProxyTargets([newSiteResource], trx);
|
||||||
await addTargets(newt.newtId, targets);
|
// await addTargets(newt.newtId, targets);
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(newSiteResource, trx); // we need to call this because we added to the admin role
|
await rebuildClientAssociations(newSiteResource, trx); // we need to call this because we added to the admin role
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!newSiteResource) {
|
if (!newSiteResource) {
|
||||||
|
|||||||
@@ -9,9 +9,7 @@ import { eq, and } from "drizzle-orm";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { removeTargets } from "../client/targets";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
|
||||||
import { generateSubnetProxyTargets } from "@server/lib/ip";
|
|
||||||
|
|
||||||
const deleteSiteResourceParamsSchema = z.strictObject({
|
const deleteSiteResourceParamsSchema = z.strictObject({
|
||||||
siteResourceId: z.string().transform(Number).pipe(z.int().positive()),
|
siteResourceId: z.string().transform(Number).pipe(z.int().positive()),
|
||||||
@@ -108,10 +106,10 @@ export async function deleteSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const targets = await generateSubnetProxyTargets([removedSiteResource], trx);
|
// const targets = await generateSubnetProxyTargets([removedSiteResource], trx);
|
||||||
await removeTargets(newt.newtId, targets);
|
// await removeTargets(newt.newtId, targets);
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(existingSiteResource, trx);
|
await rebuildClientAssociations(existingSiteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const removeClientFromSiteResourceBodySchema = z
|
const removeClientFromSiteResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -142,7 +142,7 @@ export async function removeClientFromSiteResource(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const removeRoleFromSiteResourceBodySchema = z
|
const removeRoleFromSiteResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -151,7 +151,7 @@ export async function removeRoleFromSiteResource(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const removeUserFromSiteResourceBodySchema = z
|
const removeUserFromSiteResourceBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -121,7 +121,7 @@ export async function removeUserFromSiteResource(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, inArray } from "drizzle-orm";
|
import { eq, inArray } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const setSiteResourceClientsBodySchema = z
|
const setSiteResourceClientsBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -119,17 +119,12 @@ export async function setSiteResourceClients(
|
|||||||
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
||||||
|
|
||||||
if (clientIds.length > 0) {
|
if (clientIds.length > 0) {
|
||||||
await Promise.all(
|
await trx
|
||||||
clientIds.map((clientId) =>
|
.insert(clientSiteResources)
|
||||||
trx
|
.values(clientIds.map((clientId) => ({ clientId, siteResourceId })));
|
||||||
.insert(clientSiteResources)
|
|
||||||
.values({ clientId, siteResourceId })
|
|
||||||
.returning()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq, and, ne, inArray } from "drizzle-orm";
|
import { eq, and, ne, inArray } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const setSiteResourceRolesBodySchema = z
|
const setSiteResourceRolesBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -141,16 +141,13 @@ export async function setSiteResourceRoles(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
await Promise.all(
|
if (roleIds.length > 0) {
|
||||||
roleIds.map((roleId) =>
|
await trx
|
||||||
trx
|
.insert(roleSiteResources)
|
||||||
.insert(roleSiteResources)
|
.values(roleIds.map((roleId) => ({ roleId, siteResourceId })));
|
||||||
.values({ roleId, siteResourceId })
|
}
|
||||||
.returning()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import logger from "@server/logger";
|
|||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import { eq } from "drizzle-orm";
|
import { eq } from "drizzle-orm";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { rebuildSiteClientAssociations } from "@server/lib/rebuildSiteClientAssociations";
|
import { rebuildClientAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
const setSiteResourceUsersBodySchema = z
|
const setSiteResourceUsersBodySchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -96,16 +96,13 @@ export async function setSiteResourceUsers(
|
|||||||
.delete(userSiteResources)
|
.delete(userSiteResources)
|
||||||
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
||||||
|
|
||||||
await Promise.all(
|
if (userIds.length > 0) {
|
||||||
userIds.map((userId) =>
|
await trx
|
||||||
trx
|
.insert(userSiteResources)
|
||||||
.insert(userSiteResources)
|
.values(userIds.map((userId) => ({ userId, siteResourceId })));
|
||||||
.values({ userId, siteResourceId })
|
}
|
||||||
.returning()
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
await rebuildSiteClientAssociations(siteResource, trx);
|
await rebuildClientAssociations(siteResource, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
|
|||||||
@@ -1,16 +1,28 @@
|
|||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import { db, newts, sites } from "@server/db";
|
import {
|
||||||
|
clientSiteResources,
|
||||||
|
db,
|
||||||
|
newts,
|
||||||
|
roles,
|
||||||
|
roleSiteResources,
|
||||||
|
sites,
|
||||||
|
userSiteResources
|
||||||
|
} from "@server/db";
|
||||||
import { siteResources, SiteResource } from "@server/db";
|
import { siteResources, SiteResource } 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";
|
||||||
import { eq, and } from "drizzle-orm";
|
import { eq, and, ne } from "drizzle-orm";
|
||||||
import { fromError } from "zod-validation-error";
|
import { fromError } from "zod-validation-error";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { updateTargets } from "@server/routers/client/targets";
|
import { updateTargets } from "@server/routers/client/targets";
|
||||||
import { generateSubnetProxyTargets } from "@server/lib/ip";
|
import { generateSingleSubnetProxyTargets } from "@server/lib/ip";
|
||||||
|
import {
|
||||||
|
getClientSiteResourceAccess,
|
||||||
|
rebuildClientAssociations
|
||||||
|
} from "@server/lib/rebuildClientAssociations";
|
||||||
|
|
||||||
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()),
|
||||||
@@ -23,12 +35,15 @@ const updateSiteResourceSchema = z
|
|||||||
name: z.string().min(1).max(255).optional(),
|
name: z.string().min(1).max(255).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(),
|
||||||
// proxyPort: z.int().positive().nullish(),
|
// proxyPort: z.int().positive().nullish(),
|
||||||
// destinationPort: z.int().positive().nullish(),
|
// destinationPort: z.int().positive().nullish(),
|
||||||
destination: z.string().min(1).optional(),
|
destination: z.string().min(1).optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
alias: z.string().nullish()
|
alias: z.string().nullish(),
|
||||||
|
userIds: z.array(z.string()),
|
||||||
|
roleIds: z.array(z.int()),
|
||||||
|
clientIds: z.array(z.int())
|
||||||
})
|
})
|
||||||
.strict();
|
.strict();
|
||||||
|
|
||||||
@@ -82,7 +97,16 @@ export async function updateSiteResource(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { siteResourceId, siteId, orgId } = parsedParams.data;
|
const { siteResourceId, siteId, orgId } = parsedParams.data;
|
||||||
const updateData = parsedBody.data;
|
const {
|
||||||
|
name,
|
||||||
|
mode,
|
||||||
|
destination,
|
||||||
|
alias,
|
||||||
|
enabled,
|
||||||
|
userIds,
|
||||||
|
roleIds,
|
||||||
|
clientIds
|
||||||
|
} = parsedBody.data;
|
||||||
|
|
||||||
const [site] = await db
|
const [site] = await db
|
||||||
.select()
|
.select()
|
||||||
@@ -113,85 +137,131 @@ export async function updateSiteResource(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine the final mode and validate port mode requirements
|
let updatedSiteResource: SiteResource | undefined;
|
||||||
const finalMode = updateData.mode || existingSiteResource.mode;
|
await db.transaction(async (trx) => {
|
||||||
const finalProtocol = updateData.protocol !== undefined ? updateData.protocol : existingSiteResource.protocol;
|
// Update the site resource
|
||||||
// const finalProxyPort = updateData.proxyPort !== undefined ? updateData.proxyPort : existingSiteResource.proxyPort;
|
[updatedSiteResource] = await trx
|
||||||
// const finalDestinationPort = updateData.destinationPort !== undefined ? updateData.destinationPort : existingSiteResource.destinationPort;
|
.update(siteResources)
|
||||||
|
.set({
|
||||||
// Prepare update data
|
name: name,
|
||||||
const updateValues: any = {};
|
mode: mode,
|
||||||
if (updateData.name !== undefined) updateValues.name = updateData.name;
|
destination: destination,
|
||||||
if (updateData.mode !== undefined) updateValues.mode = updateData.mode;
|
enabled: enabled,
|
||||||
if (updateData.destination !== undefined)
|
alias: alias && alias.trim() ? alias : null
|
||||||
updateValues.destination = updateData.destination;
|
})
|
||||||
if (updateData.enabled !== undefined)
|
.where(
|
||||||
updateValues.enabled = updateData.enabled;
|
and(
|
||||||
|
eq(siteResources.siteResourceId, siteResourceId),
|
||||||
// Handle nullish fields (can be undefined, null, or a value)
|
eq(siteResources.siteId, siteId),
|
||||||
if (updateData.alias !== undefined) {
|
eq(siteResources.orgId, orgId)
|
||||||
updateValues.alias =
|
)
|
||||||
updateData.alias && updateData.alias.trim()
|
|
||||||
? updateData.alias
|
|
||||||
: null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle port mode fields - include in update if explicitly provided (null or value) or if mode changed
|
|
||||||
// const isModeChangingFromPort =
|
|
||||||
// existingSiteResource.mode === "port" &&
|
|
||||||
// updateData.mode &&
|
|
||||||
// updateData.mode !== "port";
|
|
||||||
|
|
||||||
// if (updateData.protocol !== undefined || isModeChangingFromPort) {
|
|
||||||
// updateValues.protocol = finalMode === "port" ? finalProtocol : null;
|
|
||||||
// }
|
|
||||||
// if (updateData.proxyPort !== undefined || isModeChangingFromPort) {
|
|
||||||
// updateValues.proxyPort =
|
|
||||||
// finalMode === "port" ? finalProxyPort : null;
|
|
||||||
// }
|
|
||||||
// if (
|
|
||||||
// updateData.destinationPort !== undefined ||
|
|
||||||
// isModeChangingFromPort
|
|
||||||
// ) {
|
|
||||||
// updateValues.destinationPort =
|
|
||||||
// finalMode === "port" ? finalDestinationPort : null;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Update the site resource
|
|
||||||
const [updatedSiteResource] = await db
|
|
||||||
.update(siteResources)
|
|
||||||
.set(updateValues)
|
|
||||||
.where(
|
|
||||||
and(
|
|
||||||
eq(siteResources.siteResourceId, siteResourceId),
|
|
||||||
eq(siteResources.siteId, siteId),
|
|
||||||
eq(siteResources.orgId, orgId)
|
|
||||||
)
|
)
|
||||||
)
|
.returning();
|
||||||
.returning();
|
|
||||||
|
|
||||||
const [newt] = await db
|
//////////////////// update the associations ////////////////////
|
||||||
.select()
|
|
||||||
.from(newts)
|
|
||||||
.where(eq(newts.siteId, site.siteId))
|
|
||||||
.limit(1);
|
|
||||||
|
|
||||||
if (!newt) {
|
await trx
|
||||||
return next(createHttpError(HttpCode.NOT_FOUND, "Newt not found"));
|
.delete(clientSiteResources)
|
||||||
}
|
.where(eq(clientSiteResources.siteResourceId, siteResourceId));
|
||||||
|
|
||||||
const oldTargets = await generateSubnetProxyTargets([existingSiteResource]);
|
if (clientIds.length > 0) {
|
||||||
const newTargets = await generateSubnetProxyTargets([updatedSiteResource]);
|
await trx.insert(clientSiteResources).values(
|
||||||
|
clientIds.map((clientId) => ({
|
||||||
|
clientId,
|
||||||
|
siteResourceId
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await updateTargets(newt.newtId, {
|
await trx
|
||||||
oldTargets: oldTargets,
|
.delete(userSiteResources)
|
||||||
newTargets: newTargets
|
.where(eq(userSiteResources.siteResourceId, siteResourceId));
|
||||||
|
|
||||||
|
if (userIds.length > 0) {
|
||||||
|
await trx
|
||||||
|
.insert(userSiteResources)
|
||||||
|
.values(
|
||||||
|
userIds.map((userId) => ({ userId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all admin role IDs for this org to exclude from deletion
|
||||||
|
const adminRoles = await trx
|
||||||
|
.select()
|
||||||
|
.from(roles)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(roles.isAdmin, true),
|
||||||
|
eq(roles.orgId, updatedSiteResource.orgId)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
const adminRoleIds = adminRoles.map((role) => role.roleId);
|
||||||
|
|
||||||
|
if (adminRoleIds.length > 0) {
|
||||||
|
await trx.delete(roleSiteResources).where(
|
||||||
|
and(
|
||||||
|
eq(roleSiteResources.siteResourceId, siteResourceId),
|
||||||
|
ne(roleSiteResources.roleId, adminRoleIds[0]) // delete all but the admin role
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
await trx
|
||||||
|
.delete(roleSiteResources)
|
||||||
|
.where(
|
||||||
|
eq(roleSiteResources.siteResourceId, siteResourceId)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (roleIds.length > 0) {
|
||||||
|
await trx
|
||||||
|
.insert(roleSiteResources)
|
||||||
|
.values(
|
||||||
|
roleIds.map((roleId) => ({ roleId, siteResourceId }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { mergedAllClients } = await rebuildClientAssociations(
|
||||||
|
updatedSiteResource,
|
||||||
|
trx
|
||||||
|
); // we need to call this because we added to the admin role
|
||||||
|
|
||||||
|
// after everything is rebuilt above we still need to update the targets if the destination changed
|
||||||
|
if (
|
||||||
|
existingSiteResource.destination !==
|
||||||
|
updatedSiteResource.destination
|
||||||
|
) {
|
||||||
|
const [newt] = await trx
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, site.siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!newt) {
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.NOT_FOUND, "Newt not found")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const oldTargets = generateSingleSubnetProxyTargets(
|
||||||
|
existingSiteResource,
|
||||||
|
mergedAllClients
|
||||||
|
);
|
||||||
|
const newTargets = generateSingleSubnetProxyTargets(
|
||||||
|
updatedSiteResource,
|
||||||
|
mergedAllClients
|
||||||
|
);
|
||||||
|
|
||||||
|
await updateTargets(newt.newtId, {
|
||||||
|
oldTargets: oldTargets,
|
||||||
|
newTargets: newTargets
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(
|
||||||
|
`Updated site resource ${siteResourceId} for site ${siteId}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
logger.info(
|
|
||||||
`Updated site resource ${siteResourceId} for site ${siteId}`
|
|
||||||
);
|
|
||||||
|
|
||||||
return response(res, {
|
return response(res, {
|
||||||
data: updatedSiteResource,
|
data: updatedSiteResource,
|
||||||
success: true,
|
success: true,
|
||||||
|
|||||||
@@ -37,13 +37,7 @@ import { ListSitesResponse } from "@server/routers/site";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
|
|
||||||
const GeneralFormSchema = z.object({
|
const GeneralFormSchema = z.object({
|
||||||
name: z.string().nonempty("Name is required"),
|
name: z.string().nonempty("Name is required")
|
||||||
siteIds: z.array(
|
|
||||||
z.object({
|
|
||||||
id: z.string(),
|
|
||||||
text: z.string()
|
|
||||||
})
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
|
|
||||||
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
type GeneralFormValues = z.infer<typeof GeneralFormSchema>;
|
||||||
@@ -54,15 +48,11 @@ export default function GeneralPage() {
|
|||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [sites, setSites] = useState<Tag[]>([]);
|
|
||||||
const [clientSites, setClientSites] = useState<Tag[]>([]);
|
|
||||||
const [activeSitesTagIndex, setActiveSitesTagIndex] = useState<number | null>(null);
|
|
||||||
|
|
||||||
const form = useForm({
|
const form = useForm({
|
||||||
resolver: zodResolver(GeneralFormSchema),
|
resolver: zodResolver(GeneralFormSchema),
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: client?.name,
|
name: client?.name
|
||||||
siteIds: []
|
|
||||||
},
|
},
|
||||||
mode: "onChange"
|
mode: "onChange"
|
||||||
});
|
});
|
||||||
@@ -75,23 +65,6 @@ export default function GeneralPage() {
|
|||||||
const res = await api.get<AxiosResponse<ListSitesResponse>>(
|
const res = await api.get<AxiosResponse<ListSitesResponse>>(
|
||||||
`/org/${client?.orgId}/sites/`
|
`/org/${client?.orgId}/sites/`
|
||||||
);
|
);
|
||||||
|
|
||||||
const availableSites = res.data.data.sites
|
|
||||||
.filter((s) => s.type === "newt" && s.subnet)
|
|
||||||
.map((site) => ({
|
|
||||||
id: site.siteId.toString(),
|
|
||||||
text: site.name
|
|
||||||
}));
|
|
||||||
|
|
||||||
setSites(availableSites);
|
|
||||||
|
|
||||||
// Filter sites to only include those assigned to the client
|
|
||||||
const assignedSites = availableSites.filter((site) =>
|
|
||||||
client?.siteIds?.includes(parseInt(site.id))
|
|
||||||
);
|
|
||||||
setClientSites(assignedSites);
|
|
||||||
// Set the default values for the form
|
|
||||||
form.setValue("siteIds", assignedSites);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
@@ -114,8 +87,7 @@ export default function GeneralPage() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await api.post(`/client/${client?.clientId}`, {
|
await api.post(`/client/${client?.clientId}`, {
|
||||||
name: data.name,
|
name: data.name
|
||||||
siteIds: data.siteIds.map(site => parseInt(site.id))
|
|
||||||
});
|
});
|
||||||
|
|
||||||
updateClient({ name: data.name });
|
updateClient({ name: data.name });
|
||||||
@@ -130,10 +102,7 @@ export default function GeneralPage() {
|
|||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t("clientUpdateFailed"),
|
title: t("clientUpdateFailed"),
|
||||||
description: formatAxiosError(
|
description: formatAxiosError(e, t("clientUpdateError"))
|
||||||
e,
|
|
||||||
t("clientUpdateError")
|
|
||||||
)
|
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
|
|||||||
@@ -112,11 +112,11 @@ export default async function ResourcesPage(props: ResourcesPageProps) {
|
|||||||
siteName: siteResource.siteName,
|
siteName: siteResource.siteName,
|
||||||
siteAddress: siteResource.siteAddress || null,
|
siteAddress: siteResource.siteAddress || null,
|
||||||
mode: siteResource.mode || ("port" as any),
|
mode: siteResource.mode || ("port" as any),
|
||||||
protocol: siteResource.protocol,
|
// protocol: siteResource.protocol,
|
||||||
proxyPort: siteResource.proxyPort,
|
// proxyPort: siteResource.proxyPort,
|
||||||
siteId: siteResource.siteId,
|
siteId: siteResource.siteId,
|
||||||
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
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
mode: z.enum(["host", "cidr"]),
|
mode: z.enum(["host", "cidr"]),
|
||||||
destination: z.string().min(1),
|
destination: z.string().min(1),
|
||||||
siteId: z.int().positive(t("createInternalResourceDialogPleaseSelectSite")),
|
siteId: z.int().positive(t("createInternalResourceDialogPleaseSelectSite")),
|
||||||
protocol: z.enum(["tcp", "udp"]),
|
// protocol: z.enum(["tcp", "udp"]),
|
||||||
// proxyPort: z.int()
|
// proxyPort: z.int()
|
||||||
// .positive()
|
// .positive()
|
||||||
// .min(1, t("createInternalResourceDialogProxyPortMin"))
|
// .min(1, t("createInternalResourceDialogProxyPortMin"))
|
||||||
@@ -177,7 +177,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
name: "",
|
name: "",
|
||||||
siteId: availableSites[0]?.siteId || 0,
|
siteId: availableSites[0]?.siteId || 0,
|
||||||
mode: "host",
|
mode: "host",
|
||||||
protocol: "tcp",
|
// protocol: "tcp",
|
||||||
// proxyPort: undefined,
|
// proxyPort: undefined,
|
||||||
destination: "",
|
destination: "",
|
||||||
// destinationPort: undefined,
|
// destinationPort: undefined,
|
||||||
@@ -196,7 +196,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
name: "",
|
name: "",
|
||||||
siteId: availableSites[0].siteId,
|
siteId: availableSites[0].siteId,
|
||||||
mode: "host",
|
mode: "host",
|
||||||
protocol: "tcp",
|
// protocol: "tcp",
|
||||||
// proxyPort: undefined,
|
// proxyPort: undefined,
|
||||||
destination: "",
|
destination: "",
|
||||||
// destinationPort: undefined,
|
// destinationPort: undefined,
|
||||||
@@ -260,35 +260,38 @@ export default function CreateInternalResourceDialog({
|
|||||||
{
|
{
|
||||||
name: data.name,
|
name: data.name,
|
||||||
mode: data.mode,
|
mode: data.mode,
|
||||||
protocol: data.mode === "port" ? data.protocol : undefined,
|
// protocol: data.protocol,
|
||||||
// proxyPort: data.mode === "port" ? data.proxyPort : undefined,
|
// proxyPort: data.mode === "port" ? data.proxyPort : undefined,
|
||||||
// destinationPort: data.mode === "port" ? data.destinationPort : undefined,
|
// destinationPort: data.mode === "port" ? data.destinationPort : undefined,
|
||||||
destination: data.destination,
|
destination: data.destination,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
alias: data.alias && typeof data.alias === "string" && data.alias.trim() ? data.alias : undefined
|
alias: data.alias && typeof data.alias === "string" && data.alias.trim() ? data.alias : undefined,
|
||||||
|
roleIds: data.roles ? data.roles.map((r) => parseInt(r.id)) : [],
|
||||||
|
userIds: data.users ? data.users.map((u) => u.id) : [],
|
||||||
|
clientIds: data.clients ? data.clients.map((c) => parseInt(c.id)) : []
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const siteResourceId = response.data.data.siteResourceId;
|
const siteResourceId = response.data.data.siteResourceId;
|
||||||
|
|
||||||
// Set roles and users if provided
|
// // Set roles and users if provided
|
||||||
if (data.roles && data.roles.length > 0) {
|
// if (data.roles && data.roles.length > 0) {
|
||||||
await api.post(`/site-resource/${siteResourceId}/roles`, {
|
// await api.post(`/site-resource/${siteResourceId}/roles`, {
|
||||||
roleIds: data.roles.map((r) => parseInt(r.id))
|
// roleIds: data.roles.map((r) => parseInt(r.id))
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (data.users && data.users.length > 0) {
|
// if (data.users && data.users.length > 0) {
|
||||||
await api.post(`/site-resource/${siteResourceId}/users`, {
|
// await api.post(`/site-resource/${siteResourceId}/users`, {
|
||||||
userIds: data.users.map((u) => u.id)
|
// userIds: data.users.map((u) => u.id)
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
if (data.clients && data.clients.length > 0) {
|
// if (data.clients && data.clients.length > 0) {
|
||||||
await api.post(`/site-resource/${siteResourceId}/clients`, {
|
// await api.post(`/site-resource/${siteResourceId}/clients`, {
|
||||||
clientIds: data.clients.map((c) => parseInt(c.id))
|
// clientIds: data.clients.map((c) => parseInt(c.id))
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("createInternalResourceDialogSuccess"),
|
title: t("createInternalResourceDialogSuccess"),
|
||||||
@@ -444,7 +447,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="port">{t("createInternalResourceDialogModePort")}</SelectItem>
|
{/* <SelectItem value="port">{t("createInternalResourceDialogModePort")}</SelectItem> */}
|
||||||
<SelectItem value="host">{t("createInternalResourceDialogModeHost")}</SelectItem>
|
<SelectItem value="host">{t("createInternalResourceDialogModeHost")}</SelectItem>
|
||||||
<SelectItem value="cidr">{t("createInternalResourceDialogModeCidr")}</SelectItem>
|
<SelectItem value="cidr">{t("createInternalResourceDialogModeCidr")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -535,7 +538,7 @@ export default function CreateInternalResourceDialog({
|
|||||||
<FormDescription>
|
<FormDescription>
|
||||||
{mode === "host" && t("createInternalResourceDialogDestinationHostDescription")}
|
{mode === "host" && t("createInternalResourceDialogDestinationHostDescription")}
|
||||||
{mode === "cidr" && t("createInternalResourceDialogDestinationCidrDescription")}
|
{mode === "cidr" && t("createInternalResourceDialogDestinationCidrDescription")}
|
||||||
{mode === "port" && t("createInternalResourceDialogDestinationIPDescription")}
|
{/* {mode === "port" && t("createInternalResourceDialogDestinationIPDescription")} */}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ import { toast } from "@app/hooks/useToast";
|
|||||||
import { useTranslations } from "next-intl";
|
import { useTranslations } from "next-intl";
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { Separator } from "@app/components/ui/separator";
|
|
||||||
import { ListRolesResponse } from "@server/routers/role";
|
import { ListRolesResponse } from "@server/routers/role";
|
||||||
import { ListUsersResponse } from "@server/routers/user";
|
import { ListUsersResponse } from "@server/routers/user";
|
||||||
import { ListSiteResourceRolesResponse } from "@server/routers/siteResource/listSiteResourceRoles";
|
import { ListSiteResourceRolesResponse } from "@server/routers/siteResource/listSiteResourceRoles";
|
||||||
@@ -52,12 +51,13 @@ type InternalResourceData = {
|
|||||||
name: string;
|
name: string;
|
||||||
orgId: string;
|
orgId: string;
|
||||||
siteName: string;
|
siteName: string;
|
||||||
mode: "host" | "cidr" | "port";
|
// mode: "host" | "cidr" | "port";
|
||||||
protocol: string | null;
|
mode: "host" | "cidr";
|
||||||
proxyPort: number | null;
|
// protocol: string | null;
|
||||||
|
// proxyPort: number | null;
|
||||||
siteId: number;
|
siteId: number;
|
||||||
destination: string;
|
destination: string;
|
||||||
destinationPort?: number | null;
|
// destinationPort?: number | null;
|
||||||
alias?: string | null;
|
alias?: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,10 +83,10 @@ export default function EditInternalResourceDialog({
|
|||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z.string().min(1, t("editInternalResourceDialogNameRequired")).max(255, t("editInternalResourceDialogNameMaxLength")),
|
name: z.string().min(1, t("editInternalResourceDialogNameRequired")).max(255, t("editInternalResourceDialogNameMaxLength")),
|
||||||
mode: z.enum(["host", "cidr", "port"]),
|
mode: z.enum(["host", "cidr", "port"]),
|
||||||
protocol: z.enum(["tcp", "udp"]).nullish(),
|
// protocol: z.enum(["tcp", "udp"]).nullish(),
|
||||||
proxyPort: z.int().positive().min(1, t("editInternalResourceDialogProxyPortMin")).max(65535, t("editInternalResourceDialogProxyPortMax")).nullish(),
|
// proxyPort: z.int().positive().min(1, t("editInternalResourceDialogProxyPortMin")).max(65535, t("editInternalResourceDialogProxyPortMax")).nullish(),
|
||||||
destination: z.string().min(1),
|
destination: z.string().min(1),
|
||||||
destinationPort: z.int().positive().min(1, t("editInternalResourceDialogDestinationPortMin")).max(65535, t("editInternalResourceDialogDestinationPortMax")).nullish(),
|
// destinationPort: z.int().positive().min(1, t("editInternalResourceDialogDestinationPortMin")).max(65535, t("editInternalResourceDialogDestinationPortMax")).nullish(),
|
||||||
alias: z.string().nullish(),
|
alias: z.string().nullish(),
|
||||||
roles: z.array(
|
roles: z.array(
|
||||||
z.object({
|
z.object({
|
||||||
@@ -107,42 +107,42 @@ export default function EditInternalResourceDialog({
|
|||||||
})
|
})
|
||||||
).optional()
|
).optional()
|
||||||
})
|
})
|
||||||
.refine(
|
// .refine(
|
||||||
(data) => {
|
// (data) => {
|
||||||
if (data.mode === "port") {
|
// if (data.mode === "port") {
|
||||||
return data.protocol !== undefined && data.protocol !== null;
|
// return data.protocol !== undefined && data.protocol !== null;
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
message: t("editInternalResourceDialogProtocol") + " is required for port mode",
|
// message: t("editInternalResourceDialogProtocol") + " is required for port mode",
|
||||||
path: ["protocol"]
|
// path: ["protocol"]
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
.refine(
|
// .refine(
|
||||||
(data) => {
|
// (data) => {
|
||||||
if (data.mode === "port") {
|
// if (data.mode === "port") {
|
||||||
return data.proxyPort !== undefined && data.proxyPort !== null;
|
// return data.proxyPort !== undefined && data.proxyPort !== null;
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
message: t("editInternalResourceDialogSitePort") + " is required for port mode",
|
// message: t("editInternalResourceDialogSitePort") + " is required for port mode",
|
||||||
path: ["proxyPort"]
|
// path: ["proxyPort"]
|
||||||
}
|
// }
|
||||||
)
|
// )
|
||||||
.refine(
|
// .refine(
|
||||||
(data) => {
|
// (data) => {
|
||||||
if (data.mode === "port") {
|
// if (data.mode === "port") {
|
||||||
return data.destinationPort !== undefined && data.destinationPort !== null;
|
// return data.destinationPort !== undefined && data.destinationPort !== null;
|
||||||
}
|
// }
|
||||||
return true;
|
// return true;
|
||||||
},
|
// },
|
||||||
{
|
// {
|
||||||
message: t("targetPort") + " is required for port mode",
|
// message: t("targetPort") + " is required for port mode",
|
||||||
path: ["destinationPort"]
|
// path: ["destinationPort"]
|
||||||
}
|
// }
|
||||||
);
|
// );
|
||||||
|
|
||||||
type FormData = z.infer<typeof formSchema>;
|
type FormData = z.infer<typeof formSchema>;
|
||||||
|
|
||||||
@@ -160,10 +160,10 @@ export default function EditInternalResourceDialog({
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
name: resource.name,
|
name: resource.name,
|
||||||
mode: resource.mode || "host",
|
mode: resource.mode || "host",
|
||||||
protocol: (resource.protocol as "tcp" | "udp" | null | undefined) ?? undefined,
|
// protocol: (resource.protocol as "tcp" | "udp" | null | undefined) ?? undefined,
|
||||||
proxyPort: resource.proxyPort ?? undefined,
|
// proxyPort: resource.proxyPort ?? undefined,
|
||||||
destination: resource.destination || "",
|
destination: resource.destination || "",
|
||||||
destinationPort: resource.destinationPort ?? undefined,
|
// destinationPort: resource.destinationPort ?? undefined,
|
||||||
alias: resource.alias ?? null,
|
alias: resource.alias ?? null,
|
||||||
roles: [],
|
roles: [],
|
||||||
users: [],
|
users: [],
|
||||||
@@ -277,10 +277,10 @@ export default function EditInternalResourceDialog({
|
|||||||
form.reset({
|
form.reset({
|
||||||
name: resource.name,
|
name: resource.name,
|
||||||
mode: resource.mode || "host",
|
mode: resource.mode || "host",
|
||||||
protocol: (resource.protocol as "tcp" | "udp" | null | undefined) ?? undefined,
|
// protocol: (resource.protocol as "tcp" | "udp" | null | undefined) ?? undefined,
|
||||||
proxyPort: resource.proxyPort ?? undefined,
|
// proxyPort: resource.proxyPort ?? undefined,
|
||||||
destination: resource.destination || "",
|
destination: resource.destination || "",
|
||||||
destinationPort: resource.destinationPort ?? undefined,
|
// destinationPort: resource.destinationPort ?? undefined,
|
||||||
alias: resource.alias ?? null,
|
alias: resource.alias ?? null,
|
||||||
roles: [],
|
roles: [],
|
||||||
users: [],
|
users: [],
|
||||||
@@ -298,25 +298,28 @@ export default function EditInternalResourceDialog({
|
|||||||
await api.post(`/org/${orgId}/site/${resource.siteId}/resource/${resource.id}`, {
|
await api.post(`/org/${orgId}/site/${resource.siteId}/resource/${resource.id}`, {
|
||||||
name: data.name,
|
name: data.name,
|
||||||
mode: data.mode,
|
mode: data.mode,
|
||||||
protocol: data.mode === "port" ? data.protocol : null,
|
// protocol: data.mode === "port" ? data.protocol : null,
|
||||||
proxyPort: data.mode === "port" ? data.proxyPort : null,
|
// proxyPort: data.mode === "port" ? data.proxyPort : null,
|
||||||
destinationPort: data.mode === "port" ? data.destinationPort : null,
|
// destinationPort: data.mode === "port" ? data.destinationPort : null,
|
||||||
destination: data.destination,
|
destination: data.destination,
|
||||||
alias: data.alias && typeof data.alias === "string" && data.alias.trim() ? data.alias : null
|
alias: data.alias && typeof data.alias === "string" && data.alias.trim() ? data.alias : null,
|
||||||
|
roleIds: (data.roles || []).map((r) => parseInt(r.id)),
|
||||||
|
userIds: (data.users || []).map((u) => u.id),
|
||||||
|
clientIds: (data.clients || []).map((c) => parseInt(c.id))
|
||||||
});
|
});
|
||||||
|
|
||||||
// Update roles, users, and clients
|
// Update roles, users, and clients
|
||||||
await Promise.all([
|
// await Promise.all([
|
||||||
api.post(`/site-resource/${resource.id}/roles`, {
|
// api.post(`/site-resource/${resource.id}/roles`, {
|
||||||
roleIds: (data.roles || []).map((r) => parseInt(r.id))
|
// roleIds: (data.roles || []).map((r) => parseInt(r.id))
|
||||||
}),
|
// }),
|
||||||
api.post(`/site-resource/${resource.id}/users`, {
|
// api.post(`/site-resource/${resource.id}/users`, {
|
||||||
userIds: (data.users || []).map((u) => u.id)
|
// userIds: (data.users || []).map((u) => u.id)
|
||||||
}),
|
// }),
|
||||||
api.post(`/site-resource/${resource.id}/clients`, {
|
// api.post(`/site-resource/${resource.id}/clients`, {
|
||||||
clientIds: (data.clients || []).map((c) => parseInt(c.id))
|
// clientIds: (data.clients || []).map((c) => parseInt(c.id))
|
||||||
})
|
// })
|
||||||
]);
|
// ]);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: t("editInternalResourceDialogSuccess"),
|
title: t("editInternalResourceDialogSuccess"),
|
||||||
@@ -384,7 +387,7 @@ export default function EditInternalResourceDialog({
|
|||||||
</SelectTrigger>
|
</SelectTrigger>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<SelectContent>
|
<SelectContent>
|
||||||
<SelectItem value="port">{t("editInternalResourceDialogModePort")}</SelectItem>
|
{/* <SelectItem value="port">{t("editInternalResourceDialogModePort")}</SelectItem> */}
|
||||||
<SelectItem value="host">{t("editInternalResourceDialogModeHost")}</SelectItem>
|
<SelectItem value="host">{t("editInternalResourceDialogModeHost")}</SelectItem>
|
||||||
<SelectItem value="cidr">{t("editInternalResourceDialogModeCidr")}</SelectItem>
|
<SelectItem value="cidr">{t("editInternalResourceDialogModeCidr")}</SelectItem>
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
@@ -394,7 +397,7 @@ export default function EditInternalResourceDialog({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{mode === "port" && (
|
{/* {mode === "port" && (
|
||||||
<div className="grid grid-cols-2 gap-4">
|
<div className="grid grid-cols-2 gap-4">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
@@ -439,7 +442,7 @@ export default function EditInternalResourceDialog({
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -459,14 +462,14 @@ export default function EditInternalResourceDialog({
|
|||||||
<FormDescription>
|
<FormDescription>
|
||||||
{mode === "host" && t("editInternalResourceDialogDestinationHostDescription")}
|
{mode === "host" && t("editInternalResourceDialogDestinationHostDescription")}
|
||||||
{mode === "cidr" && t("editInternalResourceDialogDestinationCidrDescription")}
|
{mode === "cidr" && t("editInternalResourceDialogDestinationCidrDescription")}
|
||||||
{mode === "port" && t("editInternalResourceDialogDestinationIPDescription")}
|
{/* {mode === "port" && t("editInternalResourceDialogDestinationIPDescription")} */}
|
||||||
</FormDescription>
|
</FormDescription>
|
||||||
<FormMessage />
|
<FormMessage />
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{mode === "port" && (
|
{/* {mode === "port" && (
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="destinationPort"
|
name="destinationPort"
|
||||||
@@ -484,7 +487,7 @@ export default function EditInternalResourceDialog({
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)} */}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import {
|
|||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
DropdownMenuLabel,
|
DropdownMenuLabel,
|
||||||
DropdownMenuSeparator,
|
DropdownMenuSeparator,
|
||||||
DropdownMenuCheckboxItem
|
DropdownMenuCheckboxItem
|
||||||
} from "@app/components/ui/dropdown-menu";
|
} from "@app/components/ui/dropdown-menu";
|
||||||
import { Button } from "@app/components/ui/button";
|
import { Button } from "@app/components/ui/button";
|
||||||
@@ -164,13 +164,14 @@ export type InternalResourceRow = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
siteName: string;
|
siteName: string;
|
||||||
siteAddress: string | null;
|
siteAddress: string | null;
|
||||||
mode: "host" | "cidr" | "port";
|
// mode: "host" | "cidr" | "port";
|
||||||
protocol: string | null;
|
mode: "host" | "cidr";
|
||||||
proxyPort: number | null;
|
// protocol: string | null;
|
||||||
|
// proxyPort: number | null;
|
||||||
siteId: number;
|
siteId: number;
|
||||||
siteNiceId: string;
|
siteNiceId: string;
|
||||||
destination: string;
|
destination: string;
|
||||||
destinationPort: number | null;
|
// destinationPort: number | null;
|
||||||
alias: string | null;
|
alias: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -515,10 +516,14 @@ export default function ResourcesTable({
|
|||||||
>
|
>
|
||||||
<StatusIcon status={overallStatus} />
|
<StatusIcon status={overallStatus} />
|
||||||
<span className="text-sm">
|
<span className="text-sm">
|
||||||
{overallStatus === "online" && t("resourcesTableHealthy")}
|
{overallStatus === "online" &&
|
||||||
{overallStatus === "degraded" && t("resourcesTableDegraded")}
|
t("resourcesTableHealthy")}
|
||||||
{overallStatus === "offline" && t("resourcesTableOffline")}
|
{overallStatus === "degraded" &&
|
||||||
{overallStatus === "unknown" && t("resourcesTableUnknown")}
|
t("resourcesTableDegraded")}
|
||||||
|
{overallStatus === "offline" &&
|
||||||
|
t("resourcesTableOffline")}
|
||||||
|
{overallStatus === "unknown" &&
|
||||||
|
t("resourcesTableUnknown")}
|
||||||
</span>
|
</span>
|
||||||
<ChevronDown className="h-3 w-3" />
|
<ChevronDown className="h-3 w-3" />
|
||||||
</Button>
|
</Button>
|
||||||
@@ -770,7 +775,11 @@ export default function ResourcesTable({
|
|||||||
<div className="flex flex-col items-end gap-1 p-3">
|
<div className="flex flex-col items-end gap-1 p-3">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="ghost" size="sm" className="h-7 w-7 p-0">
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 w-7 p-0"
|
||||||
|
>
|
||||||
<Columns className="h-4 w-4" />
|
<Columns className="h-4 w-4" />
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
{t("columns") || "Columns"}
|
{t("columns") || "Columns"}
|
||||||
@@ -786,10 +795,14 @@ export default function ResourcesTable({
|
|||||||
.getAllColumns()
|
.getAllColumns()
|
||||||
.filter((column) => column.getCanHide())
|
.filter((column) => column.getCanHide())
|
||||||
.map((column) => {
|
.map((column) => {
|
||||||
const columnDef = column.columnDef as any;
|
const columnDef =
|
||||||
const friendlyName = columnDef.friendlyName;
|
column.columnDef as any;
|
||||||
const displayName = friendlyName ||
|
const friendlyName =
|
||||||
(typeof columnDef.header === "string"
|
columnDef.friendlyName;
|
||||||
|
const displayName =
|
||||||
|
friendlyName ||
|
||||||
|
(typeof columnDef.header ===
|
||||||
|
"string"
|
||||||
? columnDef.header
|
? columnDef.header
|
||||||
: column.id);
|
: column.id);
|
||||||
return (
|
return (
|
||||||
@@ -798,9 +811,13 @@ export default function ResourcesTable({
|
|||||||
className="capitalize"
|
className="capitalize"
|
||||||
checked={column.getIsVisible()}
|
checked={column.getIsVisible()}
|
||||||
onCheckedChange={(value) =>
|
onCheckedChange={(value) =>
|
||||||
column.toggleVisibility(!!value)
|
column.toggleVisibility(
|
||||||
|
!!value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onSelect={(e) =>
|
||||||
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
onSelect={(e) => e.preventDefault()}
|
|
||||||
>
|
>
|
||||||
{displayName}
|
{displayName}
|
||||||
</DropdownMenuCheckboxItem>
|
</DropdownMenuCheckboxItem>
|
||||||
@@ -925,23 +942,24 @@ export default function ResourcesTable({
|
|||||||
let displayText: string;
|
let displayText: string;
|
||||||
let copyText: string;
|
let copyText: string;
|
||||||
|
|
||||||
if (
|
// if (
|
||||||
resourceRow.mode === "port" &&
|
// resourceRow.mode === "port" &&
|
||||||
resourceRow.protocol &&
|
// // resourceRow.protocol &&
|
||||||
resourceRow.proxyPort &&
|
// // resourceRow.proxyPort &&
|
||||||
resourceRow.destinationPort
|
// // resourceRow.destinationPort
|
||||||
) {
|
// ) {
|
||||||
const protocol = resourceRow.protocol.toUpperCase();
|
// // const protocol = resourceRow.protocol.toUpperCase();
|
||||||
// For port mode: site part uses alias or site address, destination part uses destination IP
|
// // For port mode: site part uses alias or site address, destination part uses destination IP
|
||||||
// If site address has CIDR notation, extract just the IP address
|
// // If site address has CIDR notation, extract just the IP address
|
||||||
let siteAddress = resourceRow.siteAddress;
|
// let siteAddress = resourceRow.siteAddress;
|
||||||
if (siteAddress && siteAddress.includes("/")) {
|
// if (siteAddress && siteAddress.includes("/")) {
|
||||||
siteAddress = siteAddress.split("/")[0];
|
// siteAddress = siteAddress.split("/")[0];
|
||||||
}
|
// }
|
||||||
const siteDisplay = resourceRow.alias || siteAddress;
|
// const siteDisplay = resourceRow.alias || siteAddress;
|
||||||
displayText = `${protocol} ${siteDisplay}:${resourceRow.proxyPort} -> ${resourceRow.destination}:${resourceRow.destinationPort}`;
|
// // displayText = `${protocol} ${siteDisplay}:${resourceRow.proxyPort} -> ${resourceRow.destination}:${resourceRow.destinationPort}`;
|
||||||
copyText = `${siteDisplay}:${resourceRow.proxyPort}`;
|
// // copyText = `${siteDisplay}:${resourceRow.proxyPort}`;
|
||||||
} else if (resourceRow.mode === "host") {
|
// } else if (resourceRow.mode === "host") {
|
||||||
|
if (resourceRow.mode === "host") {
|
||||||
// For host mode: use alias if available, otherwise use destination
|
// For host mode: use alias if available, otherwise use destination
|
||||||
const destinationDisplay =
|
const destinationDisplay =
|
||||||
resourceRow.alias || resourceRow.destination;
|
resourceRow.alias || resourceRow.destination;
|
||||||
@@ -981,7 +999,11 @@ export default function ResourcesTable({
|
|||||||
<div className="flex flex-col items-end gap-1 p-3">
|
<div className="flex flex-col items-end gap-1 p-3">
|
||||||
<DropdownMenu>
|
<DropdownMenu>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button variant="outline" size="sm" className="h-7 w-7 p-0">
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
className="h-7 w-7 p-0"
|
||||||
|
>
|
||||||
<Columns className="h-4 w-4" />
|
<Columns className="h-4 w-4" />
|
||||||
<span className="sr-only">
|
<span className="sr-only">
|
||||||
{t("columns") || "Columns"}
|
{t("columns") || "Columns"}
|
||||||
@@ -997,10 +1019,14 @@ export default function ResourcesTable({
|
|||||||
.getAllColumns()
|
.getAllColumns()
|
||||||
.filter((column) => column.getCanHide())
|
.filter((column) => column.getCanHide())
|
||||||
.map((column) => {
|
.map((column) => {
|
||||||
const columnDef = column.columnDef as any;
|
const columnDef =
|
||||||
const friendlyName = columnDef.friendlyName;
|
column.columnDef as any;
|
||||||
const displayName = friendlyName ||
|
const friendlyName =
|
||||||
(typeof columnDef.header === "string"
|
columnDef.friendlyName;
|
||||||
|
const displayName =
|
||||||
|
friendlyName ||
|
||||||
|
(typeof columnDef.header ===
|
||||||
|
"string"
|
||||||
? columnDef.header
|
? columnDef.header
|
||||||
: column.id);
|
: column.id);
|
||||||
return (
|
return (
|
||||||
@@ -1009,9 +1035,13 @@ export default function ResourcesTable({
|
|||||||
className="capitalize"
|
className="capitalize"
|
||||||
checked={column.getIsVisible()}
|
checked={column.getIsVisible()}
|
||||||
onCheckedChange={(value) =>
|
onCheckedChange={(value) =>
|
||||||
column.toggleVisibility(!!value)
|
column.toggleVisibility(
|
||||||
|
!!value
|
||||||
|
)
|
||||||
|
}
|
||||||
|
onSelect={(e) =>
|
||||||
|
e.preventDefault()
|
||||||
}
|
}
|
||||||
onSelect={(e) => e.preventDefault()}
|
|
||||||
>
|
>
|
||||||
{displayName}
|
{displayName}
|
||||||
</DropdownMenuCheckboxItem>
|
</DropdownMenuCheckboxItem>
|
||||||
@@ -1230,99 +1260,111 @@ export default function ResourcesTable({
|
|||||||
<TabsContent value="proxy">
|
<TabsContent value="proxy">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{proxyTable
|
{proxyTable
|
||||||
.getHeaderGroups()
|
.getHeaderGroups()
|
||||||
.map((headerGroup) => (
|
.map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers
|
|
||||||
.filter((header) =>
|
|
||||||
header.column.getIsVisible()
|
|
||||||
)
|
|
||||||
.map((header) => (
|
|
||||||
<TableHead
|
|
||||||
key={header.id}
|
|
||||||
className={`whitespace-nowrap ${
|
|
||||||
header.column.id ===
|
|
||||||
"actions"
|
|
||||||
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
|
||||||
: header.column.id ===
|
|
||||||
"name"
|
|
||||||
? "md:sticky md:left-0 z-10 bg-card"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header
|
|
||||||
.column
|
|
||||||
.columnDef
|
|
||||||
.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{proxyTable.getRowModel().rows
|
|
||||||
?.length ? (
|
|
||||||
proxyTable
|
|
||||||
.getRowModel()
|
|
||||||
.rows.map((row) => (
|
|
||||||
<TableRow
|
<TableRow
|
||||||
key={row.id}
|
key={headerGroup.id}
|
||||||
data-state={
|
|
||||||
row.getIsSelected() &&
|
|
||||||
"selected"
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{row
|
{headerGroup.headers
|
||||||
.getVisibleCells()
|
.filter((header) =>
|
||||||
.map((cell) => (
|
header.column.getIsVisible()
|
||||||
<TableCell
|
)
|
||||||
|
.map((header) => (
|
||||||
|
<TableHead
|
||||||
key={
|
key={
|
||||||
cell.id
|
header.id
|
||||||
}
|
}
|
||||||
className={`whitespace-nowrap ${
|
className={`whitespace-nowrap ${
|
||||||
cell.column.id ===
|
header
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
"actions"
|
"actions"
|
||||||
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
: cell.column.id ===
|
: header
|
||||||
"name"
|
.column
|
||||||
? "md:sticky md:left-0 z-10 bg-card"
|
.id ===
|
||||||
: ""
|
"name"
|
||||||
|
? "md:sticky md:left-0 z-10 bg-card"
|
||||||
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{flexRender(
|
{header.isPlaceholder
|
||||||
cell
|
? null
|
||||||
.column
|
: flexRender(
|
||||||
.columnDef
|
header
|
||||||
.cell,
|
.column
|
||||||
cell.getContext()
|
.columnDef
|
||||||
)}
|
.header,
|
||||||
</TableCell>
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))}
|
||||||
) : (
|
</TableHeader>
|
||||||
<TableRow>
|
<TableBody>
|
||||||
<TableCell
|
{proxyTable.getRowModel().rows
|
||||||
colSpan={
|
?.length ? (
|
||||||
proxyColumns.length
|
proxyTable
|
||||||
}
|
.getRowModel()
|
||||||
className="h-24 text-center"
|
.rows.map((row) => (
|
||||||
>
|
<TableRow
|
||||||
{t(
|
key={row.id}
|
||||||
"resourcesTableNoProxyResourcesFound"
|
data-state={
|
||||||
)}
|
row.getIsSelected() &&
|
||||||
</TableCell>
|
"selected"
|
||||||
</TableRow>
|
}
|
||||||
)}
|
>
|
||||||
</TableBody>
|
{row
|
||||||
</Table>
|
.getVisibleCells()
|
||||||
|
.map((cell) => (
|
||||||
|
<TableCell
|
||||||
|
key={
|
||||||
|
cell.id
|
||||||
|
}
|
||||||
|
className={`whitespace-nowrap ${
|
||||||
|
cell
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
|
"actions"
|
||||||
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
|
: cell
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
|
"name"
|
||||||
|
? "md:sticky md:left-0 z-10 bg-card"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{flexRender(
|
||||||
|
cell
|
||||||
|
.column
|
||||||
|
.columnDef
|
||||||
|
.cell,
|
||||||
|
cell.getContext()
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={
|
||||||
|
proxyColumns.length
|
||||||
|
}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"resourcesTableNoProxyResourcesFound"
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
@@ -1336,99 +1378,111 @@ export default function ResourcesTable({
|
|||||||
<TabsContent value="internal">
|
<TabsContent value="internal">
|
||||||
<div className="overflow-x-auto">
|
<div className="overflow-x-auto">
|
||||||
<Table>
|
<Table>
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
{internalTable
|
{internalTable
|
||||||
.getHeaderGroups()
|
.getHeaderGroups()
|
||||||
.map((headerGroup) => (
|
.map((headerGroup) => (
|
||||||
<TableRow key={headerGroup.id}>
|
|
||||||
{headerGroup.headers
|
|
||||||
.filter((header) =>
|
|
||||||
header.column.getIsVisible()
|
|
||||||
)
|
|
||||||
.map((header) => (
|
|
||||||
<TableHead
|
|
||||||
key={header.id}
|
|
||||||
className={`whitespace-nowrap ${
|
|
||||||
header.column.id ===
|
|
||||||
"actions"
|
|
||||||
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
|
||||||
: header.column.id ===
|
|
||||||
"name"
|
|
||||||
? "md:sticky md:left-0 z-10 bg-card"
|
|
||||||
: ""
|
|
||||||
}`}
|
|
||||||
>
|
|
||||||
{header.isPlaceholder
|
|
||||||
? null
|
|
||||||
: flexRender(
|
|
||||||
header
|
|
||||||
.column
|
|
||||||
.columnDef
|
|
||||||
.header,
|
|
||||||
header.getContext()
|
|
||||||
)}
|
|
||||||
</TableHead>
|
|
||||||
))}
|
|
||||||
</TableRow>
|
|
||||||
))}
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
{internalTable.getRowModel().rows
|
|
||||||
?.length ? (
|
|
||||||
internalTable
|
|
||||||
.getRowModel()
|
|
||||||
.rows.map((row) => (
|
|
||||||
<TableRow
|
<TableRow
|
||||||
key={row.id}
|
key={headerGroup.id}
|
||||||
data-state={
|
|
||||||
row.getIsSelected() &&
|
|
||||||
"selected"
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{row
|
{headerGroup.headers
|
||||||
.getVisibleCells()
|
.filter((header) =>
|
||||||
.map((cell) => (
|
header.column.getIsVisible()
|
||||||
<TableCell
|
)
|
||||||
|
.map((header) => (
|
||||||
|
<TableHead
|
||||||
key={
|
key={
|
||||||
cell.id
|
header.id
|
||||||
}
|
}
|
||||||
className={`whitespace-nowrap ${
|
className={`whitespace-nowrap ${
|
||||||
cell.column.id ===
|
header
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
"actions"
|
"actions"
|
||||||
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
: cell.column.id ===
|
: header
|
||||||
"name"
|
.column
|
||||||
? "md:sticky md:left-0 z-10 bg-card"
|
.id ===
|
||||||
: ""
|
"name"
|
||||||
|
? "md:sticky md:left-0 z-10 bg-card"
|
||||||
|
: ""
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{flexRender(
|
{header.isPlaceholder
|
||||||
cell
|
? null
|
||||||
.column
|
: flexRender(
|
||||||
.columnDef
|
header
|
||||||
.cell,
|
.column
|
||||||
cell.getContext()
|
.columnDef
|
||||||
)}
|
.header,
|
||||||
</TableCell>
|
header.getContext()
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
))}
|
))}
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))
|
))}
|
||||||
) : (
|
</TableHeader>
|
||||||
<TableRow>
|
<TableBody>
|
||||||
<TableCell
|
{internalTable.getRowModel().rows
|
||||||
colSpan={
|
?.length ? (
|
||||||
internalColumns.length
|
internalTable
|
||||||
}
|
.getRowModel()
|
||||||
className="h-24 text-center"
|
.rows.map((row) => (
|
||||||
>
|
<TableRow
|
||||||
{t(
|
key={row.id}
|
||||||
"resourcesTableNoInternalResourcesFound"
|
data-state={
|
||||||
)}
|
row.getIsSelected() &&
|
||||||
</TableCell>
|
"selected"
|
||||||
</TableRow>
|
}
|
||||||
)}
|
>
|
||||||
</TableBody>
|
{row
|
||||||
</Table>
|
.getVisibleCells()
|
||||||
|
.map((cell) => (
|
||||||
|
<TableCell
|
||||||
|
key={
|
||||||
|
cell.id
|
||||||
|
}
|
||||||
|
className={`whitespace-nowrap ${
|
||||||
|
cell
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
|
"actions"
|
||||||
|
? "sticky right-0 z-10 w-auto min-w-fit bg-card"
|
||||||
|
: cell
|
||||||
|
.column
|
||||||
|
.id ===
|
||||||
|
"name"
|
||||||
|
? "md:sticky md:left-0 z-10 bg-card"
|
||||||
|
: ""
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{flexRender(
|
||||||
|
cell
|
||||||
|
.column
|
||||||
|
.columnDef
|
||||||
|
.cell,
|
||||||
|
cell.getContext()
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell
|
||||||
|
colSpan={
|
||||||
|
internalColumns.length
|
||||||
|
}
|
||||||
|
className="h-24 text-center"
|
||||||
|
>
|
||||||
|
{t(
|
||||||
|
"resourcesTableNoInternalResourcesFound"
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<DataTablePagination
|
<DataTablePagination
|
||||||
|
|||||||
Reference in New Issue
Block a user