2026-01-26 13:12:17 +00:00
|
|
|
import React, { useState, useEffect } from 'react';
|
|
|
|
|
import { Card } from '../ui/card';
|
|
|
|
|
import { Button } from '../ui/button';
|
|
|
|
|
import { Input } from '../ui/input';
|
|
|
|
|
import { Search, Filter, History, User, Activity, Globe, Loader2 } from 'lucide-react';
|
|
|
|
|
import { adminService } from '../../services/adminService';
|
|
|
|
|
import { format } from 'date-fns';
|
|
|
|
|
|
|
|
|
|
export const AdminAuditLogsView: React.FC = () => {
|
|
|
|
|
const [logs, setLogs] = useState<any[]>([]);
|
|
|
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
|
const [page, setPage] = useState(1);
|
|
|
|
|
const [total, setTotal] = useState(0);
|
|
|
|
|
|
|
|
|
|
const fetchLogs = async () => {
|
|
|
|
|
setLoading(true);
|
|
|
|
|
try {
|
|
|
|
|
const data = await adminService.getAuditLogs({ page, limit: 20 });
|
|
|
|
|
setLogs(data.logs || []);
|
|
|
|
|
setTotal(data.pagination?.total_items || 0);
|
|
|
|
|
} catch (e) {
|
|
|
|
|
console.error('Failed to fetch audit logs', e);
|
|
|
|
|
} finally {
|
|
|
|
|
setLoading(false);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
|
fetchLogs();
|
|
|
|
|
}, [page]);
|
|
|
|
|
|
|
|
|
|
return (
|
|
|
|
|
<div className="space-y-6 animate-fadeIn h-full flex flex-col">
|
|
|
|
|
<div className="flex justify-between items-center">
|
|
|
|
|
<div>
|
|
|
|
|
<h2 className="text-2xl font-display font-bold text-white mb-1">AUDIT PROTOCOL</h2>
|
2026-02-07 18:35:33 +00:00
|
|
|
<p className="text-muted-foreground font-mono text-xs uppercase tracking-widest">Global Immutable Ledger</p>
|
2026-01-26 13:12:17 +00:00
|
|
|
</div>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button variant="ghost" size="sm" onClick={() => fetchLogs()} disabled={loading}>
|
|
|
|
|
<History className={`w-4 h-4 mr-2 ${loading ? 'animate-spin' : ''}`} /> Refresh
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div className="flex flex-col md:flex-row gap-4">
|
|
|
|
|
<div className="relative flex-1">
|
2026-02-07 18:35:33 +00:00
|
|
|
<Search className="absolute left-3 top-1/2 -translate-y-1/2 w-4 h-4 text-muted-foreground" />
|
2026-01-26 13:12:17 +00:00
|
|
|
<Input placeholder="Search action, user, or IP..." className="pl-10 h-10" />
|
|
|
|
|
</div>
|
|
|
|
|
<Button variant="outline" className="h-10 border-white/10 hover:border-primary/50">
|
|
|
|
|
<Filter className="w-4 h-4 mr-2" /> All Resources
|
|
|
|
|
</Button>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-07 18:35:33 +00:00
|
|
|
<Card variant="elevated" className="flex-1 overflow-hidden flex flex-col p-0">
|
2026-01-26 13:12:17 +00:00
|
|
|
<div className="overflow-x-auto">
|
|
|
|
|
<table className="w-full text-left border-collapse">
|
|
|
|
|
<thead>
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<tr className="border-b border-white/5 bg-white/5 text-xs font-mono text-muted-foreground uppercase tracking-wider">
|
2026-01-26 13:12:17 +00:00
|
|
|
<th className="p-4">Timestamp</th>
|
|
|
|
|
<th className="p-4">User</th>
|
|
|
|
|
<th className="p-4">Action</th>
|
|
|
|
|
<th className="p-4">Resource</th>
|
|
|
|
|
<th className="p-4">Context</th>
|
|
|
|
|
<th className="p-4 text-right">Origin</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody className="divide-y divide-white/5 text-sm">
|
|
|
|
|
{loading ? (
|
|
|
|
|
<tr>
|
|
|
|
|
<td colSpan={6} className="p-12 text-center">
|
|
|
|
|
<Loader2 className="w-8 h-8 text-primary animate-spin mx-auto" />
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
) : logs.length === 0 ? (
|
|
|
|
|
<tr>
|
2026-02-07 18:35:33 +00:00
|
|
|
<td colSpan={6} className="p-12 text-center text-muted-foreground font-mono uppercase text-xs">
|
2026-01-26 13:12:17 +00:00
|
|
|
No temporal data packets recovered.
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
) : (
|
|
|
|
|
logs.map((log) => (
|
|
|
|
|
<tr key={log.id} className="hover:bg-white/5 transition-colors group border-white/5">
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<td className="p-4 font-mono text-xs text-muted-foreground">
|
2026-01-26 13:12:17 +00:00
|
|
|
{format(new Date(log.timestamp), 'MMM dd, HH:mm:ss')}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
|
|
|
|
<div className="flex items-center gap-2">
|
|
|
|
|
<User className="w-3 h-3 text-primary/70" />
|
|
|
|
|
<span className="font-bold text-white text-xs">{log.user?.username || 'System'}</span>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4">
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<span className="px-2 py-0.5 bg-primary/10 text-primary border border-primary/20 rounded text-xs uppercase font-bold">
|
2026-01-26 13:12:17 +00:00
|
|
|
{log.action}
|
|
|
|
|
</span>
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4 font-mono text-xs text-white/80">
|
2026-02-07 18:35:33 +00:00
|
|
|
{log.resource} <span className="text-muted-foreground opacity-50">#{log.resource_id?.slice(0, 8)}</span>
|
2026-01-26 13:12:17 +00:00
|
|
|
</td>
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<td className="p-4 text-xs text-muted-foreground max-w-48 truncate">
|
2026-01-26 13:12:17 +00:00
|
|
|
{JSON.stringify(log.context)}
|
|
|
|
|
</td>
|
|
|
|
|
<td className="p-4 text-right">
|
|
|
|
|
<div className="flex flex-col items-end">
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<span className="font-mono text-xs text-white/80 flex items-center gap-1">
|
2026-01-26 13:12:17 +00:00
|
|
|
<Globe className="w-2 h-2" /> {log.ip_address}
|
|
|
|
|
</span>
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<span className="text-xs text-muted-foreground truncate max-w-32" title={log.user_agent}>
|
2026-01-26 13:12:17 +00:00
|
|
|
{log.user_agent}
|
|
|
|
|
</span>
|
|
|
|
|
</div>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
))
|
|
|
|
|
)}
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{/* Pagination placeholder */}
|
|
|
|
|
<div className="p-4 border-t border-white/5 bg-white/2 flex justify-between items-center mt-auto">
|
ui(tokens): migrate text-[10px] to text-xs across 23 components
Replace all arbitrary text-[10px] / text-[9px] with the standard Tailwind
text-xs token. Also migrates nearby arbitrary width values where applicable
(max-w-[200px] → max-w-48, max-w-[120px] → max-w-32, h-[1px] → h-px).
Only documented exceptions remain: avatar xs (text-[10px] for w-6 h-6
initials) and badge JSDoc reference.
Affected areas: admin views, marketplace, player, settings, chat, upload,
education, commerce, library, PWA, search, navbar, user card.
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-08 21:47:19 +00:00
|
|
|
<span className="text-xs text-muted-foreground font-mono uppercase">
|
2026-01-26 13:12:17 +00:00
|
|
|
Packet: {logs.length} / Total: {total}
|
|
|
|
|
</span>
|
|
|
|
|
<div className="flex gap-2">
|
|
|
|
|
<Button variant="ghost" size="sm" onClick={() => setPage(p => Math.max(1, p - 1))} disabled={page === 1 || loading}>Previous</Button>
|
|
|
|
|
<Button variant="ghost" size="sm" onClick={() => setPage(p => p + 1)} disabled={logs.length < 20 || loading}>Next</Button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</Card>
|
|
|
|
|
</div>
|
|
|
|
|
);
|
|
|
|
|
};
|