feat: add admin operations suite
Local Build and Deploy / deploy (push) Successful in 50s

This commit is contained in:
Simo committed 2026-07-12 20:11:28 +02:00
1 parent 21a61068fb
commit 60278b9e71
38 files changed
+3452

No files matched your search

@@ -0,0 +1,47 @@
'use client'
import { DataTable } from '@/components/admin/data-table'
import { formatTimestamp } from '@/lib/admin-helpers'
import type { DataTableColumn, PaginatedResult } from '@/types'
interface CommandLogRow {
id: number
username: string
command: string
params: string
timestamp: number
}
const columns: DataTableColumn<CommandLogRow>[] = [
{ key: 'id', label: 'ID', sortable: true },
{ key: 'username', label: 'User' },
{ key: 'command', label: 'Command', sortable: true },
{
key: 'params',
label: 'Params',
render: (value) => (
<span className="max-w-[200px] truncate block font-mono text-sm">{String(value) || '-'}</span>
),
},
{
key: 'timestamp',
label: 'Time',
sortable: true,
render: (value) => <span>{formatTimestamp(Number(value))}</span>,
},
]
interface CommandsTableProps {
data: PaginatedResult<CommandLogRow>
}
export function CommandsTable({ data }: CommandsTableProps) {
return (
<DataTable
data={data}
columns={columns}
searchPlaceholder="Search by username, command, or params..."
exportUrl="/api/admin/export?type=commands"
/>
)
}
+28
View File
@@ -0,0 +1,28 @@
import { Skeleton } from '@/components/ui/skeleton'
export default function CommandLogsLoading() {
return (
<div className="space-y-6">
<div className="flex items-center justify-between">
<div>
<Skeleton className="h-9 w-40" />
<Skeleton className="h-5 w-64 mt-2" />
</div>
</div>
<div className="flex items-center gap-3">
<Skeleton className="h-10 w-64" />
<Skeleton className="h-10 w-24" />
</div>
<div className="rounded-md border">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="flex items-center gap-6 px-4 py-3 border-b last:border-0">
<Skeleton className="h-4 w-12" />
<Skeleton className="h-4 w-32" />
<Skeleton className="h-4 w-24" />
<Skeleton className="h-5 w-16 rounded-full" />
</div>
))}
</div>
</div>
)
}
+49
View File
@@ -0,0 +1,49 @@
import { fetchAdminList } from '@/lib/admin-list'
import { PERMS } from '@/lib/permissions'
import { CommandsTable } from './commands-table'
export default async function CommandLogsPage({
params: paramsPromise,
searchParams,
}: {
params: Promise<{ locale: string }>
searchParams: Promise<Record<string, string>>
}) {
const result = await fetchAdminList(
{
permission: PERMS.LOGS_VIEW,
model: 'commandLog',
searchFields: ['command', 'params', 'user.username'],
defaultPerPage: 50,
include: { user: { select: { username: true } } },
mapRow: (l) => ({
id: l.id,
username: l.user.username,
command: l.command,
params: l.params,
timestamp: l.timestamp,
}),
},
paramsPromise,
searchParams,
)
return (
<div className="space-y-6">
<div>
<h1 className="text-3xl font-bold tracking-tight">Command Logs</h1>
<p className="text-muted-foreground">View in-game command usage</p>
</div>
<CommandsTable
data={{
rows: result.rows,
total: result.total,
page: result.page,
perPage: result.perPage,
lastPage: result.lastPage,
}}
/>
</div>
)
}