nxtgauge-admin-solid/src/routes/admin/roles/[id]/index.tsx

137 lines
5.7 KiB
TypeScript

import { A, useParams } from '@solidjs/router';
import { createMemo, createResource, Show } from 'solid-js';
import AdminShell from '~/components/AdminShell';
const API = '/api/gateway';
type Permission = { id: string; module: string; action: string };
type Role = { id: string; name: string; description?: string; permissions: Permission[] };
async function loadRoleDetail(id: string) {
try {
const [roleRes, permsRes] = await Promise.all([
fetch(`${API}/api/admin/roles/${id}`),
fetch(`${API}/api/admin/permissions`),
]);
if (!roleRes.ok) return null;
const role: Role = await roleRes.json();
const permsData = permsRes.ok ? await permsRes.json() : { permissions: [] };
const allPerms: Permission[] = Array.isArray(permsData) ? permsData : (permsData.permissions || []);
return { role, allPerms };
} catch {
return null;
}
}
export default function RoleDetailPage() {
const params = useParams();
const [data] = createResource(() => params.id, loadRoleDetail);
const grouped = createMemo(() => {
if (!data()?.allPerms) return {};
const map: Record<string, Permission[]> = {};
(data()!.allPerms).forEach((p) => {
if (!map[p.module]) map[p.module] = [];
map[p.module].push(p);
});
return map;
});
const rolePermIds = createMemo(() => new Set((data()?.role?.permissions || []).map((p) => p.id)));
const modules = createMemo(() => Object.keys(grouped()).sort());
return (
<AdminShell>
<div class="page-hero-card page-actions">
<div>
<h1 class="page-title">Role Details</h1>
<p class="page-subtitle">View role information and assigned permissions.</p>
</div>
<div class="page-actions-right">
<A class="btn" href="/admin/roles">Back to List</A>
<Show when={data()?.role}>
<A class="btn navy" href={`/admin/roles/${params.id}/edit`}>Edit Role</A>
</Show>
</div>
</div>
<nav class="admin-link-tabs" aria-label="Role Management Navigation">
<A class="admin-link-tab active" href="/admin/roles">Internal Roles</A>
<A class="admin-link-tab" href="/admin/runtime-roles">External Runtime Roles</A>
<A class="admin-link-tab" href="/admin/onboarding-schemas">Onboarding Schemas</A>
</nav>
<Show when={data.loading}>
<div class="card">
<p class="notice">Loading role details...</p>
</div>
</Show>
<Show when={data.error}>
<div class="error-box">Failed to load role. Check that the backend is running.</div>
</Show>
<Show when={data()?.role}>
{/* Role Info */}
<div class="role-detail-card">
<div class="field-grid-2">
<div class="field">
<label>Role Name</label>
<input class="role-field-readonly" value={data()!.role.name} readOnly disabled />
</div>
<div class="field">
<label>Description</label>
<input class="role-field-readonly" value={data()!.role.description || '—'} readOnly disabled />
</div>
</div>
</div>
{/* Permission Matrix */}
<div class="card" style="padding: 0; overflow: hidden;">
<div class="table-wrap">
<table class="perm-table">
<thead>
<tr>
<th style="width:45%">Name of the module</th>
<th style="width:11%">No Access</th>
<th style="width:11%">Read</th>
<th style="width:11%">Create</th>
<th style="width:11%">Update</th>
<th style="width:11%">Delete</th>
</tr>
</thead>
<tbody>
<Show when={modules().length === 0}>
<tr>
<td colspan="6" style="text-align:center;padding:24px;color:#94a3b8;">No permissions defined.</td>
</tr>
</Show>
{modules().map((mod) => {
const perms = grouped()[mod] || [];
const actionMap: Record<string, string> = {};
perms.forEach((p) => { actionMap[p.action] = p.id; });
const hasRead = !!actionMap['Read'] && rolePermIds().has(actionMap['Read']);
const hasCreate = !!actionMap['Create'] && rolePermIds().has(actionMap['Create']);
const hasUpdate = !!actionMap['Update'] && rolePermIds().has(actionMap['Update']);
const hasDelete = !!actionMap['Delete'] && rolePermIds().has(actionMap['Delete']);
const noAccess = !hasRead && !hasCreate && !hasUpdate && !hasDelete;
return (
<tr>
<td style="font-weight:500">{mod}</td>
<td><input type="checkbox" checked={noAccess} disabled aria-label={`${mod} no access`} /></td>
<td>{actionMap['Read'] ? <input type="checkbox" checked={hasRead} disabled aria-label={`${mod} read`} /> : <span style="color:#cbd5e1"></span>}</td>
<td>{actionMap['Create'] ? <input type="checkbox" checked={hasCreate} disabled aria-label={`${mod} create`} /> : <span style="color:#cbd5e1"></span>}</td>
<td>{actionMap['Update'] ? <input type="checkbox" checked={hasUpdate} disabled aria-label={`${mod} update`} /> : <span style="color:#cbd5e1"></span>}</td>
<td>{actionMap['Delete'] ? <input type="checkbox" checked={hasDelete} disabled aria-label={`${mod} delete`} /> : <span style="color:#cbd5e1"></span>}</td>
</tr>
);
})}
</tbody>
</table>
</div>
</div>
</Show>
</AdminShell>
);
}