Initial commit: Yodmon Yodeck→Zabbix bridge

- 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>
This commit is contained in:
2026-04-17 09:31:00 +02:00
commit 9fc3e97546
18 changed files with 1027 additions and 0 deletions

150
templates/index.html Normal file
View File

@@ -0,0 +1,150 @@
<!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 &nbsp;|&nbsp;
<a href="/api/stats" class="text-muted">API: stats</a> &nbsp;
<a href="/api/players" class="text-muted">players</a> &nbsp;
<a href="/api/logs" class="text-muted">logs</a>
</div>
</body>
</html>