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() {