feat: implement comprehensive admin dashboard for server management and user oversight

This commit is contained in:
2026-02-21 03:28:21 +01:00
parent fa29bd5a85
commit 6dc940bb3a
5 changed files with 203 additions and 6 deletions

View File

@@ -6,7 +6,7 @@ import {
DialogActions, CircularProgress, Paper, Chip,
Table, TableBody, TableCell, TableContainer, TableHead, TableRow,
Switch, FormControlLabel, Divider, List, ListItem, ListItemText, ListItemSecondaryAction,
Select, MenuItem, FormControl, InputLabel
Select, MenuItem, FormControl, InputLabel, Tabs, Tab
} from '@mui/material';
import SearchIcon from '@mui/icons-material/Search';
import AddIcon from '@mui/icons-material/Add';
@@ -25,11 +25,116 @@ import GppBadIcon from '@mui/icons-material/GppBad';
import PeopleIcon from '@mui/icons-material/People';
import VerifiedUserIcon from '@mui/icons-material/VerifiedUser';
import WorkspacePremiumIcon from '@mui/icons-material/WorkspacePremium';
import AdminPanelSettingsIcon from '@mui/icons-material/AdminPanelSettings';
import StorageIcon from '@mui/icons-material/Storage';
import GroupIcon from '@mui/icons-material/Group';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import { useAccounts, type Account } from '../hooks/useAccounts';
import { useAppTheme } from '../theme/ThemeContext';
import type { ThemeType } from '../theme/SteamTheme';
import NebulaBanner from '../components/NebulaBanner';
const AdminPanel: React.FC<{ open: boolean, onClose: () => void }> = ({ open, onClose }) => {
const { adminGetStats, adminGetUsers, adminDeleteUser, adminGetAccounts, adminRemoveAccount } = useAccounts();
const [tab, setTab] = useState(0);
const [stats, setStats] = useState<any>(null);
const [users, setUsers] = useState<any[]>([]);
const [accounts, setAccounts] = useState<any[]>([]);
const [loading, setLoading] = useState(false);
const loadData = async () => {
setLoading(true);
try {
if (tab === 0) setStats(await adminGetStats());
if (tab === 1) setUsers(await adminGetUsers());
if (tab === 2) setAccounts(await adminGetAccounts());
} catch (e) {}
setLoading(false);
};
useEffect(() => { if (open) loadData(); }, [open, tab]);
const handleDeleteUser = async (id: string) => {
if (window.confirm("Wipe this user and all their accounts?")) {
await adminDeleteUser(id);
loadData();
}
};
const handleForceRemove = async (steamId: string) => {
if (window.confirm("Force remove this account from server?")) {
await adminRemoveAccount(steamId);
loadData();
}
};
return (
<Dialog open={open} onClose={onClose} maxWidth="md" fullWidth>
<DialogTitle sx={{ bgcolor: 'background.paper', color: 'text.primary', display: 'flex', alignItems: 'center', gap: 1 }}>
<AdminPanelSettingsIcon color="primary" /> Server Administration
</DialogTitle>
<Tabs value={tab} onChange={(_, v) => setTab(v)} sx={{ bgcolor: 'background.paper', borderBottom: 1, borderColor: 'divider' }}>
<Tab icon={<StorageIcon />} label="Overview" />
<Tab icon={<GroupIcon />} label="Users" />
<Tab icon={<AccountTreeIcon />} label="Global Accounts" />
</Tabs>
<DialogContent sx={{ bgcolor: 'background.paper', minHeight: 400, pt: 2 }}>
{loading ? <Box sx={{ display: 'flex', justifyContent: 'center', mt: 10 }}><CircularProgress /></Box> : (
<>
{tab === 0 && stats && (
<Box sx={{ display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: 2, mt: 2 }}>
{[
{ label: 'Total Users', value: stats.users },
{ label: 'Total Accounts', value: stats.accounts },
{ label: 'Active Cooldowns', value: stats.activeCooldowns }
].map((s) => (
<Paper key={s.label} sx={{ p: 3, textAlign: 'center', bgcolor: 'rgba(0,0,0,0.1)' }}>
<Typography variant="h4" color="primary" sx={{ fontWeight: 'bold' }}>{s.value}</Typography>
<Typography variant="caption" color="textSecondary">{s.label}</Typography>
</Paper>
))}
</Box>
)}
{tab === 1 && (
<List>
{users.map(u => (
<ListItem key={u._id} divider sx={{ borderColor: 'divider' }}>
<Avatar src={u.avatar} sx={{ mr: 2 }} />
<ListItemText primary={u.personaName} secondary={u.steamId} primaryTypographyProps={{ color: 'text.primary' }} />
<ListItemSecondaryAction>
<IconButton color="error" onClick={() => handleDeleteUser(u._id)}><DeleteIcon /></IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
)}
{tab === 2 && (
<List>
{accounts.map(a => (
<ListItem key={a.steamId} divider sx={{ borderColor: 'divider' }}>
<Avatar src={a.avatar} variant="square" sx={{ mr: 2 }} />
<ListItemText
primary={a.personaName}
secondary={`Owned by: ${a.addedBy?.personaName || 'Unknown'} (${a.steamId})`}
primaryTypographyProps={{ color: 'text.primary' }}
/>
<ListItemSecondaryAction>
<IconButton color="error" onClick={() => handleForceRemove(a.steamId)}><DeleteIcon /></IconButton>
</ListItemSecondaryAction>
</ListItem>
))}
</List>
)}
</>
)}
</DialogContent>
<DialogActions sx={{ bgcolor: 'background.paper', p: 2 }}>
<Button onClick={onClose} variant="contained" color="inherit">Close Panel</Button>
</DialogActions>
</Dialog>
);
};
const Dashboard: React.FC = () => {
const { currentTheme, setTheme } = useAppTheme();
const {
@@ -39,6 +144,7 @@ const Dashboard: React.FC = () => {
const [searchTerm, setSearchTerm] = useState('');
const [isSettingsOpen, setIsSettingsOpen] = useState(false);
const [isAdminPanelOpen, setIsAdminPanelOpen] = useState(false);
const [serverUrl, setServerUrl] = useState('');
useEffect(() => {
@@ -70,6 +176,15 @@ const Dashboard: React.FC = () => {
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, WebkitAppRegion: 'no-drag' } as any}>
{/* Admin Button - Only visible if isAdmin is true */}
{serverConfig?.isAdmin && (
<Tooltip title="Open Admin Panel">
<IconButton color="primary" onClick={() => setIsAdminPanelOpen(true)}>
<AdminPanelSettingsIcon />
</IconButton>
</Tooltip>
)}
<Box sx={{ display: 'flex', alignItems: 'center', mr: 1 }}>
{isSyncing ? (
<CircularProgress size={16} sx={{ color: 'primary.main', mr: 1 }} />
@@ -246,6 +361,9 @@ const Dashboard: React.FC = () => {
<Button onClick={() => setIsSettingsOpen(false)} color="inherit" variant="contained">Done</Button>
</DialogActions>
</Dialog>
{/* Admin Panel */}
<AdminPanel open={isAdminPanelOpen} onClose={() => setIsAdminPanelOpen(false)} />
</Box>
);
};
@@ -294,8 +412,7 @@ const AccountRow: React.FC<{
(window as any).electronAPI.getServerUserInfo()
]);
const filtered = (Array.isArray(users) ? users : []).filter(u =>
u.steamId !== selfInfo.steamId &&
u.steamId !== account.steamId
u.steamId !== selfInfo.steamId && u.steamId !== account.steamId
);
setServerUsers(filtered);
} catch (e) {}