93 lines
3.3 KiB
TypeScript
93 lines
3.3 KiB
TypeScript
|
|
import { A } from '@solidjs/router';
|
||
|
|
import { createMemo, createResource, For, Show } from 'solid-js';
|
||
|
|
import AdminShell from '~/components/AdminShell';
|
||
|
|
|
||
|
|
const API = '/api/gateway';
|
||
|
|
|
||
|
|
type RoleTemplate = {
|
||
|
|
id: string;
|
||
|
|
name: string;
|
||
|
|
description?: string;
|
||
|
|
code?: string;
|
||
|
|
audience?: string;
|
||
|
|
};
|
||
|
|
|
||
|
|
async function fetchTemplates(): Promise<RoleTemplate[]> {
|
||
|
|
try {
|
||
|
|
const res = await fetch(`${API}/api/admin/roles?audience=INTERNAL`);
|
||
|
|
if (!res.ok) return [];
|
||
|
|
const data = await res.json();
|
||
|
|
const rows = Array.isArray(data) ? data : (data.roles || []);
|
||
|
|
return rows.map((r: any) => ({
|
||
|
|
id: r.id,
|
||
|
|
name: r.name,
|
||
|
|
description: r.description || '',
|
||
|
|
code: r.code || r.key || '',
|
||
|
|
audience: r.audience || 'INTERNAL',
|
||
|
|
}));
|
||
|
|
} catch {
|
||
|
|
return [];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
export default function RoleTemplatesPage() {
|
||
|
|
const [templates] = createResource(fetchTemplates);
|
||
|
|
const count = createMemo(() => (templates() || []).length);
|
||
|
|
|
||
|
|
return (
|
||
|
|
<AdminShell>
|
||
|
|
<div class="page-hero-card page-actions">
|
||
|
|
<div>
|
||
|
|
<h1 class="page-title">Role Templates</h1>
|
||
|
|
<p class="page-subtitle">Starter role presets for faster internal role creation and cloning.</p>
|
||
|
|
</div>
|
||
|
|
<A class="btn navy" href="/admin/roles/create">Create Internal Role</A>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<section class="card" style="padding:0;overflow:hidden">
|
||
|
|
<div style="display:flex;align-items:center;justify-content:space-between;padding:14px 18px;border-bottom:1px solid #e2e8f0">
|
||
|
|
<h2 style="margin:0;font-size:16px">Available Templates</h2>
|
||
|
|
<span style="font-size:12px;color:#64748b">{count()} template{count() !== 1 ? 's' : ''}</span>
|
||
|
|
</div>
|
||
|
|
<div class="table-wrap">
|
||
|
|
<table class="list-table">
|
||
|
|
<thead>
|
||
|
|
<tr>
|
||
|
|
<th>Name</th>
|
||
|
|
<th>Description</th>
|
||
|
|
<th>Code</th>
|
||
|
|
<th class="align-right">Actions</th>
|
||
|
|
</tr>
|
||
|
|
</thead>
|
||
|
|
<tbody>
|
||
|
|
<Show when={templates.loading}>
|
||
|
|
<tr><td colspan="4" style="text-align:center;padding:32px;color:#64748b">Loading templates...</td></tr>
|
||
|
|
</Show>
|
||
|
|
<Show when={!templates.loading && count() === 0}>
|
||
|
|
<tr><td colspan="4" style="text-align:center;padding:32px;color:#94a3b8">No templates available yet.</td></tr>
|
||
|
|
</Show>
|
||
|
|
<Show when={!templates.loading && count() > 0}>
|
||
|
|
<For each={templates()}>
|
||
|
|
{(item) => (
|
||
|
|
<tr>
|
||
|
|
<td style="font-weight:600;color:#0f172a">{item.name}</td>
|
||
|
|
<td style="color:#475569">{item.description || '—'}</td>
|
||
|
|
<td style="color:#64748b;font-family:ui-monospace,SFMono-Regular,Menlo,monospace">{item.code || '—'}</td>
|
||
|
|
<td>
|
||
|
|
<div class="table-actions">
|
||
|
|
<A class="btn" href={`/admin/roles/${item.id}`}>View</A>
|
||
|
|
<A class="btn" href="/admin/roles/create">Use As Base</A>
|
||
|
|
</div>
|
||
|
|
</td>
|
||
|
|
</tr>
|
||
|
|
)}
|
||
|
|
</For>
|
||
|
|
</Show>
|
||
|
|
</tbody>
|
||
|
|
</table>
|
||
|
|
</div>
|
||
|
|
</section>
|
||
|
|
</AdminShell>
|
||
|
|
);
|
||
|
|
}
|