#!/usr/bin/env python3
import cgi, sys
sys.path.insert(0, '/opt/ngon/apps')
from managers.auth_manager import AuthManager, generate_login_page_html

_form = cgi.FieldStorage()
_auth = AuthManager('bridge_status')
_auth_required, _should_exit, _headers = _auth.require_auth(_form)

if _should_exit:
    print("Content-Type: application/json")
    if _headers:
        print(_headers)
    print("")
    print('{"success": false, "error": "Authentication required"}' if _auth_required
          else '{"success": true}')
    sys.exit(0)

if _auth_required:
    print("Content-Type: text/html")
    print("")
    print(generate_login_page_html("Wifi Bridge Status"))
    sys.exit(0)

# ---------------------------------------------------------------------------
import cgitb
import html
import json

cgitb.enable()
sys.path.insert(0, '/var/www/html/ngon')
from links import generate_dropdown_html, generate_dropdown_css, generate_dropdown_js

import urllib.request

STATUS_API = 'http://localhost:5050/api/status'


def _load_sites():
    """Return (all_gen_sites, sites_with_bridges) from live status_api.

    A site "has bridges" if any of its gens is reporting a live bridge IP
    (live_stats.live_bridge_ip) — the same authoritative signal the endpoint
    keys assignment on, so the dropdown/default track reality rather than the
    sparse config bridge_mac field.
    """
    try:
        with urllib.request.urlopen(STATUS_API, timeout=10) as r:
            cfg = json.load(r)
    except Exception:
        return [], []
    gen_sites, with_bridges = [], []
    for sname, site in (cfg.get('sites') or {}).items():
        groups = site.get('generator_groups') or {}
        if not groups:
            continue
        gen_sites.append(sname)
        has_bridge = any(
            (gen.get('live_stats') or {}).get('live_bridge_ip')
            for grp in groups.values()
            for gen in (grp.get('generators') or {}).values()
        )
        if has_bridge:
            with_bridges.append(sname)
    return sorted(gen_sites), sorted(with_bridges)


all_gen_sites, sites_with_bridges = _load_sites()

# Selected site comes only from the query string — no default, so the page
# opens on a placeholder and the operator picks which site to scan (avoids
# waiting on a first site's peplink scan they may not want).
site = _form.getvalue('site', '')

esc = html.escape
print("Content-Type: text/html\n")

# Dropdown: placeholder + "all with live bridges" + every gen site (so an
# operator can scan any site's peplinks for stray/unmapped bridges too).
opts = ['<option value=""{}>— Select a site —</option>'.format(
    ' selected' if not site else '')]
opts.append('<option value="all"{}>All sites with live bridges</option>'.format(
    ' selected' if site == 'all' else ''))
for s in all_gen_sites:
    tag = '  •' if s in sites_with_bridges else ''
    opts.append('<option value="{v}"{sel}>{label}{tag}</option>'.format(
        v=esc(s), sel=' selected' if s == site else '', label=esc(s), tag=tag))
site_options = ''.join(opts)

print(f"""<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>NGON Wifi Bridge Status</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
    <style>
        * {{ box-sizing: border-box; margin: 0; padding: 0; }}
        body {{ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
                background: #1a1a1a; color: #e0e0e0; line-height: 1.4; }}
        ::-webkit-scrollbar {{ width: 6px; height: 6px; }}
        ::-webkit-scrollbar-thumb {{ background: #666; border-radius: 3px; }}
        .header {{
            background-color: rgba(255,255,255,0.05) !important;
            border-radius: 8px !important;
            padding: 30px !important;
            margin: 20px !important;
            border: 1px solid rgba(255,255,255,0.1) !important;
            border-bottom: none !important;
            display: flex !important;
            justify-content: space-between !important;
            align-items: flex-start !important;
        }}
        .content {{ margin: 0 20px 40px; }}
        .filter-bar {{ display: flex; flex-wrap: wrap; gap: 10px; align-items: center; margin-bottom: 16px; }}
        .filter-bar label {{ color: #888; font-size: 12px; }}
        .filter-bar select {{
            background: #2a2a2a; border: 1px solid #3a3a3a; color: #e0e0e0;
            padding: 6px 10px; border-radius: 4px; font-size: 13px;
        }}
        .filter-bar select:focus {{ border-color: #00ff00; outline: none; }}
        .filter-group {{ display: flex; align-items: center; gap: 5px; }}
        .btn {{ background: #2a2a2a; border: 1px solid #3a3a3a; color: #e0e0e0; padding: 6px 14px;
                border-radius: 4px; font-size: 13px; cursor: pointer; }}
        .btn:hover {{ border-color: #00ff00; color: #00ff00; }}
        .chips {{ display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 16px; }}
        .chip {{ background: #222; border: 1px solid #333; border-radius: 8px;
                 padding: 10px 16px; min-width: 110px; text-align: center; }}
        .chip .v {{ font-size: 22px; font-weight: bold; color: #fff; }}
        .chip .l {{ font-size: 11px; color: #888; margin-top: 3px; text-transform: uppercase; letter-spacing: .5px; }}
        .chip.online .v {{ color: #00ff00; }}
        .chip.offline .v {{ color: #ff4444; }}
        .chip.unassigned .v {{ color: #ffaa00; }}
        .chip.roamed .v {{ color: #ff6600; }}
        .chip.nobridge .v {{ color: #aa44ff; }}
        .scan-note {{ color: #888; font-size: 12px; margin-bottom: 14px; }}
        .scan-note .warn {{ color: #ff6600; }}
        table {{ width: 100%; border-collapse: collapse; background: #222;
                 border: 1px solid #333; border-radius: 8px; overflow: hidden; }}
        th, td {{ padding: 9px 14px; text-align: left; border-bottom: 1px solid #2e2e2e; font-size: 13px; }}
        th {{ background: #262626; color: #888; font-size: 11px; text-transform: uppercase; letter-spacing: .5px;
              position: sticky; top: 0; }}
        tr:last-child td {{ border-bottom: none; }}
        td.empty {{ text-align: center; color: #888; padding: 26px; }}
        .mono {{ font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-variant-numeric: tabular-nums; }}
        a.iplink {{ color: #4a9eff; text-decoration: none; }}
        a.iplink:hover {{ text-decoration: underline; }}
        .badge {{ display: inline-block; font-size: 11px; font-weight: bold; padding: 2px 8px;
                  border-radius: 10px; white-space: nowrap; }}
        .badge.online {{ background: rgba(0,255,0,.12); color: #00ff00; }}
        .badge.offline {{ background: rgba(255,68,68,.15); color: #ff4444; }}
        .badge.unassigned {{ background: rgba(255,170,0,.15); color: #ffaa00; }}
        .badge.nobridge {{ background: rgba(170,68,255,.15); color: #aa44ff; }}
        .badge.wasthere {{ background: rgba(136,136,136,.15); color: #999; }}
        .tag {{ display: inline-block; font-size: 10px; font-weight: bold; padding: 1px 7px;
                border-radius: 8px; background: rgba(255,102,0,.15); color: #ff6600; }}
        .tag.stale {{ background: rgba(136,136,136,.18); color: #aaa; }}
        .sub {{ font-size: 11px; margin-top: 2px; }}
        .sub.st-run {{ color: #00ff00; }}
        .sub.st-down {{ color: #ff4444; }}
        .sub.st-unk {{ color: #888; }}
        .dim {{ color: #666; }}
        .probe-btn {{ background: #2a2a2a; border: 1px solid #3a5; color: #6c6;
                      padding: 4px 10px; border-radius: 4px; font-size: 12px; cursor: pointer;
                      white-space: nowrap; margin: 1px; }}
        .probe-btn:hover {{ border-color: #00ff00; color: #00ff00; }}
        .probe-btn:disabled {{ opacity: .5; cursor: default; }}
        .probe-btn.dev {{ border-color: #46c; color: #7ad; }}
        .probe-btn.dev:hover {{ border-color: #4a9eff; color: #4a9eff; }}
        .modal-overlay {{ display: none; position: fixed; inset: 0; background: rgba(0,0,0,.6);
                          z-index: 1000; align-items: flex-start; justify-content: center; }}
        .modal-overlay.show {{ display: flex; }}
        .modal-box {{ background: #1e1e1e; border: 1px solid #3a3a3a; border-radius: 10px;
                      margin-top: 12vh; width: 440px; max-width: 92vw; overflow: hidden; }}
        .modal-head {{ display: flex; justify-content: space-between; align-items: center;
                       padding: 14px 18px; border-bottom: 1px solid #333; }}
        .modal-head h3 {{ font-size: 15px; color: #e0e0e0; margin: 0; }}
        .modal-x {{ background: none; border: none; color: #888; font-size: 22px; cursor: pointer; line-height: 1; }}
        .modal-x:hover {{ color: #fff; }}
        .modal-body {{ padding: 18px; font-size: 14px; }}
        .modal-body .macbig {{ font-family: ui-monospace, Menlo, monospace; font-size: 20px;
                               color: #fff; margin: 6px 0 14px; letter-spacing: .5px; }}
        .modal-body .res-ok {{ color: #00ff00; font-weight: bold; }}
        .modal-body .res-warn {{ color: #ffaa00; font-weight: bold; }}
        .modal-body .res-bad {{ color: #ff6600; font-weight: bold; }}
        .modal-body .kv {{ color: #888; margin-top: 8px; }}
        .modal-body .kv b {{ color: #ccc; font-weight: 600; }}
        .section-title {{ color: #e0e0e0; font-size: 16px; margin: 6px 0 10px; }}
        {generate_dropdown_css()}
    </style>
</head>
<body>
<div class="header">
    <div class="dropdown">
        <h1 class="dropdown-title">NGON Mining - Wifi Bridge Status</h1>
        <div class="dropdown-content">{generate_dropdown_html(AuthManager.get_user_access())}</div>
    </div>
</div>

<div class="content">
    <form method="get" id="filterForm" class="filter-bar">
        <div class="filter-group">
            <label>Site</label>
            <select name="site" onchange="document.getElementById('filterForm').submit()">{site_options}</select>
        </div>
        <button type="button" class="btn" id="refreshBtn" onclick="load(true)"><i class="fas fa-rotate"></i> Refresh</button>
        <span class="scan-note" id="asOf"></span>
    </form>

    <div class="chips" id="chips"></div>
    <div class="scan-note" id="scanNote"></div>

    <div class="section-title">Gen Bridges</div>
    <table>
        <thead><tr>
            <th>Status</th><th>Gen</th><th>Site / Group</th><th>Bridge MAC</th>
            <th>Bridge IP</th><th>On Peplink</th><th>Name</th><th>Notes</th><th>Probe</th>
        </tr></thead>
        <tbody id="rows">
            <tr><td colspan="9" class="empty">Select a site above to scan its bridges.</td></tr>
        </tbody>
    </table>

    <div class="section-title" style="margin-top:22px">Gens With No Bridge</div>
    <div class="scan-note" id="nbNote"></div>
    <table>
        <thead><tr>
            <th>Status</th><th>Gen</th><th>Site / Group</th><th>Gen MAC</th><th>Notes</th>
        </tr></thead>
        <tbody id="nbRows">
            <tr><td colspan="5" class="empty">Select a site above.</td></tr>
        </tbody>
    </table>
</div>

<div class="modal-overlay" id="probeModal" onclick="if(event.target===this)closeProbe()">
    <div class="modal-box">
        <div class="modal-head">
            <h3 id="probeTitle">Probe</h3>
            <button class="modal-x" onclick="closeProbe()">&times;</button>
        </div>
        <div class="modal-body" id="probeBody"></div>
    </div>
</div>

<script>
{generate_dropdown_js()}

const SITE = {json.dumps(site)};

function el(id) {{ return document.getElementById(id); }}
function txt(v) {{ return (v === null || v === undefined || v === '') ? '—' : String(v); }}

function badge(status) {{
    if (status === 'online') return '<span class="badge online">ONLINE</span>';
    if (status === 'assigned_missing') return '<span class="badge offline">BRIDGE MISSING</span>';
    return '<span class="badge unassigned">UNASSIGNED</span>';
}}

function chip(cls, v, l) {{
    return `<div class="chip ${{cls}}"><div class="v">${{v}}</div><div class="l">${{l}}</div></div>`;
}}

function renderChips(s) {{
    el('chips').innerHTML =
        chip('online', s.online, 'Online') +
        chip('unassigned', s.unassigned, 'Unassigned') +
        chip('offline', s.assigned_missing, 'Bridge missing') +
        chip('roamed', s.roamed, 'Roamed') +
        chip('nobridge', s.no_bridge, 'No bridge') +
        chip('', s.gens_live, 'Gens live');
}}

function ageStr(sec) {{
    if (sec === null || sec === undefined) return '';
    if (sec < 90) return Math.round(sec) + 's';
    if (sec < 5400) return Math.round(sec / 60) + 'm';
    return (sec / 3600).toFixed(1) + 'h';
}}

function renderScan(d) {{
    const s = d.summary;
    let note = `Scanned ${{s.peplinks_total}} peplink(s)`;
    if (s.peplinks_unreachable > 0) {{
        const bad = (d.peplinks_scanned || []).filter(p => !p.reachable)
            .map(p => p.router_ip + ' (' + p.label + ')').join(', ');
        note += ` · <span class="warn">${{s.peplinks_unreachable}} unreachable: ${{bad}} — bridges there can't be seen</span>`;
    }}
    el('scanNote').innerHTML = note;
    el('asOf').textContent = 'as of ' + new Date(d.fetched_at).toLocaleTimeString();
}}

function renderNoBridge(list) {{
    const tb = el('nbRows');
    if (!list || !list.length) {{
        tb.innerHTML = '<tr><td colspan="5" class="empty">Every gen at this site has a working bridge.</td></tr>';
        el('nbNote').innerHTML = '';
        return;
    }}
    const never = list.filter(r => r.ever_seen === false).length;
    el('nbNote').innerHTML = `${{list.length}} gen(s) with no bridge reporting · ` +
        `<span class="warn">${{never}} never had one</span> — these are install candidates. ` +
        `Out-of-service gens and the Spares site are excluded.`;
    tb.innerHTML = list.map(r => {{
        const badgeHtml = r.ever_seen === false
            ? '<span class="badge nobridge">NO BRIDGE</span>'
            : '<span class="badge wasthere">NOT REPORTING</span>';
        const grp = r.group ? `${{txt(r.site)}} / ${{txt(r.group)}}` : txt(r.site);
        const mac = r.missing_mac
            ? '<span class="warn">no mac in config</span>'
            : `<span class="mono">${{txt(r.gen_mac)}}</span>`;
        const notes = [];
        if (r.ever_seen === false) notes.push('<span class="dim">no telemetry on record — bridge likely never fitted</span>');
        else if (r.ever_seen === null) notes.push('<span class="dim">gen_ingest unreachable — cannot tell never-fitted from not-reporting</span>');
        else notes.push(`<span class="dim">last reported ${{ageStr(r.last_seen_sec)}} ago</span>`);
        if (r.missing_mac) notes.push('<span class="warn">set the gen mac or it can never resolve</span>');
        if (r.site_scanned === false) notes.push('<span class="dim">site has no live gen, peps not scanned</span>');
        return `<tr><td>${{badgeHtml}}</td><td><span class="mono">${{txt(r.gen_id)}}</span></td>` +
               `<td>${{grp}}</td><td>${{mac}}</td><td>${{notes.join('<br>')}}</td></tr>`;
    }}).join('');
}}

function renderRows(bridges) {{
    if (!bridges || !bridges.length) {{
        el('rows').innerHTML = '<tr><td colspan="9" class="empty">No bridges found for this site.</td></tr>';
        return;
    }}
    el('rows').innerHTML = bridges.map(b => {{
        const ip = b.ip
            ? `<a class="iplink mono" href="http://${{b.ip}}" target="_blank" rel="noopener" title="Open bridge web admin (VPN only)">${{b.ip}}</a>`
            : '<span class="dim">—</span>';
        const pep = b.peplink_ip
            ? `<span class="mono">${{b.peplink_ip}}</span> <span class="dim">${{txt(b.peplink_label)}}</span>`
            : '<span class="dim">—</span>';
        const grp = b.group ? `${{txt(b.site)}} / ${{txt(b.group)}}` : txt(b.site);
        // Gen cell: id + live run state underneath
        let genCell = '<span class="dim">—</span>';
        if (b.gen_id) {{
            let st = '';
            if (b.run_state) {{
                const cls = b.run_state === 'Running' ? 'st-run'
                          : b.run_state === 'Down' ? 'st-down' : 'st-unk';
                st = `<div class="sub ${{cls}}">${{txt(b.run_state)}}</div>`;
            }}
            genCell = `<span class="mono">${{txt(b.gen_id)}}</span>${{st}}`;
        }}
        let notes = [];
        if (b.roamed) notes.push(`<span class="tag">ROAMED off ${{txt(b.group)}}</span>`);
        if (b.gen_stale) notes.push(`<span class="tag stale">gen telemetry stale ${{ageStr(b.gen_age_sec)}}</span>`);
        if (b.status === 'assigned_missing') notes.push('<span class="dim">gen is reporting via this bridge IP, but no bridge client was seen on the pep scan — its peplink may be unreachable, or the gen telemetry is stale</span>');
        if (b.status === 'unassigned') {{
            notes.push('<span class="dim">online at pep, no gen reports this IP</span>');
            if (b.config_gen) notes.push(`<span class="dim">· config maps this MAC to ${{txt(b.config_gen)}}</span>`);
        }}
        if (b.connection && b.connection !== 'wireless') notes.push(`<span class="dim">(${{b.connection}})</span>`);
        const probeBtn = b.ip
            ? `<button class="probe-btn" onclick="probe('${{b.ip}}')" title="Modbus-read the gen controller MAC (reg 18020) behind this bridge">MAC</button>
               <button class="probe-btn dev" onclick="deviceProbe('${{b.ip}}')" title="Read the bridge DHCP lease — which device is on the bulkhead (Mesa router vs DSE cell adapter)">Device</button>`
            : '<span class="dim">—</span>';
        return `<tr>
            <td>${{badge(b.status)}}</td>
            <td>${{genCell}}</td>
            <td>${{grp}}</td>
            <td class="mono">${{txt(b.bridge_mac)}}</td>
            <td>${{ip}}</td>
            <td>${{pep}}</td>
            <td>${{txt(b.client_name)}}</td>
            <td>${{notes.join(' ') || '<span class="dim">—</span>'}}</td>
            <td>${{probeBtn}}</td>
        </tr>`;
    }}).join('');
}}

function closeProbe() {{ el('probeModal').classList.remove('show'); }}

function deviceProbe(ip) {{
    const body = el('probeBody');
    el('probeTitle').textContent = 'Gen Device (DHCP lease)';
    el('probeModal').classList.add('show');
    body.innerHTML = `<div class="dim">Reading DHCP lease on bridge <span class="mono">${{ip}}</span>…</div>`;
    fetch('/api/peplink/bridge_lease?ip=' + encodeURIComponent(ip))
        .then(r => r.json())
        .then(d => {{
            if (!d.success) {{ body.innerHTML = `<div class="res-bad">Error: ${{txt(d.error)}}</div>`; return; }}
            if (!d.bridge_reachable) {{
                body.innerHTML = `<div class="kv"><b>Bridge:</b> <span class="mono">${{ip}}</span></div>
                    <div class="res-warn" style="margin-top:12px">Bridge RouterOS API unreachable</div>
                    <div class="kv">${{txt(d.error)}} — its peplink may be down.</div>`;
                return;
            }}
            let verdict;
            if (d.classification === 'mesa')
                verdict = `<div class="res-ok">✓ Mesa router — correct device</div>`;
            else if (d.classification === 'dse')
                verdict = `<div class="res-bad">✗ DSE cell adapter — MIS-WIRED</div>
                    <div class="kv">The bulkhead is on the old DSE cell adapter, not the Mesa router — that's why Modbus (:502) is dead and the gen is dark. Move the bulkhead to the Mesa router.</div>`;
            else if (d.classification === 'none')
                verdict = `<div class="res-warn">⚠ Nothing leased on .88.2</div>
                    <div class="kv">No gen-side device is pulling DHCP behind the bridge — device off, unplugged, or ether1 link down.</div>`;
            else
                verdict = `<div class="res-warn">⚠ Unknown device — ${{txt(d.label)}}</div>`;
            const meta = [];
            if (d.host) meta.push(`<div class="kv"><b>Host-name:</b> ${{txt(d.host)}}</div>`);
            if (d.mac) meta.push(`<div class="kv"><b>MAC:</b> <span class="mono">${{txt(d.mac)}}</span></div>`);
            meta.push(`<div class="kv"><b>Lease:</b> ${{txt(d.lease_status)}} · last seen ${{txt(d.last_seen)}} · ARP ${{txt(d.arp)}}</div>`);
            body.innerHTML = `
                <div class="kv"><b>Bridge:</b> <span class="mono">${{ip}}</span> → .88.2 device</div>
                <div style="margin:12px 0">${{verdict}}</div>
                ${{meta.join('')}}`;
        }})
        .catch(() => {{ body.innerHTML = '<div class="res-bad">Request failed.</div>'; }});
}}

function probe(ip) {{
    const body = el('probeBody');
    el('probeTitle').textContent = 'Probe Gen MAC';
    el('probeModal').classList.add('show');
    body.innerHTML = `<div class="dim">Probing <span class="mono">${{ip}}</span> — Modbus read of reg 18020 through the bridge…</div>`;
    fetch('/api/gen_control/probe_mac?ip=' + encodeURIComponent(ip))
        .then(r => r.status === 403 ? {{_forbidden: true}} : r.json())
        .then(d => {{
            if (d._forbidden) {{
                body.innerHTML = '<div class="res-bad">Not authorized to probe (needs gen_control or bridge_status access).</div>';
                return;
            }}
            if (!d.success) {{
                body.innerHTML = `<div class="res-bad">Error: ${{txt(d.error)}}</div>`;
                return;
            }}
            if (!d.reachable) {{
                body.innerHTML = `<div class="kv"><b>Bridge:</b> <span class="mono">${{ip}}</span></div>
                    <div class="res-warn" style="margin-top:12px">No Modbus answer on :502</div>
                    <div class="kv">${{txt(d.note)}}</div>`;
                return;
            }}
            let verdict;
            if (d.match) {{
                verdict = `<div class="res-ok">✓ Matches configured gen ${{txt(d.match.gen_id)}}</div>
                    <div class="kv"><b>${{txt(d.match.site)}} / ${{txt(d.match.group)}}</b> — this bridge is plugged into ${{txt(d.match.gen_id)}}.</div>`;
            }} else {{
                verdict = `<div class="res-warn">⚠ NOT IN ROSTER</div>
                    <div class="kv">This MAC isn't stored on any configured gen. Whichever gen this bridge feeds has the wrong/changed <b>mac</b> — set that gen's <b>mac</b> to this value in Site Manager and it'll resolve on the next config pull.</div>`;
            }}
            body.innerHTML = `
                <div class="kv"><b>Bridge:</b> <span class="mono">${{ip}}</span></div>
                <div class="kv"><b>Controller MAC (reg 18020):</b></div>
                <div class="macbig">${{txt(d.mac_pretty || d.mac)}}</div>
                ${{verdict}}`;
        }})
        .catch(() => {{ body.innerHTML = '<div class="res-bad">Request failed.</div>'; }});
}}

function load(refresh) {{
    if (!SITE) return;
    const btn = el('refreshBtn');
    btn.disabled = true;
    const q = new URLSearchParams({{ site: SITE }});
    if (refresh) q.set('refresh', '1');
    fetch('/api/peplink/gen_bridges?' + q.toString())
        .then(r => r.json())
        .then(d => {{
            if (!d.success) {{
                el('rows').innerHTML = '<tr><td colspan="9" class="empty">' +
                    (d.error || 'Failed to load') + '</td></tr>';
                el('chips').innerHTML = '';
                el('scanNote').innerHTML = '';
                el('nbRows').innerHTML = '<tr><td colspan="5" class="empty">—</td></tr>';
                return;
            }}
            renderChips(d.summary);
            renderScan(d);
            renderRows(d.bridges);
            renderNoBridge(d.gens_without_bridge);
        }})
        .catch(() => {{
            el('rows').innerHTML = '<tr><td colspan="9" class="empty">Error contacting peplink API.</td></tr>';
        }})
        .finally(() => {{ btn.disabled = false; }});
}}

if (SITE) {{
    load(false);
    setInterval(() => load(false), 60000);
}} else {{
    el('refreshBtn').disabled = true;
}}
</script>
</body>
</html>""")
