First push

This commit is contained in:
2026-03-11 10:14:26 -04:00
parent eaf4dbbc3b
commit 20ed0eeadb
11 changed files with 1333 additions and 1 deletions

199
index.php Normal file
View File

@@ -0,0 +1,199 @@
<?php
require_once __DIR__ . '/includes/functions.php';
$nodes = get_all_nodes();
$stats = global_stats();
$t_ips = top_ips();
$t_ports = top_ports();
$recent = recent_connections(DASH_RECENT_LIMIT);
$node_count = count($nodes);
$enabled_count = count(array_filter($nodes, fn($n) => (bool)$n['enabled']));
// Optional per-node filter
$filter_node = isset($_GET['node']) ? (int)$_GET['node'] : null;
if ($filter_node) {
$t_ips = top_ips_by_node($filter_node);
$recent = recent_connections(DASH_RECENT_LIMIT, $filter_node);
}
$max_ip_cnt = $t_ips ? max(array_column($t_ips, 'cnt')) : 1;
$max_port_cnt = $t_ports ? max(array_column($t_ports, 'cnt')) : 1;
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="refresh" content="30">
<title>Dashboard portspoof concentrator</title>
<style>
<?php include __DIR__ . '/includes/style.php'; ?>
</style>
</head>
<body>
<header>
<h1>portspoof<span>concentrator</span></h1>
<nav>
<a href="index.php" class="active">Dashboard</a>
<a href="nodes.php">Nodes</a>
</nav>
</header>
<main>
<!-- Stat cards -->
<div class="stats-row">
<div class="stat-card">
<div class="label">Total connections</div>
<div class="value"><?= number_format($stats['total']) ?></div>
</div>
<div class="stat-card">
<div class="label">Last <?= RATE_WINDOW_SECONDS ?>s</div>
<div class="value"><?= number_format($stats['recent']) ?></div>
</div>
<div class="stat-card">
<div class="label">Nodes</div>
<div class="value"><?= $enabled_count ?> <span class="muted" style="font-size:1rem">/ <?= $node_count ?></span></div>
</div>
<div class="stat-card">
<div class="label">Last connection</div>
<div class="value" style="font-size:1rem"><?= $stats['last'] ? h($stats['last']) : '—' ?></div>
</div>
</div>
<!-- Node filter -->
<?php if (count($nodes) > 1): ?>
<div class="card" style="padding:.75rem 1.25rem">
<span class="muted" style="font-size:.8rem">Filter by node:</span>
&nbsp;<a href="index.php" <?= !$filter_node ? 'style="color:var(--accent)"' : '' ?>>All</a>
<?php foreach ($nodes as $n): ?>
&nbsp;<a href="index.php?node=<?= (int)$n['id'] ?>"
<?= $filter_node === (int)$n['id'] ? 'style="color:var(--accent)"' : '' ?>>
<?= h($n['name']) ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Top IPs + Top Ports -->
<div class="two-col">
<div class="card">
<h2>Top source IPs</h2>
<?php if (empty($t_ips)): ?>
<p class="muted">No data yet.</p>
<?php else: ?>
<table>
<thead><tr><th>IP</th><th>Connections</th><th></th></tr></thead>
<tbody>
<?php foreach ($t_ips as $row): ?>
<tr>
<td><code><?= h($row['src_ip']) ?></code></td>
<td><?= number_format($row['cnt']) ?></td>
<td style="width:40%">
<div class="bar-wrap">
<div class="bar" style="width:<?= round($row['cnt'] / $max_ip_cnt * 100) ?>%"></div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<div class="card">
<h2>Top targeted ports</h2>
<?php if (empty($t_ports)): ?>
<p class="muted">No data yet.</p>
<?php else: ?>
<table>
<thead><tr><th>Port</th><th>Connections</th><th></th></tr></thead>
<tbody>
<?php foreach ($t_ports as $row): ?>
<tr>
<td><code><?= (int)$row['dst_port'] ?></code></td>
<td><?= number_format($row['cnt']) ?></td>
<td style="width:40%">
<div class="bar-wrap">
<div class="bar" style="width:<?= round($row['cnt'] / $max_port_cnt * 100) ?>%"></div>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<!-- Recent connections -->
<div class="card">
<h2>Recent connections <?= $filter_node ? '(filtered)' : '' ?></h2>
<?php if (empty($recent)): ?>
<p class="muted">No connections ingested yet. Make sure at least one node is configured and the fetch cron is running.</p>
<?php else: ?>
<table>
<thead>
<tr>
<th>Time (UTC)</th>
<th>Node</th>
<th>Source IP</th>
<th>Src port</th>
<th>Dst port</th>
<th>Banner len</th>
<th>Banner preview</th>
</tr>
</thead>
<tbody>
<?php foreach ($recent as $c): ?>
<tr>
<td><?= h($c['occurred_at']) ?></td>
<td><?= h($c['node_name']) ?></td>
<td><code><?= h($c['src_ip']) ?></code></td>
<td><?= (int)$c['src_port'] ?></td>
<td><code><?= (int)$c['dst_port'] ?></code></td>
<td><?= (int)$c['banner_len'] ?></td>
<td>
<?php if ($c['banner_hex']): ?>
<code title="<?= h($c['banner_hex']) ?>" style="font-size:.75rem">
<?= h(mb_substr(banner_text($c['banner_hex']), 0, 40)) ?>
</code>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Node status table -->
<div class="card">
<h2>Node status</h2>
<?php if (empty($nodes)): ?>
<p class="muted">No nodes configured. <a href="nodes.php">Add one</a>.</p>
<?php else: ?>
<table>
<thead>
<tr><th>Name</th><th>API URL</th><th>Enabled</th><th>Last fetched</th></tr>
</thead>
<tbody>
<?php foreach ($nodes as $n): ?>
<tr>
<td><a href="index.php?node=<?= (int)$n['id'] ?>"><?= h($n['name']) ?></a></td>
<td><code><?= h($n['api_url']) ?></code></td>
<td><?= $n['enabled'] ? '<span class="badge ok">yes</span>' : '<span class="badge">no</span>' ?></td>
<td><?= $n['last_fetched_at'] ? h($n['last_fetched_at']) : '<span class="muted">never</span>' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<p class="muted" style="font-size:.75rem; text-align:right">
Auto-refreshes every 30 s &nbsp;·&nbsp; <?= date('Y-m-d H:i:s') ?> server time
</p>
</main>
</body>
</html>