mirror of
https://github.com/fosrl/pangolin.git
synced 2026-08-22 20:20:29 +02:00
add reject endpoint
This commit is contained in:
+9
-1
@@ -449,8 +449,14 @@
|
|||||||
"provisioningManage": "Provisioning",
|
"provisioningManage": "Provisioning",
|
||||||
"provisioningDescription": "Manage provisioning keys and review pending sites awaiting approval.",
|
"provisioningDescription": "Manage provisioning keys and review pending sites awaiting approval.",
|
||||||
"pendingSites": "Pending Sites",
|
"pendingSites": "Pending Sites",
|
||||||
"siteApproveSuccess": "Site approved successfully",
|
"siteApproveSuccess": "Site and associated resources approved successfully",
|
||||||
"siteApproveError": "Error approving site",
|
"siteApproveError": "Error approving site",
|
||||||
|
"siteReject": "Reject Site",
|
||||||
|
"siteQuestionReject": "Are you sure you want to reject this site?",
|
||||||
|
"siteMessageReject": "This will permanently delete the site and any associated resources that are still pending.",
|
||||||
|
"siteConfirmReject": "Confirm Reject Site",
|
||||||
|
"siteRejectSuccess": "Site rejected successfully",
|
||||||
|
"siteRejectError": "Error rejecting site",
|
||||||
"provisioningKeys": "Provisioning Keys",
|
"provisioningKeys": "Provisioning Keys",
|
||||||
"searchProvisioningKeys": "Search provisioning keys...",
|
"searchProvisioningKeys": "Search provisioning keys...",
|
||||||
"provisioningKeysAdd": "Generate Provisioning Key",
|
"provisioningKeysAdd": "Generate Provisioning Key",
|
||||||
@@ -1420,6 +1426,8 @@
|
|||||||
"setupTokenDescription": "Enter the setup token from the server console.",
|
"setupTokenDescription": "Enter the setup token from the server console.",
|
||||||
"setupTokenRequired": "Setup token is required",
|
"setupTokenRequired": "Setup token is required",
|
||||||
"actionUpdateSite": "Update Site",
|
"actionUpdateSite": "Update Site",
|
||||||
|
"actionApproveSite": "Approve Site",
|
||||||
|
"actionRejectSite": "Reject Site",
|
||||||
"actionResetSiteBandwidth": "Reset Organization Bandwidth",
|
"actionResetSiteBandwidth": "Reset Organization Bandwidth",
|
||||||
"actionListSiteRoles": "List Allowed Site Roles",
|
"actionListSiteRoles": "List Allowed Site Roles",
|
||||||
"actionCreateResource": "Create Resource",
|
"actionCreateResource": "Create Resource",
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ export enum ActionsEnum {
|
|||||||
getSite = "getSite",
|
getSite = "getSite",
|
||||||
listSites = "listSites",
|
listSites = "listSites",
|
||||||
updateSite = "updateSite",
|
updateSite = "updateSite",
|
||||||
|
updateSiteApprovals = "updateSiteApprovals",
|
||||||
restartSite = "restartSite",
|
restartSite = "restartSite",
|
||||||
resetSiteBandwidth = "resetSiteBandwidth",
|
resetSiteBandwidth = "resetSiteBandwidth",
|
||||||
reGenerateSecret = "reGenerateSecret",
|
reGenerateSecret = "reGenerateSecret",
|
||||||
|
|||||||
@@ -14,8 +14,6 @@ import {
|
|||||||
} from "@server/db";
|
} from "@server/db";
|
||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { removeTargets } from "@server/routers/newt/targets";
|
import { removeTargets } from "@server/routers/newt/targets";
|
||||||
import createHttpError from "http-errors";
|
|
||||||
import HttpCode from "@server/types/HttpCode";
|
|
||||||
|
|
||||||
export type DeleteResourceResult = {
|
export type DeleteResourceResult = {
|
||||||
deletedResource: Resource;
|
deletedResource: Resource;
|
||||||
@@ -117,10 +115,10 @@ export async function runResourceDeleteSideEffects(
|
|||||||
.limit(1);
|
.limit(1);
|
||||||
|
|
||||||
if (!site) {
|
if (!site) {
|
||||||
throw createHttpError(
|
logger.debug(
|
||||||
HttpCode.NOT_FOUND,
|
`Site with ID ${target.siteId} not found during resource delete side effects; skipping target removal`
|
||||||
`Site with ID ${target.siteId} not found`
|
|
||||||
);
|
);
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (site.pubKey && site.type === "newt") {
|
if (site.pubKey && site.type === "newt") {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { and, eq, sql } from "drizzle-orm";
|
import { and, eq, inArray, sql } from "drizzle-orm";
|
||||||
import {
|
import {
|
||||||
db,
|
db,
|
||||||
|
resources,
|
||||||
siteNetworks,
|
siteNetworks,
|
||||||
siteResources,
|
siteResources,
|
||||||
targets,
|
targets,
|
||||||
@@ -97,6 +98,64 @@ export function exceedsSiteAssociatedResourceDeleteLimit(
|
|||||||
return resourceCount > MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE;
|
return resourceCount > MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function getPendingResourceIdsForSite(
|
||||||
|
siteId: number,
|
||||||
|
trx: Transaction | typeof db = db
|
||||||
|
): Promise<number[]> {
|
||||||
|
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
||||||
|
if (resourceIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await trx
|
||||||
|
.select({ resourceId: resources.resourceId })
|
||||||
|
.from(resources)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(resources.resourceId, resourceIds),
|
||||||
|
eq(resources.status, "pending")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return rows.map((row) => row.resourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPendingSiteResourceIdsForSite(
|
||||||
|
siteId: number,
|
||||||
|
orgId: string,
|
||||||
|
trx: Transaction | typeof db = db
|
||||||
|
): Promise<number[]> {
|
||||||
|
const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx);
|
||||||
|
if (siteResourceIds.length === 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const rows = await trx
|
||||||
|
.select({ siteResourceId: siteResources.siteResourceId })
|
||||||
|
.from(siteResources)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
inArray(siteResources.siteResourceId, siteResourceIds),
|
||||||
|
eq(siteResources.status, "pending")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return rows.map((row) => row.siteResourceId);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getPendingAssociatedResourceCountForSite(
|
||||||
|
siteId: number,
|
||||||
|
orgId: string,
|
||||||
|
trx: Transaction | typeof db = db
|
||||||
|
): Promise<number> {
|
||||||
|
const [resourceIds, siteResourceIds] = await Promise.all([
|
||||||
|
getPendingResourceIdsForSite(siteId, trx),
|
||||||
|
getPendingSiteResourceIdsForSite(siteId, orgId, trx)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return resourceIds.length + siteResourceIds.length;
|
||||||
|
}
|
||||||
|
|
||||||
export async function deleteAssociatedResourcesForSite(
|
export async function deleteAssociatedResourcesForSite(
|
||||||
siteId: number,
|
siteId: number,
|
||||||
orgId: string,
|
orgId: string,
|
||||||
@@ -105,12 +164,32 @@ export async function deleteAssociatedResourcesForSite(
|
|||||||
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
const resourceIds = await getResourceIdsForSite(siteId, trx);
|
||||||
const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx);
|
const siteResourceIds = await getSiteResourceIdsForSite(siteId, orgId, trx);
|
||||||
|
|
||||||
const [resources, siteResourcesDeleted] = await Promise.all([
|
const [deletedResources, siteResourcesDeleted] = await Promise.all([
|
||||||
performDeleteResources(resourceIds, trx),
|
performDeleteResources(resourceIds, trx),
|
||||||
performDeleteSiteResources(siteResourceIds, trx)
|
performDeleteSiteResources(siteResourceIds, trx)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
return { resources, siteResources: siteResourcesDeleted };
|
return { resources: deletedResources, siteResources: siteResourcesDeleted };
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function deletePendingAssociatedResourcesForSite(
|
||||||
|
siteId: number,
|
||||||
|
orgId: string,
|
||||||
|
trx: Transaction | typeof db = db
|
||||||
|
): Promise<DeleteSiteAssociatedResourcesSideEffects> {
|
||||||
|
const resourceIds = await getPendingResourceIdsForSite(siteId, trx);
|
||||||
|
const siteResourceIds = await getPendingSiteResourceIdsForSite(
|
||||||
|
siteId,
|
||||||
|
orgId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
|
||||||
|
const [deletedResources, siteResourcesDeleted] = await Promise.all([
|
||||||
|
performDeleteResources(resourceIds, trx),
|
||||||
|
performDeleteSiteResources(siteResourceIds, trx)
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { resources: deletedResources, siteResources: siteResourcesDeleted };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function runDeleteSiteAssociatedResourcesSideEffects(
|
export async function runDeleteSiteAssociatedResourcesSideEffects(
|
||||||
|
|||||||
@@ -251,11 +251,19 @@ authenticated.post(
|
|||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/site/:siteId/approve",
|
"/site/:siteId/approve",
|
||||||
verifySiteAccess,
|
verifySiteAccess,
|
||||||
verifyUserHasAction(ActionsEnum.updateSite),
|
verifyUserHasAction(ActionsEnum.updateSiteApprovals),
|
||||||
logActionAudit(ActionsEnum.updateSite),
|
logActionAudit(ActionsEnum.updateSiteApprovals),
|
||||||
site.approveSite
|
site.approveSite
|
||||||
);
|
);
|
||||||
|
|
||||||
|
authenticated.post(
|
||||||
|
"/site/:siteId/reject",
|
||||||
|
verifySiteAccess,
|
||||||
|
verifyUserHasAction(ActionsEnum.updateSiteApprovals),
|
||||||
|
logActionAudit(ActionsEnum.updateSiteApprovals),
|
||||||
|
site.rejectSite
|
||||||
|
);
|
||||||
|
|
||||||
authenticated.delete(
|
authenticated.delete(
|
||||||
"/site/:siteId",
|
"/site/:siteId",
|
||||||
verifySiteAccess,
|
verifySiteAccess,
|
||||||
|
|||||||
@@ -139,13 +139,6 @@ authenticated.post(
|
|||||||
site.updateSite
|
site.updateSite
|
||||||
);
|
);
|
||||||
|
|
||||||
authenticated.post(
|
|
||||||
"/site/:siteId/approve",
|
|
||||||
verifyApiKeySiteAccess,
|
|
||||||
verifyApiKeyHasAction(ActionsEnum.updateSite),
|
|
||||||
logActionAudit(ActionsEnum.updateSite),
|
|
||||||
site.approveSite
|
|
||||||
);
|
|
||||||
authenticated.post(
|
authenticated.post(
|
||||||
"/org/:orgId/reset-bandwidth",
|
"/org/:orgId/reset-bandwidth",
|
||||||
verifyApiKeyOrgAccess,
|
verifyApiKeyOrgAccess,
|
||||||
|
|||||||
@@ -159,15 +159,21 @@ export async function deleteSite(
|
|||||||
siteResources: []
|
siteResources: []
|
||||||
};
|
};
|
||||||
|
|
||||||
await db.transaction(async (trx) => {
|
if (deleteResources) {
|
||||||
if (deleteResources) {
|
await db.transaction(async (trx) => {
|
||||||
resourceSideEffects = await deleteAssociatedResourcesForSite(
|
resourceSideEffects = await deleteAssociatedResourcesForSite(
|
||||||
siteId,
|
siteId,
|
||||||
site.orgId,
|
site.orgId,
|
||||||
trx
|
trx
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
await runDeleteSiteAssociatedResourcesSideEffects(
|
||||||
|
resourceSideEffects
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
if (site.type == "wireguard") {
|
if (site.type == "wireguard") {
|
||||||
if (site.pubKey) {
|
if (site.pubKey) {
|
||||||
await deletePeer(site.exitNodeId!, site.pubKey);
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
@@ -180,12 +186,6 @@ export async function deleteSite(
|
|||||||
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
|
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
|
||||||
});
|
});
|
||||||
|
|
||||||
if (deleteResources) {
|
|
||||||
await runDeleteSiteAssociatedResourcesSideEffects(
|
|
||||||
resourceSideEffects
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (deletedNewt) {
|
if (deletedNewt) {
|
||||||
const payload = {
|
const payload = {
|
||||||
type: `newt/wg/terminate`,
|
type: `newt/wg/terminate`,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ export * from "./createSite";
|
|||||||
export * from "./deleteSite";
|
export * from "./deleteSite";
|
||||||
export * from "./updateSite";
|
export * from "./updateSite";
|
||||||
export * from "./approveSite";
|
export * from "./approveSite";
|
||||||
|
export * from "./rejectSite";
|
||||||
export * from "./listSites";
|
export * from "./listSites";
|
||||||
export * from "./listSiteRoles";
|
export * from "./listSiteRoles";
|
||||||
export * from "./pickSiteDefaults";
|
export * from "./pickSiteDefaults";
|
||||||
|
|||||||
@@ -0,0 +1,178 @@
|
|||||||
|
import { Request, Response, NextFunction } from "express";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { db } from "@server/db";
|
||||||
|
import { newts, sites } from "@server/db";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import response from "@server/lib/response";
|
||||||
|
import HttpCode from "@server/types/HttpCode";
|
||||||
|
import createHttpError from "http-errors";
|
||||||
|
import logger from "@server/logger";
|
||||||
|
import { deletePeer } from "../gerbil/peers";
|
||||||
|
import { fromError } from "zod-validation-error";
|
||||||
|
import { sendToClient } from "#dynamic/routers/ws";
|
||||||
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import { cleanupSiteAssociations } from "@server/lib/rebuildClientAssociations";
|
||||||
|
import { usageService } from "@server/lib/billing/usageService";
|
||||||
|
import { LimitId } from "@server/lib/billing";
|
||||||
|
import {
|
||||||
|
deletePendingAssociatedResourcesForSite,
|
||||||
|
exceedsSiteAssociatedResourceDeleteLimit,
|
||||||
|
getPendingAssociatedResourceCountForSite,
|
||||||
|
runDeleteSiteAssociatedResourcesSideEffects,
|
||||||
|
MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE,
|
||||||
|
type DeleteSiteAssociatedResourcesSideEffects
|
||||||
|
} from "@server/lib/deleteSiteAssociatedResources";
|
||||||
|
|
||||||
|
const rejectSiteParamsSchema = z.strictObject({
|
||||||
|
siteId: z.coerce.number().int().positive()
|
||||||
|
});
|
||||||
|
|
||||||
|
registry.registerPath({
|
||||||
|
method: "post",
|
||||||
|
path: "/site/{siteId}/reject",
|
||||||
|
description:
|
||||||
|
"Reject a pending site by deleting it and any associated resources that are still pending.",
|
||||||
|
tags: [OpenAPITags.Site],
|
||||||
|
request: {
|
||||||
|
params: rejectSiteParamsSchema
|
||||||
|
},
|
||||||
|
responses: {
|
||||||
|
200: {
|
||||||
|
description: "Successful response",
|
||||||
|
content: {
|
||||||
|
"application/json": {
|
||||||
|
schema: z.object({
|
||||||
|
data: z.record(z.string(), z.any()).nullable(),
|
||||||
|
success: z.boolean(),
|
||||||
|
error: z.boolean(),
|
||||||
|
message: z.string(),
|
||||||
|
status: z.number()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export async function rejectSite(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
next: NextFunction
|
||||||
|
): Promise<any> {
|
||||||
|
try {
|
||||||
|
const parsedParams = rejectSiteParamsSchema.safeParse(req.params);
|
||||||
|
if (!parsedParams.success) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
fromError(parsedParams.error).toString()
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { siteId } = parsedParams.data;
|
||||||
|
|
||||||
|
const [site] = await db
|
||||||
|
.select()
|
||||||
|
.from(sites)
|
||||||
|
.where(eq(sites.siteId, siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (!site) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.NOT_FOUND,
|
||||||
|
`Site with ID ${siteId} not found`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!site.orgId) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
`Site with ID ${siteId} has no organization`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingAssociatedResourceCount =
|
||||||
|
await getPendingAssociatedResourceCountForSite(siteId, site.orgId);
|
||||||
|
|
||||||
|
if (
|
||||||
|
exceedsSiteAssociatedResourceDeleteLimit(
|
||||||
|
pendingAssociatedResourceCount
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return next(
|
||||||
|
createHttpError(
|
||||||
|
HttpCode.BAD_REQUEST,
|
||||||
|
`Cannot reject site and associated pending resources when the site has more than ${MAX_SITE_ASSOCIATED_RESOURCES_FOR_BULK_DELETE} pending resources`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [deletedNewt] = await db
|
||||||
|
.select()
|
||||||
|
.from(newts)
|
||||||
|
.where(eq(newts.siteId, siteId))
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
let resourceSideEffects: DeleteSiteAssociatedResourcesSideEffects = {
|
||||||
|
resources: [],
|
||||||
|
siteResources: []
|
||||||
|
};
|
||||||
|
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
resourceSideEffects = await deletePendingAssociatedResourcesForSite(
|
||||||
|
siteId,
|
||||||
|
site.orgId,
|
||||||
|
trx
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
await runDeleteSiteAssociatedResourcesSideEffects(resourceSideEffects);
|
||||||
|
|
||||||
|
await db.transaction(async (trx) => {
|
||||||
|
if (site.type == "wireguard") {
|
||||||
|
if (site.pubKey) {
|
||||||
|
await deletePeer(site.exitNodeId!, site.pubKey);
|
||||||
|
}
|
||||||
|
} else if (site.type == "newt") {
|
||||||
|
await cleanupSiteAssociations(site, trx);
|
||||||
|
}
|
||||||
|
|
||||||
|
await trx.delete(sites).where(eq(sites.siteId, siteId));
|
||||||
|
await usageService.add(site.orgId, LimitId.SITES, -1, trx);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (deletedNewt) {
|
||||||
|
const payload = {
|
||||||
|
type: `newt/wg/terminate`,
|
||||||
|
data: {}
|
||||||
|
};
|
||||||
|
sendToClient(deletedNewt.newtId, payload).catch((error) => {
|
||||||
|
logger.error(
|
||||||
|
"Failed to send termination message to newt:",
|
||||||
|
error
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return response(res, {
|
||||||
|
data: null,
|
||||||
|
success: true,
|
||||||
|
error: false,
|
||||||
|
message: "Site rejected successfully",
|
||||||
|
status: HttpCode.OK
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(error);
|
||||||
|
if (createHttpError.isHttpError(error)) {
|
||||||
|
return next(error);
|
||||||
|
}
|
||||||
|
return next(
|
||||||
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ export default function PendingSitesTable({
|
|||||||
const [isRefreshing, startTransition] = useTransition();
|
const [isRefreshing, startTransition] = useTransition();
|
||||||
const [approvingIds, setApprovingIds] = useState<Set<number>>(new Set());
|
const [approvingIds, setApprovingIds] = useState<Set<number>>(new Set());
|
||||||
const [rejectingIds, setRejectingIds] = useState<Set<number>>(new Set());
|
const [rejectingIds, setRejectingIds] = useState<Set<number>>(new Set());
|
||||||
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);
|
const [isRejectModalOpen, setIsRejectModalOpen] = useState(false);
|
||||||
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
|
const [selectedSite, setSelectedSite] = useState<SiteRow | null>(null);
|
||||||
|
|
||||||
const api = createApiClient(useEnvContext());
|
const api = createApiClient(useEnvContext());
|
||||||
@@ -136,20 +136,20 @@ export default function PendingSitesTable({
|
|||||||
async function rejectSite(siteId: number) {
|
async function rejectSite(siteId: number) {
|
||||||
setRejectingIds((prev) => new Set(prev).add(siteId));
|
setRejectingIds((prev) => new Set(prev).add(siteId));
|
||||||
try {
|
try {
|
||||||
await api.delete(`/site/${siteId}`);
|
await api.post(`/site/${siteId}/reject`);
|
||||||
toast({
|
toast({
|
||||||
title: t("success"),
|
title: t("success"),
|
||||||
description: t("siteDeleted"),
|
description: t("siteRejectSuccess"),
|
||||||
variant: "default"
|
variant: "default"
|
||||||
});
|
});
|
||||||
setIsDeleteModalOpen(false);
|
setIsRejectModalOpen(false);
|
||||||
setSelectedSite(null);
|
setSelectedSite(null);
|
||||||
router.refresh();
|
router.refresh();
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast({
|
toast({
|
||||||
variant: "destructive",
|
variant: "destructive",
|
||||||
title: t("siteErrorDelete"),
|
title: t("siteRejectError"),
|
||||||
description: formatAxiosError(e, t("siteErrorDelete"))
|
description: formatAxiosError(e, t("siteRejectError"))
|
||||||
});
|
});
|
||||||
} finally {
|
} finally {
|
||||||
setRejectingIds((prev) => {
|
setRejectingIds((prev) => {
|
||||||
@@ -445,7 +445,7 @@ export default function PendingSitesTable({
|
|||||||
disabled={isApproving || isRejecting}
|
disabled={isApproving || isRejecting}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setSelectedSite(siteRow);
|
setSelectedSite(siteRow);
|
||||||
setIsDeleteModalOpen(true);
|
setIsRejectModalOpen(true);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<X className="mr-2 w-4 h-4" />
|
<X className="mr-2 w-4 h-4" />
|
||||||
@@ -493,23 +493,23 @@ export default function PendingSitesTable({
|
|||||||
<>
|
<>
|
||||||
{selectedSite && (
|
{selectedSite && (
|
||||||
<ConfirmDeleteDialog
|
<ConfirmDeleteDialog
|
||||||
open={isDeleteModalOpen}
|
open={isRejectModalOpen}
|
||||||
setOpen={(val) => {
|
setOpen={(val) => {
|
||||||
setIsDeleteModalOpen(val);
|
setIsRejectModalOpen(val);
|
||||||
if (!val) {
|
if (!val) {
|
||||||
setSelectedSite(null);
|
setSelectedSite(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
dialog={
|
dialog={
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<p>{t("siteQuestionRemove")}</p>
|
<p>{t("siteQuestionReject")}</p>
|
||||||
<p>{t("siteMessageRemove")}</p>
|
<p>{t("siteMessageReject")}</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
buttonText={t("siteConfirmDelete")}
|
buttonText={t("siteConfirmReject")}
|
||||||
onConfirm={async () => rejectSite(selectedSite.id)}
|
onConfirm={async () => rejectSite(selectedSite.id)}
|
||||||
string={selectedSite.name}
|
string={selectedSite.name}
|
||||||
title={t("siteDelete")}
|
title={t("siteReject")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<ControlledDataTable
|
<ControlledDataTable
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionGetSite")]: "getSite",
|
[t("actionGetSite")]: "getSite",
|
||||||
[t("actionListSites")]: "listSites",
|
[t("actionListSites")]: "listSites",
|
||||||
[t("actionUpdateSite")]: "updateSite",
|
[t("actionUpdateSite")]: "updateSite",
|
||||||
|
[t("actionUpdateSiteApprovals")]: "updateSiteApprovals",
|
||||||
[t("actionListSiteRoles")]: "listSiteRoles"
|
[t("actionListSiteRoles")]: "listSiteRoles"
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -78,7 +79,8 @@ function getActionsCategories(root: boolean) {
|
|||||||
[t("actionGetSiteResource")]: "getSiteResource",
|
[t("actionGetSiteResource")]: "getSiteResource",
|
||||||
[t("actionListSiteResources")]: "listSiteResources",
|
[t("actionListSiteResources")]: "listSiteResources",
|
||||||
[t("actionUpdateSiteResource")]: "updateSiteResource",
|
[t("actionUpdateSiteResource")]: "updateSiteResource",
|
||||||
[t("actionCreateResourceSessionToken")]: "createResourceSessionToken"
|
[t("actionCreateResourceSessionToken")]:
|
||||||
|
"createResourceSessionToken"
|
||||||
},
|
},
|
||||||
|
|
||||||
Target: {
|
Target: {
|
||||||
|
|||||||
Reference in New Issue
Block a user