Files
infmap/app/templates/index.html

242 lines
14 KiB
HTML

<!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.get('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.get('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>