feat(CC-084): add Platform Administration page (members/audit/danger zone/links)
This commit is contained in:
parent
a25c54970f
commit
d3ee6b98fa
1 changed files with 123 additions and 0 deletions
123
src/app/dashboard/settings/platform/page.tsx
Normal file
123
src/app/dashboard/settings/platform/page.tsx
Normal file
|
|
@ -0,0 +1,123 @@
|
|||
'use client';
|
||||
|
||||
import { useState } from 'react';
|
||||
import { useQuery } from '@tanstack/react-query';
|
||||
import Link from 'next/link';
|
||||
import { apiFetch } from '../../../../lib/api';
|
||||
import { useSession } from '../../../../components/session-provider';
|
||||
|
||||
interface AuditEvent { id: string; action: string; userId: string | null; createdAt: string; metadata: Record<string,unknown> | null; }
|
||||
interface Member { id: string; userId: string; role: string; createdAt: string; }
|
||||
|
||||
export default function SettingsPlatformPage() {
|
||||
const { activeTenant } = useSession();
|
||||
const tenantId = activeTenant?.tenantId ?? '';
|
||||
const [dangerOpen, setDangerOpen] = useState(false);
|
||||
|
||||
const { data: members = [] } = useQuery({ queryKey: ['plat-members', tenantId], queryFn: () => apiFetch<Member[]>('/v1/workspace/members', { tenantId }), enabled: Boolean(tenantId), staleTime: 120_000 });
|
||||
const { data: audit = [] } = useQuery({ queryKey: ['plat-audit', tenantId], queryFn: () => apiFetch<AuditEvent[]>('/v1/audit-log?limit=50', { tenantId }), enabled: Boolean(tenantId), staleTime: 60_000 });
|
||||
|
||||
const adminCount = members.filter((m) => m.role === 'admin' || m.role === 'owner').length;
|
||||
|
||||
const PLATFORM_LINKS = [
|
||||
{ label: 'Utilizatori & Roluri', href: '/dashboard/privacy/delegated', icon: '👥', desc: `${members.length} membri, ${adminCount} admini` },
|
||||
{ label: 'API & Dezvoltatori', href: '/dashboard/settings/api', icon: '🔑', desc: 'Chei API, endpoint-uri' },
|
||||
{ label: 'Integrări', href: '/dashboard/integrations', icon: '🔌', desc: 'Conexiuni externe active' },
|
||||
{ label: 'Export control', href: '/dashboard/data/export-control', icon: '📤', desc: 'Log exporturi date' },
|
||||
{ label: 'Security Events', href: '/dashboard/privacy/security', icon: '🛡️', desc: 'Autentificări, accesuri' },
|
||||
{ label: 'Retenție date', href: '/dashboard/privacy/retention', icon: '📁', desc: 'Politici GDPR' },
|
||||
];
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl space-y-6 p-6">
|
||||
<div>
|
||||
<h1 className="font-display text-2xl font-semibold text-ink">Administrare platformă</h1>
|
||||
<p className="text-sm text-ink-faint mt-1">Configurare avansată CEO OS — acces, integrări, GDPR, date.</p>
|
||||
</div>
|
||||
|
||||
{/* Overview */}
|
||||
<div className="grid grid-cols-3 gap-3">
|
||||
<div className="card p-4 text-center">
|
||||
<p className="text-2xl font-bold text-ink">{members.length}</p>
|
||||
<p className="text-[10px] text-ink-faint">membri workspace</p>
|
||||
</div>
|
||||
<div className="card p-4 text-center">
|
||||
<p className="text-2xl font-bold text-ink">{adminCount}</p>
|
||||
<p className="text-[10px] text-ink-faint">administratori</p>
|
||||
</div>
|
||||
<div className="card p-4 text-center">
|
||||
<p className="text-2xl font-bold text-ink">{audit.length}</p>
|
||||
<p className="text-[10px] text-ink-faint">evenimente recente</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Navigation links */}
|
||||
<div className="grid gap-2 sm:grid-cols-2">
|
||||
{PLATFORM_LINKS.map((link) => (
|
||||
<Link key={link.href} href={link.href}
|
||||
className="card p-4 flex items-center gap-3 hover:border-primary/40 transition-colors">
|
||||
<span className="text-xl">{link.icon}</span>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-ink">{link.label}</p>
|
||||
<p className="text-[10px] text-ink-faint">{link.desc}</p>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Recent audit */}
|
||||
{audit.length > 0 && (
|
||||
<div className="card p-5 space-y-3">
|
||||
<p className="text-xs font-semibold uppercase tracking-wider text-ink-faint">Activitate recentă platformă</p>
|
||||
<div className="space-y-2 max-h-64 overflow-y-auto">
|
||||
{audit.slice(0, 20).map((ev) => (
|
||||
<div key={ev.id} className="flex items-center gap-3 text-xs">
|
||||
<span className="text-ink-faint shrink-0 font-mono text-[10px]">
|
||||
{new Date(ev.createdAt).toLocaleTimeString('ro-RO', { hour: '2-digit', minute: '2-digit' })}
|
||||
</span>
|
||||
<span className="text-ink truncate">{ev.action}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<Link href="/dashboard/privacy/security" className="text-xs text-primary hover:underline">
|
||||
Vezi toate evenimentele →
|
||||
</Link>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Danger zone */}
|
||||
<div className="card p-5 border-signal-danger/30 space-y-3">
|
||||
<button onClick={() => setDangerOpen(!dangerOpen)}
|
||||
className="flex items-center gap-2 text-sm font-semibold text-signal-danger w-full">
|
||||
<span>⚠️ Zona periculoasă</span>
|
||||
<span className="ml-auto text-ink-faint">{dangerOpen ? '▲' : '▼'}</span>
|
||||
</button>
|
||||
{dangerOpen && (
|
||||
<div className="space-y-3 pt-2 border-t border-signal-danger/20">
|
||||
<p className="text-[10px] text-ink-faint">
|
||||
Acțiunile din această zonă sunt ireversibile sau pot afecta datele platformei.
|
||||
Contactează support înainte de a proceda.
|
||||
</p>
|
||||
{[
|
||||
{ label: 'Exportă toate datele (GDPR Article 20)', desc: 'Descarcă toate datele workspace-ului în format JSON', action: 'export' },
|
||||
{ label: 'Resetează setările workspace', desc: 'Revine la configurația implicită (nu șterge date)', action: 'reset-settings' },
|
||||
{ label: 'Șterge workspace-ul', desc: 'Șterge definitiv toate datele — IREVERSIBIL', action: 'delete', danger: true },
|
||||
].map((op) => (
|
||||
<div key={op.action} className="flex items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className={`text-xs font-medium ${op.danger ? 'text-signal-danger' : 'text-ink'}`}>{op.label}</p>
|
||||
<p className="text-[10px] text-ink-faint">{op.desc}</p>
|
||||
</div>
|
||||
<button disabled
|
||||
className={`rounded border px-3 py-1 text-[10px] font-medium shrink-0 opacity-60 cursor-not-allowed ${op.danger ? 'border-signal-danger text-signal-danger' : 'border-border text-ink-faint'}`}>
|
||||
{op.action === 'delete' ? 'Șterge' : op.action === 'export' ? 'Exportă' : 'Resetează'}
|
||||
</button>
|
||||
</div>
|
||||
))}
|
||||
<p className="text-[10px] text-ink-faint italic">* Butoanele sunt dezactivate — funcționalitate în dezvoltare</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Loading…
Reference in a new issue