<?php
// ═══════════════════════════════════════════════════════════════════════════════════════════════
// PURPOSE
//   Watchdog tab. Shows what every watchdog has recorded across all nodes — strike counts,
//   action levels, paused and stopped containers, restart history, OOM and reboot counts.
//
// DESIGN PRINCIPLES
//   Pure view. No PHP logic and no include/ dependency — the whole page is markup plus a
//   poll against api/watchdog.php. All parsing lives in include/watchdog.php behind that
//   endpoint, so the page cannot hold a second opinion about what a strike means.
//
//   Shows every node, not just this one. A watchdog page that only covered the host you
//   happened to open it on would miss exactly the node in trouble.
//
// OPERATIONAL SAFEGUARDS
//   Strictly read-only. There is no control on this page that clears a strike, restarts a
//   container, or resets a counter — those belong to the watchdog that owns the state.
//
//   A node that fails to report renders as unavailable rather than healthy. Absence of data
//   is never drawn as an all-clear.
//
// RENDERS
//   Per-node cards: resource / docker / system / storage / network watchdog state,
//   strike pills, restart history, reboot and OOM counts
//
// DEPENDS ON
//   api/watchdog.php   polled every 30s   → include/watchdog.php
?>
<style>
.vv-wd-card    { background:#161616;border:1px solid #2a2a2a;border-radius:6px;padding:10px;min-width:0; }
.vv-wd-sec     { font-size:10px;font-weight:bold;color:#444;letter-spacing:.07em;text-transform:uppercase;margin-bottom:6px; }
.vv-wd-row     { display:flex;justify-content:space-between;align-items:baseline;gap:6px;margin:2px 0; }
.vv-wd-lbl     { font-size:11px;color:#444;white-space:nowrap; }
.vv-wd-val     { font-size:12px;color:#bbb;text-align:right; }
.vv-wd-sep     { border:none;border-top:1px solid #1e1e1e;margin:6px 0; }
.vv-wd-pill    { font-size:10px;padding:1px 6px;border-radius:2px;background:#1e1e1e;color:#666;border:1px solid #272727; }
.vv-wd-pill.ok { background:#0d1f0d;color:#4caf50;border-color:#1a3a1a; }
.vv-wd-pill.warn { background:#1f1500;color:#ffb74d;border-color:#3a2800; }
.vv-wd-pill.err  { background:#200d0d;color:#ef5350;border-color:#3a1a1a; }
.vv-wd-pill-row { display:flex;flex-wrap:wrap;gap:4px;margin-top:4px; }
.vv-wd-bar     { height:5px;border-radius:2px;background:#1e1e1e;margin-top:3px;overflow:hidden; }
.vv-wd-bar-fill{ height:100%;border-radius:2px;transition:width .3s; }
.vv-wd-badge   { font-size:11px;font-weight:bold;padding:2px 8px;border-radius:3px; }
.vv-wd-badge.ok   { background:#0d1f0d;color:#4caf50; }
.vv-wd-badge.soft { background:#1f1f00;color:#cddc39; }
.vv-wd-badge.med  { background:#1f1000;color:#ffb74d; }
.vv-wd-badge.hard { background:#200d0d;color:#ef5350; }
.vv-wd-node-h  { display:flex;align-items:center;gap:8px;margin-bottom:10px; }
.vv-wd-node-id { font-size:12px;font-weight:bold;color:#666;letter-spacing:.06em;text-transform:uppercase; }
.vv-wd-dot     { width:6px;height:6px;border-radius:50%;flex-shrink:0; }
.vv-wd-strike-row { display:flex;align-items:baseline;gap:6px;margin:2px 0; }
.vv-wd-strike-name{ font-size:11px;color:#777;flex:1; }
.vv-wd-strike-cnt { font-size:11px;font-weight:bold;color:#ffb74d; }
.vv-wd-reboot-ts  { font-size:11px;color:#555;margin:1px 0; }
.vv-wd-ctr-row { display:flex;justify-content:space-between;align-items:baseline;margin:2px 0; }
.vv-wd-ctr-name{ font-size:11px;color:#888; }
.vv-wd-ctr-lim { font-size:11px;color:#555; }
.vv-wd-pressure{ grid-column:1/-1;border-color:#3a2000;background:#1a1000; }

/* One host per row — inner grid sizes all cards equally */
.vv-wd-host-row {
  grid-column: 1 / -1;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  gap: 12px;
}
@media (max-width: 900px) {
  .vv-wd-host-row { grid-template-columns: 1fr; }
}
</style>

<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:12px;padding:0 2px;">
  <span style="font-size:13px;font-weight:bold;color:#888;text-transform:uppercase;letter-spacing:.06em;">Watchdog</span>
  <span style="font-size:11px;color:#3a3a3a;" id="vv-wd-ts"></span>
</div>

<div id="vv-wd-grid" style="display:grid;grid-template-columns:repeat(8,1fr);gap:12px;">
  <div style="grid-column:1/-1;color:#444;font-size:12px;padding:16px 0;text-align:center;">Loading…</div>
</div>

<script>
(function() {

const GB = 1073741824;

function _fmtBytes(b) {
  if (!b) return '0';
  if (b >= GB)      return (b / GB).toFixed(1) + 'G';
  if (b >= 1048576) return (b / 1048576).toFixed(0) + 'M';
  return (b / 1024).toFixed(0) + 'K';
}

function _relTime(ts) {
  if (!ts) return '—';
  const s = Math.floor(Date.now() / 1000) - ts;
  if (s < 60)    return 'just now';
  if (s < 3600)  return Math.floor(s / 60) + 'm ago';
  if (s < 86400) return Math.floor(s / 3600) + 'h ago';
  return Math.floor(s / 86400) + 'd ago';
}

function _dur(s) {
  const d = Math.floor(s / 86400), h = Math.floor((s % 86400) / 3600), m = Math.floor((s % 3600) / 60);
  if (d) return d + 'd ' + h + 'h';
  if (h) return h + 'h ' + m + 'm';
  return m + 'm';
}

function _row(lbl, val) {
  return `<div class="vv-wd-row"><span class="vv-wd-lbl">${lbl}</span><span class="vv-wd-val">${val}</span></div>`;
}

function _bar(pct, col) {
  return `<div class="vv-wd-bar"><div class="vv-wd-bar-fill" style="width:${Math.min(pct,100)}%;background:${col}"></div></div>`;
}

function _pill(label, cls) {
  return `<span class="vv-wd-pill ${cls}">${label}</span>`;
}

function _levelLabel(level) {
  return ['OK', 'SOFT', 'MEDIUM', 'HARD'][level] || '?';
}
function _levelCls(level) {
  return ['ok', 'soft', 'med', 'hard'][level] || 'ok';
}

// ── Pressure alert card ───────────────────────────────────────────────────────
function _pressureCard(node) {
  const st = node.states;
  if (!st || st.rw_level === 0) return '';
  const level = st.rw_level;
  const cls   = _levelCls(level);
  const label = _levelLabel(level);

  const paused  = (st.rw_paused  || []).filter(Boolean);
  const stopped = (st.rw_stopped || []).filter(Boolean);
  const pausedHtml  = paused.length  ? paused.map(c  => _pill(c, 'warn')).join('') : '';
  const stoppedHtml = stopped.length ? stopped.map(c => _pill(c, 'err')).join('') : '';

  return `<div class="vv-wd-card vv-wd-pressure" style="grid-column:1/-1">
    <div style="display:flex;align-items:center;gap:10px;margin-bottom:8px;">
      <span class="vv-wd-badge ${cls}">PRESSURE ${label}</span>
      <span style="font-size:11px;color:#7a5020;">${node.id} (${node.hostname})</span>
      ${st.mem_shutdown ? `<span class="vv-wd-badge hard" style="margin-left:auto;">MEM SHUTDOWN ACTIVE</span>` : ''}
    </div>
    ${paused.length  ? `<div style="margin-bottom:4px;"><span class="vv-wd-lbl">Paused:</span> <span class="vv-wd-pill-row" style="display:inline-flex;">${pausedHtml}</span></div>` : ''}
    ${stopped.length ? `<div><span class="vv-wd-lbl">Stopped:</span> <span class="vv-wd-pill-row" style="display:inline-flex;">${stoppedHtml}</span></div>` : ''}
  </div>`;
}

// ── System health card ────────────────────────────────────────────────────────
function _systemCard(node, cfg) {
  const sys = node.system;
  if (!sys) {
    return `<div class="vv-wd-card">
      <div class="vv-wd-node-h">
        <span class="vv-wd-dot" style="background:#444"></span>
        <span class="vv-wd-node-id">${node.id}</span>
        <span style="font-size:11px;color:#3a3a3a;">${node.hostname}</span>
        <span class="vv-wd-badge" style="margin-left:auto;background:#1a1a1a;color:#444">UNREACHABLE</span>
      </div>
    </div>`;
  }

  const memGb    = sys.mem_avail / GB;
  const memTotGb = sys.mem_total / GB;
  const usedPct  = memTotGb > 0 ? ((memTotGb - memGb) / memTotGb) * 100 : 0;
  const memCol   = memGb < cfg.sys_mem_gb  ? '#ef5350'
                 : memGb < cfg.rw_hard_gb  ? '#ef5350'
                 : memGb < cfg.rw_medium_gb ? '#ffb74d'
                 : memGb < cfg.rw_soft_gb  ? '#cddc39' : '#4caf50';

  const loadPct  = sys.cores > 0 ? (sys.load1 / (sys.cores * cfg.rw_load_med)) * 100 : 0;
  const loadCol  = sys.load1 > sys.cores * cfg.rw_load_med ? '#ef5350'
                 : sys.load1 > sys.cores * cfg.rw_load_soft ? '#ffb74d'
                 : '#4caf50';

  const apiOnly   = sys.api_only === true;
  const daemonDot = sys.daemon_ok === null ? '#555' : sys.daemon_ok ? '#4caf50' : '#ef5350';
  const daemonTxt = sys.daemon_ok === null ? '—' : sys.daemon_ok ? 'daemon ok' : 'daemon err';

  const st    = node.states || {};
  const level = st.rw_level || 0;
  const dotCol = level >= 3 ? '#ef5350' : level >= 2 ? '#ffb74d' : level >= 1 ? '#cddc39'
               : apiOnly ? '#4a7a9b'    // blue-grey: API-only, no watchdog state
               : '#4caf50';

  return `<div class="vv-wd-card">
    <div class="vv-wd-node-h">
      <span class="vv-wd-dot" style="background:${dotCol}"></span>
      <span class="vv-wd-node-id">${node.id}</span>
      <span style="font-size:11px;color:#3a3a3a;">${node.hostname}</span>
      <span class="vv-wd-badge ${apiOnly ? '' : _levelCls(level)}"
            style="margin-left:auto;${apiOnly ? 'background:#0d1f2a;color:#4a9eff;' : ''}"
            >${apiOnly ? 'API ONLY' : _levelLabel(level)}</span>
    </div>
    <div class="vv-wd-sec">System</div>
    ${_row('RAM free', `<span style="color:${memCol}">${_fmtBytes(sys.mem_avail)}</span> / ${_fmtBytes(sys.mem_total)}`)}
    ${_bar(usedPct, memCol)}
    <div style="display:flex;justify-content:space-between;margin-top:1px;font-size:10px;color:#333">
      <span>free</span>
      <span>${cfg.rw_soft_gb}G soft · ${cfg.rw_hard_gb}G hard · ${cfg.sys_mem_gb}G reboot</span>
    </div>
    <div style="height:5px"></div>
    ${!apiOnly ? `${_row('Load avg', `<span style="color:${loadCol}">${sys.load1.toFixed(2)}</span> / ${sys.cores} cores`)}
    ${_bar(loadPct, loadCol)}
    <div style="height:5px"></div>` : ''}
    ${_row('Uptime', sys.uptime ? _dur(sys.uptime) : '—')}
    ${_row('Docker', `<span style="color:${daemonDot}">${daemonTxt}</span>`)}
    ${!apiOnly ? (sys.oom_count > 0 ? _row('OOM kills', `<span style="color:#ef5350">${sys.oom_count}</span>`) : _row('OOM kills', '<span style="color:#333">0</span>')) : ''}
    ${apiOnly ? `<div style="font-size:10px;color:#333;margin-top:6px;">Watchdog state unavailable — SSH not configured</div>` : ''}
  </div>`;
}

// ── Docker watchdog state card ────────────────────────────────────────────────
function _dockerCard(node, cfg) {
  const st = node.states;
  if (!st) return `<div class="vv-wd-card"><div class="vv-wd-sec">Docker Watchdog</div><div style="color:#3a3a3a;font-size:11px;padding:8px 0;">No data</div></div>`;

  const strikes   = Object.entries(st.ctr_strikes || {});
  const skiplist  = st.skiplist || [];
  const restarts  = (st.restarts || []).slice(0, 10);
  const daemonCls = st.daemon_strikes > 0 ? 'err' : 'ok';
  const allOk     = strikes.length === 0 && skiplist.length === 0 && st.daemon_strikes === 0;

  // Group restarts by container for last-24h summary
  const rCounts = {};
  for (const r of restarts) {
    rCounts[r.name] = (rCounts[r.name] || 0) + 1;
  }

  let strikesHtml = '';
  if (strikes.length === 0 && st.daemon_strikes === 0) {
    strikesHtml = '<div style="color:#333;font-size:11px;">0 active strikes</div>';
  } else {
    if (st.daemon_strikes > 0) {
      strikesHtml += `<div class="vv-wd-strike-row"><span class="vv-wd-strike-name">daemon</span><span class="vv-wd-strike-cnt">${st.daemon_strikes}</span></div>`;
    }
    for (const [name, cnt] of strikes) {
      strikesHtml += `<div class="vv-wd-strike-row"><span class="vv-wd-strike-name">${name}</span><span class="vv-wd-strike-cnt">${cnt} / ${cfg.cpu_fail_lim}</span></div>`;
    }
  }

  let skipHtml = '';
  if (skiplist.length === 0) {
    skipHtml = '<div style="color:#333;font-size:11px;">empty</div>';
  } else {
    skipHtml = `<div class="vv-wd-pill-row">${skiplist.map(c => _pill(c, 'err')).join('')}</div>`;
  }

  let restartHtml = '';
  const rcEntries = Object.entries(rCounts);
  if (rcEntries.length === 0) {
    restartHtml = '<div style="color:#333;font-size:11px;">none (24h)</div>';
  } else {
    restartHtml = rcEntries.map(([n, c]) =>
      `<div class="vv-wd-row"><span class="vv-wd-lbl">${n}</span><span class="vv-wd-val" style="color:${c >= cfg.restart_limit ? '#ef5350' : '#ffb74d'}">${c}×</span></div>`
    ).join('');
  }

  return `<div class="vv-wd-card">
    <div class="vv-wd-sec">Docker Watchdog</div>
    <div style="display:flex;gap:6px;margin-bottom:8px;">
      ${_pill(st.daemon_restart ? 'daemon restarted' : 'daemon ok', daemonCls)}
      ${allOk ? _pill('all clear', 'ok') : ''}
    </div>
    <div class="vv-wd-sec">Strikes</div>
    ${strikesHtml}
    <hr class="vv-wd-sep">
    <div class="vv-wd-sec">Skip list</div>
    ${skipHtml}
    <hr class="vv-wd-sep">
    <div class="vv-wd-sec">Restarts (24h)</div>
    ${restartHtml}
  </div>`;
}

// ── Stability / reboot card ───────────────────────────────────────────────────
function _stabilityCard(node, cfg) {
  const st = node.states;
  if (!st) return `<div class="vv-wd-card"><div class="vv-wd-sec">Stability</div><div style="color:#3a3a3a;font-size:11px;padding:8px 0;">No data</div></div>`;

  const reboots   = st.reboots || [];
  const sysStr    = Object.entries(st.sys_strikes || {});
  const rebootCls = reboots.length >= cfg.reboot_limit ? 'err' : reboots.length > 0 ? 'warn' : 'ok';

  let sysHtml = '';
  if (sysStr.length === 0) {
    sysHtml = '<div style="color:#333;font-size:11px;">0 active strikes</div>';
  } else {
    sysHtml = sysStr.map(([k, v]) =>
      `<div class="vv-wd-strike-row"><span class="vv-wd-strike-name" style="font-size:10px;">${k.replace(/_/g,' ')}</span><span class="vv-wd-strike-cnt">${v}</span></div>`
    ).join('');
  }

  const rebootHtml = reboots.length === 0
    ? '<div style="color:#333;font-size:11px;">none (12h)</div>'
    : reboots.map(ts => `<div class="vv-wd-reboot-ts">${_relTime(ts)}</div>`).join('');

  return `<div class="vv-wd-card">
    <div class="vv-wd-sec">Stability</div>
    <div style="display:flex;gap:6px;margin-bottom:8px;">
      ${_pill(`${reboots.length} / ${cfg.reboot_limit} reboots`, rebootCls)}
      ${_pill(`${cfg.reboot_window}h window`, '')}
    </div>
    <div class="vv-wd-sec">Strikes</div>
    ${sysHtml}
    <hr class="vv-wd-sep">
    <div class="vv-wd-sec">Reboot history</div>
    ${rebootHtml}
  </div>`;
}

// ── Config inventory card ─────────────────────────────────────────────────────
function _configCard(node) {
  const cfg = node.config || {};
  const mon  = Object.entries(cfg.monitored  || {});
  const req  = cfg.required  || [];
  const ign  = cfg.ignore    || [];
  const paus = cfg.pause_list|| [];
  const stop = cfg.stop_list || [];
  const crit = cfg.critical  || [];

  const monHtml = mon.length === 0
    ? '<div style="color:#333;font-size:11px;">none</div>'
    : mon.map(([name, mb]) => {
        const gb = (mb / 1024).toFixed(0);
        return `<div class="vv-wd-ctr-row"><span class="vv-wd-ctr-name">${name}</span><span class="vv-wd-ctr-lim">${gb} GB</span></div>`;
      }).join('');

  const reqHtml = req.length === 0
    ? '<div style="color:#333;font-size:11px;">none</div>'
    : `<div class="vv-wd-pill-row">${req.map(c => _pill(c, crit.includes(c) ? '' : '')).join('')}</div>`;

  const ignHtml = ign.length === 0
    ? '<div style="color:#3a3a3a;font-size:11px;">none</div>'
    : `<div class="vv-wd-pill-row">${ign.map(c => _pill(c, '')).join('')}</div>`;

  const pausHtml = paus.length === 0
    ? '<div style="color:#3a3a3a;font-size:11px;">none</div>'
    : `<div class="vv-wd-pill-row">${paus.map(c => _pill(c, 'warn')).join('')}</div>`;

  const stopHtml = stop.length === 0
    ? '<div style="color:#3a3a3a;font-size:11px;">none</div>'
    : `<div class="vv-wd-pill-row">${stop.map(c => _pill(c, 'err')).join('')}</div>`;

  return `<div class="vv-wd-card" style="grid-column:span 4;">
    <div style="display:flex;gap:6px;align-items:center;margin-bottom:8px;">
      <span class="vv-wd-node-id">${node.id}</span>
      <span style="font-size:11px;color:#3a3a3a;">${node.hostname}</span>
    </div>
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;">
      <div>
        <div class="vv-wd-sec">Mem limits (Tier 1)</div>
        ${monHtml}
        <div style="height:8px"></div>
        <div class="vv-wd-sec">Required</div>
        ${reqHtml}
      </div>
      <div>
        <div class="vv-wd-sec">Pause at medium pressure</div>
        ${pausHtml}
        <div style="height:8px"></div>
        <div class="vv-wd-sec">Stop at hard pressure</div>
        ${stopHtml}
        <div style="height:8px"></div>
        <div class="vv-wd-sec">Scan ignore</div>
        ${ignHtml}
      </div>
    </div>
  </div>`;
}

// ── Storage watchdog card ─────────────────────────────────────────────────────
function _storageCard(node, cfg) {
  const st      = node.states;
  const nodeCfg = node.config || {};
  if (!st) return `<div class="vv-wd-card"><div class="vv-wd-sec">Storage Watchdog</div><div style="color:#3a3a3a;font-size:11px;padding:8px 0;">No data</div></div>`;

  const storWd      = st.storage_wd || {};
  const growthStr   = Object.entries(storWd.growth_strikes || {});
  const logStr      = Object.entries(storWd.log_strikes    || {});
  const totalIssues = growthStr.length + logStr.length;
  const allClear    = totalIssues === 0;

  // Baseline info
  const bCount = storWd.baseline_count ?? 0;
  const bAge   = storWd.baseline_age_sec;
  let baselineNote = bCount > 0
    ? `${bCount} containers tracked`
    : 'no baseline yet (builds after first cycle)';
  if (bAge != null && bCount > 0) {
    const bAgeStr = bAge < 120 ? bAge + 's ago' : bAge < 3600 ? Math.floor(bAge/60) + 'm ago' : Math.floor(bAge/3600) + 'h ago';
    baselineNote += ` · updated ${bAgeStr}`;
  }

  // Suppress ceilings configured for this host
  const sizes    = Object.entries(nodeCfg.appdata_sizes || {});
  const sizesHtml = sizes.length
    ? sizes.map(([c, mb]) => _pill(`${c} <${Math.round(mb/1024)}GB`, '')).join('')
    : '';

  let growthHtml = '';
  if (growthStr.length === 0) {
    growthHtml = '<div style="color:#333;font-size:11px;">no active strikes</div>';
  } else {
    growthHtml = growthStr.map(([name, cnt]) =>
      `<div class="vv-wd-strike-row">
        <span class="vv-wd-strike-name">${name}</span>
        <span class="vv-wd-strike-cnt">${cnt} / ${cfg.stor_strike_lim}</span>
      </div>`
    ).join('');
  }

  let logHtml = '';
  if (logStr.length === 0) {
    logHtml = '<div style="color:#333;font-size:11px;">no active strikes</div>';
  } else {
    logHtml = logStr.map(([key, cnt]) => {
      const display = key.length > 36 ? '…' + key.slice(-36) : key;
      return `<div class="vv-wd-strike-row">
        <span class="vv-wd-strike-name" style="font-size:10px;" title="${key}">${display}</span>
        <span class="vv-wd-strike-cnt">${cnt} / ${cfg.stor_strike_lim}</span>
      </div>`;
    }).join('');
  }

  return `<div class="vv-wd-card">
    <div class="vv-wd-sec">Storage Watchdog</div>
    <div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:6px;">
      ${allClear ? _pill('all clear', 'ok') : _pill(totalIssues + ' active strike' + (totalIssues !== 1 ? 's' : ''), 'warn')}
      ${_pill('growth >' + cfg.growth_gb + 'GB/cycle', '')}
      ${_pill('log max ' + cfg.log_max_gb + 'GB', '')}
      ${cfg.truncate_logs ? _pill('auto-truncate on', 'ok') : _pill('auto-truncate off', '')}
    </div>
    <div style="font-size:10px;color:#3a3a3a;margin-bottom:8px;">${baselineNote}</div>
    <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px;">
      <div>
        <div class="vv-wd-sec">Growth strikes</div>
        ${growthHtml}
      </div>
      <div>
        <div class="vv-wd-sec">Log size strikes</div>
        ${logHtml}
      </div>
    </div>
    ${sizes.length ? `<hr class="vv-wd-sep"><div class="vv-wd-sec">Suppress ceilings (this host)</div><div class="vv-wd-pill-row">${sizesHtml}</div>` : ''}
  </div>`;
}

// ── Network watchdog card ─────────────────────────────────────────────────────
function _networkCard(node, cfg) {
  const st      = node.states;
  const nodeCfg = node.config || {};
  if (!st) return `<div class="vv-wd-card"><div class="vv-wd-sec">Network Watchdog</div><div style="color:#3a3a3a;font-size:11px;padding:8px 0;">No data</div></div>`;

  const netWd      = st.network_wd || {};
  const npmStr     = netWd.npm_strikes ?? 0;
  const npmCls     = npmStr >= cfg.npm_strike_lim ? 'err' : npmStr > 0 ? 'warn' : 'ok';
  const ddnsDomain = nodeCfg.ddns_domain    || '';
  const ddnsCtr    = nodeCfg.ddns_container || '';
  const npmUrl     = nodeCfg.npm_url        || '';

  const ddnsHtml = ddnsDomain
    ? `${_row('DDNS domain',   `<span style="color:#888;">${ddnsDomain}</span>`)}
       ${ddnsCtr ? _row('DDNS container', `<span style="color:#888;">${ddnsCtr}</span>`) : ''}`
    : _row('DDNS', '<span style="color:#444;">not configured for this host</span>');

  const npmHtml = npmUrl
    ? `${_row('NPM URL', `<span style="color:#888;font-size:10px;">${npmUrl}</span>`)}
       ${_row('NPM strikes', `<span class="vv-wd-pill ${npmCls}" style="font-size:10px;">${npmStr} / ${cfg.npm_strike_lim}</span>`)}`
    : _row('NPM check', '<span style="color:#444;">not configured for this host</span>');

  return `<div class="vv-wd-card">
    <div class="vv-wd-sec">Network Watchdog</div>
    <div style="display:flex;gap:5px;flex-wrap:wrap;margin-bottom:8px;">
      ${cfg.net_wd_enabled ? _pill('enabled', 'ok') : _pill('disabled', '')}
      ${_pill('Tailscale check ' + (cfg.ts_check ? 'on' : 'off'), cfg.ts_check ? '' : '')}
      ${npmUrl ? _pill('NPM ' + npmStr + '/' + cfg.npm_strike_lim + ' strikes', npmCls) : ''}
    </div>
    ${ddnsHtml}
    <hr class="vv-wd-sep">
    ${npmHtml}
  </div>`;
}

// ── Main render ───────────────────────────────────────────────────────────────
function _render(data) {
  const nodes = data.nodes || [];
  const cfg   = data.cfg   || {};
  let html = '';

  // Per-host: pressure alert (if active) then all 5 watchdog cards in one equal-spaced row
  for (const node of nodes) {
    html += _pressureCard(node);
    html += `<div class="vv-wd-host-row">
      ${_systemCard(node, cfg)}
      ${_dockerCard(node, cfg)}
      ${_stabilityCard(node, cfg)}
      ${_storageCard(node, cfg)}
      ${_networkCard(node, cfg)}
    </div>`;
  }

  // Config inventory — separate row, each node span 4 (2 nodes = full row)
  for (const node of nodes) html += _configCard(node);

  if (!html) html = '<div style="grid-column:1/-1;color:#444;font-size:12px;padding:16px 0;text-align:center;">No nodes configured.</div>';

  document.getElementById('vv-wd-grid').innerHTML = html;

  const ts = data.ts
    ? new Date(data.ts * 1000).toLocaleString([], {
        month:'numeric', day:'numeric', year:'numeric',
        hour:'2-digit', minute:'2-digit', second:'2-digit'})
    : '';
  document.getElementById('vv-wd-ts').textContent = ts ? 'Updated: ' + ts : '';
}

function vvWdLoad() {
  fetch('/plugins/varaverk/api/watchdog.php')
    .then(r => r.json())
    .then(_render)
    .catch(() => {
      document.getElementById('vv-wd-grid').innerHTML =
        '<div style="grid-column:1/-1;color:#555;font-size:12px;padding:24px 0;text-align:center;">Error loading watchdog data — check API</div>';
    });
}

vvWdLoad();
setInterval(vvWdLoad, 30000);

})();
</script>
