# CLAUDE.md This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. ## Commands ```bash npm install # install dependencies npm start # run production server (port 3000) npm run dev # run with --watch (auto-restart on file changes) ``` There are no tests or linters configured. Docker: ```bash docker-compose up -d --build ``` ## Architecture Single-process Node.js app. `server.js` acts as both an API proxy (keeping the Zabbix token server-side) and a static file host for the vanilla JS frontend in `public/`. ``` server.js Express backend — Zabbix JSON-RPC proxy + REST API public/ index.html Shell: header, view toggle, countries layout container, modal style.css Light theme, CSS variables for hex sizing, hex grid layout app.js All frontend logic — fetch, render, interactions .env Runtime config (gitignored) docker-compose.yml Reads ZABBIX_TOKEN from env or .env ``` ## Config (env vars) | Variable | Default | Purpose | |---|---|---| | `ZABBIX_URL` | `https://monitor.stranto.com` | Zabbix instance | | `ZABBIX_TOKEN` | — | API bearer token (required) | | `CUSTOMER_TAG_VALUE` | `QWE` | Zabbix host tag `customer=` that identifies KFC hosts | | `COUNTRY_TAG` | `country` | Zabbix host tag name used for country grouping | | `PORT` | `3000` | HTTP port | ## Zabbix API conventions - Auth: `Authorization: Bearer ` header (Zabbix 6.4+ style — NOT the legacy `auth` body field) - All hosts are scoped by tag `customer=QWE` (operator `1` = equals) - Host groups use `selectHostGroups` / `host.hostgroups` — NOT the deprecated `selectGroups` / `host.groups` - Countries come from a `country` host tag (e.g. `AT`, `SK`) - Restaurants group by `location` host tag; device types group by host group, keyed as `groupid__country` - Severity 0 (Not classified) and 1 (Information) are treated as OK throughout - Acknowledged problems are excluded via `withLastEventUnacknowledged: true` - `expandDescription: true` on `trigger.get` resolves `{HOST.NAME}` and other macros in descriptions ## Backend API endpoints | Endpoint | Returns | |---|---| | `GET /api/restaurants` | Array of `{ location, country, hostCount, problemCount, severity, problems[] }` | | `GET /api/devices` | Same shape but grouped by host group; `name` instead of `location` | | `GET /api/stats` | `{ [countryCode]: { hostCount, itemCount, triggerCount } }` — fetched in parallel with grid data | | `GET /api/detail?type=&id=` | Per-host problem detail for modal | | `GET /api/config` | `{ zabbixUrl, customerTagValue }` | | `GET /api/health` | Zabbix connectivity check | Each `problems[]` entry: `{ description, priority, lastchange (unix seconds), hostName }`, sorted by severity desc then time desc. ## Frontend data flow 1. Boot: fetch `/api/config`, then fetch grid data + `/api/stats` in parallel 2. Items are grouped by `country` field and rendered as equal-width `.country-col` columns 3. Each column renders: flag + country header → hex grid → problem list (locations with active problems) 4. Single click → detail modal (`/api/detail`); double click → Zabbix Problems page in new tab (240ms timer separates the two) 5. Auto-refresh every 30 seconds ## Hex grid geometry For equal gaps on all 6 sides, the hex dimensions must satisfy `--hex-w = --hex-h × sin(60°) ≈ --hex-h × 0.866`. The row vertical overlap is: ```css margin-top: calc(var(--hex-h) * -0.25 + var(--hex-gap) * 0.866); ``` Changing `--hex-gap` without updating `margin-top` will make diagonal gaps unequal. The even-row offset (`margin-left` on `.hex-row.offset`) is always `(hex-w + hex-gap) / 2`. ## Zabbix Problems URL format (7.x) Parameters use **no** `filter_` prefix. Key params: `show=1` (recent), `evaltype=0`, `tags[N][tag/value/operator]`, `groupids[]`, `severities[]=2..5`. The legacy `filter_show`, `filter_tags`, `filter_set` format does not work in Zabbix 7.x. ## Severity mapping | Zabbix priority | Label | Colour | |---|---|---| | -1 (no problems / ack / info) | OK | green `#2da44e` | | 2 | Warning | amber `#c69026` | | 3 | Average | orange `#e16f24` | | 4 | High | red `#d1242f` | | 5 | Disaster | purple `#8250df` (pulses) |