- Yodeck API poller (every 10 min, paginated, 310 players) - SQLite persistence (players + activity logs) - SNMP v2c agent via net-snmp pass_persist - Zabbix API auto host creation/update (6.0+) - Flask web dashboard with live player status and log - Docker deployment with persistent volume - dev_server.py for local testing without Docker Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
151 lines
5.3 KiB
HTML
151 lines
5.3 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<title>Yodmon – Yodeck Monitor</title>
|
||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css">
|
||
<meta http-equiv="refresh" content="60">
|
||
<style>
|
||
body { background: #f0f2f5; }
|
||
.stat-card { border: none; box-shadow: 0 1px 4px rgba(0,0,0,.08); }
|
||
.online { color: #198754; font-weight: 600; }
|
||
.offline { color: #dc3545; font-weight: 600; }
|
||
.badge-yodeck_fetch { background: #0d6efd; }
|
||
.badge-zabbix_sync { background: #6f42c1; }
|
||
.badge-snmp_transfer { background: #20c997; }
|
||
.badge-error { background: #dc3545; }
|
||
.scroll-table { max-height: 420px; overflow-y: auto; }
|
||
thead.sticky-top th { position: sticky; top: 0; z-index: 1; }
|
||
</style>
|
||
</head>
|
||
<body>
|
||
|
||
<nav class="navbar navbar-dark bg-dark mb-4 px-3">
|
||
<span class="navbar-brand fw-bold fs-5">Yodmon</span>
|
||
<span class="text-secondary small">Yodeck → Zabbix Bridge</span>
|
||
</nav>
|
||
|
||
<div class="container-fluid px-4">
|
||
|
||
<!-- Summary cards -->
|
||
<div class="row g-3 mb-4">
|
||
<div class="col-6 col-md-3">
|
||
<div class="card stat-card text-center py-3 h-100">
|
||
<div class="display-6 fw-bold text-primary">{{ total }}</div>
|
||
<div class="text-muted small">Total Players</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card stat-card text-center py-3 h-100">
|
||
<div class="display-6 fw-bold text-success">{{ online }}</div>
|
||
<div class="text-muted small">Online</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card stat-card text-center py-3 h-100">
|
||
<div class="display-6 fw-bold text-danger">{{ total - online }}</div>
|
||
<div class="text-muted small">Offline</div>
|
||
</div>
|
||
</div>
|
||
<div class="col-6 col-md-3">
|
||
<div class="card stat-card text-center py-3 h-100">
|
||
<div class="fs-6 fw-semibold text-secondary">
|
||
{% set last_fetch = logs | selectattr('event_type', 'equalto', 'yodeck_fetch') | first %}
|
||
{% if last_fetch %}{{ last_fetch.timestamp[:19] }} UTC{% else %}—{% endif %}
|
||
</div>
|
||
<div class="text-muted small">Last API Fetch</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Player table -->
|
||
<div class="card shadow-sm mb-4">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<span class="fw-semibold">Players</span>
|
||
<span class="badge bg-secondary">{{ total }}</span>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<div class="scroll-table">
|
||
<table class="table table-sm table-hover mb-0">
|
||
<thead class="table-dark sticky-top">
|
||
<tr>
|
||
<th>Yodeck ID</th>
|
||
<th>Name</th>
|
||
<th>Workspace</th>
|
||
<th>Type</th>
|
||
<th>Status</th>
|
||
<th>Last Seen (UTC)</th>
|
||
<th>Updating</th>
|
||
<th>Registered</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for p in players %}
|
||
<tr>
|
||
<td class="text-muted font-monospace">{{ p.id }}</td>
|
||
<td>{{ p.name }}</td>
|
||
<td>{{ p.workspace_name or '—' }}</td>
|
||
<td>{{ p.player_type or '—' }}</td>
|
||
<td>
|
||
{% if p.online %}
|
||
<span class="online">● Online</span>
|
||
{% else %}
|
||
<span class="offline">● Offline</span>
|
||
{% endif %}
|
||
</td>
|
||
<td class="font-monospace small">{{ (p.last_seen or '—')[:19] }}</td>
|
||
<td>{% if p.updating %}<span class="text-warning fw-semibold">Yes</span>{% else %}No{% endif %}</td>
|
||
<td>{% if p.registered %}<span class="text-success">✓</span>{% else %}<span class="text-danger">✗</span>{% endif %}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Activity log -->
|
||
<div class="card shadow-sm mb-4">
|
||
<div class="card-header d-flex justify-content-between align-items-center">
|
||
<span class="fw-semibold">Activity Log</span>
|
||
<span class="badge bg-secondary">last 200</span>
|
||
</div>
|
||
<div class="card-body p-0">
|
||
<div class="scroll-table">
|
||
<table class="table table-sm table-hover mb-0">
|
||
<thead class="table-dark sticky-top">
|
||
<tr>
|
||
<th style="width:180px">Timestamp (UTC)</th>
|
||
<th style="width:160px">Event</th>
|
||
<th>Message</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for l in logs %}
|
||
<tr>
|
||
<td class="font-monospace small text-muted">{{ l.timestamp[:19] }}</td>
|
||
<td>
|
||
<span class="badge badge-{{ l.event_type }}">{{ l.event_type }}</span>
|
||
</td>
|
||
<td class="small">{{ l.message }}</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</div>
|
||
|
||
<div class="text-center text-muted small pb-3">
|
||
Page auto-refreshes every 60 seconds |
|
||
<a href="/api/stats" class="text-muted">API: stats</a>
|
||
<a href="/api/players" class="text-muted">players</a>
|
||
<a href="/api/logs" class="text-muted">logs</a>
|
||
</div>
|
||
|
||
</body>
|
||
</html>
|