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" /> +
+
+ +