Rework device type view to group by deviceType tag

- /api/devices now groups hosts by deviceType tag instead of host group;
  hosts without the tag are skipped
- /api/detail device lookup filters by deviceType tag instead of groupid
- getTag() is now case-insensitive on the tag name
- Removed selectHostGroups from fetchKFCData (no longer needed)
- Frontend: hex id and Zabbix deep-link use deviceType instead of groupid
- Smaller hex label (1.2rem) for device type view via hex-item--device class
- Skip DOM re-render on auto-refresh when data is unchanged (lastDataKey diff)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-21 14:59:17 +02:00
parent 55b9935f5b
commit 101aaa6e32
3 changed files with 32 additions and 21 deletions

View File

@@ -29,7 +29,8 @@ async function zabbix(method, params) {
// ── Helpers ───────────────────────────────────────────────────────────────────
function getTag(tags, name) {
const t = tags.find(t => t.tag === name);
const lower = name.toLowerCase();
const t = tags.find(t => t.tag.toLowerCase() === lower);
return t ? t.value : null;
}
@@ -48,10 +49,9 @@ function problemCount(triggers) {
async function fetchKFCData() {
const hosts = await zabbix('host.get', {
output: ['hostid', 'host', 'name'],
tags: [{ tag: 'customer', value: CUSTOMER_TAG_VALUE, operator: '1' }],
selectTags: 'extend',
selectHostGroups: ['groupid', 'name'],
output: ['hostid', 'host', 'name'],
tags: [{ tag: 'customer', value: CUSTOMER_TAG_VALUE, operator: '1' }],
selectTags: 'extend',
});
if (hosts.length === 0) return { hosts: [], triggersByHost: {} };
@@ -158,22 +158,20 @@ app.get('/api/restaurants', async (req, res) => {
app.get('/api/devices', async (req, res) => {
try {
const country = req.query.country || '';
const { hosts, triggersByHost } = await fetchKFCData();
const map = {};
for (const host of hosts) {
const hostCountry = getTag(host.tags, COUNTRY_TAG);
if (country && hostCountry && hostCountry.toLowerCase() !== country.toLowerCase()) continue;
const deviceType = getTag(host.tags, 'deviceType');
if (!deviceType) continue;
for (const group of host.hostgroups) {
const key = `${group.groupid}__${hostCountry || ''}`;
if (!map[key]) {
map[key] = { groupid: group.groupid, name: group.name, country: hostCountry, hosts: [], triggers: [] };
}
map[key].hosts.push(host);
map[key].triggers.push(...(triggersByHost[host.hostid] || []));
const key = `${deviceType}__${hostCountry || ''}`;
if (!map[key]) {
map[key] = { deviceType, name: deviceType, country: hostCountry, hosts: [], triggers: [] };
}
map[key].hosts.push(host);
map[key].triggers.push(...(triggersByHost[host.hostid] || []));
}
const result = Object.values(map)
@@ -193,7 +191,7 @@ app.get('/api/devices', async (req, res) => {
}
problems.sort((a, b) => b.priority - a.priority || b.lastchange - a.lastchange);
return {
groupid: g.groupid,
deviceType: g.deviceType,
name: g.name,
country: g.country,
hostCount: g.hosts.length,
@@ -220,7 +218,7 @@ app.get('/api/detail', async (req, res) => {
const filtered = type === 'restaurant'
? hosts.filter(h => getTag(h.tags, 'location') === id)
: hosts.filter(h => h.hostgroups.some(g => g.groupid === id));
: hosts.filter(h => getTag(h.tags, 'deviceType') === id);
const items = filtered.map(host => ({
hostid: host.hostid,