From 2eb6850cb91665699fccf349f0a999c686f7d19e Mon Sep 17 00:00:00 2001 From: Alex Holliday Date: Sun, 14 Dec 2025 00:24:53 +0000 Subject: [PATCH] diagnostics access --- .../src/pages/diagnostic/DiagnosticPage.tsx | 9 +- .../pages/diagnostic/DiagnosticsAccessTab.tsx | 163 ++++++++++++++++++ server/src/routes/me.ts | 15 ++ 3 files changed, 186 insertions(+), 1 deletion(-) create mode 100644 client/src/pages/diagnostic/DiagnosticsAccessTab.tsx diff --git a/client/src/pages/diagnostic/DiagnosticPage.tsx b/client/src/pages/diagnostic/DiagnosticPage.tsx index 11aeec507..aca2e881c 100644 --- a/client/src/pages/diagnostic/DiagnosticPage.tsx +++ b/client/src/pages/diagnostic/DiagnosticPage.tsx @@ -2,7 +2,8 @@ import { BasePage, Tab, Tabs, InfoBox } from "@/components/design-elements"; import { DiagnosticsLogTab } from "@/pages/diagnostic/DiagnosticsLogTab"; import { DiagnosticsQueueTab } from "@/pages/diagnostic/DiagnosticsQueueTab"; import DiagnosticsConfigTab from "@/pages/diagnostic/DiagnosticsConfigTab"; -import { FileText, ListTodo, Settings } from "lucide-react"; +import DiagnosticsAccessTab from "@/pages/diagnostic/DiagnosticsAccessTab"; +import { FileText, ListTodo, Settings, Shield } from "lucide-react"; import { useState } from "react"; const DiagnosticPage = () => { @@ -34,10 +35,16 @@ const DiagnosticPage = () => { value="config" icon={} /> + } + /> {tabValue === "logs" && } {tabValue === "jobs" && } {tabValue === "config" && } + {tabValue === "access" && } ); }; diff --git a/client/src/pages/diagnostic/DiagnosticsAccessTab.tsx b/client/src/pages/diagnostic/DiagnosticsAccessTab.tsx new file mode 100644 index 000000000..1bc176f67 --- /dev/null +++ b/client/src/pages/diagnostic/DiagnosticsAccessTab.tsx @@ -0,0 +1,163 @@ +import Stack from "@mui/material/Stack"; +import Typography from "@mui/material/Typography"; +import { useTheme } from "@mui/material/styles"; +import Divider from "@mui/material/Divider"; +import Box from "@mui/material/Box"; +import { StatBox, Table } from "@/components/design-elements"; +import type { Header } from "@/components/design-elements/Table"; +import { useGet } from "@/hooks/UseApi"; +import { useAppSelector } from "@/hooks/AppHooks"; +import type { Entitlements } from "@/types/entitlements"; + +type PermissionsResponse = { + message: string; + data: { + org: string[]; + team: { teamId: string; permission: string }[]; + }; +}; + +type EntitlementRow = { id?: string | number; key: string; value: any }; +type PermissionRow = { + id?: string | number; + scope: "org" | "team"; + team?: string; + permission: string; +}; + +export default function DiagnosticsAccessTab() { + const theme = useTheme(); + const user = useAppSelector((s: any) => s.auth?.user); + const selectedTeamId = useAppSelector((s: any) => s.auth?.selectedTeamId); + const { response: permsRes } = useGet( + "/me/permissions", + {}, + { refreshInterval: 10000 } + ); + const { response: entsRes } = useGet<{ entitlements: Entitlements }>( + "/me/entitlements", + {}, + { refreshInterval: 30000 } + ); + + const orgPerms = permsRes?.data?.org || []; + const teamPerms = permsRes?.data?.team || []; + + const entitlementsObj = entsRes?.entitlements as Entitlements | undefined; + const entitlements: EntitlementRow[] = entitlementsObj + ? (Object.entries(entitlementsObj) as [keyof Entitlements, any][]).map( + ([key, value]) => ({ id: String(key), key: String(key), value }) + ) + : []; + + const teamIdToName = new Map( + (Array.isArray(user?.teams) ? user?.teams : []).map((t: any) => [ + String(t?.id ?? t?._id ?? ""), + String(t?.name ?? t?.id ?? t?._id ?? "unknown"), + ]) + ); + const permissionRows: PermissionRow[] = [ + ...orgPerms.map((p, idx) => ({ + id: `org-${idx}-${String(p)}`, + scope: "org" as const, + team: "-", + permission: String(p), + })), + ...teamPerms.map((tp, idx) => ({ + id: `team-${idx}-${String(tp?.teamId ?? idx)}-${String(tp?.permission)}`, + scope: "team" as const, + team: teamIdToName.get(String(tp?.teamId)) ?? String(tp?.teamId ?? "-"), + permission: String(tp?.permission), + })), + ]; + + const permHeaders: Header[] = [ + { id: "scope", content: "Scope", render: (r) => r.scope }, + { id: "team", content: "Team", render: (r) => r.team ?? "-" }, + { id: "permission", content: "Permission", render: (r) => r.permission }, + ]; + + const entHeaders: Header[] = [ + { id: "key", content: "Entitlement", render: (r) => r.key }, + { id: "value", content: "Value", render: (r) => r.value }, + ]; + + const clientOrgPermsArr: string[] = Array.isArray(user?.org?.permissions) + ? (user?.org?.permissions as any[]).map((p) => String(p)) + : []; + const clientTeamsArr: any[] = Array.isArray(user?.teams) + ? user?.teams.filter( + (t: any) => + String(t?.id ?? t?._id ?? "") === String(selectedTeamId ?? "") + ) + : []; + + const clientEntitlementsObj = (user?.entitlements || {}) as Record< + string, + unknown + >; + const clientEntitlements: EntitlementRow[] = Object.entries( + clientEntitlementsObj + ).map(([key, value]) => ({ id: key, key, value })); + + const clientPermissionRows: PermissionRow[] = [ + ...clientOrgPermsArr.map((p, idx) => ({ + id: `c-org-${idx}-${p}`, + scope: "org" as const, + team: "-", + permission: p, + })), + ...clientTeamsArr.flatMap((t) => { + const perms = t?.permissions; + if (Array.isArray(perms)) + return perms.map((p: any, i: number) => ({ + id: `c-team-${t?.id}-${i}-${String(p)}`, + scope: "team" as const, + team: String(t?.name ?? t?.id ?? "unknown"), + permission: String(p), + })); + if (typeof perms === "string") + return [ + { + id: `c-team-${t?.id}-0-${String(perms)}`, + scope: "team" as const, + team: String(t?.name ?? t?.id ?? "unknown"), + permission: String(perms), + }, + ]; + return []; + }), + ]; + + const clientPermHeaders: Header[] = [ + { id: "scope", content: "Scope", render: (r) => r.scope }, + { id: "team", content: "Team", render: (r) => r.team ?? "-" }, + { id: "permission", content: "Permission", render: (r) => r.permission }, + ]; + + return ( + + + Server permissions + + + + + Server entitlements +
+ + + + + + Client permissions +
+ + + + Client entitlements +
+ + + ); +} diff --git a/server/src/routes/me.ts b/server/src/routes/me.ts index ff7e41bcf..bd1a2c01b 100644 --- a/server/src/routes/me.ts +++ b/server/src/routes/me.ts @@ -19,6 +19,21 @@ class MeRoutes { return res.json({ entitlements }); } ); + + this.router.get("/permissions", verifyToken, addUserContext, (req, res) => { + const orgPerms = req.user?.roles?.orgRole?.permissions || []; + const teamPerms = req.user?.roles?.teamRole?.permissions || []; + const currentTeamId = req.user?.currentTeamId; + return res.json({ + message: "OK", + data: { + org: orgPerms, + team: Array.isArray(teamPerms) + ? teamPerms.map((p: string) => ({ teamId: currentTeamId, permission: p })) + : [], + }, + }); + }); }; getRouter() {