Files
pterodactyl_addon/resources/scripts/addons/advanced-admin/roles/RolesPage.tsx

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>
);
}