Tab-based admin page at /admin/rbac with split-pane entity views, inheritance visualization, OIDC badges, and role/group management. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
67 lines
1.8 KiB
TypeScript
67 lines
1.8 KiB
TypeScript
import { useSearchParams } from 'react-router';
|
|
import { useAuthStore } from '../../../auth/auth-store';
|
|
import { DashboardTab } from './DashboardTab';
|
|
import { UsersTab } from './UsersTab';
|
|
import { GroupsTab } from './GroupsTab';
|
|
import { RolesTab } from './RolesTab';
|
|
import styles from './RbacPage.module.css';
|
|
|
|
const TABS = ['dashboard', 'users', 'groups', 'roles'] as const;
|
|
type TabKey = (typeof TABS)[number];
|
|
|
|
const TAB_LABELS: Record<TabKey, string> = {
|
|
dashboard: 'Dashboard',
|
|
users: 'Users',
|
|
groups: 'Groups',
|
|
roles: 'Roles',
|
|
};
|
|
|
|
export function RbacPage() {
|
|
const roles = useAuthStore((s) => s.roles);
|
|
|
|
if (!roles.includes('ADMIN')) {
|
|
return (
|
|
<div className={styles.page}>
|
|
<div className={styles.accessDenied}>
|
|
Access Denied — this page requires the ADMIN role.
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return <RbacContent />;
|
|
}
|
|
|
|
function RbacContent() {
|
|
const [searchParams, setSearchParams] = useSearchParams();
|
|
const rawTab = searchParams.get('tab');
|
|
const activeTab: TabKey = TABS.includes(rawTab as TabKey) ? (rawTab as TabKey) : 'dashboard';
|
|
|
|
function setTab(tab: TabKey) {
|
|
setSearchParams({ tab }, { replace: true });
|
|
}
|
|
|
|
return (
|
|
<div className={styles.page}>
|
|
<div className={styles.tabs}>
|
|
{TABS.map((tab) => (
|
|
<button
|
|
key={tab}
|
|
type="button"
|
|
className={`${styles.tab} ${activeTab === tab ? styles.tabActive : ''}`}
|
|
onClick={() => setTab(tab)}
|
|
>
|
|
{TAB_LABELS[tab]}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<div className={styles.tabContent}>
|
|
{activeTab === 'dashboard' && <DashboardTab />}
|
|
{activeTab === 'users' && <UsersTab />}
|
|
{activeTab === 'groups' && <GroupsTab />}
|
|
{activeTab === 'roles' && <RolesTab />}
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|