Files
portspoof_concentrator/index.php
2026-03-11 16:26:39 -04:00

223 lines
7.1 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require_once __DIR__ . '/includes/auth.php';
require_login();
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;
$upstream_version = fetch_upstream_version();
$update_available = $upstream_version && is_newer_version($upstream_version, APP_VERSION);
?>
<!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>
<a href="settings.php">Settings</a>
<?php if (auth_enabled()): ?>
<a href="logout.php" style="color:var(--muted)">Sign out</a>
<?php endif; ?>
</nav>
</header>
<main>
<?php if ($update_available): ?>
<div class="alert warn">
Update available: <strong>v<?= h($upstream_version) ?></strong> —
you are running <strong>v<?= h(APP_VERSION) ?></strong>.
<a href="https://git.ny.daprogs.com/DAProgs/portspoof_concentrator" target="_blank">View release ↗</a>
</div>
<?php endif; ?>
<!-- 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 <small class="muted" style="font-size:.7rem;font-weight:400">(24 h)</small></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 <small class="muted" style="font-size:.7rem;font-weight:400">(24 h)</small></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)' : '' ?>
<a href="api/connections.php<?= $filter_node ? '?node_id=' . $filter_node : '' ?>"
style="float:right;font-size:.75rem;font-weight:400;color:var(--accent);text-decoration:none"
target="_blank">JSON API ↗</a>
</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>
<?php include __DIR__ . '/includes/footer.php'; ?>
</body>
</html>