106 lines
3.4 KiB
HTML
106 lines
3.4 KiB
HTML
{% extends "base.html" %}
|
|
{% block content %}
|
|
<h1>Network Overview</h1>
|
|
|
|
{% if stats is none %}
|
|
<div class="card">
|
|
<div class="no-data">
|
|
Could not reach the Nebula stats endpoint at <code class="mono">{{ request.app.state.stats_url if request.app.state is defined else "configured URL" }}</code>.
|
|
Make sure <code class="mono">stats.enabled</code> and <code class="mono">stats.listen</code> are set in your Nebula config.
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
|
|
{# ── top-level counters ── #}
|
|
{% set meta = stats.get("meta", {}) %}
|
|
{% set network = stats.get("network", {}) %}
|
|
|
|
<div class="stat-grid">
|
|
<div class="stat-box">
|
|
<div class="label">Nebula Version</div>
|
|
<div class="value" style="font-size:15px; padding-top:4px;">{{ meta.get("version", "—") }}</div>
|
|
</div>
|
|
{% set peers = hostmap.get("Hosts", {}) if hostmap else {} %}
|
|
<div class="stat-box">
|
|
<div class="label">Active Peers</div>
|
|
<div class="value">{{ peers | length }}</div>
|
|
</div>
|
|
{% set counters = stats.get("counters", {}) %}
|
|
<div class="stat-box">
|
|
<div class="label">Tx Bytes</div>
|
|
<div class="value">{{ "{:,}".format(counters.get("send_bytes", 0)) }}</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="label">Rx Bytes</div>
|
|
<div class="value">{{ "{:,}".format(counters.get("recv_bytes", 0)) }}</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="label">Tx Packets</div>
|
|
<div class="value">{{ "{:,}".format(counters.get("send_packets", 0)) }}</div>
|
|
</div>
|
|
<div class="stat-box">
|
|
<div class="label">Rx Packets</div>
|
|
<div class="value">{{ "{:,}".format(counters.get("recv_packets", 0)) }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
{# ── raw stats JSON ── #}
|
|
<details style="margin-bottom:20px;">
|
|
<summary>Raw stats JSON</summary>
|
|
<pre>{{ stats | tojson(indent=2) }}</pre>
|
|
</details>
|
|
|
|
{% endif %}
|
|
|
|
{# ── hostmap ── #}
|
|
<div class="card">
|
|
<h2>Peer Hostmap</h2>
|
|
{% if hostmap is none %}
|
|
<div class="no-data">Hostmap unavailable — stats endpoint not reachable.</div>
|
|
{% else %}
|
|
{% set hosts = hostmap.get("Hosts", {}) %}
|
|
{% if not hosts %}
|
|
<div class="no-data">No peers connected.</div>
|
|
{% else %}
|
|
<table>
|
|
<thead>
|
|
<tr>
|
|
<th>Overlay IP</th>
|
|
<th>Remote Addrs</th>
|
|
<th>Index</th>
|
|
<th>Relay?</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for overlay_ip, host in hosts.items() %}
|
|
<tr>
|
|
<td class="mono">{{ overlay_ip }}</td>
|
|
<td>
|
|
{% for addr in host.get("RemoteAddrs", []) %}
|
|
<span class="mono badge badge-gray" style="margin-right:4px;">{{ addr }}</span>
|
|
{% else %}
|
|
<span class="badge badge-gray">none</span>
|
|
{% endfor %}
|
|
</td>
|
|
<td class="mono">{{ host.get("LocalIndex", "—") }}</td>
|
|
<td>
|
|
{% if host.get("Relay") %}
|
|
<span class="badge badge-blue">relay</span>
|
|
{% else %}
|
|
<span class="badge badge-gray">no</span>
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
{% endif %}
|
|
<details style="margin-top:16px;">
|
|
<summary>Raw hostmap JSON</summary>
|
|
<pre>{{ hostmap | tojson(indent=2) }}</pre>
|
|
</details>
|
|
{% endif %}
|
|
</div>
|
|
|
|
{% endblock %}
|