Add Zabbix template with offline and last-seen triggers

- zabbix_template.yaml: importable template "Yodmon Yodeck Player"
  - 5 SNMP items using {$YODECK_ID} host macro for per-host OIDs
  - Trigger: offline warning after 30 minutes (yodeck.online < 1)
  - Trigger: last seen > 4 hours (now() - yodeck.last_seen_ts > 14400)
- snmp/pass_persist.py: add col 7 — last_seen as Unix timestamp
- app/zabbix.py: link hosts to template, upsert {$YODECK_ID} macro;
  existing hosts get template linked on next sync automatically
- app/config.py: add ZABBIX_TEMPLATE setting

Import zabbix_template.yaml once via Zabbix UI, then redeploy.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-17 10:39:48 +02:00
parent c35850dc3d
commit e16b542d1b
5 changed files with 175 additions and 10 deletions

View File

@@ -12,6 +12,7 @@ ZABBIX_API_TOKEN = os.environ.get('ZABBIX_API_TOKEN', '')
ZABBIX_USER = os.environ.get('ZABBIX_USER', 'Admin')
ZABBIX_PASSWORD = os.environ.get('ZABBIX_PASSWORD', '')
ZABBIX_HOST_GROUP = os.environ.get('ZABBIX_HOST_GROUP', 'Yodeck Players')
ZABBIX_TEMPLATE = os.environ.get('ZABBIX_TEMPLATE', 'Yodmon Yodeck Player')
ZABBIX_SNMP_COMMUNITY = os.environ.get('ZABBIX_SNMP_COMMUNITY', 'public')
# IP/hostname of this app reachable by the Zabbix server for SNMP polling
APP_HOST = os.environ.get('APP_HOST', '127.0.0.1')

View File

@@ -11,7 +11,7 @@ import logging
import requests
from app.config import (
ZABBIX_URL, ZABBIX_API_TOKEN, ZABBIX_USER, ZABBIX_PASSWORD,
ZABBIX_HOST_GROUP, ZABBIX_SNMP_COMMUNITY,
ZABBIX_HOST_GROUP, ZABBIX_TEMPLATE, ZABBIX_SNMP_COMMUNITY,
APP_HOST, ENTERPRISE_OID,
)
@@ -90,6 +90,7 @@ class ZabbixClient:
return self._call('host.get', {
'groupids': groupid,
'output': ['hostid', 'host', 'name'],
'selectParentTemplates': ['templateid'],
})
def create_host(self, hostname, visible_name, groupid):
@@ -116,6 +117,34 @@ class ZabbixClient:
def update_host_name(self, hostid, visible_name):
self._call('host.update', {'hostid': hostid, 'name': visible_name})
# ------------------------------------------------------------------ templates
def get_template_id(self, name):
result = self._call('template.get', {'filter': {'host': name}, 'output': ['templateid']})
return result[0]['templateid'] if result else None
def link_template(self, hostid, templateid):
# host.update with templates replaces the list — fetch existing first to avoid unlinking others
existing = self._call('host.get', {
'hostids': hostid,
'selectParentTemplates': ['templateid'],
'output': [],
})
current = [{'templateid': t['templateid']} for t in existing[0].get('parentTemplates', [])]
if not any(t['templateid'] == templateid for t in current):
self._call('host.update', {'hostid': hostid, 'templates': current + [{'templateid': templateid}]})
def upsert_host_macro(self, hostid, macro, value):
existing = self._call('usermacro.get', {
'hostids': hostid,
'filter': {'macro': macro},
'output': ['hostmacroid'],
})
if existing:
self._call('usermacro.update', {'hostmacroid': existing[0]['hostmacroid'], 'value': value})
else:
self._call('usermacro.create', {'hostid': hostid, 'macro': macro, 'value': value})
# ------------------------------------------------------------------ items
def _get_interface_id(self, hostid):
@@ -157,7 +186,7 @@ class ZabbixClient:
def sync_to_zabbix(players, add_log_fn):
"""Sync player list to Zabbix: create missing hosts, update names."""
"""Sync player list to Zabbix: create missing hosts, update names, link template."""
if not ZABBIX_URL:
log.debug("ZABBIX_URL not configured — skipping Zabbix sync")
return
@@ -165,7 +194,10 @@ def sync_to_zabbix(players, add_log_fn):
zbx = ZabbixClient()
try:
zbx.login()
groupid = zbx.ensure_hostgroup(ZABBIX_HOST_GROUP)
groupid = zbx.ensure_hostgroup(ZABBIX_HOST_GROUP)
template_id = zbx.get_template_id(ZABBIX_TEMPLATE) if ZABBIX_TEMPLATE else None
if ZABBIX_TEMPLATE and not template_id:
log.warning("Template '%s' not found in Zabbix — import zabbix_template.yaml first", ZABBIX_TEMPLATE)
existing = {h['host']: h for h in zbx.get_hosts_in_group(groupid)}
created = updated = 0
@@ -176,13 +208,25 @@ def sync_to_zabbix(players, add_log_fn):
if hostname not in existing:
hostid = zbx.create_host(hostname, visible, groupid)
zbx.add_player_items(hostid, yid)
zbx.upsert_host_macro(hostid, '{$YODECK_ID}', str(yid))
if template_id:
zbx.link_template(hostid, template_id)
else:
zbx.add_player_items(hostid, yid)
created += 1
log.info("Created Zabbix host: %s (%s)", hostname, visible)
else:
if existing[hostname]['name'] != visible:
zbx.update_host_name(existing[hostname]['hostid'], visible)
h = existing[hostname]
if h['name'] != visible:
zbx.update_host_name(h['hostid'], visible)
updated += 1
# Link template and set macro on existing hosts that don't have it yet
if template_id:
linked = {t['templateid'] for t in h.get('parentTemplates', [])}
if template_id not in linked:
zbx.upsert_host_macro(h['hostid'], '{$YODECK_ID}', str(yid))
zbx.link_template(h['hostid'], template_id)
updated += 1
msg = f"Zabbix sync complete: {created} created, {updated} updated ({len(players)} total)"
add_log_fn('zabbix_sync', msg, {'created': created, 'updated': updated, 'total': len(players)})