Feature: Sicherheit Tab - SSH Login Audit + Session History

This commit is contained in:
cynfo3000
2026-03-04 23:16:18 +01:00
parent 57a5a3d3ee
commit 06189a1468
4 changed files with 401 additions and 0 deletions
+110
View File
@@ -19,6 +19,7 @@ const allTabs = [
{ id: 'dhcp', label: 'DHCP', platforms: ['freebsd'] },
{ id: 'certs', label: 'Zertifikate', platforms: ['freebsd'] },
{ id: 'updates', label: 'Updates', platforms: ['freebsd'] },
{ id: 'security', label: 'Sicherheit', platforms: ['freebsd'] },
{ id: 'backups', label: 'Backups' },
{ id: 'agent', label: 'Agent' },
]
@@ -126,6 +127,8 @@ export default function AgentPanel({ agentId, customers, onClose, onReload }) {
<CertsTab sys={sys} />
) : tab === 'updates' ? (
<UpdatesTab agentId={agentId} />
) : tab === 'security' ? (
<SecurityTab sys={sys} />
) : tab === 'backups' ? (
platform === 'linux' ? <PVEBackupsTab sys={sys} /> : <BackupsTab agentId={agentId} />
) : tab === 'agent' ? (
@@ -1384,6 +1387,113 @@ function PVEBackupsTab({ sys }) {
)
}
function SecurityTab({ sys }) {
const sec = sys?.security
if (!sec) return <div className="text-gray-500">Keine Sicherheitsdaten verfuegbar</div>
const summary = sec.summary || {}
const logins = sec.ssh_logins || []
const sessions = sec.sessions || []
const cards = [
{ label: 'Akzeptiert', value: summary.total_accepted || 0, color: 'text-green-400' },
{ label: 'Abgelehnt', value: summary.total_failed || 0, color: 'text-red-400' },
{ label: 'Eindeutige IPs', value: summary.unique_ips || 0, color: 'text-blue-400' },
{ label: 'Letzter Login', value: summary.last_login ? new Date(summary.last_login).toLocaleString('de-DE') : '-', color: 'text-gray-300', small: true },
]
return (
<div className="space-y-4">
{/* Summary Cards */}
<div className="grid grid-cols-2 md:grid-cols-4 gap-3">
{cards.map((c, i) => (
<div key={i} className="bg-gray-800/50 rounded-lg border border-gray-700/50 p-3">
<div className="text-gray-500 text-xs mb-1">{c.label}</div>
<div className={`${c.small ? 'text-sm' : 'text-xl font-bold'} ${c.color}`}>{c.value}</div>
</div>
))}
</div>
{/* SSH Logins */}
<div>
<h3 className="text-sm font-medium text-gray-400 mb-2">SSH-Logins</h3>
{logins.length === 0 ? (
<div className="text-gray-500 text-sm">Keine SSH-Login-Events</div>
) : (
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 overflow-hidden">
<table className="w-full text-xs">
<thead>
<tr className="text-left text-gray-500 border-b border-gray-700">
<th className="px-3 py-2">Zeitstempel</th>
<th className="px-3 py-2">User</th>
<th className="px-3 py-2">Quell-IP</th>
<th className="px-3 py-2">Port</th>
<th className="px-3 py-2">Methode</th>
<th className="px-3 py-2">Status</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700/50">
{logins.map((e, i) => (
<tr key={i}>
<td className="px-3 py-1.5 text-gray-400">{e.timestamp ? new Date(e.timestamp).toLocaleString('de-DE') : '-'}</td>
<td className="px-3 py-1.5 text-white">{e.user}</td>
<td className="px-3 py-1.5 text-gray-300 font-mono">{e.source}</td>
<td className="px-3 py-1.5 text-gray-400">{e.port}</td>
<td className="px-3 py-1.5">
<span className="px-1.5 py-0.5 bg-gray-700 rounded text-gray-400 text-xs">{e.method}</span>
</td>
<td className="px-3 py-1.5">
<span className="flex items-center gap-1.5">
<span className={`w-1.5 h-1.5 rounded-full ${e.status === 'accepted' ? 'bg-green-400' : 'bg-red-400'}`} />
<span className={e.status === 'accepted' ? 'text-green-400' : 'text-red-400'}>{e.status}</span>
</span>
</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
{/* Sessions */}
<div>
<h3 className="text-sm font-medium text-gray-400 mb-2">Sessions</h3>
{sessions.length === 0 ? (
<div className="text-gray-500 text-sm">Keine Sessions</div>
) : (
<div className="bg-gray-800/50 rounded-lg border border-gray-700/50 overflow-hidden">
<table className="w-full text-xs">
<thead>
<tr className="text-left text-gray-500 border-b border-gray-700">
<th className="px-3 py-2">User</th>
<th className="px-3 py-2">TTY</th>
<th className="px-3 py-2">Von</th>
<th className="px-3 py-2">Login</th>
<th className="px-3 py-2">Logout</th>
<th className="px-3 py-2">Dauer</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-700/50">
{sessions.map((s, i) => (
<tr key={i}>
<td className="px-3 py-1.5 text-white">{s.user}</td>
<td className="px-3 py-1.5 text-gray-400 font-mono">{s.tty}</td>
<td className="px-3 py-1.5 text-gray-300 font-mono">{s.from || '-'}</td>
<td className="px-3 py-1.5 text-gray-400">{s.login}</td>
<td className="px-3 py-1.5 text-gray-400">{s.logout || '-'}</td>
<td className="px-3 py-1.5 text-gray-400">{s.duration || '-'}</td>
</tr>
))}
</tbody>
</table>
</div>
)}
</div>
</div>
)
}
function BackupsTab({ agentId }) {
const [backups, setBackups] = useState([])
const [loading, setLoading] = useState(true)