143 lines
4.5 KiB
TypeScript
143 lines
4.5 KiB
TypeScript
"use client";
|
|
|
|
import { useEffect, useState } from "react";
|
|
import { Table, TextField, SelectField, useToast } from "@nexarch/shl";
|
|
import type { TableColumn } from "@nexarch/shl";
|
|
import {
|
|
ApiError,
|
|
Assignment,
|
|
Role,
|
|
RoleInfo,
|
|
assignRole,
|
|
fetchRoleHistory,
|
|
fetchRoles,
|
|
} from "../../lib/api";
|
|
|
|
export default function RolesPage() {
|
|
const { push } = useToast();
|
|
const [roles, setRoles] = useState<RoleInfo[] | null>(null);
|
|
const [loadError, setLoadError] = useState<string | null>(null);
|
|
|
|
const [targetUserId, setTargetUserId] = useState("");
|
|
const [targetRole, setTargetRole] = useState<Role>("user");
|
|
const [assignError, setAssignError] = useState<string | null>(null);
|
|
const [submitting, setSubmitting] = useState(false);
|
|
|
|
const [historyUserId, setHistoryUserId] = useState("");
|
|
const [history, setHistory] = useState<Assignment[] | null>(null);
|
|
|
|
useEffect(() => {
|
|
fetchRoles()
|
|
.then(setRoles)
|
|
.catch(() => setLoadError("Rollen konnten nicht geladen werden. Sind Sie als Tenant-Admin angemeldet?"));
|
|
}, []);
|
|
|
|
async function onAssign(event: React.FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
setAssignError(null);
|
|
setSubmitting(true);
|
|
try {
|
|
await assignRole(targetUserId, targetRole);
|
|
push("Rolle zugewiesen.", "success");
|
|
} catch (err) {
|
|
// Akzeptanzkriterium 2: die Backend-Meldung bei Selbst-Eskalation wird
|
|
// unveraendert angezeigt, keine eigene Interpretation im Frontend.
|
|
setAssignError(err instanceof ApiError ? err.message : "Rolle konnte nicht zugewiesen werden.");
|
|
} finally {
|
|
setSubmitting(false);
|
|
}
|
|
}
|
|
|
|
async function onLoadHistory(event: React.FormEvent<HTMLFormElement>) {
|
|
event.preventDefault();
|
|
try {
|
|
setHistory(await fetchRoleHistory(historyUserId));
|
|
} catch {
|
|
setHistory([]);
|
|
}
|
|
}
|
|
|
|
const historyColumns: TableColumn<Assignment>[] = [
|
|
{ key: "role", header: "Rolle", render: (a) => a.Role },
|
|
{ key: "grantedBy", header: "Vergeben von", render: (a) => a.GrantedBy },
|
|
];
|
|
|
|
if (loadError) {
|
|
return (
|
|
<main style={{ maxWidth: 640, margin: "40px auto", padding: "0 16px" }}>
|
|
<p role="alert">{loadError}</p>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<main style={{ maxWidth: 640, margin: "40px auto", padding: "0 16px" }}>
|
|
<h1>Rollen & Rechte</h1>
|
|
|
|
<section aria-labelledby="rollen-heading">
|
|
<h2 id="rollen-heading">Rollen und Grundrechte</h2>
|
|
{roles?.map((r) => (
|
|
<div key={r.role} style={{ marginBottom: 12 }}>
|
|
<strong>{roleLabel(r.role)}</strong>
|
|
<ul>
|
|
{r.permissions.map((p) => (
|
|
<li key={p}>{p}</li>
|
|
))}
|
|
</ul>
|
|
</div>
|
|
))}
|
|
</section>
|
|
|
|
<section aria-labelledby="zuweisen-heading">
|
|
<h2 id="zuweisen-heading">Rolle zuweisen</h2>
|
|
<form onSubmit={onAssign} noValidate>
|
|
<TextField
|
|
label="Benutzer-ID"
|
|
required
|
|
value={targetUserId}
|
|
onChange={(e) => setTargetUserId(e.target.value)}
|
|
/>
|
|
<SelectField label="Rolle" value={targetRole} onChange={(e) => setTargetRole(e.target.value as Role)}>
|
|
<option value="user">Benutzer</option>
|
|
<option value="tenant_admin">Tenant-Admin</option>
|
|
</SelectField>
|
|
{assignError && (
|
|
<p role="alert" style={{ color: "var(--shl-color-danger)" }}>
|
|
{assignError}
|
|
</p>
|
|
)}
|
|
<button type="submit" disabled={submitting}>
|
|
{submitting ? "Wird zugewiesen…" : "Zuweisen"}
|
|
</button>
|
|
</form>
|
|
</section>
|
|
|
|
<section aria-labelledby="verlauf-heading">
|
|
<h2 id="verlauf-heading">Nachvollziehbarkeit (Audit-Verlauf)</h2>
|
|
<p>Zeigt jede Rollenzuweisung eines Benutzers chronologisch — wer wann welche Rolle vergeben hat.</p>
|
|
<form onSubmit={onLoadHistory} noValidate>
|
|
<TextField
|
|
label="Benutzer-ID"
|
|
required
|
|
value={historyUserId}
|
|
onChange={(e) => setHistoryUserId(e.target.value)}
|
|
/>
|
|
<button type="submit">Verlauf anzeigen</button>
|
|
</form>
|
|
{history && (
|
|
<Table
|
|
columns={historyColumns}
|
|
rows={history}
|
|
rowKey={(a) => `${a.UserID}-${a.Role}-${a.GrantedBy}`}
|
|
caption="Rollenzuweisungs-Verlauf"
|
|
/>
|
|
)}
|
|
</section>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
function roleLabel(role: Role): string {
|
|
return role === "tenant_admin" ? "Tenant-Admin" : "Benutzer";
|
|
}
|