Basis: Certvia dev@a48c5fb als Fundament für Craftvia
CI / build-and-check (push) Canceled after 0s
CI / audit (push) Canceled after 0s
CI / sbom (push) Canceled after 0s

Unveränderter Stand von certvia/dev (a48c5fb) plus Craftvia-Spezifikation
und Brandbook unter docs/craftvia/. ISMS-Module werden im Folgecommit entfernt.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-14 11:05:39 +02:00
co-authored by Claude Opus 5
commit c8e6f30a27
720 changed files with 140143 additions and 0 deletions
+110
View File
@@ -0,0 +1,110 @@
import Link from "next/link";
import { redirect } from "next/navigation";
import { ArrowLeft } from "lucide-react";
import { requireSession } from "@/server/auth";
import { dbForTenant } from "@/server/db";
import { hasPermission, PERMISSIONS, ROLE_DEFS } from "@/server/rbac";
import { createUser, updateUser, setUserRoles, setUserStatus } from "@/server/actions/tenant-users";
import { PageHead } from "@/components/mockup-ui";
import { Modal } from "@/components/modal";
import { UserTable } from "@/components/user-table";
import { UserCreateForm, UserEditForm } from "@/components/user-forms";
import { RoleManager, CreateRoleForm } from "@/components/role-manager";
/**
* Benutzer- & Rollenverwaltung im Mandanten (Paket B). Sichtbar nur mit user:manage
* bzw. role:manage; strikt auf den eigenen Mandanten begrenzt (dbForTenant).
* Benutzer werden über Popups (?new/?edit) angelegt/bearbeitet, die Tabelle zeigt nur.
*/
export default async function UsersRolesPage({
searchParams,
}: {
searchParams: Promise<{ new?: string; edit?: string; newRole?: string }>;
}) {
const session = await requireSession();
const canUsers = hasPermission(session, "user:manage");
const canRoles = hasPermission(session, "role:manage");
if (!canUsers && !canRoles) redirect("/dashboard");
const db = dbForTenant(session.user.tenantId);
const sp = await searchParams;
const [users, roles] = await Promise.all([
db.user.findMany({
select: { id: true, name: true, email: true, status: true, userRoles: { select: { role: { select: { id: true, name: true } } } } },
orderBy: { createdAt: "asc" },
}),
db.role.findMany({
include: { rolePermissions: { include: { permission: true } }, _count: { select: { userRoles: true } } },
orderBy: { name: "asc" },
}),
]);
const standardKeys = new Set(Object.keys(ROLE_DEFS));
const roleOptions = roles.map((r) => ({ id: r.id, name: r.name }));
const tableUsers = users.map((u) => ({
id: u.id, name: u.name, email: u.email, status: u.status,
roleNames: u.userRoles.map((ur) => ur.role.name), isSelf: u.id === session.user.id,
}));
const managedRoles = roles.map((r) => ({
id: r.id, key: r.key, name: r.name, isStandard: standardKeys.has(r.key),
permKeys: r.rolePermissions.map((rp) => rp.permission.key), userCount: r._count.userRoles,
}));
const editUser = sp.edit ? users.find((u) => u.id === sp.edit) : null;
const base = "/settings/users";
return (
<main className="flex-1 p-6">
<Link href="/settings" className="inline-flex items-center gap-1.5 text-[12.5px] font-semibold text-muted-foreground hover:text-foreground">
<ArrowLeft className="size-4" /> Zurück zu Einstellungen
</Link>
<div className="mt-3">
<PageHead crumb="Organisation" title="Benutzer & Rollen" sub="Benutzer anlegen und verwalten, Rollen und Berechtigungen pflegen — nur für den eigenen Mandanten." />
</div>
{canUsers && (
<section className="mt-5">
<h2 className="mb-3 font-heading text-sm font-semibold">Benutzer</h2>
<UserTable users={tableUsers} newHref={`${base}?new=1`} editHref={(id) => `${base}?edit=${id}`} />
</section>
)}
{canRoles && (
<section className="mt-8">
<h2 className="mb-3 font-heading text-sm font-semibold">Rollen &amp; Berechtigungen</h2>
<p className="mb-3 text-[12.5px] text-muted-foreground">Rolle anklicken, um ihre Berechtigungen zu sehen und (bei eigenen Rollen) zu bearbeiten.</p>
<RoleManager roles={managedRoles} allPermissions={[...PERMISSIONS]} newRoleHref={`${base}?newRole=1`} />
</section>
)}
{canUsers && sp.new && (
<Modal title="Benutzer anlegen" sub="Neuen Nutzer mit Rollen und Initialpasswort anlegen" closeHref={base} closeLabel="Schließen">
<UserCreateForm action={createUser} roles={roleOptions} closeHref={base} />
</Modal>
)}
{canRoles && sp.newRole && (
<Modal title="Eigene Rolle anlegen" sub="Rollenname und Berechtigungen festlegen (mandantenintern)" closeHref={base} closeLabel="Schließen">
<div className="p-5">
<CreateRoleForm allPermissions={[...PERMISSIONS]} closeHref={base} />
</div>
</Modal>
)}
{canUsers && editUser && (
<Modal title={`Benutzer bearbeiten — ${editUser.name}`} sub={editUser.email} closeHref={base} closeLabel="Schließen">
<UserEditForm
user={{
id: editUser.id, name: editUser.name, email: editUser.email, status: editUser.status,
roleIds: editUser.userRoles.map((ur) => ur.role.id), isSelf: editUser.id === session.user.id,
}}
roles={roleOptions}
updateAction={updateUser.bind(null, editUser.id)}
rolesAction={setUserRoles.bind(null, editUser.id)}
statusAction={setUserStatus.bind(null, editUser.id, editUser.status === "ACTIVE" ? "DEACTIVATED" : "ACTIVE")}
/>
</Modal>
)}
</main>
);
}