{include file='modules/servers/puqWebHosting/templates/header.tpl'}

{* Dashboard layout (top → bottom):
   1. Dashboard bar — domain, deploy status, refresh / sync usage
   2. Actions block — every navigation/quick-action button in one place
   3. Two columns Web | Mail — each holds: SSL card + usage stack (1 per row)
*}

<style>
/* Dashboard-local tweaks on top of the shared .puq-* toolkit (header.tpl). */
.puq-dash-bar { display:flex; flex-wrap:wrap; align-items:center; gap:14px; padding:10px 14px; background:#fff; border:1px solid var(--puq-border); border-radius:var(--puq-radius); margin-bottom:12px; }
.puq-dash-bar h2 { margin:0; font-size:18px; font-weight:600; }
.puq-dash-bar .domain-link { color:#337ab7; text-decoration:none; }
.puq-dash-bar .domain-link:hover { text-decoration:underline; }
.puq-dash-bar .spacer { flex:1; }
.puq-dash-bar .meta { color:var(--puq-muted); font-size:11px; }
.puq-dash-bar .meta code { background:var(--puq-bg); padding:1px 5px; border-radius:3px; font-size:11px; }

.puq-cols { display:grid; grid-template-columns: 1fr 1fr; gap:12px; }
@media (max-width: 900px) { .puq-cols { grid-template-columns: 1fr; } }

/* SSL summary card — compact, fits inside the Web/Mail column.
   ACTIVE → status pill + hosts + expiry date.
   OTHER  → status pill + hosts + "Request SSL" button. */
.puq-ssl-card { display:flex; align-items:flex-start; gap:10px; padding:10px 12px; background:#f9f9fb; border:1px solid var(--puq-border); border-radius:var(--puq-radius); margin-bottom:0; font-size:12px; height:100%; }
.puq-ssl-icon { font-size:18px; flex-shrink:0; margin-top:2px; }
.puq-ssl-body { flex:1; min-width:0; }
.puq-ssl-body .ssl-line { margin-top:4px; color:var(--puq-muted); }
.puq-ssl-body code { background:#fff; padding:1px 5px; border-radius:3px; font-size:11px; border:1px solid #eee; word-break:break-all; }

/* Unified Resources card — SSL row + per-role usage grids in one box.
   .puq-ssl-row stacks both SSL summaries side-by-side at the top.
   .puq-resources-section wraps each role (web/mail) with a header label
   + server pill + 3-col grid of compact metric cards. A subtle border-top
   separates Mail from Web — keeps "what's what" clear without two outer
   cards. */
.puq-ssl-row { display:grid; grid-template-columns: 1fr 1fr; gap:10px; margin-bottom:14px; }
@media (max-width: 700px) { .puq-ssl-row { grid-template-columns: 1fr; } }

.puq-resources-section { margin-top:14px; padding-top:12px; border-top:1px solid var(--puq-border); }
.puq-resources-section:first-of-type { margin-top:0; padding-top:0; border-top:none; }
.puq-resources-header { display:flex; align-items:center; gap:8px; margin-bottom:10px; font-size:13px; font-weight:600; color:#444; }
.puq-resources-header > i { font-size:14px; }
.puq-resources-header .puq-badge { margin-left:auto; font-size:11px; }

.puq-usage-grid-3 { display:grid; grid-template-columns: repeat(3, 1fr); gap:8px; }
@media (max-width: 900px) { .puq-usage-grid-3 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 520px) { .puq-usage-grid-3 { grid-template-columns: 1fr; } }

/* Legacy class — still used by per-role stacks if ever needed. */
.puq-usage-stack { display:flex; flex-direction:column; gap:8px; }
.puq-usage-card { background:#fff; border:1px solid var(--puq-border); border-radius:var(--puq-radius); padding:10px 12px; }
.puq-usage-card .head { display:flex; align-items:center; gap:8px; font-size:12px; color:var(--puq-muted); margin-bottom:4px; }
.puq-usage-card .head i { width:24px; height:24px; border-radius:5px; color:#fff; display:flex; align-items:center; justify-content:center; font-size:12px; flex-shrink:0; }
.puq-usage-card .head .label { font-weight:600; color:#444; font-size:12px; }
.puq-usage-card .head .pct { margin-left:auto; font-size:11px; padding:2px 7px; border-radius:3px; color:#fff; }
.puq-usage-card .val { display:flex; align-items:baseline; gap:6px; font-size:14px; font-weight:600; }
.puq-usage-card .val .lim { color:var(--puq-muted); font-weight:400; font-size:12px; }
.puq-usage-card .bar { margin-top:6px; }

/* Actions block — flex grid of buttons, all in one place near the top. */
.puq-actions-card .puq-card-body { padding:12px 14px; }
.puq-action-grid { display:grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap:8px; }
.puq-action-grid .puq-btn { justify-content:flex-start; padding:9px 12px; font-size:12px; }
.puq-action-section-label { font-size:10px; color:var(--puq-muted); text-transform:uppercase; letter-spacing:.5px; font-weight:600; margin:8px 0 4px; }
.puq-action-section-label:first-child { margin-top:0; }

/* Auto-refresh indicator in the dash-bar — replaces the old Refresh button.
   Idle: muted clock icon + "auto-refresh 5s". In-flight: spinner + "refreshing…".
   Click toggles pause/resume so the user can stop polling for a noisy moment. */
.puq-autorefresh { display:inline-flex; align-items:center; gap:6px; font-size:11px; color:var(--puq-muted); cursor:pointer; user-select:none; padding:4px 8px; border-radius:4px; }
.puq-autorefresh:hover { background:var(--puq-bg); color:#555; }
.puq-autorefresh.is-busy { color:var(--puq-primary); }
.puq-autorefresh.is-paused { color:var(--puq-warning); }
.puq-autorefresh i { font-size:12px; }
</style>

<div class="puq-dash-bar">
    <h2><i class="fa fa-globe" style="color:var(--puq-primary);"></i> <a class="domain-link" href="http://{$domain|escape:'url'}" target="_blank" rel="noopener noreferrer">{$domain|escape:'html'}</a></h2>
    <span id="puq-deploy-pill"><span class="puq-shimmer" style="width:60px;"></span></span>
    <span class="meta" id="puq-svc-meta"><span class="puq-shimmer" style="width:240px;"></span></span>
    <span class="spacer"></span>
    <span class="puq-autorefresh" id="puq-autorefresh" title="{$L.ca_autorefresh_tooltip}">
        <i class="fa fa-circle-notch"></i>
        <span class="puq-autorefresh-label">{$L.ca_autorefresh_label}</span>
    </span>
    <button type="button" class="puq-btn puq-btn-outline puq-btn-sm" id="puq-btn-refresh-all" title="{$L.ca_refresh_everything_tooltip}"><i class="fa fa-cloud-download-alt"></i> {$L.ca_refresh_everything}</button>
    <button type="button" class="puq-btn puq-btn-primary puq-btn-sm" id="puq-btn-sync" title="{$L.ca_sync_usage_tooltip}"><i class="fa fa-sync"></i> <span class="puq-btn-sync-label">{$L.ca_sync_usage}</span></button>
</div>

<div id="puq-dash-msg"></div>

{* Async-op banner — populated by JS when overviewData.ops.web|.mail is set. *}
<div id="puq-ops-banner" style="display:none;"></div>
<div id="puq-ns-warning" style="display:none;"></div>

<div class="puq-card puq-actions-card">
    <div class="puq-card-header">
        <i class="fa fa-bolt" style="color:var(--puq-warning);"></i> {$L.ca_quick_actions}
    </div>
    <div class="puq-card-body">
        <div id="puq-all-actions"><div class="puq-loader"><i class="fas fa-circle-notch fa-spin"></i></div></div>
    </div>
</div>

<div class="puq-card">
    <div class="puq-card-header">
        <i class="fa fa-server" style="color:var(--puq-primary);"></i> {$L.ca_resources}
    </div>
    <div class="puq-card-body">
        <div class="puq-ssl-row">
            <div id="puq-web-ssl"><div class="puq-loader"><i class="fas fa-circle-notch fa-spin"></i></div></div>
            <div id="puq-mail-ssl"><div class="puq-loader"><i class="fas fa-circle-notch fa-spin"></i></div></div>
        </div>

        <div class="puq-resources-section" id="puq-section-web" style="display:none;">
            <div class="puq-resources-header">
                <i class="fa fa-globe" style="color:var(--puq-primary);"></i>
                <span>{$L.ca_web_hosting}</span>
                <span id="puq-web-server" class="puq-badge puq-badge-muted">—</span>
            </div>
            <div id="puq-web-usage"><div class="puq-loader"><i class="fas fa-circle-notch fa-spin"></i></div></div>
        </div>

        <div class="puq-resources-section" id="puq-section-mail" style="display:none;">
            <div class="puq-resources-header">
                <i class="fa fa-envelope" style="color:var(--puq-success);"></i>
                <span>{$L.ca_mail}</span>
                <span id="puq-mail-server" class="puq-badge puq-badge-muted">—</span>
            </div>
            <div id="puq-mail-usage"><div class="puq-loader"><i class="fas fa-circle-notch fa-spin"></i></div></div>
        </div>
    </div>
</div>

<script>window.puqServiceId = {$service_id};</script>
{literal}
<script>
(function() {
    // Route through the shared puqClientAjax helper (header.tpl) so writes
    // travel as application/json POSTs — form-urlencoded POSTs to
    // clientarea.php get 302'd by WHMCS's anti-forgery layer and the
    // response is an HTML blob, not JSON.
    function ajax(action, data, cb) {
        if (typeof window.puqClientAjax !== 'function') {
            cb({status:'error', msg:'puqClientAjax helper missing'});
            return;
        }
        window.puqClientAjax(action, data || {}, cb, 'POST');
    }
    var esc = window.puqEsc; // local alias — keeps the dashboard JS readable
    function fmtMB(v){
        v = parseFloat(v) || 0;
        if (v === 0) return '0';
        if (v >= 1024) return (v/1024).toFixed(1) + ' GB';
        return Math.round(v) + ' MB';
    }
    function fmtLimitMB(lim){
        lim = parseFloat(lim) || 0;
        if (lim <= 0) return '∞';
        if (lim >= 1024) return (lim/1024).toFixed(1) + ' GB';
        return Math.round(lim) + ' MB';
    }
    function fmtLimitCount(lim){
        lim = parseInt(lim, 10) || 0;
        return lim > 0 ? String(lim) : '∞';
    }
    function pctBar(used, lim){
        used = parseFloat(used) || 0;
        lim  = parseFloat(lim)  || 0;
        if (lim <= 0) return '';
        var pct = Math.min(100, Math.round((used / lim) * 100));
        var color = pct >= 90 ? 'var(--puq-danger)' : pct >= 75 ? 'var(--puq-warning)' : 'var(--puq-success)';
        return '<div class="puq-bar"><div class="puq-bar-fill" style="width:' + pct + '%;background:' + color + ';"></div></div>';
    }
    function pctPill(used, lim){
        used = parseFloat(used) || 0;
        lim  = parseFloat(lim)  || 0;
        if (lim <= 0) return '';
        var pct = Math.min(100, Math.round((used / lim) * 100));
        var color = pct >= 90 ? 'var(--puq-danger)' : pct >= 75 ? 'var(--puq-warning)' : 'var(--puq-success)';
        return '<span class="pct" style="background:' + color + ';">' + pct + '%</span>';
    }

    // Always-green bar — for counters where "at the limit" is the normal /
    // expected state (e.g. rotating backups: slot fills up by design and is
    // not a warning). Width still reflects fill so 0 vs 2/5 vs 5/5 is visible.
    function pctBarAlwaysOk(used, lim){
        used = parseFloat(used) || 0;
        lim  = parseFloat(lim)  || 0;
        if (lim <= 0) return '';
        var pct = Math.min(100, Math.round((used / lim) * 100));
        return '<div class="puq-bar"><div class="puq-bar-fill" style="width:' + pct + '%;background:var(--puq-success);"></div></div>';
    }

    // usageCard(icon, color, label, used, limit, type, alwaysOk?)
    //   alwaysOk=true → hides the % pill and renders the bar in success color
    //   regardless of fill. Use for backup tiles where reaching the limit is
    //   normal (Hestia rotates older backups when the slot count is hit).
    function usageCard(icon, color, label, used, limit, type, alwaysOk){
        var fmt    = type === 'mb' ? fmtMB        : function(v){ return parseInt(v||0,10); };
        var fmtLim = type === 'mb' ? fmtLimitMB   : fmtLimitCount;
        return '<div class="puq-usage-card">' +
            '<div class="head">' +
                '<i class="fa ' + icon + '" style="background:' + color + ';"></i>' +
                '<span class="label">' + esc(label) + '</span>' +
                (alwaysOk ? '' : pctPill(used, limit)) +
            '</div>' +
            '<div class="val">' + fmt(used) + ' <span class="lim">/ ' + fmtLim(limit) + '</span></div>' +
            (alwaysOk ? pctBarAlwaysOk(used, limit) : pctBar(used, limit)) +
        '</div>';
    }

    // Compact SSL summary card. Same render logic for web and mail (just
    // a label difference). ACTIVE → expiry date, OTHER → Request button.
    function sslCard(role, ssl, domain){
        var icon = role === 'web' ? 'fa-lock' : 'fa-envelope';
        var hosts = ssl && ssl.required_hosts ? ssl.required_hosts : [];
        if (!hosts.length) hosts = [domain]; // fallback for not-yet-seeded
        var hostsLine = '<div class="ssl-line"><i class="fa fa-globe"></i> ' + puqT('ca_hosts_required') + ' ' +
            hosts.map(function(h){ return '<code>' + esc(h) + '</code>'; }).join(' ') + '</div>';

        // What the installed cert is ACTUALLY issued for — Subject CN + SANs.
        // Lets the customer see whether their uploaded/auto-issued cert covers
        // the required hosts above (handy when CN is a wildcard like *.example.com).
        var issuedLine = '';
        if (ssl && (ssl.subject || ssl.san)) {
            var issuedNames = [];
            if (ssl.subject) issuedNames.push(ssl.subject);
            if (ssl.san) {
                ssl.san.split(',').forEach(function(s){
                    s = s.trim();
                    if (s && issuedNames.indexOf(s) === -1) issuedNames.push(s);
                });
            }
            if (issuedNames.length) {
                issuedLine = '<div class="ssl-line"><i class="fa fa-certificate"></i> ' + puqT('ca_issued_for') + ' ' +
                    issuedNames.map(function(h){ return '<code>' + esc(h) + '</code>'; }).join(' ') + '</div>';
            }
        }

        var st = (ssl && ssl.status) ? ssl.status : 'none';
        var stLabel = ({active:puqT('ca_ssl_active'), installing:puqT('ca_ssl_installing'), wrong_subject:puqT('ca_ssl_configuring'),
                        expired:puqT('ca_ssl_expired'), frozen:puqT('ca_ssl_frozen'), failed:puqT('ca_ssl_failed'), none:puqT('ca_ssl_not_installed')})[st] || st.toUpperCase();
        var stBadgeClass = st === 'active' ? 'puq-badge-success'
                         : (st === 'expired' || st === 'failed' || st === 'frozen' ? 'puq-badge-danger'
                         : 'puq-badge-warning');
        var iconColor = st === 'active' ? 'var(--puq-success)'
                      : (st === 'expired' || st === 'failed' || st === 'frozen' ? 'var(--puq-danger)'
                      : (st === 'installing' || st === 'wrong_subject' ? 'var(--puq-warning)' : 'var(--puq-muted)'));

        var inProg = ssl && ssl.in_progress ? ssl.in_progress : null;
        var thirdLine;
        if (inProg) {
            // Backend has a stage in progress — hide the Request button so
            // the customer can't enqueue duplicates. Show a generic stage
            // label (no task ids, no internal action names).
            var stageLabel = inProg.stage || puqT('ca_working_on_cert');
            var taskStatusBadge = inProg.task_status === 'running'
                ? '<span class="puq-badge puq-badge-info">' + puqT('ca_in_progress') + '</span>'
                : '<span class="puq-badge puq-badge-warning">' + puqT('ca_queued') + '</span>';
            thirdLine = '<div class="ssl-line" style="margin-top:6px;">'
                + '<i class="fa fa-spinner fa-spin"></i> '
                + esc(stageLabel) + ' ' + taskStatusBadge
                + '</div>';
            if (st === 'active' && ssl.expires_at) {
                // Cert already active — likely a renewal cycle. Still show expiry.
                thirdLine += '<div class="ssl-line"><i class="fa fa-calendar-alt"></i> ' + puqT('ca_expires') + ' <strong>'
                    + esc(ssl.expires_at) + '</strong></div>';
            }
        } else if (st === 'active') {
            thirdLine = ssl && ssl.expires_at
                ? '<div class="ssl-line"><i class="fa fa-calendar-alt"></i> ' + puqT('ca_expires') + ' <strong>' + esc(ssl.expires_at) + '</strong></div>'
                : '<div class="ssl-line"><i class="fa fa-check"></i> ' + puqT('ca_active') + '</div>';
        } else {
            thirdLine = '<div class="ssl-line" style="margin-top:6px;">' +
                '<button type="button" class="puq-btn puq-btn-primary puq-btn-sm puq-ssl-request" data-kind="' + esc(role) + '">' +
                '<i class="fa fa-bolt"></i> ' + puqT('ca_request_ssl') + '</button></div>';
        }

        return '<div class="puq-ssl-card">' +
            '<i class="fa ' + icon + ' puq-ssl-icon" style="color:' + iconColor + ';"></i>' +
            '<div class="puq-ssl-body">' +
                '<strong>SSL</strong> <span class="puq-badge ' + stBadgeClass + '">' + stLabel + '</span>' +
                hostsLine +
                issuedLine +
                thirdLine +
            '</div>' +
        '</div>';
    }

    function render(d) {
        // Top bar
        var ds = d.deploy_status || 'unknown';
        var dsClass = ds === 'active' ? 'puq-badge-success' : (ds === 'error' ? 'puq-badge-danger' : 'puq-badge-warning');
        $('#puq-deploy-pill').html('<span class="puq-badge ' + dsClass + '">' + esc(ds) + '</span>');

        // Async-op banner (restore in progress, backup creating). Renders as
        // an alert at the top of the dashboard with a spinner + friendly
        // copy. Returns truthy if there's an ongoing op so we can drive
        // auto-refresh on the page while it runs.
        var $opsBanner = $('#puq-ops-banner');
        var ops = d.ops || {};
        var opsLines = [];
        ['web', 'mail'].forEach(function(role){
            var op = ops[role];
            if (!op) return;
            var roleLabel = role === 'web' ? puqT('ca_website') : puqT('ca_mail');
            var kindLabel;
            if (op.kind === 'restore') {
                kindLabel = puqT('ca_op_restore');
            } else if (op.kind === 'backup') {
                kindLabel = puqT('ca_op_backup');
            } else {
                kindLabel = puqT('ca_op_generic');
            }
            var since = op.started_at ? ' (' + puqT('ca_since') + ' ' + esc(op.started_at.substring(0,16)) + ')' : '';
            opsLines.push(
                '<div class="puq-msg puq-msg-warning" style="margin-bottom:8px;font-size:13px;">'
              + '<i class="fas fa-circle-notch fa-spin"></i> '
              + '<strong>' + roleLabel + '</strong> ' + kindLabel + since
              + '</div>'
            );
        });
        if (opsLines.length) {
            $opsBanner.html(opsLines.join('')).show();
        } else {
            $opsBanner.hide().empty();
        }

        // Nameserver delegation warning — only when WE manage DNS and the live
        // delegation does NOT include our nameservers. Tells the customer which
        // NS to set at their registrar (group ns1..ns8). ns_match===null means
        // "no verdict" (external DNS, not deployed, or lookup empty) → no banner.
        var $nsWarn = $('#puq-ns-warning');
        var dns = d.dns || {};
        if (dns.ns_match === false && dns.expected_ns && dns.expected_ns.length) {
            var nsList = dns.expected_ns.map(function(n){
                return '<li><code>' + esc(n) + '</code></li>';
            }).join('');
            var actual = (dns.actual_ns && dns.actual_ns.length)
                ? '<div class="small" style="margin-top:6px;opacity:.85;">' + puqT('ca_ns_current') + ' '
                  + dns.actual_ns.map(function(n){ return '<code>' + esc(n) + '</code>'; }).join(', ') + '</div>'
                : '';
            $nsWarn.html(
                '<div class="puq-msg puq-msg-warning" style="margin-bottom:10px;">'
              + '<i class="fa fa-exclamation-triangle"></i> <strong>' + puqT('ca_ns_mismatch_title') + '</strong>'
              + '<div style="margin-top:4px;">' + puqT('ca_ns_mismatch_body') + '</div>'
              + '<ul style="margin:6px 0 0 4px;list-style:none;padding-left:0;">' + nsList + '</ul>'
              + actual
              + '</div>'
            ).show();
        } else {
            $nsWarn.hide().empty();
        }

        var metaBits = [];
        if (d.ns1) metaBits.push('NS1: <code>' + esc(d.ns1) + '</code>');
        if (d.ns2) metaBits.push('NS2: <code>' + esc(d.ns2) + '</code>');
        if (d.usage_updated) metaBits.push(puqT('ca_updated') + ' ' + esc((d.usage_updated||'').substring(0,16)));
        $('#puq-svc-meta').html(metaBits.join(' &nbsp;·&nbsp; '));

        // Server pills
        if (d.servers && d.servers.web)  $('#puq-web-server').removeClass().addClass('puq-badge puq-badge-primary').text(d.servers.web.name);
        if (d.servers && d.servers.mail) $('#puq-mail-server').removeClass().addClass('puq-badge puq-badge-success').text(d.servers.mail.name);

        // Hide whole Web / Mail sections when the role isn't part of the
        // package (General → Package contents checkbox OR disk_quota=0).
        var roles = d.package_roles || {web: true, mail: true};
        $('#puq-section-web').toggle(!!roles.web);
        $('#puq-section-mail').toggle(!!roles.mail);
        $('#puq-web-ssl').toggle(!!roles.web);
        $('#puq-mail-ssl').toggle(!!roles.mail);

        var p = d.permissions || {};
        var on = function(k){ return p[k] === 'on'; };
        var u = d.usage || {}, l = d.limits || {}, c = d.counts || {};

        // ===== COMBINED ACTIONS BLOCK =====
        // Two sections: "External tools" (distinct because they open
        // in new tabs / different panels) and one flat "Management"
        // grid that mixes web + mail buttons — no per-side split since
        // the customer thinks in terms of "what do I want to manage",
        // not "is this on the web or mail side".
        var actions = '';
        var ext = '', mgmt = '';

        // External tools — open in new tab, distinct primary colour.
        // File Manager: synchronous SSO handshake (~200-500 ms) — backend
        // POSTs HMAC-signed creds to chosen engine on the server, gets back
        // a one-time URL, we open in new tab.
        //
        // Per-server, EITHER engine (filegator / net2ftp) may be installed.
        // Render:
        //   • none installed   → no button
        //   • one installed    → single button, opens that engine
        //   • both installed   → dropdown with both choices
        // One button per installed engine — distinct labels so the customer
        // sees both options instantly without expanding anything. When only
        // one is installed → only one button renders. When neither → none.
        var fm = (d.servers && d.servers.web && d.servers.web.file_managers) || null;
        if (on('allow_file_manager_link') && fm) {
            if (fm.filegator) {
                ext += '<button type="button" class="puq-btn puq-btn-primary puq-fm-open" data-engine="filegator">'
                    +    '<i class="fa fa-folder-open"></i> ' + puqT('ca_file_manager') + '</button>';
            }
            if (fm.net2ftp) {
                ext += '<button type="button" class="puq-btn puq-btn-primary puq-fm-open" data-engine="net2ftp">'
                    +    '<i class="fa fa-terminal"></i> ' + puqT('ca_file_manager_chmod') + '</button>';
            }
        }
        if (on('allow_webmail_link') && d.links.webmail)
            ext += '<a class="puq-btn puq-btn-primary" href="' + esc(d.links.webmail) + '" target="_blank" rel="noopener"><i class="fa fa-envelope-open-text"></i> ' + puqT('ca_open_webmail') + '</a>';
        if (d.links.phpmyadmin)
            ext += '<a class="puq-btn puq-btn-muted" href="' + esc(d.links.phpmyadmin) + '" target="_blank" rel="noopener"><i class="fa fa-database"></i> phpMyAdmin</a>';

        // Management — order: Web settings → Custom SSL → FTP → DNS records
        //   → Databases → Mailboxes → Cron → DNS verify → Backups (LAST).
        // Each button is gated by BOTH the permission AND the package role.
        var hasWeb  = !!roles.web;
        var hasMail = !!roles.mail;
        if (on('allow_hosting_settings') && hasWeb)
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.web_settings) + '"><i class="fa fa-globe"></i> ' + puqT('ca_web_settings') + '</a>';
        if (on('allow_ssl_install') && (hasWeb || hasMail))
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.custom_ssl) + '"><i class="fa fa-lock"></i> ' + puqT('ca_custom_ssl') + '</a>';
        if (on('allow_create_ftp') && hasWeb)
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.ftp) + '"><i class="fa fa-folder-open"></i> FTP <small>(' + (c.ftp_accounts||0) + ')</small></a>';
        if (on('allow_dns_edit'))
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.dns_records) + '"><i class="fa fa-sitemap"></i> ' + puqT('ca_dns_records') + ' <small>(' + (c.dns_records||0) + ')</small></a>';
        if (on('allow_create_db') && hasWeb)
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.databases) + '"><i class="fa fa-database"></i> ' + puqT('ca_databases') + ' <small>(' + (c.databases||0) + ')</small></a>';
        if (on('allow_create_mailbox') && hasMail)
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.mailboxes) + '"><i class="fa fa-inbox"></i> ' + puqT('ca_mailboxes') + ' <small>(' + (c.mailboxes||0) + ')</small></a>';
        if (on('allow_cron_jobs') && hasWeb)
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.cron_jobs) + '"><i class="fa fa-clock"></i> ' + puqT('ca_cron_jobs') + ' <small>(' + (c.cron_jobs||0) + ')</small></a>';
        if (on('allow_dns_edit'))
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.required_dns) + '"><i class="fa fa-check-circle"></i> ' + puqT('ca_dns_verify') + '</a>';
        if (on('allow_backups') && (hasWeb || hasMail)) {
            // Unified services keep ONE account-wide backup (role='all'); split
            // services keep per-role web+mail backups. Count the right bucket.
            var bkCount = (d.deployment_mode === 'unified')
                ? (c.backups_all || 0)
                : ((c.backups_web || 0) + (c.backups_mail || 0));
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.backups) + '"><i class="fa fa-archive"></i> ' + puqT('ca_backups') + ' <small>(' + bkCount + ')</small></a>';
        }
        if (on('allow_logs_view') && hasWeb)
            mgmt += '<a class="puq-btn puq-btn-outline" href="' + esc(d.links.logs) + '"><i class="fa fa-file-alt"></i> ' + puqT('ca_logs') + '</a>';

        if (ext)  actions += '<div class="puq-action-section-label">' + puqT('ca_external_tools') + '</div><div class="puq-action-grid">' + ext + '</div>';
        if (mgmt) actions += '<div class="puq-action-section-label">' + puqT('ca_management') + '</div><div class="puq-action-grid">' + mgmt + '</div>';
        if (!actions) actions = '<div class="text-muted">' + puqT('ca_no_actions') + '</div>';
        $('#puq-all-actions').html(actions);

        // ===== SSL summaries (side-by-side row above the usage grids) =====
        $('#puq-web-ssl').html(sslCard('web', (d.ssl||{}).web, d.domain));
        $('#puq-mail-ssl').html(sslCard('mail', (d.ssl||{}).mail, d.domain));

        // ===== WEB role — 3-col grid =====
        var webUsage = '';
        webUsage += usageCard('fa-folder',        'var(--puq-primary)', puqT('ca_web_disk'),      u.disk_web_mb || 0, l.web_disk_quota || 0, 'mb');
        webUsage += usageCard('fa-arrow-down',    'var(--puq-warning)', puqT('ca_web_bandwidth'), u.bw_used_mb  || 0, l.web_bandwidth  || 0, 'mb');
        webUsage += usageCard('fa-sitemap',       'var(--puq-success)', puqT('ca_dns_records'),   c.dns_records || 0, l.dns_records    || 0, 'count');
        webUsage += usageCard('fa-database',      'var(--puq-danger)',  puqT('ca_databases'),     c.databases   || 0, l.web_databases  || 0, 'count');
        webUsage += usageCard('fa-folder-open',   '#16a085',            puqT('ca_ftp_accounts'),  c.ftp_accounts|| 0, l.web_ftp_accounts || 0, 'count');
        webUsage += usageCard('fa-clock',         '#9b59b6',            puqT('ca_cron_jobs'),     c.cron_jobs   || 0, l.web_cron_jobs  || 0, 'count');
        webUsage += usageCard('fa-archive',       '#3498db',            puqT('ca_web_backups'),   c.backups_web || 0, l.web_backups    || 0, 'count', true);
        $('#puq-web-usage').html('<div class="puq-usage-grid-3">' + webUsage + '</div>');

        // ===== MAIL role — 3-col grid =====
        var mailUsage = '';
        mailUsage += usageCard('fa-envelope',     'var(--puq-success)', puqT('ca_mail_disk'),      u.disk_mail_mb   || 0, l.mail_disk_quota || 0, 'mb');
        mailUsage += usageCard('fa-at',           '#e67e22',            puqT('ca_mail_accounts'),  c.mailboxes      || 0, l.mail_accounts   || 0, 'count');
        mailUsage += usageCard('fa-archive',      '#3498db',            puqT('ca_mail_backups'),   c.backups_mail   || 0, l.mail_backups    || 0, 'count', true);
        $('#puq-mail-usage').html('<div class="puq-usage-grid-3">' + mailUsage + '</div>');
    }

    // ============================================================
    // Auto-refresh — replaces the old "Refresh" button. Fires every
    // AUTO_REFRESH_MS (5 s) unconditionally; the user can pause/resume
    // by clicking the indicator in the dash-bar. While a load() is in
    // flight we mark the indicator .is-busy so the user sees activity.
    // ============================================================
    var AUTO_REFRESH_MS = 5000;
    var autoRefreshTimer = null;
    var autoRefreshPaused = false;
    var loadInFlight = false;

    function setAutoRefreshIndicator(state) {
        var $ar = $('#puq-autorefresh');
        var $i = $ar.find('i');
        var $lbl = $ar.find('.puq-autorefresh-label');
        $ar.removeClass('is-busy is-paused');
        if (state === 'paused') {
            $ar.addClass('is-paused');
            $i.attr('class', 'fa fa-pause');
            $lbl.text(puqT('ca_paused'));
        } else if (state === 'busy') {
            $ar.addClass('is-busy');
            $i.attr('class', 'fa fa-circle-notch fa-spin');
            $lbl.text(puqT('ca_refreshing'));
        } else { // idle
            $i.attr('class', 'fa fa-circle-notch');
            $lbl.text(puqT('ca_autorefresh_label'));
        }
    }

    function scheduleAutoRefresh() {
        if (autoRefreshTimer) { clearTimeout(autoRefreshTimer); autoRefreshTimer = null; }
        if (autoRefreshPaused) return;
        autoRefreshTimer = setTimeout(load, AUTO_REFRESH_MS);
    }

    // Tracks whether the current pageload saw an active sync_usage chain. We
    // use it to detect the in-flight→idle transition and pop a "Done" toast +
    // refresh local counters when the chain finishes — without spamming the
    // toast every time the user opens the page on an idle service.
    var syncWasInFlight = false;

    function load() {
        if (loadInFlight) return; // skip overlapping ticks
        loadInFlight = true;
        if (!autoRefreshPaused) setAutoRefreshIndicator('busy');

        ajax('overviewData', {}, function(r){
            loadInFlight = false;
            if (!autoRefreshPaused) setAutoRefreshIndicator('idle');

            if (r.status !== 'success' || !r.data || typeof r.data !== 'object') {
                $('#puq-dash-msg').html('<div class="puq-msg puq-msg-error"><i class="fa fa-exclamation-triangle"></i> ' + esc(r.msg || puqT('ca_failed_to_load')) + '</div>');
                scheduleAutoRefresh();
                return;
            }
            $('#puq-dash-msg').empty();
            render(r.data);

            // ---- Sync-usage button lock — auto from server state ----
            // The button is also INSTANTLY locked client-side on click (see
            // below) without waiting for AJAX, to defeat double-clicks.
            // This block then keeps it locked as long as the chain is alive
            // and unlocks it when sync_in_progress=false comes back.
            var syncNow = !!(r.data && r.data.sync_in_progress);
            var $btn = $('#puq-btn-sync');
            if (syncNow) {
                $btn.prop('disabled', true).addClass('disabled');
                $btn.find('.puq-btn-sync-label').text(puqT('ca_recompute_in_progress'));
                $btn.find('i').removeClass('fa-sync').addClass('fa-spinner fa-spin');
                syncWasInFlight = true;
            } else {
                $btn.prop('disabled', false).removeClass('disabled');
                $btn.find('.puq-btn-sync-label').text(puqT('ca_sync_usage'));
                $btn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-sync');
                if (syncWasInFlight) {
                    syncWasInFlight = false;
                    puqToast('success', puqT('ca_toast_sync_complete'));
                }
            }

            scheduleAutoRefresh();
        });
    }

    // Click on the auto-refresh indicator → pause/resume.
    $('#puq-autorefresh').on('click', function(e){
        e.preventDefault();
        autoRefreshPaused = !autoRefreshPaused;
        if (autoRefreshPaused) {
            if (autoRefreshTimer) { clearTimeout(autoRefreshTimer); autoRefreshTimer = null; }
            setAutoRefreshIndicator('paused');
        } else {
            setAutoRefreshIndicator(loadInFlight ? 'busy' : 'idle');
            load();
        }
    });

    // "Refresh everything" — enqueue service.resyncRole for BOTH roles and a
     // usage sync. Idempotent: the AJAX coalesces if a refresh is already in
     // flight. Visual feedback: spinner on the button + toast on response.
    $('#puq-btn-refresh-all').on('click', function(e){
        e.preventDefault();
        var $btn = $(this);
        if ($btn.prop('disabled')) return;
        $btn.prop('disabled', true);
        var origHtml = $btn.html();
        $btn.html('<i class="fa fa-spinner fa-spin"></i> ' + puqT('ca_refreshing'));
        ajax('refreshAll', {}, function(r){
            if (r && r.status === 'success') {
                puqToast('success', puqT('ca_toast_refresh_queued'));
                // Trigger usage sync in parallel.
                ajax('syncUsageNow', {}, function(){});
            } else {
                puqToast('error', (r && r.msg) || puqT('ca_refresh_failed'));
            }
            setTimeout(function(){
                $btn.prop('disabled', false).html(origHtml);
            }, 4000);
        });
    });

    $('#puq-btn-sync').on('click', function(e){
        e.preventDefault();
        var $btn = $(this);
        if ($btn.prop('disabled')) return;

        // INSTANT lock — flips UI to "recompute in progress" the moment the
        // user clicks, BEFORE the AJAX even goes out. Defeats double-clicks
        // and matches the pattern from Web settings page where the action
        // is committed instantly without waiting for a server response.
        $btn.prop('disabled', true).addClass('disabled');
        $btn.find('.puq-btn-sync-label').text('Recompute in progress…');
        $btn.find('i').removeClass('fa-sync').addClass('fa-spinner fa-spin');
        syncWasInFlight = true;

        ajax('syncUsageNow', {}, function(r){
            if (r.status === 'success') {
                if ((r.data.queued || 0) > 0) {
                    puqToast('success', puqT('ca_toast_recompute_dispatched_1') + ' ' + r.data.queued + ' ' + puqT('ca_toast_recompute_dispatched_2'));
                } else if ((r.data.skipped || 0) > 0) {
                    puqToast('info', puqT('ca_toast_recompute_running'));
                }
                // Don't unlock here — let load() decide based on sync_in_progress.
                load();
            } else {
                puqToast('error', r.msg || puqT('ca_sync_failed'));
                // Failure → release the lock so user can retry.
                $btn.prop('disabled', false).removeClass('disabled');
                $btn.find('.puq-btn-sync-label').text(puqT('ca_sync_usage'));
                $btn.find('i').removeClass('fa-spinner fa-spin').addClass('fa-sync');
                syncWasInFlight = false;
            }
        });
    });

    // File Manager — synchronous SSO handshake. Backend POSTs HMAC-signed
    // creds to the chosen engine on the server, gets back a one-time URL,
    // we open it in a new tab. Whole flow takes ~200-500 ms.
    // When data-engine is set ('filegator' / 'net2ftp'), the backend uses
    // that engine; otherwise it auto-picks (filegator preferred).
    $(document).on('click', '.puq-fm-open', function(e){
        e.preventDefault();
        var btn = this;
        var engine = $(btn).data('engine') || '';
        puqBtnSpin(btn, puqT('ca_opening'));
        ajax('fileManagerUrl', { engine: engine }, function(r){
            puqBtnRestore(btn);
            if (r.status === 'success' && r.data && r.data.url) {
                window.open(r.data.url, '_blank', 'noopener,noreferrer');
            } else {
                puqToast('error', r.msg || puqT('ca_file_manager_unavailable'));
            }
        });
    });

    // Request-SSL button — async; UI polls the SSL card for stage updates.
    // Returns immediately with "queued". load() reloads the overview which
    // now shows the in_progress spinner; the 10s auto-poll keeps it fresh
    // until cert flips to active (~60-90 s for fresh issuance).
    $(document).on('click', '.puq-ssl-request', function(e){
        e.preventDefault();
        var $btn = $(this);
        // Guard against double-click: this handler fires on `document` so a
        // second click hits the same node before load() re-renders the card.
        // Mark+disable synchronously so the second click bounces.
        if ($btn.prop('disabled') || $btn.data('puq-locked')) return;
        $btn.data('puq-locked', 1);
        var kind = $btn.data('kind');
        puqBtnSpin($btn.get(0), puqT('ca_queuing'));
        ajax('sslAutoIssueNow', { kind: kind }, function(r){
            if (r.status === 'success') {
                puqToast('success', r.msg || puqT('ca_toast_issue_queued'));
                // Deliberately do NOT restore the button — load() will
                // re-render the SSL card into its `in_progress` state, which
                // replaces the Request button with the "Checking SSL
                // readiness — queued" line. Restoring here would briefly
                // re-enable the button during the load() round-trip.
                load();
            } else {
                puqBtnRestore($btn.get(0));
                $btn.removeData('puq-locked');
                puqToast('error', r.msg || puqT('ca_ssl_request_failed'));
            }
        });
    });

    $(document).ready(load);
})();
</script>
{/literal}
