From d5287f4bdd27fee92f7fd980fea0eda4e1d6ed37 Mon Sep 17 00:00:00 2001 From: Sharang Parnerkar <30073382+mighty840@users.noreply.github.com> Date: Tue, 14 Apr 2026 22:50:55 +0200 Subject: [PATCH] refactor(admin): split rbac page.tsx into colocated components Co-Authored-By: Claude Opus 4.6 (1M context) --- admin-compliance/app/sdk/rbac/_api.ts | 23 + .../sdk/rbac/_components/AssignRoleModal.tsx | 64 ++ .../rbac/_components/CreateNamespaceModal.tsx | 65 ++ .../sdk/rbac/_components/CreateRoleModal.tsx | 59 ++ .../rbac/_components/CreateTenantModal.tsx | 58 ++ .../app/sdk/rbac/_components/ModalBase.tsx | 17 + .../sdk/rbac/_components/NamespacesTab.tsx | 73 ++ .../app/sdk/rbac/_components/PoliciesTab.tsx | 97 ++ .../app/sdk/rbac/_components/PolicyModal.tsx | 115 +++ .../app/sdk/rbac/_components/RolesTab.tsx | 64 ++ .../app/sdk/rbac/_components/TenantsTab.tsx | 68 ++ .../sdk/rbac/_components/UserRoleLookup.tsx | 24 + .../app/sdk/rbac/_components/UsersTab.tsx | 84 ++ admin-compliance/app/sdk/rbac/_types.ts | 64 ++ admin-compliance/app/sdk/rbac/page.tsx | 844 +----------------- 15 files changed, 918 insertions(+), 801 deletions(-) create mode 100644 admin-compliance/app/sdk/rbac/_api.ts create mode 100644 admin-compliance/app/sdk/rbac/_components/AssignRoleModal.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/CreateNamespaceModal.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/CreateRoleModal.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/CreateTenantModal.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/ModalBase.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/NamespacesTab.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/PoliciesTab.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/PolicyModal.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/RolesTab.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/TenantsTab.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/UserRoleLookup.tsx create mode 100644 admin-compliance/app/sdk/rbac/_components/UsersTab.tsx create mode 100644 admin-compliance/app/sdk/rbac/_types.ts diff --git a/admin-compliance/app/sdk/rbac/_api.ts b/admin-compliance/app/sdk/rbac/_api.ts new file mode 100644 index 0000000..d4b7518 --- /dev/null +++ b/admin-compliance/app/sdk/rbac/_api.ts @@ -0,0 +1,23 @@ +// ============================================================================= +// RBAC API HELPERS +// ============================================================================= + +export const API = '/api/sdk/v1/rbac' + +export async function apiFetch(path: string, opts?: RequestInit): Promise { + const res = await fetch(`${API}/${path}`, { + headers: { 'Content-Type': 'application/json' }, + ...opts, + }) + if (!res.ok) { + const err = await res.text() + throw new Error(`HTTP ${res.status}: ${err}`) + } + return res.json() +} + +export function formatDate(iso: string): string { + return new Date(iso).toLocaleString('de-DE', { + day: '2-digit', month: '2-digit', year: 'numeric', + }) +} diff --git a/admin-compliance/app/sdk/rbac/_components/AssignRoleModal.tsx b/admin-compliance/app/sdk/rbac/_components/AssignRoleModal.tsx new file mode 100644 index 0000000..d87a497 --- /dev/null +++ b/admin-compliance/app/sdk/rbac/_components/AssignRoleModal.tsx @@ -0,0 +1,64 @@ +'use client' + +import React, { useState } from 'react' +import { ModalBase } from './ModalBase' +import { apiFetch } from '../_api' + +export function AssignRoleModal({ onClose, onAssigned }: { onClose: () => void; onAssigned: () => void }) { + const [form, setForm] = useState({ user_id: '', role_id: '', namespace_id: '', expires_at: '' }) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + const handleSubmit = async () => { + if (!form.user_id || !form.role_id) { setError('User-ID und Rolle sind Pflichtfelder'); return } + setSaving(true) + try { + await apiFetch('user-roles', { + method: 'POST', + body: JSON.stringify({ + user_id: form.user_id, + role_id: form.role_id, + namespace_id: form.namespace_id || null, + expires_at: form.expires_at || null, + }), + }) + onAssigned() + } catch (e) { setError(e instanceof Error ? e.message : 'Fehler') } + finally { setSaving(false) } + } + + return ( + + {error &&
{error}
} +
+
+ + setForm(f => ({ ...f, user_id: e.target.value }))} + placeholder="UUID" className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm font-mono" /> +
+
+ + setForm(f => ({ ...f, role_id: e.target.value }))} + placeholder="UUID" className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm font-mono" /> +
+
+ + setForm(f => ({ ...f, namespace_id: e.target.value }))} + placeholder="Leer = Global" className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm font-mono" /> +
+
+ + setForm(f => ({ ...f, expires_at: e.target.value }))} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm" /> +
+
+
+ + +
+
+ ) +} diff --git a/admin-compliance/app/sdk/rbac/_components/CreateNamespaceModal.tsx b/admin-compliance/app/sdk/rbac/_components/CreateNamespaceModal.tsx new file mode 100644 index 0000000..72d29e5 --- /dev/null +++ b/admin-compliance/app/sdk/rbac/_components/CreateNamespaceModal.tsx @@ -0,0 +1,65 @@ +'use client' + +import React, { useState } from 'react' +import { ModalBase } from './ModalBase' +import { apiFetch } from '../_api' + +export function CreateNamespaceModal({ tenantId, onClose, onCreated }: { tenantId: string; onClose: () => void; onCreated: () => void }) { + const [form, setForm] = useState({ name: '', slug: '', isolation_level: 'shared', classification: 'internal' }) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + const handleSubmit = async () => { + if (!form.name) { setError('Name ist Pflichtfeld'); return } + setSaving(true) + try { + await apiFetch(`tenants/${tenantId}/namespaces`, { method: 'POST', body: JSON.stringify(form) }) + onCreated() + } catch (e) { setError(e instanceof Error ? e.message : 'Fehler') } + finally { setSaving(false) } + } + + return ( + + {error &&
{error}
} +
+
+ + setForm(f => ({ ...f, name: e.target.value }))} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm" /> +
+
+ + setForm(f => ({ ...f, slug: e.target.value }))} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm font-mono" /> +
+
+ + +
+
+ + +
+
+
+ + +
+
+ ) +} diff --git a/admin-compliance/app/sdk/rbac/_components/CreateRoleModal.tsx b/admin-compliance/app/sdk/rbac/_components/CreateRoleModal.tsx new file mode 100644 index 0000000..eee7a76 --- /dev/null +++ b/admin-compliance/app/sdk/rbac/_components/CreateRoleModal.tsx @@ -0,0 +1,59 @@ +'use client' + +import React, { useState } from 'react' +import { ModalBase } from './ModalBase' +import { apiFetch } from '../_api' + +export function CreateRoleModal({ onClose, onCreated }: { onClose: () => void; onCreated: () => void }) { + const [form, setForm] = useState({ name: '', description: '', permissions: '' }) + const [saving, setSaving] = useState(false) + const [error, setError] = useState(null) + + const handleSubmit = async () => { + if (!form.name) { setError('Name ist Pflichtfeld'); return } + setSaving(true) + try { + await apiFetch('roles', { + method: 'POST', + body: JSON.stringify({ + name: form.name, + description: form.description, + permissions: form.permissions.split(',').map(s => s.trim()).filter(Boolean), + }), + }) + onCreated() + } catch (e) { setError(e instanceof Error ? e.message : 'Fehler') } + finally { setSaving(false) } + } + + return ( + + {error &&
{error}
} +
+
+ + setForm(f => ({ ...f, name: e.target.value }))} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm" /> +
+
+ + setForm(f => ({ ...f, description: e.target.value }))} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm" /> +
+
+ +