feat(platform): Sistema > Permissões (catálogo RBAC, só leitura)
GET /platform/roles novo — lista as 4 roles do sistema com as permissions de cada uma, mais o catálogo completo de permissions. roles/permissions não têm RLS (catálogo global do seed); só leitura, RBAC é system-defined, sem UI de criar role customizada ainda. Frontend: /platform/sistema/permissoes, um card por role + tabela do catálogo completo. Testado ponta a ponta: as 4 roles reais (platform_super_admin 33 permissions, tenant_admin 30, supervisor 17, agent 2) corretas. Smoke test nas 19 telas do tenant + 9 telas platform, todas 200. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BFaBaBSQGhyXGEgtTYZGV8
This commit is contained in:
24
apps/frontend/src/app/platform/sistema/permissoes/page.tsx
Normal file
24
apps/frontend/src/app/platform/sistema/permissoes/page.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { requireSession } from "@/lib/session";
|
||||
import { apiFetch } from "@/lib/api";
|
||||
import { PermissoesView } from "./permissoes-view";
|
||||
|
||||
interface RoleWithPermissions {
|
||||
id: string;
|
||||
key: string;
|
||||
name: string;
|
||||
scope: "PLATFORM" | "TENANT";
|
||||
isSystem: boolean;
|
||||
permissionKeys: string[];
|
||||
}
|
||||
|
||||
interface Permission {
|
||||
id: string;
|
||||
key: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export default async function PermissoesPage() {
|
||||
const session = await requireSession();
|
||||
const data = await apiFetch<{ roles: RoleWithPermissions[]; permissions: Permission[] }>("/platform/roles", session.accessToken);
|
||||
return <PermissoesView roles={data.roles} permissions={data.permissions} />;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
import { ShieldCheck } from "lucide-react";
|
||||
import { Panel, PanelHeader } from "@/components/ui/panel";
|
||||
import { Pill } from "@/components/ui/pill";
|
||||
import { TBody, TD, TH, THead, TR, Table } from "@/components/ui/table";
|
||||
|
||||
interface RoleWithPermissions {
|
||||
id: string;
|
||||
key: string;
|
||||
name: string;
|
||||
scope: "PLATFORM" | "TENANT";
|
||||
isSystem: boolean;
|
||||
permissionKeys: string[];
|
||||
}
|
||||
|
||||
interface Permission {
|
||||
id: string;
|
||||
key: string;
|
||||
description: string | null;
|
||||
}
|
||||
|
||||
export function PermissoesView({ roles, permissions }: { roles: RoleWithPermissions[]; permissions: Permission[] }) {
|
||||
return (
|
||||
<div className="space-y-5">
|
||||
<div>
|
||||
<h1 className="text-lg font-semibold text-foreground">Permissões</h1>
|
||||
<p className="mt-1 max-w-2xl text-sm text-muted-foreground">
|
||||
RBAC do sistema (agente.md secao 142-145) — roles e o catálogo de permissions são definidos no seed, sem
|
||||
UI de criar role customizada ainda; esta tela é só leitura.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="space-y-4">
|
||||
{roles.map((role) => (
|
||||
<Panel key={role.id}>
|
||||
<div className="flex flex-wrap items-center justify-between gap-2 border-b border-border px-5 py-4">
|
||||
<div className="flex items-center gap-2">
|
||||
<ShieldCheck className="h-4 w-4 text-muted-foreground" aria-hidden />
|
||||
<h2 className="text-sm font-semibold text-foreground">{role.name}</h2>
|
||||
<Pill>{role.key}</Pill>
|
||||
<Pill tone={role.scope === "PLATFORM" ? "accent" : "neutral"}>{role.scope === "PLATFORM" ? "Plataforma" : "Tenant"}</Pill>
|
||||
</div>
|
||||
<span className="text-xs text-muted-foreground">{role.permissionKeys.length} permission(ões)</span>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-1.5 p-5">
|
||||
{role.permissionKeys.length === 0 ? (
|
||||
<span className="text-sm text-muted-foreground">Nenhuma permission atribuída.</span>
|
||||
) : (
|
||||
role.permissionKeys.map((key) => (
|
||||
<span key={key} className="rounded-full border border-border bg-muted px-2 py-0.5 font-mono text-xs text-muted-foreground">
|
||||
{key}
|
||||
</span>
|
||||
))
|
||||
)}
|
||||
</div>
|
||||
</Panel>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<Panel>
|
||||
<PanelHeader title="Catálogo de permissions" description={`${permissions.length} permission(ões) no sistema`} />
|
||||
<Table>
|
||||
<THead>
|
||||
<TR>
|
||||
<TH>Chave</TH>
|
||||
<TH>Descrição</TH>
|
||||
</TR>
|
||||
</THead>
|
||||
<TBody>
|
||||
{permissions.map((p) => (
|
||||
<TR key={p.id}>
|
||||
<TD className="font-mono text-xs text-foreground">{p.key}</TD>
|
||||
<TD className="text-muted-foreground">{p.description ?? "—"}</TD>
|
||||
</TR>
|
||||
))}
|
||||
</TBody>
|
||||
</Table>
|
||||
</Panel>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -84,7 +84,11 @@ export const PLATFORM_NAV: NavSection[] = [
|
||||
href: "/platform/sistema/usuarios",
|
||||
description: "Todos os usuários da plataforma, cross-tenant",
|
||||
},
|
||||
{ label: "Permissões" },
|
||||
{
|
||||
label: "Permissões",
|
||||
href: "/platform/sistema/permissoes",
|
||||
description: "Catálogo de roles e permissions do sistema",
|
||||
},
|
||||
{
|
||||
label: "Auditoria",
|
||||
href: "/platform/sistema/auditoria",
|
||||
|
||||
Reference in New Issue
Block a user