<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>DRACS - Configuration</title>
    <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='images/dracs.svg') }}">
    <style>
        * { box-sizing: border-box; }
        html { height: 100%; }
        body {
            height: 100%;
            margin: 0;
            padding: 0;
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
            background-color: #f5f5f5;
            font-size: 14px;
            display: flex;
            flex-direction: column;
        }
        button, input, select, textarea { font-family: inherit; }
        .container {
            flex: 1;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            background-color: white;
            padding: 16px 24px 8px;
        }
        .header {
            flex-shrink: 0;
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
        }
        .header-left {
            display: flex;
            align-items: center;
            gap: 15px;
        }
        .logo { height: 48px; width: auto; }
        .theme-toggle {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-left: 20px;
        }
        .theme-toggle label { font-size: 14px; color: #555; font-weight: 500; }
        .toggle-switch {
            width: 44px; height: 24px;
            background-color: #ccc;
            border-radius: 12px;
            cursor: pointer;
            position: relative;
            transition: background-color 0.3s;
        }
        .toggle-switch::after {
            content: '';
            position: absolute;
            top: 2px; left: 2px;
            width: 20px; height: 20px;
            background-color: white;
            border-radius: 50%;
            transition: transform 0.3s;
        }
        body.dark-theme .toggle-switch { background-color: #4CAF50; }
        body.dark-theme .toggle-switch::after { transform: translateX(20px); }

        h1 { margin: 0; font-size: 1.4rem; }

        .close-btn {
            padding: 8px 20px;
            background-color: #6c757d;
            color: white; border: none;
            border-radius: 4px; cursor: pointer; font-size: 14px;
        }
        .close-btn:hover { background-color: #5a6268; }

        /* Controls bar */
        .controls-bar {
            flex-shrink: 0;
            display: flex;
            align-items: center;
            gap: 16px;
            margin-bottom: 8px;
        }
        .page-size-label { font-size: 13px; color: #555; }
        .page-size-select {
            padding: 4px 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 13px;
        }
        .results-info { font-size: 13px; color: #777; }

        /* Main content */
        #main-content {
            flex: 1;
            display: flex;
            flex-direction: column;
            overflow: hidden;
            min-height: 0;
            padding-top: 4px;
        }

        /* ── Filters section ── */
        .filters-section {
            flex-shrink: 0;
            border: 1px solid #ddd;
            border-radius: 4px;
            margin-bottom: 8px;
            overflow: hidden;
        }
        .filters-header {
            display: flex;
            align-items: center;
            gap: 8px;
            padding: 6px 10px;
            cursor: pointer;
            user-select: none;
            background-color: #f8f9fa;
            font-size: 13px;
            font-weight: 600;
        }
        .filters-header:hover { background-color: #e9ecef; }
        .filters-triangle {
            font-size: 9px;
            display: inline-block;
            transition: transform 0.2s;
            color: #666;
        }
        .filters-triangle.open { transform: rotate(90deg); }
        .filters-active-badge {
            margin-left: auto;
            font-size: 11px;
            font-weight: 600;
            color: white;
            background-color: #0d6efd;
            border-radius: 10px;
            padding: 1px 8px;
        }
        .filters-body {
            padding: 10px 14px;
            border-top: 1px solid #ddd;
            display: flex;
            flex-direction: column;
            gap: 10px;
        }
        .filter-row {
            display: flex;
            align-items: center;
            flex-wrap: wrap;
            gap: 14px;
        }
        .filter-label {
            font-size: 13px;
            color: #555;
            white-space: nowrap;
            font-weight: 500;
            min-width: 70px;
        }
        .filter-hostname-input {
            padding: 3px 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 13px;
            width: 260px;
        }
        .filter-check-label {
            display: flex;
            align-items: center;
            gap: 5px;
            font-size: 13px;
            cursor: pointer;
            white-space: nowrap;
            color: #333;
        }
        .filter-check-label input[type="checkbox"] { cursor: pointer; }
        .filter-expiry-select {
            padding: 3px 8px;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-size: 13px;
        }

        /* Scrollable table wrapper */
        .config-table-wrapper {
            flex: 1;
            overflow-y: auto;
            overflow-x: auto;
            min-height: 0;
            border: 1px solid #ddd;
            border-radius: 4px;
        }
        table.config-table {
            border-collapse: separate;
            border-spacing: 0;
            min-width: 100%;
        }
        table.config-table td, table.config-table th {
            border-right: 1px solid #ddd;
            border-bottom: 1px solid #ddd;
            padding: 6px 10px;
            white-space: nowrap;
            font-size: 13px;
            vertical-align: middle;
            text-align: center;
        }
        table.config-table td:first-child,
        table.config-table thead th:first-child { border-left: 1px solid #ddd; }
        table.config-table td:first-child { text-align: left; }
        table.config-table tr:nth-child(even) { background: #f9f9f9; }
        table.config-table tr:hover { background: #f1f4ff; }
        table.config-table tbody tr { user-select: none; }
        table.config-table thead th {
            position: sticky;
            top: 0; z-index: 2;
            background-color: white;
            border-top: 1px solid #ddd;
        }
        th.attr-header {
            text-align: center;
            font-size: 12px; font-weight: 600;
            color: #333; white-space: nowrap; padding: 6px 10px;
        }
        th.hostname-header {
            text-align: left; min-width: 220px; font-weight: 600;
            position: sticky; left: 0; z-index: 3;
            box-shadow: 2px 0 4px rgba(0,0,0,0.08);
        }
        table.config-table td:first-child {
            position: sticky; left: 0; z-index: 1;
            background-color: white;
            box-shadow: 2px 0 4px rgba(0,0,0,0.08);
        }
        table.config-table tr:nth-child(even) td:first-child { background: #f9f9f9; }
        table.config-table tr:hover td:first-child { background: #f1f4ff; }

        /* Status indicators */
        .ok   { color: #28a745; font-weight: bold; cursor: default; }
        .bad  { color: #dc3545; font-weight: bold; cursor: default; }
        .expiry-yellow { color: #c8860a; font-weight: bold; }
        .expiry-orange { color: #e07000; font-weight: bold; }
        .expiry-red    { color: #dc3545; font-weight: bold; }
        .expiry-ok     { color: #28a745; }
        .na { color: #bbb; }

        /* Pagination */
        .pagination-container {
            flex-shrink: 0;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            padding: 8px 0 4px;
        }
        .pagination-container button {
            padding: 4px 10px;
            border: 1px solid #ccc;
            border-radius: 4px;
            background: white;
            cursor: pointer; font-size: 13px;
        }
        .pagination-container button:disabled { opacity: 0.4; cursor: default; }
        .pagination-container button.active { background: #0d6efd; color: white; border-color: #0d6efd; }
        .pagination-info { font-size: 13px; color: #555; }

        /* ── Dark theme ── */
        body.dark-theme { background-color: #1a1a1a; color: #e0e0e0; }
        body.dark-theme .container { background-color: #2d2d2d; }
        body.dark-theme h1 { color: #e0e0e0; }
        body.dark-theme .theme-toggle label { color: #aaa; }
        body.dark-theme table.config-table td,
        body.dark-theme table.config-table th { border-color: #444; color: #ddd; }
        body.dark-theme table.config-table tr:nth-child(even) { background: #363636; }
        body.dark-theme table.config-table tr:hover { background: #424242; }
        body.dark-theme table.config-table thead th { background-color: #2d2d2d; }
        body.dark-theme table.config-table td:first-child { background-color: #2d2d2d; }
        body.dark-theme table.config-table tr:nth-child(even) td:first-child { background: #363636; }
        body.dark-theme table.config-table tr:hover td:first-child { background: #424242; }
        body.dark-theme th.attr-header { color: #ccc; }
        body.dark-theme .page-size-label,
        body.dark-theme .results-info,
        body.dark-theme .pagination-info { color: #aaa; }
        body.dark-theme .page-size-select {
            background: #424242; color: #ddd; border-color: #444;
        }
        body.dark-theme .pagination-container button {
            background: #424242; color: #ddd; border-color: #444;
        }
        body.dark-theme .pagination-container button.active { background: #0d6efd; color: white; }
        body.dark-theme .close-btn { background-color: #5a6268; }
        body.dark-theme .config-table-wrapper { border-color: #444; }
        body.dark-theme .filters-section { border-color: #444; }
        body.dark-theme .filters-header { background-color: #363636; color: #ddd; }
        body.dark-theme .filters-header:hover { background-color: #424242; }
        body.dark-theme .filters-triangle { color: #aaa; }
        body.dark-theme .filters-body { border-top-color: #444; }
        body.dark-theme .filter-label { color: #aaa; }
        body.dark-theme .filter-check-label { color: #ddd; }
        body.dark-theme .filter-hostname-input,
        body.dark-theme .filter-expiry-select {
            background-color: #424242; color: #ddd; border-color: #444;
        }

        #loading   { flex-shrink: 0; text-align: center; color: #888; padding: 40px 0; font-size: 1.1em; }
        #error-msg { flex-shrink: 0; color: #dc3545; padding: 20px 0; }
        body.dark-theme #loading { color: #aaa; }

        /* Row selection */
        .config-row-selected { background-color: #cce5ff !important; }
        body.dark-theme .config-row-selected { background-color: #1e5a96 !important; }
        table.config-table tr.config-row-selected td:first-child { background-color: #cce5ff !important; }
        body.dark-theme table.config-table tr.config-row-selected td:first-child { background-color: #1e5a96 !important; }
        table.config-table tbody tr { cursor: pointer; }

        /* Refresh button */
        .refresh-btn {
            padding: 8px 20px;
            background-color: #198754;
            color: white; border: none;
            border-radius: 4px; cursor: pointer; font-size: 14px;
            margin-right: 8px;
        }
        .refresh-btn:hover:not(:disabled) { background-color: #146c43; }
        .refresh-btn:disabled { opacity: 0.4; cursor: default; }
        [data-theme="dark"] .refresh-btn { background-color: #157347; }
        [data-theme="dark"] .refresh-btn:hover:not(:disabled) { background-color: #0f5132; }

        /* Edit button */
        .edit-btn {
            padding: 8px 20px;
            background-color: #0d6efd;
            color: white; border: none;
            border-radius: 4px; cursor: pointer; font-size: 14px;
            margin-right: 8px;
        }
        .edit-btn:hover:not(:disabled) { background-color: #0b5ed7; }
        .edit-btn:disabled { opacity: 0.4; cursor: default; }

        /* Auto-refresh ring button */
        .ar-wrapper { position: relative; display: inline-flex; }
        .ar-btn {
            display: inline-flex; align-items: center; justify-content: center;
            width: 34px; height: 34px; padding: 0;
            background: transparent; border: none; border-radius: 50%;
            cursor: pointer; color: #6c757d;
        }
        .ar-btn:hover { background: rgba(0,0,0,0.06); }
        .ar-btn.ar-live { color: #0d6efd; }
        body.dark-theme .ar-btn { color: #aaa; }
        body.dark-theme .ar-btn:hover { background: rgba(255,255,255,0.08); }
        body.dark-theme .ar-btn.ar-live { color: #6ea8fe; }
        .ar-menu {
            position: absolute; left: 0; top: calc(100% + 4px);
            background: white; border: 1px solid #ccc;
            border-radius: 4px; box-shadow: 0 2px 10px rgba(0,0,0,0.15);
            z-index: 200; min-width: 80px; overflow: hidden;
        }
        body.dark-theme .ar-menu { background: #363636; border-color: #444; }
        .ar-option {
            padding: 7px 14px; cursor: pointer;
            font-size: 13px; white-space: nowrap;
        }
        .ar-option:hover { background: #f0f4ff; }
        body.dark-theme .ar-option:hover { background: #424242; }
        .ar-option.ar-sel { font-weight: 600; color: #0d6efd; }

        /* Edit modal */
        .edit-modal-overlay {
            position: fixed; inset: 0;
            background: rgba(0,0,0,0.5);
            z-index: 1000; display: flex;
            align-items: center; justify-content: center;
        }
        .edit-modal-box {
            background: white; border-radius: 8px;
            padding: 24px; min-width: 420px; max-width: 560px; width: 90%;
            box-shadow: 0 8px 32px rgba(0,0,0,0.3);
        }
        .edit-modal-box h3 { margin: 0 0 6px; font-size: 1.1rem; }
        .edit-modal-subtitle { margin: 0 0 16px; font-size: 13px; color: #555; }
        #edit-options { display: flex; flex-direction: column; gap: 10px; margin-bottom: 4px; }
        #edit-options label {
            display: flex; align-items: center; gap: 10px;
            font-size: 14px; cursor: pointer;
        }
        #edit-options code { font-size: 12px; color: #0d6efd; }
        .edit-modal-footer { display: flex; gap: 10px; justify-content: flex-end; margin-top: 20px; }
        .edit-modal-footer button {
            padding: 8px 20px; border-radius: 4px; font-size: 14px;
            border: 1px solid #ccc; cursor: pointer; background: #f0f0f0; color: #333;
        }
        #apply-btn { background: #0d6efd; color: white; border-color: #0d6efd; }
        #apply-btn:disabled { opacity: 0.4; cursor: default; }
        #apply-btn:not(:disabled):hover { background: #0b5ed7; }
        #edit-progress { font-size: 13px; color: #555; margin-top: 8px; }
        body.dark-theme .edit-modal-box { background: #2d2d2d; color: #e0e0e0; }
        body.dark-theme .edit-modal-subtitle { color: #aaa; }
        body.dark-theme #edit-options label { color: #ddd; }
        body.dark-theme .edit-modal-footer button { background: #424242; color: #ddd; border-color: #444; }
        body.dark-theme #apply-btn { background: #0d6efd; color: white; border-color: #0d6efd; }
    </style>
</head>
<body>
<script>if(localStorage.getItem('theme')==='dark')document.body.classList.add('dark-theme');</script>
<div class="container">
    <div class="header">
        <div class="header-left">
            <img src="{{ url_for('static', filename='images/dracs.svg') }}" alt="DRACS Logo" class="logo">
            <h1>DRACS Configuration</h1>
            <div class="theme-toggle">
                <label for="theme-toggle-switch">Dark Mode</label>
                <div id="theme-toggle-switch" class="toggle-switch" onclick="toggleTheme()"></div>
            </div>
        </div>
        <div style="display:flex;align-items:center;gap:8px;">
            <div class="ar-wrapper" id="ar-wrapper">
                <button class="ar-btn" id="ar-btn" onclick="toggleArMenu(event)" title="Auto-refresh interval">
                    <svg viewBox="0 0 24 24" width="20" height="20" aria-hidden="true">
                        <circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2.5" opacity="0.25"/>
                        <circle id="ar-arc" cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="2.5"
                                stroke-dasharray="0 56.55" transform="rotate(-90 12 12)" stroke-linecap="round"/>
                    </svg>
                </button>
                <div class="ar-menu" id="ar-menu" style="display:none;">
                    <div class="ar-option" data-val="0"   onclick="setArInterval(0)">Off</div>
                    <div class="ar-option" data-val="15"  onclick="setArInterval(15)">15 s</div>
                    <div class="ar-option" data-val="30"  onclick="setArInterval(30)">30 s</div>
                    <div class="ar-option" data-val="60"  onclick="setArInterval(60)">1 min</div>
                    <div class="ar-option" data-val="300" onclick="setArInterval(300)">5 min</div>
                </div>
            </div>
            {% if is_admin %}<button id="refresh-btn" class="refresh-btn" onclick="applyConfigRefresh()" disabled>Refresh</button><button id="edit-btn" class="edit-btn" onclick="openEditModal()" disabled>Edit</button>{% endif %}
            <button class="close-btn" onclick="closePage()">Close</button>
        </div>
    </div>

    <div id="loading">Loading configuration data&hellip;</div>
    <div id="error-msg" style="display:none;"></div>

    <div id="main-content" style="display:none;">
        <div class="controls-bar">
            <span class="page-size-label">Rows per page:</span>
            <select class="page-size-select" id="page-size-select" onchange="changePageSize()">
                <option value="20">20</option>
                <option value="50">50</option>
                <option value="100">100</option>
                <option value="500">500</option>
                <option value="1000">1000</option>
                <option value="0">ALL</option>
            </select>
            <span class="results-info" id="results-info"></span>
        </div>

        <div class="filters-section">
            <div class="filters-header" onclick="toggleFilters()">
                <span class="filters-triangle" id="filters-triangle">&#9658;</span>
                <span>Filters</span>
                <span class="filters-active-badge" id="filters-badge" style="display:none;"></span>
            </div>
            <div class="filters-body" id="filters-body" style="display:none;">
                <div class="filter-row">
                    <span class="filter-label">Hostname:</span>
                    <input type="text" id="f-hostname" class="filter-hostname-input"
                           placeholder="partial match…" oninput="onFilterChange()">
                </div>
                <div class="filter-row">
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-ps-rapid-on" onchange="onFilterChange()">
                        PS Rapid On Enabled
                    </label>
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-dns-from-dhcp" onchange="onFilterChange()">
                        DNS from DHCP Disabled
                    </label>
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-ipmi-lan" onchange="onFilterChange()">
                        IPMI LAN Disabled
                    </label>
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-host-header" onchange="onFilterChange()">
                        Host Header Check Enabled
                    </label>
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-sys-profile" onchange="onFilterChange()">
                        Sys Profile mismatch
                    </label>
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-idrac-hostname" onchange="onFilterChange()">
                        iDRAC Hostname mismatch
                    </label>
                    <label class="filter-check-label">
                        <input type="checkbox" id="f-ssl-self-signed" onchange="onFilterChange()">
                        SSL self-signed
                    </label>
                </div>
                <div class="filter-row">
                    <span class="filter-label">SSL Expiry &le;</span>
                    <select id="f-ssl-expiry" class="filter-expiry-select" onchange="onFilterChange()">
                        <option value="-1">Off</option>
                        <option value="0">Expired</option>
                        <option value="30">30 days</option>
                        <option value="60">60 days</option>
                    </select>
                </div>
            </div>
        </div>

        <div class="config-table-wrapper">
            <table class="config-table" id="config-table">
                <thead id="config-thead"></thead>
                <tbody id="config-tbody"></tbody>
            </table>
        </div>

        <div class="pagination-container" id="pagination"></div>
    </div>
</div>

{% if is_admin %}
<div id="edit-modal-overlay" class="edit-modal-overlay" style="display:none;"
     onclick="if(event.target===this)closeEditModal()">
  <div class="edit-modal-box" onclick="event.stopPropagation()">
    <h3>Edit iDRAC Settings</h3>
    <p class="edit-modal-subtitle">Applying to <strong id="edit-host-count">0</strong> host(s)</p>
    <div id="edit-options">
      <!-- Populated dynamically from writable+enabled attr_defs by openEditModal() -->
    </div>
    <div id="edit-progress" style="display:none;"></div>
    <div class="edit-modal-footer">
      <button id="apply-btn" onclick="applyConfigEdit()" disabled>Apply</button>
      <button id="cancel-btn" onclick="closeEditModal()">Cancel</button>
    </div>
  </div>
</div>
{% endif %}

<div id="notify-modal-overlay" class="edit-modal-overlay" style="display:none;"
     onclick="if(event.target===this)closeNotifyModal()">
  <div class="edit-modal-box" style="max-width:420px" onclick="event.stopPropagation()">
    <h3 id="notify-modal-title">Error</h3>
    <p id="notify-modal-message" style="margin:12px 0 20px"></p>
    <div class="edit-modal-footer">
      <button onclick="closeNotifyModal()">OK</button>
    </div>
  </div>
</div>

<script>
const isAdmin = {{ 'true' if is_admin else 'false' }};
(function () {
    // For int_bool attrs: "good" stored value (as string).
    const INT_BOOL_GOOD_VALUES = {
        idrac_hostname:  '1',   // 1 = matches expected FQDN
        ssl_self_signed: '0',   // 0 = CA-signed (not self-signed)
        ssl_valid_name:  '1',   // 1 = name validated
    };

    let allRows      = [];
    let attrDefs     = [];    // full catalog from API
    let visibleAttrs = [];    // enabled subset, [{id,name,label,push_key,display_type,
                              //                    post_push_command,is_writable,
                              //                    choices,site_settings}, ...]
    let pageSize     = 20;
    let currentPage  = 1;
    let displayedTotal = 0;
    let selectedConfigRows = [];
    let lastClickedTr = null;

    function getQueryParam(name) {
        return new URLSearchParams(window.location.search).get(name) || '';
    }

    function closePage() {
        sessionStorage.removeItem('dracs_config_hosts');
        const site = getQueryParam('site');
        window.location.href = site ? '/?site=' + encodeURIComponent(site) : '/';
    }

    function toggleTheme() {
        const dark = document.body.classList.toggle('dark-theme');
        localStorage.setItem('theme', dark ? 'dark' : 'light');
    }

    function showError(msg) {
        document.getElementById('loading').style.display = 'none';
        const el = document.getElementById('error-msg');
        el.textContent = msg;
        el.style.display = '';
    }

    // ── Filters ──────────────────────────────────────────────────────────────

    window.toggleFilters = function () {
        const body = document.getElementById('filters-body');
        const tri  = document.getElementById('filters-triangle');
        const open = body.style.display === 'none';
        body.style.display = open ? '' : 'none';
        tri.classList.toggle('open', open);
    };

    function readFilters() {
        return {
            hostname:      document.getElementById('f-hostname').value.trim().toLowerCase(),
            psRapidOn:     document.getElementById('f-ps-rapid-on').checked,
            dnsFromDhcp:   document.getElementById('f-dns-from-dhcp').checked,
            ipmiLan:       document.getElementById('f-ipmi-lan').checked,
            hostHeader:    document.getElementById('f-host-header').checked,
            sysProfile:    document.getElementById('f-sys-profile').checked,
            idracHostname: document.getElementById('f-idrac-hostname').checked,
            sslSelfSigned: document.getElementById('f-ssl-self-signed').checked,
            sslExpiry:     parseInt(document.getElementById('f-ssl-expiry').value, 10),
        };
    }

    function attrVal(row, name) {
        return (row.attrs || {})[name]?.value ?? null;
    }

    function isBoolDrift(row, attrName) {
        const def = attrDefs.find(d => d.name === attrName);
        if (!def || !def.site_settings.enabled) return false;
        const desired = def.site_settings.desired_value;
        if (!desired) return false;
        const val = attrVal(row, attrName);
        return val !== null && val !== desired;
    }

    function isIntBoolBad(row, attrName) {
        const val = attrVal(row, attrName);
        if (val === null) return false;
        const good = INT_BOOL_GOOD_VALUES[attrName];
        return good !== undefined && String(val) !== String(good);
    }

    function applyFilters(rows) {
        const f = readFilters();
        return rows.filter(row => {
            if (f.hostname && !row.hostname.toLowerCase().includes(f.hostname))
                return false;
            if (f.psRapidOn && !isBoolDrift(row, 'ps_rapid_on'))
                return false;
            if (f.dnsFromDhcp && !isBoolDrift(row, 'dns_from_dhcp'))
                return false;
            if (f.ipmiLan && !isBoolDrift(row, 'ipmi_lan_enable'))
                return false;
            if (f.hostHeader && !isBoolDrift(row, 'host_header_check'))
                return false;
            if (f.sysProfile && !isBoolDrift(row, 'sys_profile'))
                return false;
            if (f.idracHostname && !isIntBoolBad(row, 'idrac_hostname'))
                return false;
            if (f.sslSelfSigned && !isIntBoolBad(row, 'ssl_self_signed'))
                return false;
            if (f.sslExpiry >= 0) {
                const expVal = attrVal(row, 'ssl_expiry');
                if (!expVal) return false;
                const days = (new Date(expVal) - new Date()) / 86400000;
                if (days > f.sslExpiry) return false;
            }
            return true;
        });
    }

    function updateBadge() {
        const f = readFilters();
        const n = [
            f.hostname, f.psRapidOn, f.dnsFromDhcp, f.ipmiLan, f.hostHeader,
            f.sysProfile, f.idracHostname, f.sslSelfSigned, f.sslExpiry >= 0,
        ].filter(Boolean).length;
        const badge = document.getElementById('filters-badge');
        badge.textContent = n + ' active';
        badge.style.display = n > 0 ? '' : 'none';
    }

    window.onFilterChange = function () {
        currentPage = 1;
        updateBadge();
        renderTable();
    };

    // ── Helpers ───────────────────────────────────────────────────────────────

    function sslExpiryClass(expiry) {
        if (!expiry) return 'na';
        const ms   = new Date(expiry) - new Date();
        const days = ms / 86400000;
        if (days < 0)  return 'expiry-red';
        if (days < 30) return 'expiry-orange';
        if (days < 60) return 'expiry-yellow';
        return 'expiry-ok';
    }

    function escHtml(s) {
        return String(s)
            .replace(/&/g, '&amp;').replace(/</g, '&lt;')
            .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
    }

    function makeTip(pushKey, val, collectedAt) {
        const parts = [];
        if (pushKey) parts.push(`${pushKey}: ${val ?? '—'}`);
        if (collectedAt) parts.push(`Collected: ${collectedAt.replace('T', ' ').slice(0, 19)}`);
        return parts.join('\n');
    }

    function boolCell(val, goodVal, tip) {
        if (val === null || val === undefined) return '<span class="na">—</span>';
        const ok = String(val) === String(goodVal);
        const ta = tip ? ` title="${escHtml(tip)}"` : '';
        return ok
            ? `<span class="ok"${ta}>✓</span>`
            : `<span class="bad"${ta}>✗</span>`;
    }

    function intBoolCell(attrName, val, tip) {
        if (val === null || val === undefined) return '<span class="na">—</span>';
        const good = INT_BOOL_GOOD_VALUES[attrName];
        const ok   = good !== undefined ? String(val) === String(good) : val === '1';
        const ta   = tip ? ` title="${escHtml(tip)}"` : '';
        return ok
            ? `<span class="ok"${ta}>✓</span>`
            : `<span class="bad"${ta}>✗</span>`;
    }

    // ── Render ────────────────────────────────────────────────────────────────

    function renderTable() {
        const displayRows = applyFilters(allRows);
        displayedTotal = displayRows.length;

        const start = pageSize === 0 ? 0 : (currentPage - 1) * pageSize;
        const end   = pageSize === 0 ? displayedTotal : Math.min(start + pageSize, displayedTotal);
        const slice = displayRows.slice(start, end);

        let countStr;
        if (displayedTotal === 0) {
            countStr = allRows.length > 0
                ? `No hosts match the current filters (${allRows.length} total)`
                : 'No data';
        } else {
            countStr = `Showing ${start + 1}–${end} of ${displayedTotal}`;
            if (displayedTotal < allRows.length)
                countStr += ` (filtered from ${allRows.length})`;
        }
        document.getElementById('results-info').textContent = countStr;

        // Header
        const thead = document.getElementById('config-thead');
        let hrow = '<tr><th class="hostname-header">Hostname</th>';
        for (const def of visibleAttrs)
            hrow += `<th class="attr-header">${escHtml(def.label)}</th>`;
        hrow += '</tr>';
        thead.innerHTML = hrow;

        // Body
        const tbody = document.getElementById('config-tbody');
        let html = '';
        for (const row of slice) {
            html += `<tr data-hostname="${escHtml(row.hostname || '')}">`;
            html += `<td>${escHtml(row.hostname || '')}</td>`;

            for (const def of visibleAttrs) {
                const entry = (row.attrs || {})[def.name] || {};
                const val = entry.value !== undefined ? entry.value : null;
                const tip = makeTip(def.push_key, val, entry.collected_at || null);

                if (def.display_type === 'bool') {
                    const goodVal = def.site_settings.desired_value;
                    html += `<td data-field="${escHtml(def.name)}">${boolCell(val, goodVal, tip)}</td>`;

                } else if (def.display_type === 'int_bool') {
                    html += `<td data-field="${escHtml(def.name)}">${intBoolCell(def.name, val, tip)}</td>`;

                } else if (def.display_type === 'date') {
                    const cls   = sslExpiryClass(val);
                    const label = val ? escHtml(val) : '—';
                    const ta    = tip ? ` title="${escHtml(tip)}"` : '';
                    html += `<td data-field="${escHtml(def.name)}"><span class="${cls}"${ta}>${label}</span></td>`;

                } else {
                    const ta = tip ? ` title="${escHtml(tip)}"` : '';
                    html += `<td data-field="${escHtml(def.name)}"${ta}>${escHtml(val ?? '—')}</td>`;
                }
            }

            html += '</tr>';
        }
        tbody.innerHTML = html;

        if (selectedConfigRows.length > 0) {
            tbody.querySelectorAll('tr').forEach(tr => {
                if (selectedConfigRows.includes(tr.dataset.hostname))
                    tr.classList.add('config-row-selected');
            });
        }
        updateEditButton();
        updateRefreshButton();

        renderPagination();
    }

    function renderPagination() {
        const pag = document.getElementById('pagination');
        if (pageSize === 0 || displayedTotal <= pageSize) { pag.innerHTML = ''; return; }
        const totalPages = Math.ceil(displayedTotal / pageSize);
        let html = '';
        html += `<button onclick="goPage(${currentPage - 1})" ${currentPage === 1 ? 'disabled' : ''}>&#8249;</button>`;
        for (let p = 1; p <= totalPages; p++) {
            if (totalPages > 12 && Math.abs(p - currentPage) > 3 && p !== 1 && p !== totalPages) {
                if (p === 2 || p === totalPages - 1) html += '<span>…</span>';
                continue;
            }
            html += `<button class="${p === currentPage ? 'active' : ''}" onclick="goPage(${p})">${p}</button>`;
        }
        html += `<button onclick="goPage(${currentPage + 1})" ${currentPage === totalPages ? 'disabled' : ''}>&#8250;</button>`;
        pag.innerHTML = html;
    }

    window.goPage = function (p) {
        const total = pageSize === 0 ? 1 : Math.ceil(displayedTotal / pageSize);
        if (p < 1 || p > total) return;
        currentPage = p;
        renderTable();
    };

    window.changePageSize = function () {
        pageSize = parseInt(document.getElementById('page-size-select').value);
        currentPage = 1;
        renderTable();
    };

    window.closePage    = closePage;
    window.toggleTheme  = toggleTheme;

    // ── Row selection ──────────────────────────────────────────────────────────

    function updateEditButton() {
        const btn = document.getElementById('edit-btn');
        if (btn) btn.disabled = selectedConfigRows.length === 0;
    }

    function updateRefreshButton() {
        const btn = document.getElementById('refresh-btn');
        if (btn) btn.disabled = selectedConfigRows.length === 0;
    }

    function handleRowClick(event) {
        const tr = event.target.closest('tr');
        if (!tr || !tr.dataset.hostname) return;
        const hostname = tr.dataset.hostname;
        const tbody = document.getElementById('config-tbody');

        if (event.shiftKey && lastClickedTr) {
            const allTrs = Array.from(tbody.querySelectorAll('tr'));
            const a = allTrs.indexOf(lastClickedTr);
            const b = allTrs.indexOf(tr);
            const [lo, hi] = a < b ? [a, b] : [b, a];
            selectedConfigRows = [];
            tbody.querySelectorAll('tr').forEach(r => r.classList.remove('config-row-selected'));
            for (let i = lo; i <= hi; i++) {
                if (allTrs[i].dataset.hostname) {
                    selectedConfigRows.push(allTrs[i].dataset.hostname);
                    allTrs[i].classList.add('config-row-selected');
                }
            }
        } else if (event.ctrlKey || event.metaKey) {
            const idx = selectedConfigRows.indexOf(hostname);
            if (idx >= 0) {
                selectedConfigRows.splice(idx, 1);
                tr.classList.remove('config-row-selected');
            } else {
                selectedConfigRows.push(hostname);
                tr.classList.add('config-row-selected');
            }
        } else {
            selectedConfigRows = [hostname];
            tbody.querySelectorAll('tr').forEach(r => r.classList.remove('config-row-selected'));
            tr.classList.add('config-row-selected');
        }
        lastClickedTr = tr;
        updateEditButton();
        updateRefreshButton();
    }

    // ── Notification modal ────────────────────────────────────────────────────

    function showNotifyModal(title, message) {
        document.getElementById('notify-modal-title').textContent = title;
        document.getElementById('notify-modal-message').textContent = message;
        document.getElementById('notify-modal-overlay').style.display = '';
    }

    window.closeNotifyModal = function () {
        document.getElementById('notify-modal-overlay').style.display = 'none';
    };

    // ── Refresh ───────────────────────────────────────────────────────────────

    window.applyConfigRefresh = async function () {
        const btn = document.getElementById('refresh-btn');
        const editBtn = document.getElementById('edit-btn');
        if (btn) { btn.disabled = true; btn.textContent = 'Refreshing…'; }
        if (editBtn) editBtn.disabled = true;

        const site = getQueryParam('site');
        const refreshedHosts = [...selectedConfigRows];

        // Snapshot the earliest collected_at across all attrs for each host.
        const beforeMap = {};
        for (const h of refreshedHosts) {
            const row = allRows.find(r => r.hostname === h);
            if (!row || !row.attrs) { beforeMap[h] = null; continue; }
            const timestamps = Object.values(row.attrs)
                .map(a => a.collected_at).filter(Boolean);
            beforeMap[h] = timestamps.length ? timestamps.sort()[0] : null;
        }

        try {
            const resp = await fetch('/api/config-refresh', {
                method: 'POST', credentials: 'same-origin',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ site, hosts: refreshedHosts }),
            });
            const data = await resp.json();
            if (!data.success) {
                if (btn) { btn.disabled = false; btn.textContent = 'Refresh'; }
                if (editBtn) editBtn.disabled = refreshedHosts.length === 0;
                showNotifyModal('Refresh Failed', data.message);
                return;
            }
        } catch (e) {
            if (btn) { btn.disabled = false; btn.textContent = 'Refresh'; }
            if (editBtn) editBtn.disabled = refreshedHosts.length === 0;
            return;
        }

        // Poll every 3 s until all refreshed hosts have a newer collected_at (90 s max)
        const hostsJson = sessionStorage.getItem('dracs_config_hosts');
        let allHostnames = [];
        if (hostsJson) { try { allHostnames = JSON.parse(hostsJson); } catch (e) {} }

        const POLL_MS = 3000;
        const MAX_POLLS = 30;
        let polls = 0;

        const restoreButtons = () => {
            if (btn) { btn.disabled = refreshedHosts.length === 0; btn.textContent = 'Refresh'; }
            if (editBtn) editBtn.disabled = refreshedHosts.length === 0;
        };

        const poll = async () => {
            polls++;
            try {
                const r = await fetch('/api/config-data', {
                    method: 'POST', credentials: 'same-origin',
                    headers: { 'Content-Type': 'application/json' },
                    body: JSON.stringify({ site, hosts: allHostnames }),
                });
                const d = await r.json();
                if (d.success) {
                    const newRows = d.data || [];
                    const allUpdated = refreshedHosts.every(h => {
                        const newRow = newRows.find(r => r.hostname === h);
                        if (!newRow || !newRow.attrs) return false;
                        const before = beforeMap[h];
                        const timestamps = Object.values(newRow.attrs)
                            .map(a => a.collected_at).filter(Boolean);
                        const after = timestamps.length ? timestamps.sort()[0] : null;
                        if (before === null) return after !== null;
                        return after && after > before;
                    });
                    if (allUpdated || polls >= MAX_POLLS) {
                        allRows = newRows;
                        renderTable();
                        restoreButtons();
                        return;
                    }
                }
            } catch (e) {}
            if (polls >= MAX_POLLS) { restoreButtons(); return; }
            setTimeout(poll, POLL_MS);
        };

        setTimeout(poll, POLL_MS);
    };

    // ── Edit modal ────────────────────────────────────────────────────────────

    window.openEditModal = function () {
        document.getElementById('edit-host-count').textContent = selectedConfigRows.length;

        // Build checkboxes from writable+enabled attr_defs.
        const writableEnabled = visibleAttrs.filter(d => d.is_writable);
        const optDiv = document.getElementById('edit-options');
        optDiv.innerHTML = '';
        for (const def of writableEnabled) {
            const desired = def.site_settings.desired_value;
            // Single-choice attrs (e.g. idrac_hostname) have an implicit desired push value.
            const effectiveDesired = desired || (def.choices.length === 1 ? def.choices[0].push_value : null);
            const desiredLabel = def.choices.find(c => c.push_value === effectiveDesired)?.label || effectiveDesired || '';
            const id = `ec-attr-${def.id}`;
            const lbl = document.createElement('label');
            if (!effectiveDesired) {
                lbl.style.cssText = 'opacity:0.45; cursor:not-allowed;';
                lbl.innerHTML = `<input type="checkbox" id="${escHtml(id)}" data-attr-id="${def.id}" disabled>
                    ${escHtml(def.push_key || def.name)} <span style="font-style:italic; color:#888;">(no desired value set)</span>`;
            } else {
                lbl.innerHTML = `<input type="checkbox" id="${escHtml(id)}" data-attr-id="${def.id}" onchange="updateApplyBtn()">
                    ${escHtml(def.push_key || def.name)} → <code>${escHtml(desiredLabel)}</code>`;
            }
            optDiv.appendChild(lbl);
        }

        document.getElementById('apply-btn').disabled = true;
        document.getElementById('cancel-btn').textContent = 'Cancel';
        optDiv.style.display = '';
        document.getElementById('edit-progress').style.display = 'none';
        document.getElementById('edit-progress').innerHTML = '';
        document.getElementById('edit-modal-overlay').style.display = '';
    };

    window.closeEditModal = function () {
        document.getElementById('edit-modal-overlay').style.display = 'none';
    };

    window.updateApplyBtn = function () {
        const anyChecked = Array.from(
            document.querySelectorAll('#edit-options input[type="checkbox"]')
        ).some(cb => cb.checked);
        document.getElementById('apply-btn').disabled = !anyChecked;
    };

    window.applyConfigEdit = async function () {
        document.getElementById('edit-options').style.display = 'none';
        document.getElementById('apply-btn').disabled = true;
        document.getElementById('cancel-btn').textContent = 'Close';
        const prog = document.getElementById('edit-progress');
        prog.style.display = '';
        prog.innerHTML = '<p>Submitting jobs…</p>';

        // Build push_settings from checked attrs.
        const push_settings = [];
        for (const def of visibleAttrs.filter(d => d.is_writable)) {
            const cb = document.getElementById(`ec-attr-${def.id}`);
            if (!cb || !cb.checked) continue;
            const pushValue = def.site_settings.desired_value
                || (def.choices.length === 1 ? def.choices[0].push_value : '');
            push_settings.push({
                attr_name:        def.name,
                push_key:         def.push_key,
                push_value:       pushValue,
                post_push_command: def.post_push_command || null,
            });
        }

        const site = getQueryParam('site');
        let resp, data;
        try {
            resp = await fetch('/api/config-edit', {
                method: 'POST', credentials: 'same-origin',
                headers: {'Content-Type': 'application/json'},
                body: JSON.stringify({site, hosts: selectedConfigRows, push_settings}),
            });
            data = await resp.json();
        } catch (e) {
            prog.innerHTML = `<p style="color:#dc3545">Network error: ${e.message}</p>`;
            return;
        }
        if (!data.success) {
            prog.innerHTML = `<p style="color:#dc3545">${data.message || 'Unknown error'}</p>`;
            return;
        }
        startEditPolling(data.parent_job_id, data.job_count);
    };

    function startEditPolling(parentId, total) {
        const prog = document.getElementById('edit-progress');
        prog.innerHTML = `<p>Running… (0/${total} complete)</p>`;

        const interval = setInterval(async () => {
            let r;
            try {
                r = await fetch(`/api/config-edit/status/${parentId}`,
                    {credentials: 'same-origin'}).then(x => x.json());
            } catch (e) { return; }
            if (!r.success) return;

            const done = r.parent.completed_count + r.parent.failed_count;
            prog.innerHTML = `<p>Running… (${done}/${r.parent.total_count} complete)</p>`;
            applyTableUpdates(r.children);

            if (['completed', 'failed'].includes(r.parent.status)) {
                clearInterval(interval);
                const failN = r.parent.failed_count;
                prog.innerHTML = failN > 0
                    ? `<p style="color:#dc3545">${failN} of ${r.parent.total_count} host(s) failed. Check job logs.</p>`
                    : `<p style="color:#28a745">All ${r.parent.total_count} host(s) updated successfully.</p>`;
            }
        }, 3000);
    }

    function renderFieldValue(def, entry) {
        const val = entry?.value ?? null;
        const tip = makeTip(def.push_key, val, entry?.collected_at || null);
        if (def.display_type === 'bool') {
            return boolCell(val, def.site_settings.desired_value, tip);
        }
        if (def.display_type === 'int_bool') {
            return intBoolCell(def.name, val, tip);
        }
        if (def.display_type === 'date') {
            const cls   = sslExpiryClass(val);
            const label = val ? escHtml(val) : '—';
            const ta    = tip ? ` title="${escHtml(tip)}"` : '';
            return `<span class="${cls}"${ta}>${label}</span>`;
        }
        const ta = tip ? ` title="${escHtml(tip)}"` : '';
        return `<span${ta}>${escHtml(val ?? '—')}</span>`;
    }

    function applyTableUpdates(children) {
        for (const child of children) {
            if (child.status !== 'completed' || !child.config) continue;
            const hn = child.hostname.replace(/"/g, '\\"');
            const row = document.querySelector(`#config-tbody tr[data-hostname="${hn}"]`);
            if (!row) continue;
            const attrs = child.config.attrs || {};
            for (const def of visibleAttrs) {
                const cell = row.querySelector(`td[data-field="${def.name}"]`);
                if (cell) cell.innerHTML = renderFieldValue(def, attrs[def.name]);
            }
        }
    }

    async function init() {
        const site = getQueryParam('site');
        const hostsJson = sessionStorage.getItem('dracs_config_hosts');
        let hostnames = [];
        if (hostsJson) {
            try { hostnames = JSON.parse(hostsJson); } catch (e) { hostnames = []; }
        }

        let resp, data;
        try {
            resp = await fetch('/api/config-data', {
                method: 'POST',
                credentials: 'same-origin',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ site, hosts: hostnames }),
            });
            data = await resp.json();
        } catch (e) {
            showError('Failed to load configuration data: ' + e.message);
            return;
        }
        if (!data.success) {
            showError('Error: ' + (data.message || 'Unknown error'));
            return;
        }

        attrDefs     = data.attr_defs || [];
        allRows      = data.data || [];
        visibleAttrs = attrDefs.filter(d => d.site_settings.enabled && d.name !== 'ssl_fingerprint');
        if (visibleAttrs.length === 0) visibleAttrs = attrDefs.filter(d => d.name !== 'ssl_fingerprint');

        document.getElementById('loading').style.display = 'none';
        document.getElementById('main-content').style.display = '';

        if (isAdmin) {
            document.getElementById('config-tbody').addEventListener('click', handleRowClick);
        }

        renderTable();
    }

    // ── Auto-refresh ring ──────────────────────────────────────────────────────
    const AR_VALID = [0, 15, 30, 60, 300];
    const AR_C = 56.55; // 2π × r=9

    let arInterval   = 0;    // selected seconds (0 = off)
    let arCycleStart = null; // performance.now() timestamp
    let arRafId      = null;
    let arTimerId    = null;

    function arSetArc(p) {
        const arc = document.getElementById('ar-arc');
        if (!arc) return;
        arc.setAttribute('stroke-dasharray',
            `${(AR_C * p).toFixed(2)} ${(AR_C * (1 - p)).toFixed(2)}`);
    }

    function arTick() {
        if (!arCycleStart || arInterval <= 0) return;
        const p = Math.min((performance.now() - arCycleStart) / (arInterval * 1000), 1);
        arSetArc(p);
        if (p < 1) arRafId = requestAnimationFrame(arTick);
    }

    function arBegin() {
        cancelAnimationFrame(arRafId);
        clearTimeout(arTimerId);
        arCycleStart = performance.now();
        arRafId  = requestAnimationFrame(arTick);
        arTimerId = setTimeout(arFire, arInterval * 1000);
    }

    function arStop() {
        cancelAnimationFrame(arRafId);
        clearTimeout(arTimerId);
        arCycleStart = null;
        arRafId = null;
        arTimerId = null;
    }

    async function arFire() {
        arStop();
        arSetArc(1);
        const site = getQueryParam('site');
        const hostsJson = sessionStorage.getItem('dracs_config_hosts');
        let hostnames = [];
        if (hostsJson) { try { hostnames = JSON.parse(hostsJson); } catch (_) {} }
        try {
            const r = await fetch('/api/config-data', {
                method: 'POST', credentials: 'same-origin',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ site, hosts: hostnames }),
            });
            const rdata = await r.json();
            if (rdata.success) {
                attrDefs     = rdata.attr_defs || attrDefs;
                allRows      = rdata.data || [];
                visibleAttrs = attrDefs.filter(def => def.site_settings.enabled && def.name !== 'ssl_fingerprint');
                if (visibleAttrs.length === 0) visibleAttrs = attrDefs.filter(d => d.name !== 'ssl_fingerprint');
                renderTable();
            }
        } catch (_) {}
        if (arInterval > 0) arBegin();
    }

    function arSyncUI() {
        document.querySelectorAll('.ar-option').forEach(el => {
            el.classList.toggle('ar-sel', parseInt(el.dataset.val, 10) === arInterval);
        });
        const btn = document.getElementById('ar-btn');
        if (btn) btn.classList.toggle('ar-live', arInterval > 0);
    }

    window.toggleArMenu = function(e) {
        e.stopPropagation();
        const m = document.getElementById('ar-menu');
        if (m) m.style.display = m.style.display === 'none' ? '' : 'none';
    };

    window.setArInterval = function(s) {
        arInterval = s;
        localStorage.setItem('dracs_ar_interval', s);
        const m = document.getElementById('ar-menu');
        if (m) m.style.display = 'none';
        arStop();
        arSetArc(0);
        arSyncUI();
        if (arInterval > 0) arBegin();
    };

    // Restore saved preference and close menu on outside click
    const _arSaved = parseInt(localStorage.getItem('dracs_ar_interval') || '0', 10);
    if (AR_VALID.includes(_arSaved)) arInterval = _arSaved;
    arSyncUI();
    if (arInterval > 0) arBegin();
    document.addEventListener('click', function(e) {
        const m = document.getElementById('ar-menu');
        const w = document.getElementById('ar-wrapper');
        if (m && w && !w.contains(e.target)) m.style.display = 'none';
    });
    // ── End auto-refresh ring ──────────────────────────────────────────────────

    init();
})();
</script>
</body>
</html>
