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:
2026-08-29 20:41:19 -03:00
parent c95c6805fb
commit 03ec0d556b
7 changed files with 182 additions and 2 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 171 KiB

View 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} />;
}

View File

@@ -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 é 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>
);
}

View File

@@ -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",