Yay
This commit is contained in:
241
app/templates/index.html
Normal file
241
app/templates/index.html
Normal file
@@ -0,0 +1,241 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="refresh" content="60">
|
||||
<title>Infrastructure Map</title>
|
||||
<link rel="stylesheet" href="/static/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<h1>Infrastructure Map</h1>
|
||||
<span class="subtitle">Auto-refreshes every 60s</span>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
{% for group_name, servers in groups.items() %}
|
||||
<section class="group">
|
||||
<h2 class="group-header">{{ group_name }}</h2>
|
||||
<div class="server-grid">
|
||||
{% for server in servers %}
|
||||
{% set d = server.details or {} %}
|
||||
{% set sys = d.get('system', {}) if d.get('system') else {} %}
|
||||
{% set cpu = d.get('cpu', {}) if d.get('cpu') else {} %}
|
||||
{% set mem = d.get('memory', {}) if d.get('memory') else {} %}
|
||||
{% set cpu_pct = cpu.get('usage_percent', '0')|float %}
|
||||
{% set mem_pct = mem.get('usage_percent', '0')|float %}
|
||||
{% set disk_usages = d.get('disk_usage', []) if d.get('disk_usage') else [] %}
|
||||
{% set root_disk = namespace(pct=0.0) %}
|
||||
{% for du in disk_usages %}
|
||||
{% if du.get('mount') == '/' %}
|
||||
{% set root_disk.pct = du.get('usage_percent', '0')|float %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if root_disk.pct == 0.0 and disk_usages|length > 0 %}
|
||||
{% set root_disk.pct = disk_usages[0].get('usage_percent', '0')|float %}
|
||||
{% endif %}
|
||||
|
||||
<div class="server-card {% if not server.is_online %}offline{% endif %}"
|
||||
onclick="toggleDetails(this)">
|
||||
<div class="card-summary">
|
||||
<div class="card-header">
|
||||
<span class="status-dot {% if server.is_online %}online{% else %}offline{% endif %}"></span>
|
||||
<span class="server-name">{{ server.hostname }}</span>
|
||||
</div>
|
||||
<div class="server-ip">{{ server.primary_ip or 'No IP' }}</div>
|
||||
<div class="server-os">{{ sys.get('os_pretty', '') }}</div>
|
||||
|
||||
{% if server.is_online %}
|
||||
<div class="usage-bars">
|
||||
<div class="usage-row">
|
||||
<span class="usage-label">CPU</span>
|
||||
<div class="usage-bar-bg">
|
||||
<div class="usage-bar-fill" style="width: {{ cpu_pct }}%; background: {{ cpu_pct|usage_color }};"></div>
|
||||
</div>
|
||||
<span class="usage-pct">{{ '%.0f'|format(cpu_pct) }}%</span>
|
||||
</div>
|
||||
<div class="usage-row">
|
||||
<span class="usage-label">RAM</span>
|
||||
<div class="usage-bar-bg">
|
||||
<div class="usage-bar-fill" style="width: {{ mem_pct }}%; background: {{ mem_pct|usage_color }};"></div>
|
||||
</div>
|
||||
<span class="usage-pct">{{ '%.0f'|format(mem_pct) }}%</span>
|
||||
</div>
|
||||
<div class="usage-row">
|
||||
<span class="usage-label">DISK</span>
|
||||
<div class="usage-bar-bg">
|
||||
<div class="usage-bar-fill" style="width: {{ root_disk.pct }}%; background: {{ root_disk.pct|usage_color }};"></div>
|
||||
</div>
|
||||
<span class="usage-pct">{{ '%.0f'|format(root_disk.pct) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="offline-label">Unreachable</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- Expanded Details -->
|
||||
<div class="card-details" style="display: none;">
|
||||
<div class="details-grid">
|
||||
<!-- System Info -->
|
||||
<div class="detail-section">
|
||||
<h4>System</h4>
|
||||
<table>
|
||||
<tr><td>Hostname</td><td>{{ sys.get('hostname', '-') }}</td></tr>
|
||||
<tr><td>OS</td><td>{{ sys.get('os_pretty', '-') }}</td></tr>
|
||||
<tr><td>Kernel</td><td>{{ sys.get('kernel', '-') }}</td></tr>
|
||||
<tr><td>Arch</td><td>{{ sys.get('arch', '-') }}</td></tr>
|
||||
<tr><td>Uptime</td><td>{{ sys.get('uptime_seconds', '')|format_uptime }}</td></tr>
|
||||
<tr><td>Board</td><td>{{ sys.get('board_vendor', '') }} {{ sys.get('board_name', '') }}</td></tr>
|
||||
<tr><td>Board Version</td><td>{{ sys.get('board_version', '-') }}</td></tr>
|
||||
<tr><td>BIOS</td><td>{{ sys.get('bios_version', '-') }} ({{ sys.get('bios_date', '-') }})</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- CPU -->
|
||||
<div class="detail-section">
|
||||
<h4>CPU</h4>
|
||||
<table>
|
||||
<tr><td>Model</td><td>{{ cpu.get('model', '-') }}</td></tr>
|
||||
<tr><td>Cores</td><td>{{ cpu.get('cores', '-') }}</td></tr>
|
||||
<tr><td>Sockets</td><td>{{ cpu.get('sockets', '-') }}</td></tr>
|
||||
<tr><td>Threads/Core</td><td>{{ cpu.get('threads_per_core', '-') }}</td></tr>
|
||||
<tr><td>Usage</td><td>{{ cpu.get('usage_percent', '-') }}%</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Memory -->
|
||||
<div class="detail-section">
|
||||
<h4>Memory</h4>
|
||||
<table>
|
||||
<tr><td>Total</td><td>{{ mem.get('total_mb', '')|format_mb }}</td></tr>
|
||||
<tr><td>Used</td><td>{{ mem.get('used_mb', '')|format_mb }}</td></tr>
|
||||
<tr><td>Available</td><td>{{ mem.get('available_mb', '')|format_mb }}</td></tr>
|
||||
<tr><td>Usage</td><td>{{ mem.get('usage_percent', '-') }}%</td></tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- GPUs -->
|
||||
{% set gpus = d.get('gpu', []) if d.get('gpu') else [] %}
|
||||
{% if gpus %}
|
||||
<div class="detail-section">
|
||||
<h4>GPUs</h4>
|
||||
<table>
|
||||
{% for gpu in gpus %}
|
||||
<tr><td>GPU {{ loop.index0 }}</td><td>{{ gpu.get('description', '-') }}</td></tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<!-- Network -->
|
||||
<div class="detail-section wide">
|
||||
<h4>Network Interfaces</h4>
|
||||
<table>
|
||||
<tr class="table-header"><td>Interface</td><td>IPv4</td><td>IPv6</td><td>MAC</td><td>State</td><td>Speed</td><td>Driver</td></tr>
|
||||
{% set nets = d.get('net', []) if d.get('net') else [] %}
|
||||
{% for iface in nets %}
|
||||
<tr>
|
||||
<td>{{ iface.get('name', iface.get('_name', '-')) }}</td>
|
||||
<td>{{ iface.get('ipv4', '-') or '-' }}</td>
|
||||
<td>{{ iface.get('ipv6', '-') or '-' }}</td>
|
||||
<td>{{ iface.get('mac', '-') or '-' }}</td>
|
||||
<td>{{ iface.get('state', '-') }}</td>
|
||||
<td>{% if iface.get('speed_mbps') %}{{ iface.speed_mbps }} Mbps{% else %}-{% endif %}</td>
|
||||
<td>{{ iface.get('driver', '-') or '-' }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
{% set routing = d.get('routing', {}) if d.get('routing') else {} %}
|
||||
{% set dns = d.get('dns', {}) if d.get('dns') else {} %}
|
||||
{% set ts = d.get('tailscale', {}) if d.get('tailscale') else {} %}
|
||||
<table style="margin-top: 8px;">
|
||||
<tr><td>Gateway</td><td>{{ routing.get('gateway', '-') }} ({{ routing.get('interface', '-') }})</td></tr>
|
||||
<tr><td>DNS</td><td>
|
||||
{% set servers_val = dns.get('server', '-') %}
|
||||
{% if servers_val is string %}{{ servers_val }}{% elif servers_val is iterable %}{{ servers_val|join(', ') }}{% else %}-{% endif %}
|
||||
</td></tr>
|
||||
{% if ts.get('installed') == 'true' %}
|
||||
<tr><td>Tailscale IP</td><td>{{ ts.get('ipv4', '-') }}</td></tr>
|
||||
<tr><td>Tailscale Name</td><td>{{ ts.get('hostname', '-') }}</td></tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<!-- Disks -->
|
||||
<div class="detail-section wide">
|
||||
<h4>Storage</h4>
|
||||
{% set disks = d.get('disk', []) if d.get('disk') else [] %}
|
||||
{% if disks %}
|
||||
<table>
|
||||
<tr class="table-header"><td>Device</td><td>Size</td></tr>
|
||||
{% for disk in disks %}
|
||||
<tr>
|
||||
<td>{{ disk.get('name', '-') }}</td>
|
||||
<td>{{ disk.get('size_bytes', '')|format_bytes }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
|
||||
{% if disk_usages %}
|
||||
<table style="margin-top: 8px;">
|
||||
<tr class="table-header"><td>Mount</td><td>Total</td><td>Used</td><td>Available</td><td>Usage</td></tr>
|
||||
{% for du in disk_usages %}
|
||||
<tr>
|
||||
<td>{{ du.get('mount', '-') }}</td>
|
||||
<td>{{ du.get('total_bytes', '')|format_bytes }}</td>
|
||||
<td>{{ du.get('used_bytes', '')|format_bytes }}</td>
|
||||
<td>{{ du.get('available_bytes', '')|format_bytes }}</td>
|
||||
<td>
|
||||
<span class="disk-pct" style="color: {{ du.get('usage_percent', '0')|float|usage_color }}">
|
||||
{{ du.get('usage_percent', '-') }}%
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% if d.get('error') %}
|
||||
<div class="detail-section wide">
|
||||
<h4>Error</h4>
|
||||
<p class="error-text">{{ d.error }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<div class="last-updated">
|
||||
Last collected: {{ server.last_collected.strftime('%Y-%m-%d %H:%M:%S UTC') if server.last_collected else 'Never' }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
{% else %}
|
||||
<div class="empty-state">
|
||||
<h2>No servers configured</h2>
|
||||
<p>Edit <code>infrastructure.conf</code> to add your servers.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</main>
|
||||
|
||||
<script>
|
||||
function toggleDetails(card) {
|
||||
const details = card.querySelector('.card-details');
|
||||
const isOpen = details.style.display !== 'none';
|
||||
// Close all other details
|
||||
document.querySelectorAll('.card-details').forEach(d => d.style.display = 'none');
|
||||
document.querySelectorAll('.server-card').forEach(c => c.classList.remove('expanded'));
|
||||
if (!isOpen) {
|
||||
details.style.display = 'block';
|
||||
card.classList.add('expanded');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user