Files
govoplan-core/webui/src/features/admin/AdminOverviewPanel.tsx

95 lines
7.3 KiB
TypeScript

import { useEffect, useState } from "react";
import type { ApiSettings } from "../../types";
import { fetchAdminOverview, type AdminOverview } from "../../api/admin";
import Card from "../../components/Card";
import Button from "../../components/Button";
import AdminPageLayout from "./components/AdminPageLayout";
import { adminErrorMessage } from "./adminUtils";
export default function AdminOverviewPanel({ settings, onSelect, availableSections }: { settings: ApiSettings; onSelect: (section: string) => void; availableSections: ReadonlySet<string> }) {
const [overview, setOverview] = useState<AdminOverview | null>(null);
const [error, setError] = useState("");
const [loading, setLoading] = useState(true);
async function load() {
setLoading(true);
setError("");
try { setOverview(await fetchAdminOverview(settings)); }
catch (err) { setError(adminErrorMessage(err)); }
finally { setLoading(false); }
}
useEffect(() => { void load(); }, [settings.accessToken, settings.apiBaseUrl]);
const hasSystemMetrics = Boolean(overview && [
overview.tenant_count,
overview.system_account_count,
overview.system_group_template_count,
overview.system_role_template_count
].some((value) => value !== null && value !== undefined));
return (
<AdminPageLayout title="Administration" description="System-wide governance and tenant-local access management, separated by scope and enforced by the backend." loading={loading} error={error} actions={<Button onClick={() => void load()} disabled={loading}>Reload</Button>}>
{overview && <>
{hasSystemMetrics && <>
<div className="admin-overview-section-label">System</div>
<div className="metric-grid">
<Metric title="Tenants" value={overview.tenant_count ?? "—"} text="Registered tenant spaces." />
<Metric title="Users" value={overview.system_account_count ?? "—"} text="Global login accounts across all tenants." />
<Metric title="Central groups" value={overview.system_group_template_count ?? "—"} text="System-governed group definitions." />
<Metric title="Tenant roles" value={overview.system_role_template_count ?? "—"} text="Centrally governed tenant roles." />
</div>
</>}
{hasSystemArea(availableSections) && <Card title="System administration">
<div className="admin-overview-grid">
{availableSections.has("system-settings") && <AreaLink title="Settings" text="Instance defaults and tenant governance capabilities." onClick={() => onSelect("system-settings")} />}
{availableSections.has("system-retention") && <AreaLink title="Retention" text="Instance privacy retention policy and lower-level limiting permissions." onClick={() => onSelect("system-retention")} />}
{availableSections.has("system-tenants") && <AreaLink title="Tenants" text="Create, suspend and govern tenant spaces." onClick={() => onSelect("system-tenants")} />}
{availableSections.has("system-users") && <AreaLink title="Users" text="Global accounts, tenant memberships and system-role assignments." onClick={() => onSelect("system-users")} />}
{availableSections.has("system-groups") && <AreaLink title="Central groups" text="Group definitions made available or required in selected tenants." onClick={() => onSelect("system-groups")} />}
{availableSections.has("system-roles") && <AreaLink title="System roles" text="Instance-wide roles assigned directly to global accounts." onClick={() => onSelect("system-roles")} />}
{availableSections.has("system-role-templates") && <AreaLink title="Tenant roles" text="Centrally governed tenant roles and their availability across tenants." onClick={() => onSelect("system-role-templates")} />}
{availableSections.has("system-audit") && <AreaLink title="Audit" text="System-level administrative history." onClick={() => onSelect("system-audit")} />}
</div>
</Card>}
<div className="admin-overview-section-label">Active tenant: {overview.active_tenant_name}</div>
<div className="metric-grid">
<Metric title="Tenant users" value={`${overview.active_user_count}/${overview.user_count}`} text="Active and total memberships." />
<Metric title="Groups" value={overview.group_count} text="Tenant-local and centrally managed groups." />
<Metric title="Roles" value={overview.role_count} text="Tenant-local and centrally managed roles." />
<Metric title="API keys" value={overview.active_api_key_count} text="Active tenant automation credentials." />
</div>
<Card title="Tenant administration">
<div className="admin-overview-grid">
{availableSections.has("tenant-settings") && <AreaLink title="Settings" text="Tenant-specific settings and governance overrides." onClick={() => onSelect("tenant-settings")} />}
{availableSections.has("tenant-users") && <AreaLink title="Users" text="Membership status, groups and direct roles in the active tenant." onClick={() => onSelect("tenant-users")} />}
{availableSections.has("tenant-groups") && <AreaLink title="Groups" text="Tenant memberships and inherited roles." onClick={() => onSelect("tenant-groups")} />}
{availableSections.has("tenant-roles") && <AreaLink title="Roles" text="Tenant permission bundles and system-managed role copies." onClick={() => onSelect("tenant-roles")} />}
{availableSections.has("tenant-api-keys") && <AreaLink title="API keys" text="Scoped automation credentials capped by owner permissions." onClick={() => onSelect("tenant-api-keys")} />}
{availableSections.has("tenant-mail-servers") && <AreaLink title="Mail servers" text="Reusable encrypted SMTP/IMAP profiles and mail policy." onClick={() => onSelect("tenant-mail-servers")} />}
{availableSections.has("tenant-retention") && <AreaLink title="Retention" text="Tenant-level privacy retention limits inherited by owned objects." onClick={() => onSelect("tenant-retention")} />}
{availableSections.has("tenant-user-retention") && <AreaLink title="User retention" text="Retention limits for user-owned campaigns." onClick={() => onSelect("tenant-user-retention")} />}
{availableSections.has("tenant-group-retention") && <AreaLink title="Group retention" text="Retention limits for group-owned campaigns." onClick={() => onSelect("tenant-group-retention")} />}
{availableSections.has("tenant-audit") && <AreaLink title="Audit" text="Tenant-level administrative history only." onClick={() => onSelect("tenant-audit")} />}
</div>
</Card>
</>}
</AdminPageLayout>
);
}
function hasSystemArea(sections: ReadonlySet<string>): boolean {
return ["system-settings", "system-retention", "system-tenants", "system-users", "system-groups", "system-roles", "system-role-templates", "system-audit"].some((section) => sections.has(section));
}
function Metric({ title, value, text }: { title: string; value: string | number; text: string }) {
return <Card title={title}><strong className="module-big-number">{value}</strong><p className="muted">{text}</p></Card>;
}
function AreaLink({ title, text, onClick }: { title: string; text: string; onClick: () => void }) {
return <button className="admin-overview-link" onClick={onClick}><strong>{title}</strong><span>{text}</span></button>;
}