mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-17 06:24:32 +00:00
🚧 wip
This commit is contained in:
@@ -166,6 +166,10 @@
|
|||||||
"resourcesSearch": "Search resources...",
|
"resourcesSearch": "Search resources...",
|
||||||
"resourceAdd": "Add Resource",
|
"resourceAdd": "Add Resource",
|
||||||
"resourceErrorDelte": "Error deleting resource",
|
"resourceErrorDelte": "Error deleting resource",
|
||||||
|
"resourcePoliciesTitle": "Manage Resource Policies",
|
||||||
|
"resourcePoliciesDescription": "Create and manage authentication policies to control access to your resources",
|
||||||
|
"resourcePoliciesSearch": "Search policies...",
|
||||||
|
"resourcePoliciesAdd": "Add Policy",
|
||||||
"authentication": "Authentication",
|
"authentication": "Authentication",
|
||||||
"protected": "Protected",
|
"protected": "Protected",
|
||||||
"notProtected": "Not Protected",
|
"notProtected": "Not Protected",
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
* This file is not licensed under the AGPLv3.
|
* This file is not licensed under the AGPLv3.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
import { Request, Response, NextFunction } from "express";
|
import { Request, Response, NextFunction } from "express";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import {
|
import {
|
||||||
@@ -36,6 +35,8 @@ import { sql, eq, or, inArray, and, count, ilike, asc } from "drizzle-orm";
|
|||||||
import logger from "@server/logger";
|
import logger from "@server/logger";
|
||||||
import { fromZodError } from "zod-validation-error";
|
import { fromZodError } from "zod-validation-error";
|
||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
|
import type { PaginatedResponse } from "@server/types/Pagination";
|
||||||
|
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
|
||||||
|
|
||||||
const listResourcePoliciesParamsSchema = z.strictObject({
|
const listResourcePoliciesParamsSchema = z.strictObject({
|
||||||
orgId: z.string()
|
orgId: z.string()
|
||||||
@@ -56,7 +57,7 @@ const listResourcePoliciesSchema = z.object({
|
|||||||
.optional()
|
.optional()
|
||||||
.catch(1)
|
.catch(1)
|
||||||
.default(1),
|
.default(1),
|
||||||
query: z.string().optional(),
|
query: z.string().optional()
|
||||||
});
|
});
|
||||||
|
|
||||||
function queryResourcePoliciesBase() {
|
function queryResourcePoliciesBase() {
|
||||||
@@ -65,43 +66,11 @@ function queryResourcePoliciesBase() {
|
|||||||
resourcePolicyId: resourcePolicies.resourcePolicyId,
|
resourcePolicyId: resourcePolicies.resourcePolicyId,
|
||||||
name: resourcePolicies.name,
|
name: resourcePolicies.name,
|
||||||
niceId: resourcePolicies.niceId,
|
niceId: resourcePolicies.niceId,
|
||||||
passwordId: resourcePassword.passwordId,
|
orgId: resourcePolicies.orgId
|
||||||
sso: resourcePolicies.sso,
|
|
||||||
pincodeId: resourcePincode.pincodeId,
|
|
||||||
whitelist: resourcePolicies.emailWhitelistEnabled,
|
|
||||||
headerAuthId: resourceHeaderAuth.headerAuthId,
|
|
||||||
headerAuthExtendedCompatibilityId:
|
|
||||||
resourceHeaderAuthExtendedCompatibility.headerAuthExtendedCompatibilityId
|
|
||||||
})
|
})
|
||||||
.from(resourcePolicies)
|
.from(resourcePolicies);
|
||||||
.leftJoin(
|
|
||||||
resourcePassword,
|
|
||||||
eq(resourcePassword.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
|
||||||
)
|
|
||||||
.leftJoin(
|
|
||||||
resourcePincode,
|
|
||||||
eq(resourcePincode.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
|
||||||
)
|
|
||||||
.leftJoin(
|
|
||||||
resourceHeaderAuth,
|
|
||||||
eq(resourceHeaderAuth.resourcePolicyId, resourcePolicies.resourcePolicyId)
|
|
||||||
)
|
|
||||||
.leftJoin(
|
|
||||||
resourceHeaderAuthExtendedCompatibility,
|
|
||||||
eq(
|
|
||||||
resourceHeaderAuthExtendedCompatibility.resourcePolicyId,
|
|
||||||
resourcePolicies.resourcePolicyId
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: replaced with `PaginatedResponse<T>` when paginated table PR is merged
|
|
||||||
export type ListResourcePoliciesResponse = {
|
|
||||||
policies: Awaited<ReturnType<typeof queryResourcePoliciesBase>>;
|
|
||||||
pagination: { total: number; pageSize: number; page: number; };
|
|
||||||
};
|
|
||||||
|
|
||||||
registry.registerPath({
|
registry.registerPath({
|
||||||
method: "get",
|
method: "get",
|
||||||
path: "/org/{orgId}/resource-policies",
|
path: "/org/{orgId}/resource-policies",
|
||||||
@@ -116,8 +85,6 @@ registry.registerPath({
|
|||||||
responses: {}
|
responses: {}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export async function listResourcePolicies(
|
export async function listResourcePolicies(
|
||||||
req: Request,
|
req: Request,
|
||||||
res: Response,
|
res: Response,
|
||||||
@@ -133,10 +100,11 @@ export async function listResourcePolicies(
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const { page, pageSize, query, } =
|
const { page, pageSize, query } = parsedQuery.data;
|
||||||
parsedQuery.data;
|
|
||||||
|
|
||||||
const parsedParams = listResourcePoliciesParamsSchema.safeParse(req.params);
|
const parsedParams = listResourcePoliciesParamsSchema.safeParse(
|
||||||
|
req.params
|
||||||
|
);
|
||||||
if (!parsedParams.success) {
|
if (!parsedParams.success) {
|
||||||
return next(
|
return next(
|
||||||
createHttpError(
|
createHttpError(
|
||||||
@@ -166,7 +134,7 @@ export async function listResourcePolicies(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let accessibleResourcePolicies: Array<{ resourcePolicyId: number; }>;
|
let accessibleResourcePolicies: Array<{ resourcePolicyId: number }>;
|
||||||
if (req.user) {
|
if (req.user) {
|
||||||
accessibleResourcePolicies = await db
|
accessibleResourcePolicies = await db
|
||||||
.select({
|
.select({
|
||||||
@@ -175,7 +143,10 @@ export async function listResourcePolicies(
|
|||||||
.from(userResources)
|
.from(userResources)
|
||||||
.fullJoin(
|
.fullJoin(
|
||||||
roleResources,
|
roleResources,
|
||||||
eq(userResources.resourcePolicyId, roleResources.resourcePolicyId)
|
eq(
|
||||||
|
userResources.resourcePolicyId,
|
||||||
|
roleResources.resourcePolicyId
|
||||||
|
)
|
||||||
)
|
)
|
||||||
.where(
|
.where(
|
||||||
or(
|
or(
|
||||||
@@ -198,7 +169,10 @@ export async function listResourcePolicies(
|
|||||||
|
|
||||||
const conditions = [
|
const conditions = [
|
||||||
and(
|
and(
|
||||||
inArray(resourcePolicies.resourcePolicyId, accessibleResourceIds),
|
inArray(
|
||||||
|
resourcePolicies.resourcePolicyId,
|
||||||
|
accessibleResourceIds
|
||||||
|
),
|
||||||
eq(resourcePolicies.orgId, orgId)
|
eq(resourcePolicies.orgId, orgId)
|
||||||
)
|
)
|
||||||
];
|
];
|
||||||
@@ -207,13 +181,12 @@ export async function listResourcePolicies(
|
|||||||
conditions.push(
|
conditions.push(
|
||||||
or(
|
or(
|
||||||
ilike(resourcePolicies.name, "%" + query + "%"),
|
ilike(resourcePolicies.name, "%" + query + "%"),
|
||||||
ilike(resourcePolicies.niceId, "%" + query + "%"),
|
ilike(resourcePolicies.niceId, "%" + query + "%")
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseQuery = queryResourcePoliciesBase()
|
const baseQuery = queryResourcePoliciesBase().where(and(...conditions));
|
||||||
.where(and(...conditions));
|
|
||||||
|
|
||||||
// we need to add `as` so that drizzle filters the result as a subquery
|
// we need to add `as` so that drizzle filters the result as a subquery
|
||||||
const countQuery = db.$count(baseQuery.as("filtered_policies"));
|
const countQuery = db.$count(baseQuery.as("filtered_policies"));
|
||||||
@@ -240,8 +213,6 @@ export async function listResourcePolicies(
|
|||||||
message: "Resources retrieved successfully",
|
message: "Resources retrieved successfully",
|
||||||
status: HttpCode.OK
|
status: HttpCode.OK
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
return next(
|
return next(
|
||||||
|
|||||||
@@ -1,3 +1,6 @@
|
|||||||
|
import type { ResourcePolicy } from "@server/db";
|
||||||
|
import type { PaginatedResponse } from "@server/types/Pagination";
|
||||||
|
|
||||||
export type GetMaintenanceInfoResponse = {
|
export type GetMaintenanceInfoResponse = {
|
||||||
resourceId: number;
|
resourceId: number;
|
||||||
name: string;
|
name: string;
|
||||||
@@ -8,3 +11,9 @@ export type GetMaintenanceInfoResponse = {
|
|||||||
maintenanceMessage: string | null;
|
maintenanceMessage: string | null;
|
||||||
maintenanceEstimatedTime: string | null;
|
maintenanceEstimatedTime: string | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type ListResourcePoliciesResponse = PaginatedResponse<{
|
||||||
|
policies: Array<
|
||||||
|
Pick<ResourcePolicy, "resourcePolicyId" | "niceId" | "name" | "orgId">
|
||||||
|
>;
|
||||||
|
}>;
|
||||||
|
|||||||
@@ -11,7 +11,6 @@ import {
|
|||||||
GetLoginPageResponse
|
GetLoginPageResponse
|
||||||
} from "@server/routers/loginPage/types";
|
} from "@server/routers/loginPage/types";
|
||||||
import { AxiosResponse } from "axios";
|
import { AxiosResponse } from "axios";
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
export interface AuthPageProps {
|
export interface AuthPageProps {
|
||||||
params: Promise<{ orgId: string }>;
|
params: Promise<{ orgId: string }>;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { Layout } from "@app/components/Layout";
|
|||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
import { pullEnv } from "@app/lib/pullEnv";
|
import { pullEnv } from "@app/lib/pullEnv";
|
||||||
import { orgNavSections } from "@app/app/navigation";
|
import { orgNavSections } from "@app/app/navigation";
|
||||||
|
import { getCachedOrgUser } from "@app/lib/api/getCachedOrgUser";
|
||||||
|
|
||||||
export const dynamic = "force-dynamic";
|
export const dynamic = "force-dynamic";
|
||||||
|
|
||||||
@@ -48,13 +49,7 @@ export default async function SettingsLayout(props: SettingsLayoutProps) {
|
|||||||
const t = await getTranslations();
|
const t = await getTranslations();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const getOrgUser = cache(() =>
|
const orgUser = await getCachedOrgUser(params.orgId, user.userId);
|
||||||
internal.get<AxiosResponse<GetOrgUserResponse>>(
|
|
||||||
`/org/${params.orgId}/user/${user.userId}`,
|
|
||||||
cookie
|
|
||||||
)
|
|
||||||
);
|
|
||||||
const orgUser = await getOrgUser();
|
|
||||||
|
|
||||||
if (!orgUser.data.data.isAdmin && !orgUser.data.data.isOwner) {
|
if (!orgUser.data.data.isAdmin && !orgUser.data.data.isOwner) {
|
||||||
throw new Error(t("userErrorNotAdminOrOwner"));
|
throw new Error(t("userErrorNotAdminOrOwner"));
|
||||||
|
|||||||
@@ -1,8 +1,18 @@
|
|||||||
|
import { ResourcePoliciesTable } from "@app/components/ResourcePoliciesTable";
|
||||||
|
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||||
|
import { internal } from "@app/lib/api";
|
||||||
|
import { authCookieHeader } from "@app/lib/api/cookies";
|
||||||
|
import { getCachedOrg } from "@app/lib/api/getCachedOrg";
|
||||||
|
import OrgProvider from "@app/providers/OrgProvider";
|
||||||
|
import type { GetOrgResponse } from "@server/routers/org";
|
||||||
|
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
|
||||||
|
import type { AxiosResponse } from "axios";
|
||||||
import { getTranslations } from "next-intl/server";
|
import { getTranslations } from "next-intl/server";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
export interface ResourcePoliciesPageProps {
|
export interface ResourcePoliciesPageProps {
|
||||||
params: Promise<{ orgId: string }>;
|
params: Promise<{ orgId: string }>;
|
||||||
searchParams: Promise<{ view?: string }>;
|
searchParams: Promise<Record<string, string>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default async function ResourcePoliciesPage(
|
export default async function ResourcePoliciesPage(
|
||||||
@@ -10,5 +20,52 @@ export default async function ResourcePoliciesPage(
|
|||||||
) {
|
) {
|
||||||
const params = await props.params;
|
const params = await props.params;
|
||||||
const t = await getTranslations();
|
const t = await getTranslations();
|
||||||
return <></>;
|
const searchParams = new URLSearchParams(await props.searchParams);
|
||||||
|
|
||||||
|
let org: GetOrgResponse | null = null;
|
||||||
|
try {
|
||||||
|
const res = await getCachedOrg(params.orgId);
|
||||||
|
org = res.data.data;
|
||||||
|
} catch {
|
||||||
|
redirect(`/${params.orgId}/settings/resources`);
|
||||||
|
}
|
||||||
|
|
||||||
|
let policies: ListResourcePoliciesResponse["policies"] = [];
|
||||||
|
let pagination: ListResourcePoliciesResponse["pagination"] = {
|
||||||
|
total: 0,
|
||||||
|
page: 1,
|
||||||
|
pageSize: 20
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await internal.get<
|
||||||
|
AxiosResponse<ListResourcePoliciesResponse>
|
||||||
|
>(
|
||||||
|
`/org/${params.orgId}/resource-policies?${searchParams.toString()}`,
|
||||||
|
await authCookieHeader()
|
||||||
|
);
|
||||||
|
const responseData = res.data.data;
|
||||||
|
policies = responseData.policies;
|
||||||
|
pagination = responseData.pagination;
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SettingsSectionTitle
|
||||||
|
title={t("resourcePoliciesTitle")}
|
||||||
|
description={t("resourcePoliciesDescription")}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<OrgProvider org={org}>
|
||||||
|
<ResourcePoliciesTable
|
||||||
|
policies={policies}
|
||||||
|
orgId={params.orgId}
|
||||||
|
rowCount={pagination.total}
|
||||||
|
pagination={{
|
||||||
|
pageIndex: pagination.page - 1,
|
||||||
|
pageSize: pagination.pageSize
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</OrgProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
|
||||||
import { startTransition, useActionState, useState } from "react";
|
|
||||||
import { useForm } from "react-hook-form";
|
|
||||||
import z from "zod";
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
FormControl,
|
FormControl,
|
||||||
FormDescription,
|
|
||||||
FormField,
|
FormField,
|
||||||
FormItem,
|
FormItem,
|
||||||
FormLabel,
|
FormLabel,
|
||||||
FormMessage
|
FormMessage
|
||||||
} from "@app/components/ui/form";
|
} from "@app/components/ui/form";
|
||||||
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useActionState } from "react";
|
||||||
|
import { useForm } from "react-hook-form";
|
||||||
|
import z from "zod";
|
||||||
import {
|
import {
|
||||||
SettingsSection,
|
SettingsSection,
|
||||||
SettingsSectionBody,
|
SettingsSectionBody,
|
||||||
@@ -21,21 +21,19 @@ import {
|
|||||||
SettingsSectionHeader,
|
SettingsSectionHeader,
|
||||||
SettingsSectionTitle
|
SettingsSectionTitle
|
||||||
} from "./Settings";
|
} from "./Settings";
|
||||||
import { useTranslations } from "next-intl";
|
|
||||||
|
|
||||||
import type { GetLoginPageBrandingResponse } from "@server/routers/loginPage/types";
|
|
||||||
import { Input } from "./ui/input";
|
|
||||||
import { ExternalLink, InfoIcon, XIcon } from "lucide-react";
|
|
||||||
import { Button } from "./ui/button";
|
|
||||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
|
||||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
import { useRouter } from "next/navigation";
|
|
||||||
import { toast } from "@app/hooks/useToast";
|
|
||||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||||
import { build } from "@server/build";
|
import { build } from "@server/build";
|
||||||
import { PaidFeaturesAlert } from "./PaidFeaturesAlert";
|
|
||||||
import { Alert, AlertDescription, AlertTitle } from "./ui/alert";
|
|
||||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||||
|
import type { GetLoginPageBrandingResponse } from "@server/routers/loginPage/types";
|
||||||
|
import { XIcon } from "lucide-react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { PaidFeaturesAlert } from "./PaidFeaturesAlert";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Input } from "./ui/input";
|
||||||
|
|
||||||
export type AuthPageCustomizationProps = {
|
export type AuthPageCustomizationProps = {
|
||||||
orgId: string;
|
orgId: string;
|
||||||
|
|||||||
182
src/components/ResourcePoliciesTable.tsx
Normal file
182
src/components/ResourcePoliciesTable.tsx
Normal file
@@ -0,0 +1,182 @@
|
|||||||
|
"use client";
|
||||||
|
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||||
|
import { useNavigationContext } from "@app/hooks/useNavigationContext";
|
||||||
|
import { toast } from "@app/hooks/useToast";
|
||||||
|
import { createApiClient } from "@app/lib/api";
|
||||||
|
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
|
||||||
|
import type { PaginationState } from "@tanstack/react-table";
|
||||||
|
import { useTranslations } from "next-intl";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useTransition } from "react";
|
||||||
|
import type { ExtendedColumnDef } from "./ui/data-table";
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger
|
||||||
|
} from "./ui/dropdown-menu";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { MoreHorizontal, ArrowRight } from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { ControlledDataTable } from "./ui/controlled-data-table";
|
||||||
|
import { useDebouncedCallback } from "use-debounce";
|
||||||
|
|
||||||
|
type ResourcePolicyRow = ListResourcePoliciesResponse["policies"][number];
|
||||||
|
|
||||||
|
export type ResourcePoliciesTableProps = {
|
||||||
|
policies: Array<ResourcePolicyRow>;
|
||||||
|
orgId: string;
|
||||||
|
pagination: PaginationState;
|
||||||
|
rowCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function ResourcePoliciesTable({
|
||||||
|
policies,
|
||||||
|
orgId,
|
||||||
|
pagination,
|
||||||
|
rowCount
|
||||||
|
}: ResourcePoliciesTableProps) {
|
||||||
|
const router = useRouter();
|
||||||
|
const {
|
||||||
|
navigate: filter,
|
||||||
|
isNavigating: isFiltering,
|
||||||
|
searchParams
|
||||||
|
} = useNavigationContext();
|
||||||
|
const t = useTranslations();
|
||||||
|
|
||||||
|
const { env } = useEnvContext();
|
||||||
|
|
||||||
|
const api = createApiClient({ env });
|
||||||
|
|
||||||
|
const [isRefreshing, startTransition] = useTransition();
|
||||||
|
const [isNavigatingToAddPage, startNavigation] = useTransition();
|
||||||
|
|
||||||
|
const refreshData = () => {
|
||||||
|
startTransition(() => {
|
||||||
|
try {
|
||||||
|
router.refresh();
|
||||||
|
} catch (error) {
|
||||||
|
toast({
|
||||||
|
title: t("error"),
|
||||||
|
description: t("refreshError"),
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const proxyColumns: ExtendedColumnDef<ResourcePolicyRow>[] = [
|
||||||
|
{
|
||||||
|
accessorKey: "name",
|
||||||
|
enableHiding: false,
|
||||||
|
friendlyName: t("name"),
|
||||||
|
header: () => <span className="p-3">{t("name")}</span>
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "niceId",
|
||||||
|
accessorKey: "nice",
|
||||||
|
friendlyName: t("identifier"),
|
||||||
|
enableHiding: true,
|
||||||
|
header: () => <span className="p-3">{t("identifier")}</span>,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
return <span>{row.original.niceId || "-"}</span>;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "actions",
|
||||||
|
enableHiding: false,
|
||||||
|
header: () => <span className="p-3"></span>,
|
||||||
|
cell: ({ row }) => {
|
||||||
|
const policyRow = row.original;
|
||||||
|
return (
|
||||||
|
<div className="flex items-center gap-2 justify-end">
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="ghost" className="h-8 w-8 p-0">
|
||||||
|
<span className="sr-only">
|
||||||
|
{t("openMenu")}
|
||||||
|
</span>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<Link
|
||||||
|
className="block w-full"
|
||||||
|
href={`/${policyRow.orgId}/settings/resources/proxy/${policyRow.niceId}`}
|
||||||
|
>
|
||||||
|
<DropdownMenuItem>
|
||||||
|
{t("viewSettings")}
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</Link>
|
||||||
|
<DropdownMenuItem
|
||||||
|
onClick={() => {
|
||||||
|
// setSelectedResource(resourceRow);
|
||||||
|
// setIsDeleteModalOpen(true);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className="text-red-500">
|
||||||
|
{t("delete")}
|
||||||
|
</span>
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
<Link
|
||||||
|
href={`/${policyRow.orgId}/settings/resources/proxy/${policyRow.niceId}`}
|
||||||
|
>
|
||||||
|
<Button variant={"outline"}>
|
||||||
|
{t("edit")}
|
||||||
|
<ArrowRight className="ml-2 w-4 h-4" />
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const handlePaginationChange = (newPage: PaginationState) => {
|
||||||
|
searchParams.set("page", (newPage.pageIndex + 1).toString());
|
||||||
|
searchParams.set("pageSize", newPage.pageSize.toString());
|
||||||
|
filter({
|
||||||
|
searchParams
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSearchChange = useDebouncedCallback((query: string) => {
|
||||||
|
searchParams.set("query", query);
|
||||||
|
searchParams.delete("page");
|
||||||
|
filter({
|
||||||
|
searchParams
|
||||||
|
});
|
||||||
|
}, 300);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<ControlledDataTable
|
||||||
|
columns={proxyColumns}
|
||||||
|
rows={policies}
|
||||||
|
tableId="resource-policies"
|
||||||
|
searchPlaceholder={t("resourcePoliciesSearch")}
|
||||||
|
pagination={pagination}
|
||||||
|
rowCount={rowCount}
|
||||||
|
onSearch={handleSearchChange}
|
||||||
|
onPaginationChange={handlePaginationChange}
|
||||||
|
onAdd={() =>
|
||||||
|
startNavigation(() =>
|
||||||
|
router.push(
|
||||||
|
`/${orgId}/settings/resources/policies/create`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
addButtonText={t("resourcePoliciesAdd")}
|
||||||
|
onRefresh={refreshData}
|
||||||
|
isRefreshing={isRefreshing || isFiltering}
|
||||||
|
isNavigatingToAddPage={isNavigatingToAddPage}
|
||||||
|
enableColumnVisibility
|
||||||
|
columnVisibility={{ niceId: false }}
|
||||||
|
stickyLeftColumn="name"
|
||||||
|
stickyRightColumn="actions"
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user