103 lines
4.0 KiB
TypeScript
103 lines
4.0 KiB
TypeScript
/**
|
|
* Roles Management Page — Admin view for role-based permissions
|
|
* Ball Studios <https://git.balls.studio>
|
|
*/
|
|
|
|
import React, { useEffect, useState } from 'react';
|
|
import http from '@/api/http';
|
|
import { ServerContext } from '@/state/server';
|
|
import PageContentBlock from '@/components/elements/PageContentBlock';
|
|
import tw from 'twin.macro';
|
|
|
|
interface Role {
|
|
id: number;
|
|
name: string;
|
|
color: string;
|
|
description: string;
|
|
permissions: string[];
|
|
created_at: string;
|
|
}
|
|
|
|
interface UserRole {
|
|
role: Role;
|
|
override: string | null;
|
|
source: string;
|
|
}
|
|
|
|
export default function RolesPage() {
|
|
const uuid = ServerContext.useStoreState((s) => s.server.data!.uuid);
|
|
const [userRoles, setUserRoles] = useState<UserRole[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [error, setError] = useState('');
|
|
|
|
useEffect(() => {
|
|
http.get(`/api/client/servers/${uuid}/addons/permissions/effective/me`)
|
|
.then(({ data }: any) => setUserRoles(data.data ?? []))
|
|
.catch(() => setError('Failed to load role data.'))
|
|
.finally(() => setLoading(false));
|
|
}, [uuid]);
|
|
|
|
return (
|
|
<PageContentBlock title={'Role Permissions'}>
|
|
<div css={tw`mb-6`}>
|
|
<h2 css={tw`text-2xl font-bold text-neutral-100 mb-1`}>🛡 Role Permissions</h2>
|
|
<p css={tw`text-neutral-400 text-sm`}>
|
|
Your effective permissions for this server, managed by the Ball Studios Advanced Admin addon.
|
|
</p>
|
|
</div>
|
|
|
|
{loading && (
|
|
<div css={tw`text-neutral-400 py-8 text-center`}>Loading permissions…</div>
|
|
)}
|
|
|
|
{error && (
|
|
<div css={tw`p-4 rounded bg-red-900 border border-red-600 text-red-200 text-sm mb-4`}>
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{!loading && !error && userRoles.length === 0 && (
|
|
<div css={tw`p-6 rounded bg-neutral-800 border border-neutral-700 text-neutral-400 text-sm text-center`}>
|
|
No roles assigned. Contact an administrator.
|
|
</div>
|
|
)}
|
|
|
|
<div css={tw`grid gap-4`}>
|
|
{userRoles.map((ur, i) => (
|
|
<div key={i} css={tw`p-4 rounded-lg bg-neutral-800 border border-neutral-700`}>
|
|
<div css={tw`flex items-center gap-3 mb-2`}>
|
|
<span
|
|
css={tw`w-3 h-3 rounded-full inline-block flex-shrink-0`}
|
|
style={{ backgroundColor: ur.role?.color ?? '#6366f1' }}
|
|
/>
|
|
<span css={tw`font-semibold text-neutral-100`}>
|
|
{ur.role?.name ?? 'Unknown Role'}
|
|
</span>
|
|
{ur.source && (
|
|
<span css={tw`ml-auto text-xs text-neutral-500 bg-neutral-900 px-2 py-0.5 rounded`}>
|
|
via {ur.source}
|
|
</span>
|
|
)}
|
|
</div>
|
|
{ur.role?.description && (
|
|
<p css={tw`text-neutral-400 text-sm mb-3`}>{ur.role.description}</p>
|
|
)}
|
|
{ur.role?.permissions?.length > 0 && (
|
|
<div css={tw`flex flex-wrap gap-1`}>
|
|
{ur.role.permissions.map((perm: string) => (
|
|
<span
|
|
key={perm}
|
|
css={tw`text-xs font-mono px-2 py-0.5 rounded bg-indigo-900 text-indigo-300 border border-indigo-700`}
|
|
>
|
|
{perm}
|
|
</span>
|
|
))}
|
|
</div>
|
|
)}
|
|
</div>
|
|
))}
|
|
</div>
|
|
</PageContentBlock>
|
|
);
|
|
}
|