<?php
// web-files/pages/valkey-admin.php
// Root-only fleet overview of all per-customer Valkey instances.
// Actions: restart an instance, recreate an instance (after pulling a new
// image, e.g. for a security patch), or pull the pinned image so future
// recreates pick it up.
$csrf_token = $_SESSION['csrf_token'] ?? '';
?>
<div class="container-fluid py-4">
  <div class="d-flex justify-content-between align-items-center mb-3">
    <div class="text-muted">All enabled customer Valkey instances.</div>
    <div>
      <button class="btn btn-outline-primary" id="vka-pull-btn">
        <i class="fa fa-cloud-download me-1" aria-hidden="true"></i> Pull latest image
      </button>
      <button class="btn btn-outline-secondary ms-2" id="vka-refresh-btn">
        <i class="fa fa-refresh me-1" aria-hidden="true"></i> Refresh
      </button>
    </div>
  </div>

  <div id="vka-pull-result" class="alert alert-secondary" style="display:none"></div>

  <div class="card">
    <div class="table-responsive">
      <table class="table table-striped mb-0 align-middle whp-table-fixed">
        <thead>
          <tr>
            <th style="width:12rem">Customer</th>
            <th style="width:7rem">Mode</th>
            <th style="width:6rem">Memory</th>
            <th style="width:9rem">Eviction</th>
            <th style="width:7rem">Version</th>
            <th style="width:7rem">Status</th>
            <th style="width:6rem">Uptime</th>
            <th style="width:6rem">Keys</th>
            <th style="width:6rem">Used</th>
            <th style="width:12rem">Last snapshot</th>
            <th class="text-end" style="width:13rem">Actions</th>
          </tr>
        </thead>
        <tbody id="vka-tbody">
          <tr><td colspan="11" class="text-center text-muted py-4"><em>Loading…</em></td></tr>
        </tbody>
      </table>
    </div>
  </div>
</div>

<script>
(function() {
    var csrfToken = <?php echo json_encode($csrf_token); ?>;
    var $tbody  = document.getElementById('vka-tbody');
    var $pull   = document.getElementById('vka-pull-btn');
    var $pullRx = document.getElementById('vka-pull-result');
    var $refresh = document.getElementById('vka-refresh-btn');

    function clearChildren(el) { while (el.firstChild) el.removeChild(el.firstChild); }

    function api(action, method, data) {
        var opts = {
            headers: {'X-CSRF-Token': csrfToken, 'Accept': 'application/json'},
            credentials: 'same-origin',
            method: method || 'GET'
        };
        var url = '/api/valkey.php';
        if (method === 'POST') {
            data = data || {};
            data.action = action;
            opts.headers['Content-Type'] = 'application/json';
            opts.body = JSON.stringify(data);
        } else {
            url += '?action=' + encodeURIComponent(action);
            if (data && data.target_user) url += '&target_user=' + encodeURIComponent(data.target_user);
        }
        return fetch(url, opts).then(function(r) {
            return r.text().then(function(body) {
                var j = null; try { j = JSON.parse(body); } catch (e) {}
                return {ok: r.ok, status: r.status, body: body, data: j};
            });
        });
    }

    function fmtUptime(sec) {
        if (!sec) return '—';
        if (sec < 120) return sec + 's';
        if (sec < 7200) return Math.round(sec / 60) + 'm';
        return Math.round(sec / 3600) + 'h';
    }

    function fmtBytes(b) {
        if (!b) return '—';
        if (b < 1024) return b + ' B';
        if (b < 1048576) return Math.round(b / 1024) + ' KB';
        return Math.round(b / 1048576) + ' MB';
    }

    function fmtTime(unix) {
        if (!unix) return '—';
        return new Date(unix * 1000).toLocaleString();
    }

    function statusBadge(s) {
        var cls = 'bg-secondary';
        if (s === 'running') cls = 'bg-success';
        else if (s === 'stopped') cls = 'bg-warning text-dark';
        else if (s === 'not_enabled') cls = 'bg-secondary';
        var span = document.createElement('span');
        span.className = 'badge ' + cls;
        span.textContent = s;
        return span;
    }

    function cell(text) {
        var td = document.createElement('td');
        td.textContent = text == null ? '' : String(text);
        return td;
    }

    // Truncating cell: wraps the value in a span.whp-cell-truncate with a
    // title="<full value>" so the fixed-layout table can ellipsize long content
    // (long usernames, eviction policy names, snapshot timestamps) without
    // breaking column widths. extraClass lets the Customer column be the greedy
    // primary (whp-cell-primary).
    function truncCell(text, extraClass) {
        var td = document.createElement('td');
        var val = text == null ? '' : String(text);
        var span = document.createElement('span');
        span.className = 'whp-cell-truncate' + (extraClass ? ' ' + extraClass : '');
        span.textContent = val;
        span.title = val;
        td.appendChild(span);
        return td;
    }

    function actionButton(label, cls, handler) {
        var b = document.createElement('button');
        b.type = 'button';
        b.className = 'btn btn-sm ' + cls;
        b.textContent = label;
        b.addEventListener('click', handler);
        return b;
    }

    function render(instances) {
        clearChildren($tbody);
        if (!instances || !instances.length) {
            var tr = document.createElement('tr');
            var td = document.createElement('td');
            td.colSpan = 11;
            td.className = 'text-center text-muted py-4';
            td.textContent = 'No customer Valkey instances are currently enabled.';
            tr.appendChild(td); $tbody.appendChild(tr);
            return;
        }
        instances.forEach(function(i) {
            var tr = document.createElement('tr');
            tr.appendChild(truncCell(i.username, 'whp-cell-primary'));
            tr.appendChild(cell(i.mode));
            tr.appendChild(cell(i.memory_mb + ' MB'));
            tr.appendChild(truncCell(i.eviction_policy));
            tr.appendChild(cell(i.version));

            var stTd = document.createElement('td');
            stTd.appendChild(statusBadge(i.status));
            tr.appendChild(stTd);

            tr.appendChild(cell(fmtUptime(i.uptime_sec)));
            tr.appendChild(cell(i.key_count));
            tr.appendChild(cell(fmtBytes(i.memory_used_bytes)));
            tr.appendChild(truncCell(i.mode === 'snapshot' ? fmtTime(i.last_save_unix) : '—'));

            var actTd = document.createElement('td');
            actTd.className = 'text-end';
            var restart = actionButton('Restart', 'btn-outline-secondary me-1', function() {
                if (!confirm('Restart Valkey for ' + i.username + '?')) return;
                api('admin_restart', 'POST', {target_user: i.username}).then(function(r) {
                    if (!r.ok || !r.data || !r.data.success) {
                        alert('Restart failed: ' + (r.data && r.data.error ? r.data.error : 'HTTP ' + r.status));
                    }
                    load();
                });
            });
            var recreate = actionButton('Recreate', 'btn-outline-warning', function() {
                if (!confirm('Recreate Valkey container for ' + i.username +
                    '?\n\nThe container is force-removed and replaced with a fresh one using the current pinned image. ' +
                    'Snapshot-mode data persists via dump.rdb on disk; cache-mode data is lost.')) return;
                api('admin_recreate', 'POST', {target_user: i.username}).then(function(r) {
                    if (!r.ok || !r.data || !r.data.success) {
                        alert('Recreate failed: ' + (r.data && r.data.error ? r.data.error : 'HTTP ' + r.status));
                    }
                    load();
                });
            });
            actTd.appendChild(restart); actTd.appendChild(recreate);
            tr.appendChild(actTd);
            $tbody.appendChild(tr);
        });
    }

    function load() {
        api('admin_list', 'GET').then(function(r) {
            if (!r.ok || !r.data || !r.data.success) {
                clearChildren($tbody);
                var tr = document.createElement('tr');
                var td = document.createElement('td');
                td.colSpan = 11;
                td.className = 'text-center text-danger py-4';
                td.textContent = 'Failed to load: HTTP ' + r.status + ' ' + (r.data && r.data.error ? r.data.error : '');
                tr.appendChild(td); $tbody.appendChild(tr);
                return;
            }
            render(r.data.instances);
        });
    }

    $pull.addEventListener('click', function() {
        if (!confirm('Pull the latest pinned Valkey image now?')) return;
        $pull.disabled = true;
        $pullRx.style.display = '';
        $pullRx.className = 'alert alert-secondary';
        $pullRx.textContent = 'Pulling image…';
        api('admin_pull_image', 'POST', {}).then(function(r) {
            $pull.disabled = false;
            if (!r.ok || !r.data || !r.data.success) {
                $pullRx.className = 'alert alert-danger';
                $pullRx.textContent = 'Pull failed: ' + (r.data && r.data.result && r.data.result.error ? r.data.result.error : 'HTTP ' + r.status);
                return;
            }
            $pullRx.className = 'alert alert-success';
            $pullRx.textContent = 'Image pulled. Use Recreate on each instance to adopt the new image.';
        });
    });

    $refresh.addEventListener('click', load);

    load();
    setInterval(load, 20000);
})();
</script>
