feat(platform): Sistema > Usuários/Auditoria, Infraestrutura > Saúde

Três endpoints novos, todos platform-only: GET /platform/users (cross-
tenant, users não tem RLS) + PATCH .../status (desabilitar tem efeito
real — login() já checava status ACTIVE desde a PHASE 04); GET
/platform/audit-log (últimos 200 eventos, audit_logs também sem RLS,
linha imutável); GET /platform/health (Postgres/Redis + FreeSWITCH via
conexão ESL avulsa, sem manter estado).

Achado de arquitetura documentado explicitamente na própria tela: o check
de FreeSWITCH sempre falha neste ambiente porque apps/api roda no host e
a porta 8021 é deliberadamente não publicada (decisão da PHASE 01/05) —
não é um bug, é a rede isolada do jeito certo.

Frontend: /platform/sistema/usuarios, /auditoria, /platform/
infraestrutura/saude. Testado ponta a ponta contra dados reais (3
usuários da plataforma, audit log com eventos reais desta sessão,
inclusive uma referência órfã tratada corretamente). Smoke test nas 19
telas anteriores, 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:04:05 -03:00
parent 2c1269a83a
commit a23e68b011
17 changed files with 654 additions and 3 deletions

View File

@@ -47,6 +47,42 @@ export const TENANT_STATUS_LABELS: Record<string, string> = {
CANCELLED: "Cancelado",
};
export interface PlatformUser {
id: string;
email: string;
name: string;
status: "ACTIVE" | "DISABLED";
mustChangePassword: boolean;
createdAt: string;
isPlatformUser: boolean;
}
export interface AuditLogEntry {
id: string;
action: string;
tenantId: string | null;
tenantName: string | null;
userId: string | null;
userEmail: string | null;
entityType: string | null;
entityId: string | null;
before: unknown;
after: unknown;
createdAt: string;
}
export interface HealthCheck {
status: "ok" | "fail";
latencyMs: number;
}
export interface PlatformHealth {
postgres: HealthCheck;
redis: HealthCheck;
freeswitch: HealthCheck;
checkedAt: string;
}
export const PLAN_LIMIT_FIELDS: { key: keyof Plan; label: string }[] = [
{ key: "maxExtensions", label: "Ramais" },
{ key: "maxAgents", label: "Agentes" },