import React, { useEffect, useState } from 'react' import { Plus, Trash2, CheckCircle } from 'lucide-react' import { api } from '../api/client' interface BlockedIp { id: number; ip: string; reason: string | null; createdAt: string } interface SecurityEvent { id: number; type: string; severity: string; message: string; sourceIp: string | null; resolved: boolean; createdAt: string } const severityColor: Record = { info: 'text-blue-400 bg-blue-500/10 border-blue-500/20', warning: 'text-amber-400 bg-amber-500/10 border-amber-500/20', critical: 'text-red-400 bg-red-500/10 border-red-500/20', } export default function Security() { const [blocked, setBlocked] = useState([]) const [events, setEvents] = useState([]) const [newIp, setNewIp] = useState('') const [reason, setReason] = useState('') const fetch = async () => { try { const [b, e] = await Promise.all([ api.get('/nixserver/security/blocked-ips'), api.get('/nixserver/security/events'), ]) setBlocked(b.data.data) setEvents(e.data.data.items ?? e.data.data) } catch {} } useEffect(() => { fetch() }, []) const block = async (e: React.FormEvent) => { e.preventDefault() try { await api.post('/nixserver/security/blocked-ips', { ip: newIp, reason }); setNewIp(''); setReason(''); fetch() } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } const unblock = async (id: number) => { try { await api.delete(`/nixserver/security/blocked-ips/${id}`); fetch() } catch (err: unknown) { alert((err as { response?: { data?: { error?: string } } })?.response?.data?.error ?? 'Failed') } } const resolve = async (id: number) => { try { await api.post(`/nixserver/security/events/${id}/resolve`); fetch() } catch {} } return (

Security

IP blocking, events, and Fail2Ban

{/* Block IP */}

Block IP Address

setNewIp(e.target.value)} placeholder="192.168.1.1" required className="bg-[#0f1117] border border-[#2a2d3e] text-white text-sm rounded-lg px-3 py-2 w-48 focus:outline-none focus:border-indigo-500" /> setReason(e.target.value)} placeholder="Reason (optional)" className="bg-[#0f1117] border border-[#2a2d3e] text-white text-sm rounded-lg px-3 py-2 flex-1 focus:outline-none focus:border-indigo-500" />
{/* Blocked IPs */}

Blocked IPs ({blocked.length})

{['IP', 'Reason', 'Blocked', ''].map(h => )} {blocked.map(b => ( ))} {blocked.length === 0 && }
{h}
{b.ip} {b.reason ?? '—'} {new Date(b.createdAt).toLocaleDateString()}
No blocked IPs
{/* Security Events */}

Security Events

{events.slice(0, 20).map(ev => (
{ev.severity}

{ev.message}

{ev.sourceIp && {ev.sourceIp} · }{new Date(ev.createdAt).toLocaleString()}

{!ev.resolved && ( )}
))} {events.length === 0 &&
No security events
}
) }