{* Per-service admin panel.
   Rendered inline into the WHMCS clientsservices.php Custom Fields row
   via the AdminServicesTabFields hook (no addon-page route).
   Tabs (lazy AJAX bodies): overview, tasks, mailboxes, databases, ftp,
   dns, cron, backups, logs, deploy. *}
{include file="$dir/templates/_task_log_modal.tpl"}

{literal}
<style>
/* Containment: stop any inner element (long log lines, wide tables, etc.)
   from growing the panel beyond the WHMCS column it lives in. Without
   these, a 1500-char Apache log line stretches the parent form and
   produces a horizontal scrollbar on the outer WHMCS page. Scoped to
   the panel root so we don't disturb WHMCS's own body styles. */
#puq-svc-root, #puq-svc-root *, #puq-svc-root *::before, #puq-svc-root *::after { box-sizing:border-box; }
#puq-svc-root { width:100%; max-width:100%; min-width:0; overflow:hidden; }
.puq-svc-tab-content, .tab-pane { min-width:0; max-width:100%; }

.puq-svc-card { padding:10px 14px; margin-bottom:12px; background:#fff; border:1px solid #ddd; border-radius:3px; }
.puq-svc-bar { display:flex; align-items:center; gap:14px; flex-wrap:wrap; }
.puq-svc-bar h3 { margin:0; font-size:18px; }
.puq-svc-bar h3 .domain { color:#337ab7; }
.puq-svc-bar .spacer { flex:1; }
.puq-svc-meta { display:flex; gap:18px; font-size:11px; color:#555; margin-top:8px; padding-top:6px; border-top:1px solid #eee; flex-wrap:wrap; }
.puq-svc-meta code { background:#f5f5f5; padding:1px 4px; border-radius:2px; }
.puq-svc-tab-content { border:1px solid #ddd; border-top:none; padding:14px; background:#fafafa; min-height:360px; }
.puq-tab-loading { padding:40px; text-align:center; color:#999; }
</style>
{/literal}

{if !$service_row}
    <div class="alert alert-warning">
        <i class="fa fa-exclamation-triangle"></i> Service id <code>{$puq_service_id}</code> not found.
        <a href="{$meta.admin_module_url}&page=services_summary">Back to services</a>.
    </div>
{else}
{* Vanity services are a per-service WEB account + ONE mailbox on the SHARED
   provider mail user (+ a single DNS record pushed to the provider's zone).
   So the admin panel hides the per-service DNS identity/zone, the multi-mailbox
   + mail-backup + mail-SSL bits (provider-managed), and shows the single
   mailbox address instead. Normal hosting keeps the full Web&DNS + Mail view. *}
{assign var=isVanity value=($service_row.deployment_mode eq 'vanity')}
{assign var=vMailDom value=$service_row.vanity_mail_domain}
{if $vMailDom eq ''}{assign var=vMailDom value=$service_row.vanity_parent_domain}{/if}
<div id="puq-svc-root">
<div class="puq-svc-card" id="puq-svc-card">
    <div class="puq-svc-bar">
        <h3>
            <i class="fa fa-globe"></i>
            <a class="domain" href="http://{$service_row.domain|escape:'html'}/" target="_blank" rel="noopener" title="Open http://{$service_row.domain|escape:'html'}/ in a new tab">{$service_row.domain|escape:'html'}</a>
            <button type="button" class="btn btn-default btn-xs" id="puq-svc-change-domain"
                    title="Change domain (DESTRUCTIVE — wipes everything and redeploys)">
                <i class="fa fa-exchange-alt"></i> Change domain
            </button>
        </h3>
        <span class="label label-default" id="puq-svc-status">{$service_row.status|escape:'html'}</span>
        <span class="label label-info" id="puq-svc-deploy-status">{$service_row.deploy_status|escape:'html'}</span>
        {if $service_row.deployment_mode eq 'unified'}
        <span class="label label-primary" title="One Hestia user holds web+mail (+dns when local) — see Unified deployment docs">unified</span>
        {/if}
        {if $isVanity}
        <span class="label" style="background:#8e44ad;" title="Vanity: per-service web account + a single mailbox on the SHARED provider mail user; DNS is a single record on the provider's zone">vanity</span>
        {/if}
        <span class="spacer"></span>
        <button type="button" class="btn btn-default btn-sm" id="puq-svc-edit-row" title="Edit raw DB fields (admin escape hatch)"><i class="fa fa-edit"></i> Edit DB row</button>
        <button type="button" class="btn btn-info btn-sm" id="puq-svc-reveal-creds" title="Show decrypted web/mail/dns passwords (admin-only)"><i class="fa fa-key"></i> Reveal credentials</button>
        <button type="button" class="btn btn-primary btn-sm" id="puq-svc-send-welcome" title="Re-send the welcome email to the customer ({if $isVanity}Vanity Account Ready{else}Account Ready{/if})"><i class="fa fa-paper-plane"></i> Send welcome email</button>
        <button type="button" class="btn btn-warning btn-sm" id="puq-svc-redeploy"><i class="fa fa-sync"></i> Redeploy service</button>
        <button type="button" class="btn btn-danger btn-sm" id="puq-svc-factory-reset"><i class="fa fa-bomb"></i> Factory reset</button>
    </div>
    {if $service_row.deploy_error}
    <div class="alert alert-danger" style="margin-top:8px;padding:6px 10px;font-size:12px;">
        <i class="fa fa-exclamation-triangle"></i> Deploy error: {$service_row.deploy_error|escape:'html'}
    </div>
    {/if}
    {* Restore-in-progress banner — rendered server-side from the per-role
       lock flag. Refreshed by loadResourceCounts when it sees the flag
       transition. JS clears innerHTML when both locks are 0. *}
    <div id="puq-svc-ops-banner" style="margin-top:8px;">
        {if $service_row.deployment_mode eq 'unified' and $service_row.web_restore_lock}
        {* Unified-mode lock anchors on web_restore_lock but covers the whole
           account — the banner reflects the broader scope so admin doesn't
           think only web is frozen. *}
        <div class="alert alert-warning" style="padding:6px 10px;font-size:12px;margin-bottom:6px;">
            <i class="fas fa-circle-notch fa-spin"></i> <strong>Account</strong> is being restored from a backup{if $service_row.web_restore_started_at} (since {$service_row.web_restore_started_at|truncate:16:''|escape:'html'}){/if} — every client mutation (web, mail, dns) is locked until the probe sees the queue drain.
        </div>
        {else}
            {if $service_row.web_restore_lock}
            <div class="alert alert-warning" style="padding:6px 10px;font-size:12px;margin-bottom:6px;">
                <i class="fas fa-circle-notch fa-spin"></i> <strong>Web</strong> is being restored from a backup{if $service_row.web_restore_started_at} (since {$service_row.web_restore_started_at|truncate:16:''|escape:'html'}){/if} — client mutations on the web role are locked until the probe sees the queue drain.
            </div>
            {/if}
            {if $service_row.mail_restore_lock}
            <div class="alert alert-warning" style="padding:6px 10px;font-size:12px;margin-bottom:6px;">
                <i class="fas fa-circle-notch fa-spin"></i> <strong>Mail</strong> is being restored from a backup{if $service_row.mail_restore_started_at} (since {$service_row.mail_restore_started_at|truncate:16:''|escape:'html'}){/if} — client mutations on the mail role are locked until the probe sees the queue drain.
            </div>
            {/if}
        {/if}
    </div>
    {* Combined identity + usage panel. Each group carries its own
       role-scoped identity row(s) (web+dns or mail) at the top, then the
       usage cards below — populated by loadResourceCounts() via AJAX. *}
    <div id="puq-svc-usage-bars" class="puq-svc-usage-bars">
        <div class="puq-usage-group">
            <div class="puq-usage-group-title"><i class="fa fa-globe" style="color:#337ab7;"></i> {if $isVanity}Website{else}Web &amp; DNS{/if}</div>
            <div class="puq-usage-identity">
                <div class="puq-id-row">
                    <i class="fa fa-server"></i> web
                    <code>{$service_row.web_username|default:'—'|escape:'html'}</code>
                    <span class="at">@</span>
                    {if $web_server}<a href="{$meta.addon_module_url}&page=server_actions&server_id={$web_server.id}" target="_blank">{$web_server.name|escape:'html'}</a>{else}<span class="text-muted">—</span>{/if}
                    <span class="label label-default puq-real puq-checking" id="puq-real-web" title="Probing panel…"><i class="fa fa-spinner fa-spin"></i></span>
                    {if $service_row.domain}
                        <span class="puq-id-sep">·</span>
                        <a class="puq-id-host" href="http://{$service_row.domain|escape:'html'}/" target="_blank" rel="noopener" title="Open http://{$service_row.domain|escape:'html'}/ in new tab"><i class="fa fa-external-link-alt"></i> {$service_row.domain|escape:'html'}</a>
                    {/if}
                </div>
                {if $isVanity}
                {* Vanity: no per-service zone — a single record on the
                   provider's zone (cluster). Show an informational note, not a
                   probe-able dns user/zone row. *}
                <div class="puq-id-row">
                    <i class="fa fa-network-wired text-muted"></i> dns
                    <span class="text-muted" style="font-size:12px;">single record on the provider zone (cluster-managed)</span>
                </div>
                {else}
                <div class="puq-id-row">
                    <i class="fa fa-network-wired"></i> dns
                    <code>{$service_row.dns_username|default:'—'|escape:'html'}</code>
                    <span class="at">@</span>
                    {if $dns_servers}
                        {foreach from=$dns_servers item=ds name=dnsi}
                            <a href="{$meta.addon_module_url}&page=server_actions&server_id={$ds.id}" target="_blank">{$ds.name|escape:'html'}</a>
                            <span class="label label-default puq-real puq-checking puq-real-dns" data-server-id="{$ds.id}" title="Probing panel…"><i class="fa fa-spinner fa-spin"></i></span>
                            {if !$smarty.foreach.dnsi.last}<span class="puq-id-sep">·</span>{/if}
                        {/foreach}
                    {else}
                        <span class="text-muted">none</span>
                    {/if}
                </div>
                {/if}
            </div>
            <div class="puq-ssl-block" data-kind="web"><div class="puq-ssl-loading"><i class="fa fa-spinner fa-spin"></i> SSL…</div></div>
            <div class="puq-usage-grid puq-usage-grid-loading">
                <div class="puq-usage-loading"><i class="fa fa-spinner fa-spin"></i> Loading usage…</div>
            </div>
        </div>
        <div class="puq-usage-group">
            <div class="puq-usage-group-title"><i class="fa fa-envelope" style="color:#5cb85c;"></i> {if $isVanity}Mailbox{else}Mail{/if}</div>
            <div class="puq-usage-identity">
                {if $isVanity}
                {* Vanity: ONE mailbox on the SHARED provider mail user. Show the
                   mailbox address (name@provider-domain) + which shared user /
                   server hosts it. No per-service domain probe (the shared user
                   owns many tenants → "NO DOMAIN" was misleading). Webmail is on
                   the PROVIDER mail domain, not the vanity fqdn. *}
                <div class="puq-id-row">
                    <i class="fa fa-at"></i> mailbox
                    <code>{if $service_row.vanity_name ne '' and $vMailDom ne ''}{$service_row.vanity_name|escape:'html'}@{$vMailDom|escape:'html'}{else}—{/if}</code>
                </div>
                <div class="puq-id-row">
                    <i class="fa fa-server text-muted"></i>
                    <span class="text-muted" style="font-size:12px;">shared user</span>
                    <code>{$service_row.mail_username|default:'—'|escape:'html'}</code>
                    <span class="at">@</span>
                    {if $mail_server}<a href="{$meta.addon_module_url}&page=server_actions&server_id={$mail_server.id}" target="_blank">{$mail_server.name|escape:'html'}</a>{else}<span class="text-muted">—</span>{/if}
                    {if $vMailDom ne ''}
                    <span class="puq-id-sep">·</span>
                    <a class="puq-id-host" href="https://webmail.{$vMailDom|escape:'html'}/" target="_blank" rel="noopener" title="Open webmail.{$vMailDom|escape:'html'} in new tab"><i class="fa fa-external-link-alt"></i> webmail.{$vMailDom|escape:'html'}</a>
                    {/if}
                </div>
                {else}
                <div class="puq-id-row">
                    <i class="fa fa-envelope"></i> mail
                    <code>{$service_row.mail_username|default:'—'|escape:'html'}</code>
                    <span class="at">@</span>
                    {if $mail_server}<a href="{$meta.addon_module_url}&page=server_actions&server_id={$mail_server.id}" target="_blank">{$mail_server.name|escape:'html'}</a>{else}<span class="text-muted">—</span>{/if}
                    <span class="label label-default puq-real puq-checking" id="puq-real-mail" title="Probing panel…"><i class="fa fa-spinner fa-spin"></i></span>
                </div>
                {if $service_row.domain}
                <div class="puq-id-row">
                    <i class="fa fa-globe text-muted"></i>
                    <a class="puq-id-host" href="http://mail.{$service_row.domain|escape:'html'}/" target="_blank" rel="noopener" title="Open mail.{$service_row.domain|escape:'html'} in new tab"><i class="fa fa-external-link-alt"></i> mail.{$service_row.domain|escape:'html'}</a>
                    <span class="puq-id-sep">·</span>
                    <a class="puq-id-host" href="http://webmail.{$service_row.domain|escape:'html'}/" target="_blank" rel="noopener" title="Open webmail.{$service_row.domain|escape:'html'} in new tab"><i class="fa fa-external-link-alt"></i> webmail.{$service_row.domain|escape:'html'}</a>
                </div>
                {/if}
                {/if}
            </div>
            {if $isVanity}
            <div class="text-muted" style="font-size:12px;padding:4px 2px;"><i class="fa fa-lock"></i> Mail SSL is managed on the provider mail domain (not per-service).</div>
            {else}
            <div class="puq-ssl-block" data-kind="mail"><div class="puq-ssl-loading"><i class="fa fa-spinner fa-spin"></i> SSL…</div></div>
            {/if}
            <div class="puq-usage-grid puq-usage-grid-loading">
                <div class="puq-usage-loading"><i class="fa fa-spinner fa-spin"></i> Loading usage…</div>
            </div>
        </div>
    </div>
</div>

{* Change domain modal — destructive. Wipes everything (Hestia users, all
   local child rows) and re-runs the full provisioning chain under the
   new name. Gated by typed-current-domain match. *}
<div class="modal fade" id="puq-svc-change-domain-modal" tabindex="-1" role="dialog">
    <div class="modal-dialog" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title"><i class="fa fa-globe"></i> Change service domain</h4>
            </div>
            <div class="modal-body">
                <div class="alert alert-danger" style="padding:10px 14px;font-size:13px;">
                    <strong><i class="fa fa-exclamation-triangle"></i> Destructive — there is no undo.</strong><br>
                    Changing the domain will:
                    <ul style="margin:6px 0 6px 0;padding-left:18px;">
                        <li>delete the Hestia <strong>web</strong> user (and all web domains, databases, FTP)</li>
                        <li>delete the Hestia <strong>mail</strong> user (and all mailboxes)</li>
                        <li>delete every <strong>DNS zone + record</strong> on every cluster member</li>
                        <li>wipe local rows for mailboxes / databases / ftp / cron / backups / dns</li>
                    </ul>
                    Then a fresh deploy starts with the new domain. The service
                    will be unavailable until the new chain completes.
                </div>
                <div class="form-group">
                    <label>Current domain</label>
                    <input type="text" class="form-control input-sm" value="{$service_row.domain|escape:'html'}" disabled>
                </div>
                <div class="form-group">
                    <label>New domain</label>
                    <input type="text" class="form-control input-sm" id="puq-svc-new-domain" placeholder="newexample.com" autocomplete="off">
                    <span class="help-block small">Lowercase only. Must be a valid registered domain. Cannot be the current domain.</span>
                </div>
                <div class="form-group">
                    <label>Type the <strong>current</strong> domain to confirm</label>
                    <input type="text" class="form-control input-sm" id="puq-svc-confirm-domain" placeholder="{$service_row.domain|escape:'html'}" autocomplete="off">
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-danger" id="puq-svc-change-domain-submit" disabled>
                    <i class="fa fa-exchange-alt"></i> Change &amp; redeploy
                </button>
            </div>
        </div>
    </div>
</div>

{* Edit DB row modal — admin escape hatch (proxmox-style). *}
<div class="modal fade" id="puq-svc-edit-modal" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title">Edit puqWebHosting_services row #{$puq_service_id}</h4>
            </div>
            <div class="modal-body">
                <div class="alert alert-warning" style="padding:8px 12px;font-size:12px;">
                    <i class="fa fa-exclamation-triangle"></i> Direct DB edit — bypasses provisioner validation. Use for fixing rows that got into a bad state.
                </div>
                {* CRITICAL: every input in this modal is rendered DISABLED so
                   it doesn't submit to the WHMCS outer form (clientsservices.php
                   wraps the whole page in a <form action="clientsservices.php">).
                   Inputs in a "nested" form still belong to the OUTER form per
                   HTML5 (nested <form> is invalid and flattened). Without
                   disabled, names like `id`/`domain`/`status` would override
                   WHMCS's own fields when admin clicks the WHMCS "Save Changes"
                   button → redirected to "Service ID Not Found".
                   The JS modal-open handler removes `disabled` so the form
                   works normally while open; modal-hide restores it. *}
                {* Values rendered server-side from $service_row.
                   IMPORTANT: each input carries `form="puq-svc-edit-form"`
                   — HTML5 form-owner attribute. Browsers honour this even
                   when the modal is nested inside the outer WHMCS form
                   (clientsservices.php wraps the whole page in a `<form>`,
                   and per spec nested forms get flattened — without an
                   explicit form attr these inputs would be submitted with
                   WHMCS's outer form on Save Changes click, blowing away
                   the URL with stray `name=domain`/`name=status` params).
                   The `form` attribute pins ownership to our form ONLY.
                   That's why we no longer need the `disabled` toggle dance
                   that was flaky across jQuery/Bootstrap versions. *}
                {assign var="_limits_raw" value=$service_row.limits|default:'{}'}
                {assign var="_usage_raw"  value=$service_row.usage|default:'{}'}
                <form id="puq-svc-edit-form" class="puq-isolate-form">
                    <input type="hidden" name="id" form="puq-svc-edit-form" value="{$puq_service_id}">
                    <div class="row">
                        <div class="col-md-6 form-group"><label>Domain</label><input name="domain" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.domain|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>Status</label><input name="status" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.status|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>Deploy status</label><input name="deploy_status" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.deploy_status|default:''|escape:'html'}"></div>
                    </div>
                    <div class="form-group"><label>Deploy error</label><textarea name="deploy_error" form="puq-svc-edit-form" class="form-control input-sm" rows="2">{$service_row.deploy_error|default:''|escape:'html'}</textarea></div>
                    <div class="row">
                        <div class="col-md-3 form-group"><label>Web server id</label><input name="web_server_id" form="puq-svc-edit-form" type="number" class="form-control input-sm" value="{$service_row.web_server_id|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>Web username</label><input name="web_username" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.web_username|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>Mail server id</label><input name="mail_server_id" form="puq-svc-edit-form" type="number" class="form-control input-sm" value="{$service_row.mail_server_id|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>Mail username</label><input name="mail_username" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.mail_username|default:''|escape:'html'}"></div>
                    </div>
                    <div class="row">
                        <div class="col-md-3 form-group"><label>DNS username</label><input name="dns_username" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.dns_username|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>External DNS</label><input name="external_dns" form="puq-svc-edit-form" type="number" min="0" max="1" class="form-control input-sm" value="{$service_row.external_dns|default:'0'|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>NS verified at</label><input name="ns_verified_at" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.ns_verified_at|default:''|escape:'html'}"></div>
                        <div class="col-md-3 form-group"><label>DNS verified at</label><input name="dns_verified_at" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.dns_verified_at|default:''|escape:'html'}"></div>
                    </div>
                    <div class="row">
                        <div class="col-md-3 form-group"><label>DKIM selector</label><input name="dkim_selector" form="puq-svc-edit-form" class="form-control input-sm" value="{$service_row.dkim_selector|default:''|escape:'html'}"></div>
                        <div class="col-md-9 form-group">
                            <label>DKIM key <small class="text-muted">(public, base64)</small></label>
                            <textarea name="dkim_key" form="puq-svc-edit-form" class="form-control input-sm" rows="2" style="font-family:monospace;font-size:10px;">{$service_row.dkim_key|default:''|escape:'html'}</textarea>
                        </div>
                    </div>
                    <div class="form-group">
                        <label>Limits (JSON)</label>
                        <textarea name="limits" form="puq-svc-edit-form" class="form-control input-sm" rows="6" style="font-family:monospace;font-size:11px;">{$_limits_raw|escape:'html'}</textarea>
                    </div>
                    <div class="form-group">
                        <label>Usage (JSON)</label>
                        <textarea name="usage" form="puq-svc-edit-form" class="form-control input-sm" rows="4" style="font-family:monospace;font-size:11px;">{$_usage_raw|escape:'html'}</textarea>
                    </div>
                </form>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
                <button type="button" class="btn btn-primary" id="puq-svc-edit-save"><i class="fa fa-save"></i> Save row</button>
            </div>
        </div>
    </div>
</div>

{* Reveal-credentials modal. Decrypted passwords are fetched lazily on
   button click (never embedded in page render) and cleared from the DOM
   when the modal closes — keeps plaintext out of inspector history. *}
<div class="modal fade" id="puq-svc-creds-modal" tabindex="-1" role="dialog">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            <div class="modal-header">
                <button type="button" class="close" data-dismiss="modal">&times;</button>
                <h4 class="modal-title"><i class="fa fa-key"></i> Service credentials</h4>
            </div>
            <div class="modal-body">
                <div class="alert alert-warning" style="padding:6px 10px;font-size:12px;">
                    <i class="fa fa-shield-alt"></i> These are the unix/FTP/panel
                    passwords stored encrypted in <code>puqWebHosting_services.*_password_enc</code>.
                    The Hestia user account has the SAME password (set via
                    <code>v-add-user</code>), so this is also the customer's FTP login.
                </div>
                {if $service_row.deployment_mode eq 'unified'}
                <div class="alert alert-info" style="padding:6px 10px;font-size:12px;">
                    <i class="fa fa-info-circle"></i> This service runs in
                    <strong>unified deployment</strong> — web, mail and dns share
                    one Hestia user account. The three credential blocks below
                    hold the same username and password.
                </div>
                {/if}
                <div id="puq-svc-creds-body">
                    <div class="text-center text-muted" style="padding:30px;">
                        <i class="fa fa-spinner fa-spin"></i> Loading...
                    </div>
                </div>
            </div>
            <div class="modal-footer">
                <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
            </div>
        </div>
    </div>
</div>

{literal}
<style>
#puq-svc-creds-body table { margin-bottom:0; }
#puq-svc-creds-body th { width:90px; font-weight:600; color:#555; }
#puq-svc-creds-body code { background:#f5f5f5; padding:2px 6px; border-radius:2px; font-size:13px; word-break:break-all; }
#puq-svc-creds-body .puq-creds-role { margin-bottom:12px; padding:8px 12px; border:1px solid #e5e8ec; border-radius:4px; background:#fafbfc; }
#puq-svc-creds-body .puq-creds-role h5 { margin:0 0 6px; font-size:13px; text-transform:uppercase; letter-spacing:0.5px; color:#337ab7; }
#puq-svc-creds-body .puq-creds-copy { cursor:pointer; color:#888; margin-left:6px; }
#puq-svc-creds-body .puq-creds-copy:hover { color:#337ab7; }
</style>
{/literal}

{literal}
<style>
.puq-tab-count { display:none; margin-left:5px; padding:1px 7px; font-size:11px; background:#5bc0de; color:#fff; border-radius:9px; vertical-align:middle; }
.puq-tab-count.puq-warn { background:#f0ad4e; }
.puq-tab-count.puq-zero { background:#bbb; }
.puq-svc-bar .label.puq-real { transition: background-color 0.25s; }
.puq-svc-bar .label.puq-checking { background:#e8e8e8; color:#666; }
.puq-svc-usage-bars {
    display:grid;
    grid-template-columns: 2fr 1fr;
    gap:14px;
    margin-top:14px;
}
@media (max-width: 900px) { .puq-svc-usage-bars { grid-template-columns: 1fr; } }
.puq-usage-group {
    background:#fff;
    border:1px solid #e5e8ec;
    border-radius:6px;
    padding:8px 10px 10px;
}
.puq-usage-group-title {
    font-size:11px; text-transform:uppercase; letter-spacing:0.5px;
    color:#888; font-weight:600; margin:0 0 8px; padding-left:2px;
    display:flex; align-items:center; gap:6px;
}
.puq-usage-group-title i { font-size:11px; }
.puq-usage-identity {
    background:#f7f8fa;
    border:1px solid #e9ecef;
    border-radius:4px;
    padding:6px 10px;
    margin-bottom:8px;
    font-size:11px;
}
.puq-usage-identity .puq-id-row {
    display:flex; align-items:center; flex-wrap:wrap; gap:5px;
    line-height:1.6;
}
.puq-usage-identity .puq-id-row + .puq-id-row {
    margin-top:2px; padding-top:2px; border-top:1px dashed #e3e6ea;
}
.puq-usage-identity .puq-id-row > i { color:#888; width:13px; }
.puq-usage-identity .puq-id-row code { background:#fff; padding:0 4px; border:1px solid #e3e6ea; border-radius:2px; }
.puq-usage-identity .puq-id-row .at { color:#aaa; }
.puq-usage-identity .puq-id-row a { font-weight:600; }
.puq-usage-identity .puq-id-row .label { font-size:10px; padding:1px 6px; }
.puq-usage-identity .puq-id-sep { color:#bbb; margin:0 4px; }
.puq-usage-identity .puq-id-host {
    color:#337ab7; font-weight:600; text-decoration:none;
    background:#eff6ff; border:1px solid #d3e3f7; border-radius:3px;
    padding:1px 7px; font-size:11px;
}
.puq-usage-identity .puq-id-host:hover { background:#dceaff; text-decoration:none; }
.puq-usage-identity .puq-id-host i.fa-external-link-alt { font-size:9px; margin-right:3px; opacity:0.7; }
.puq-usage-grid {
    display:grid;
    grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
    gap:8px;
}
.puq-usage-loading {
    grid-column: 1 / -1;
    padding:18px 12px; text-align:center; font-size:12px; color:#999;
}
.puq-usage-loading .fa-spinner { margin-right:6px; }
.puq-usage-grid.is-loading > .puq-usage-cell { opacity:0.5; pointer-events:none; }
.puq-ssl-block {
    background:#fff;
    border:1px solid #e5e8ec;
    border-radius:4px;
    padding:6px 10px;
    margin-bottom:8px;
    font-size:11px;
}
.puq-ssl-loading { padding:8px 4px; color:#999; text-align:center; }
.puq-ssl-row { display:flex; align-items:center; flex-wrap:wrap; gap:8px; }
.puq-ssl-row .puq-ssl-label { font-weight:600; display:flex; align-items:center; gap:5px; }
.puq-ssl-row .puq-ssl-label i { width:13px; color:#888; }
.puq-ssl-row .puq-ssl-info { color:#666; font-size:11px; flex:1; min-width:160px; }
.puq-ssl-row .puq-ssl-info .v { color:#333; font-weight:500; }
.puq-ssl-row .label { font-size:10px; padding:2px 6px; }
.puq-ssl-row .puq-ssl-btn { font-size:10px; padding:2px 7px; }
.puq-ssl-detail { margin-top:5px; padding-top:5px; border-top:1px dashed #ececec; color:#888; font-size:10px; line-height:1.5; }
.puq-ssl-detail code { background:#f5f5f5; padding:0 3px; border-radius:2px; font-size:10px; }
.puq-ssl-status-active        { background:#5cb85c; color:#fff; }
.puq-ssl-status-expired       { background:#d9534f; color:#fff; }
.puq-ssl-status-none          { background:#999;    color:#fff; }
.puq-ssl-status-failed        { background:#d9534f; color:#fff; }
.puq-ssl-status-frozen        { background:#f0ad4e; color:#fff; }
.puq-ssl-status-pending       { background:#5bc0de; color:#fff; }
.puq-ssl-status-installing    { background:#5bc0de; color:#fff; }
.puq-ssl-status-wrong_subject { background:#5bc0de; color:#fff; }
.puq-ssl-row strong.puq-ssl-host { color:#337ab7; font-weight:600; }
.puq-ssl-row a { color:#337ab7; text-decoration:none; }
.puq-ssl-row a:hover { text-decoration:underline; }
.puq-ssl-detail a.puq-host-link { color:#5b6571; text-decoration:none; border-bottom:1px dashed #bcc4cd; }
.puq-ssl-detail a.puq-host-link:hover { color:#337ab7; border-bottom-color:#337ab7; }
.puq-ssl-detail .puq-host-chip {
    display:inline-block; background:#eff6ff; border:1px solid #d3e3f7;
    border-radius:3px; padding:0 6px; margin:1px 3px 1px 0; color:#337ab7;
    font-family:monospace; font-size:10px; text-decoration:none;
}
.puq-ssl-detail .puq-host-chip:hover { background:#dceaff; color:#1d4ed8; text-decoration:none; }
.puq-ssl-detail .puq-host-chip.puq-host-miss { background:#fbeaea; border-color:#f5c2c7; color:#a94442; }
.puq-usage-cell {
    background:#fafbfc;
    border:1px solid #e5e8ec;
    border-radius:6px;
    padding:8px 10px;
    font-size:12px;
    transition: box-shadow 0.15s;
}
.puq-usage-cell:hover { box-shadow:0 1px 4px rgba(0,0,0,0.08); }
.puq-usage-cell .puq-usage-head {
    display:flex; align-items:center; justify-content:space-between;
    margin-bottom:6px; gap:8px;
}
.puq-usage-cell .puq-usage-name { font-weight:600; color:#444; display:flex; align-items:center; gap:6px; }
.puq-usage-cell .puq-usage-name i { width:14px; text-align:center; }
.puq-usage-cell .puq-usage-value { color:#222; font-size:11px; white-space:nowrap; }
.puq-usage-cell .puq-usage-value .num { font-weight:600; }
.puq-usage-cell .puq-usage-value .lim { color:#999; }
.puq-usage-cell .progress {
    height:6px; margin:0; border-radius:3px;
    background:#e9ecef; box-shadow:none; overflow:hidden;
}
.puq-usage-cell .progress-bar { transition: width 0.4s ease-out; min-width:1px; }
.puq-usage-cell .progress-bar.bg-success { background:linear-gradient(90deg,#5cb85c,#449d44); }
.puq-usage-cell .progress-bar.bg-warning { background:linear-gradient(90deg,#f0ad4e,#ec971f); }
.puq-usage-cell .progress-bar.bg-danger  { background:linear-gradient(90deg,#d9534f,#c9302c); }
.puq-usage-cell .progress-bar.bg-empty   { background:#bbb; }
/* Per-category icon colors */
.puq-usage-cell.cat-web    .puq-usage-name i { color:#337ab7; }
.puq-usage-cell.cat-mail   .puq-usage-name i { color:#5cb85c; }
.puq-usage-cell.cat-bw     .puq-usage-name i { color:#f0ad4e; }
.puq-usage-cell.cat-dns    .puq-usage-name i { color:#9d4edd; }
.puq-usage-cell.cat-db     .puq-usage-name i { color:#dc3545; }
.puq-usage-cell.cat-ftp    .puq-usage-name i { color:#17a2b8; }
.puq-usage-cell.cat-people .puq-usage-name i { color:#fd7e14; }
.puq-usage-cell.cat-cron   .puq-usage-name i { color:#6f42c1; }
.puq-usage-cell.cat-backup .puq-usage-name i { color:#20c997; }
</style>
{/literal}
<ul class="nav nav-tabs" id="puq-svc-tabs">
    <li{if $initial_tab eq 'overview'}     class="active"{/if}><a href="#tab-overview"     data-toggle="tab" data-tab="overview"><i class="fa fa-info-circle"></i> Overview</a></li>
    <li{if $initial_tab eq 'web_settings'} class="active"{/if}><a href="#tab-web_settings" data-toggle="tab" data-tab="web_settings"><i class="fa fa-cog"></i> Web settings</a></li>
    <li{if $initial_tab eq 'ssl'}          class="active"{/if}><a href="#tab-ssl"          data-toggle="tab" data-tab="ssl"><i class="fa fa-lock"></i> SSL</a></li>
    <li{if $initial_tab eq 'ftp'}          class="active"{/if}><a href="#tab-ftp"          data-toggle="tab" data-tab="ftp"><i class="fa fa-folder-open"></i> FTP <span class="puq-tab-count" id="puq-cnt-ftp"></span></a></li>
    <li{if $initial_tab eq 'databases'}    class="active"{/if}><a href="#tab-databases"    data-toggle="tab" data-tab="databases"><i class="fa fa-database"></i> Databases <span class="puq-tab-count" id="puq-cnt-databases"></span></a></li>
    {if !$isVanity}
    <li{if $initial_tab eq 'dns'}          class="active"{/if}><a href="#tab-dns"          data-toggle="tab" data-tab="dns"><i class="fa fa-network-wired"></i> DNS <span class="puq-tab-count" id="puq-cnt-dns_records"></span></a></li>
    {/if}
    <li{if $initial_tab eq 'mailboxes'}    class="active"{/if}><a href="#tab-mailboxes"    data-toggle="tab" data-tab="mailboxes"><i class="fa fa-envelope"></i> {if $isVanity}Mailbox{else}Mailboxes{/if} <span class="puq-tab-count" id="puq-cnt-mailboxes"></span></a></li>
    <li{if $initial_tab eq 'cron'}         class="active"{/if}><a href="#tab-cron"         data-toggle="tab" data-tab="cron"><i class="fa fa-clock"></i> Cron <span class="puq-tab-count" id="puq-cnt-cron"></span></a></li>
    <li{if $initial_tab eq 'backups'}      class="active"{/if}><a href="#tab-backups"      data-toggle="tab" data-tab="backups"><i class="fa fa-archive"></i> Backups <span class="puq-tab-count" id="puq-cnt-backups"></span></a></li>
    <li{if $initial_tab eq 'deploy'}       class="active"{/if}><a href="#tab-deploy"       data-toggle="tab" data-tab="deploy"><i class="fa fa-rocket"></i> Deploy <span class="puq-tab-count" id="puq-cnt-tasks_open"></span></a></li>
    <li{if $initial_tab eq 'tasks'}        class="active"{/if}><a href="#tab-tasks"        data-toggle="tab" data-tab="tasks"><i class="fa fa-list"></i> Tasks <span class="puq-tab-count" id="puq-cnt-tasks_total"></span></a></li>
    <li{if $initial_tab eq 'logs'}         class="active"{/if}><a href="#tab-logs"         data-toggle="tab" data-tab="logs"><i class="fa fa-file-alt"></i> Logs</a></li>
</ul>
<div class="tab-content puq-svc-tab-content">
    <div class="tab-pane{if $initial_tab eq 'overview'}     active{/if}" id="tab-overview"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'web_settings'} active{/if}" id="tab-web_settings"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'ssl'}          active{/if}" id="tab-ssl"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'ftp'}          active{/if}" id="tab-ftp"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'databases'}    active{/if}" id="tab-databases"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    {if !$isVanity}
    <div class="tab-pane{if $initial_tab eq 'dns'}          active{/if}" id="tab-dns"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    {/if}
    <div class="tab-pane{if $initial_tab eq 'mailboxes'}    active{/if}" id="tab-mailboxes"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'cron'}         active{/if}" id="tab-cron"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'backups'}      active{/if}" id="tab-backups"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'deploy'}       active{/if}" id="tab-deploy"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'tasks'}        active{/if}" id="tab-tasks"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
    <div class="tab-pane{if $initial_tab eq 'logs'}         active{/if}" id="tab-logs"><div class="puq-tab-loading"><i class="fa fa-spinner fa-spin"></i> Loading…</div></div>
</div>
</div>{* /#puq-svc-root *}
{/if}

{literal}
<script>
(function(){
    var baseUrl   = '{/literal}{$meta.admin_module_url|escape:javascript}&ajax={literal}';
    var adminUrl  = '{/literal}{$meta.admin_module_url|escape:javascript}{literal}';
    var puqId     = parseInt('{/literal}{$puq_service_id}{literal}', 10) || 0;
    var whmcsId   = parseInt('{/literal}{$service_row.whmcs_service_id|default:0}{literal}', 10) || 0;
    var initialTab = '{/literal}{$initial_tab|escape:javascript}{literal}';
    var isVanity   = {/literal}{if $isVanity}true{else}false{/if}{literal};

    if (puqId <= 0) return;

    function fmtDate(s){ return s ? esc(String(s).substring(0,19).replace('T',' ')) : '—'; }

    var tabsLoaded = {};

    function loadTab(tab, cb) {
        if (tabsLoaded[tab]) { if (cb) cb(); return; }
        var $pane = $('#tab-' + tab);
        $.ajax({
            url: baseUrl + 'renderServiceDetailsTab&tab=' + encodeURIComponent(tab) + '&id=' + puqId,
            dataType: 'json',
            success: function(r) {
                if (r.status === 'success' && r.data && r.data.html != null) {
                    $pane.html(r.data.html);
                    tabsLoaded[tab] = true;
                    initTab(tab);
                } else {
                    $pane.html('<div class="alert alert-danger">' + esc(r.msg || 'Failed to load tab') + '</div>');
                }
                if (cb) cb();
            },
            error: function() { $pane.html('<div class="alert alert-danger">Request failed.</div>'); if (cb) cb(); }
        });
    }

    function initTab(tab) {
        // Each tab init function is defined globally on window.puqSvc.* — kept
        // in this single script so tabs can rely on shared helpers (esc, fmt,
        // baseUrl/puqId). Per-tab handlers are wired via event delegation on
        // the tab-pane container.
        var fn = (window.puqSvc && window.puqSvc['init_' + tab]) || null;
        if (typeof fn === 'function') fn({
            baseUrl: baseUrl, adminUrl: adminUrl, puqId: puqId, whmcsId: whmcsId,
            esc: esc, fmtDate: fmtDate,
            armAutoRefresh: armAutoRefresh
        });
    }

    // Shared auto-refresh helper. Tabs call:
    //   load = armAutoRefresh(tab, { selector: '#table', load: load,
    //                                states: ['queued','deploying',...] });
    // ...and get back a wrapped load() that schedules a 10 s tick whenever at
    // least one row in `selector` shows a label matching `states`. Leaving the
    // tab clears the timer.
    var puqSvcRefreshTimers = {};
    function armAutoRefresh(tab, opts) {
        opts = opts || {};
        var sel = opts.selector;
        var states = opts.states || ['queued','deploying','pending_delete','deleting','creating','restoring'];
        var originalLoad = opts.load;
        if (typeof originalLoad !== 'function' || !sel) return originalLoad;

        function clearT() {
            if (puqSvcRefreshTimers[tab]) {
                clearTimeout(puqSvcRefreshTimers[tab]);
                puqSvcRefreshTimers[tab] = null;
            }
        }
        function arm() {
            clearT();
            var $labels = $(sel).find('.label');
            var transient = $labels.filter(function(){
                return $.inArray($.trim($(this).text()), states) >= 0;
            }).length > 0;
            if (transient) puqSvcRefreshTimers[tab] = setTimeout(wrapped, 10000);
        }
        var wrapped = function() { originalLoad(); setTimeout(arm, 200); };
        // Clear on tab leave so we don't poll invisible tabs.
        $('#puq-svc-tabs a[data-tab="' + tab + '"]').off('hide.bs.tab.puqar').on('hide.bs.tab.puqar', clearT);
        return wrapped;
    }

    // Document-level handlers for header buttons.
    // Header Redeploy is the hard-reset path: rejected unless deploy_status='error'.
    $('#puq-svc-redeploy').on('click', function(){
        puqConfirm({
            type: 'danger', icon: 'fa-sync',
            title: 'Hard-reset redeploy?',
            text: 'Deletes the panel user(s) for this service and re-creates everything from scratch. <strong>Only works when the service is in error state.</strong>',
            ok: 'Redeploy', cancel: 'Cancel',
            onConfirm: function(){
                return new Promise(function(resolve, reject){
                    $.ajax({
                        url: baseUrl + 'redeployService', type: 'POST', data: { service_id: whmcsId }, dataType: 'json',
                        success: function(r) {
                            if (r.status === 'success') {
                                showNotification(r.msg || 'Redeploy queued.', 'success');
                                if (tabsLoaded['deploy'] && window.puqSvc && window.puqSvc.refresh_deploy) {
                                    window.puqSvc.refresh_deploy();
                                }
                                resolve();
                            } else {
                                reject(new Error(r.msg || 'Redeploy failed'));
                            }
                        },
                        error: function(){ reject(new Error('Request failed')); }
                    });
                });
            }
        });
    });
    // Send welcome email — re-sends the lifecycle welcome (Account Ready, or
    // Vanity Account Ready for vanity profiles). The controller picks the
    // event/template by profile; this label is purely informational.
    var welcomeLabel = '{/literal}{if $isVanity}Vanity Account Ready{else}Account Ready{/if}{literal}';
    $('#puq-svc-send-welcome').on('click', function(){
        var btn = this;
        puqConfirm({
            type: 'info', icon: 'fa-paper-plane',
            title: 'Send welcome email?',
            text: 'Sends the <strong>' + welcomeLabel + '</strong> email to the customer now. For vanity services this includes the mailbox login, password, webmail link and mail-client settings.',
            ok: 'Send', cancel: 'Cancel',
            onConfirm: function(){
                return new Promise(function(resolve, reject){
                    $.ajax({
                        url: baseUrl + 'sendWelcomeEmail', type: 'POST', data: { id: puqId }, dataType: 'json',
                        success: function(r) {
                            if (r.status === 'success') {
                                showNotification(r.msg || 'Welcome email sent.', 'success');
                                resolve();
                            } else {
                                reject(new Error(r.msg || 'Send failed'));
                            }
                        },
                        error: function(){ reject(new Error('Request failed')); }
                    });
                });
            }
        });
    });
    // Edit DB row modal — values are pre-filled server-side. Inputs carry
    // `form="puq-svc-edit-form"` so they cannot leak into the outer WHMCS
    // form on Save Changes (HTML5 form-owner association overrides physical
    // nesting). No disabled/enable dance — just open the modal.
    $('#puq-svc-edit-row').on('click', function(){
        $('#puq-svc-edit-modal').modal('show');
    });
    // Reveal credentials — fetches decrypted passwords on-demand. Plaintext
    // is rendered into the modal body, cleared on close (NOT into the page
    // markup at render time) so it doesn't sit in the DOM or page-source.
    $('#puq-svc-reveal-creds').on('click', function(){
        var $body = $('#puq-svc-creds-body');
        $body.html('<div class="text-center text-muted" style="padding:30px;">'
                 + '<i class="fa fa-spinner fa-spin"></i> Decrypting...</div>');
        $('#puq-svc-creds-modal').modal('show');
        $.getJSON(baseUrl + 'revealServiceCredentials&service_id=' + encodeURIComponent(whmcsId), function(r){
            if (!r || r.status !== 'success') {
                $body.html('<div class="alert alert-danger">' + (r && r.msg ? r.msg : 'Failed to load') + '</div>');
                return;
            }
            var roles = [
                {key:'web',  label:'Web (FTP / panel / unix)', icon:'fa-globe'},
                {key:'mail', label:'Mail (separate Hestia user)', icon:'fa-envelope'},
                {key:'dns',  label:'DNS (separate Hestia user)', icon:'fa-network-wired'}
            ];
            var html = '';
            roles.forEach(function(role){
                var data = r.data[role.key] || {};
                var hasPass = data.password && data.password.length > 0;
                html += '<div class="puq-creds-role">'
                     + '  <h5><i class="fa ' + role.icon + '"></i> ' + role.label + '</h5>'
                     + '  <table class="table table-condensed table-borderless"><tbody>'
                     + '    <tr><th>Login:</th><td><code>' + (data.username ? esc(data.username) : '<span class="text-muted">— not yet generated —</span>') + '</code>'
                     + (data.username ? ' <i class="fa fa-copy puq-creds-copy" data-val="' + esc(data.username) + '" title="Copy"></i>' : '')
                     + '    </td></tr>'
                     + '    <tr><th>Password:</th><td>'
                     + (hasPass
                          ? '<code>' + esc(data.password) + '</code>'
                            + ' <i class="fa fa-copy puq-creds-copy" data-val="' + esc(data.password) + '" title="Copy"></i>'
                          : '<span class="text-muted">— not stored / decrypt failed —</span>')
                     + '    </td></tr>'
                     + '  </tbody></table>'
                     + '</div>';
            });
            $body.html(html);
            $body.find('.puq-creds-copy').on('click', function(){
                var v = $(this).data('val') || '';
                var ta = document.createElement('textarea');
                ta.value = v; document.body.appendChild(ta);
                ta.select(); try { document.execCommand('copy'); } catch(e){}
                document.body.removeChild(ta);
                var $icon = $(this);
                $icon.removeClass('fa-copy').addClass('fa-check').css('color', '#5cb85c');
                setTimeout(function(){ $icon.removeClass('fa-check').addClass('fa-copy').css('color',''); }, 1200);
            });
        }).fail(function(){
            $body.html('<div class="alert alert-danger">Network error</div>');
        });
    });
    // Wipe plaintext when modal closes — keeps secrets out of DOM history.
    $('#puq-svc-creds-modal').on('hidden.bs.modal', function(){
        $('#puq-svc-creds-body').html('');
    });
    $('#puq-svc-edit-save').on('click', function(){
        var btn = this; btnSpinner(btn, 'Saving...');
        $.ajax({
            url: baseUrl + 'editServiceRow', type: 'POST',
            data: $('#puq-svc-edit-form').serialize(), dataType: 'json',
            success: function(r){
                if (r.status === 'success') {
                    showNotification(r.msg || 'Saved.', 'success');
                    $('#puq-svc-edit-modal').modal('hide');
                    setTimeout(function(){ location.reload(); }, 600);
                } else {
                    showNotification(r.msg || 'Save failed', 'error');
                }
            },
            error: function(){ showNotification('Request failed.', 'error'); },
            complete: function(){ btnRestore(btn, 'save', 'Save row'); }
        });
    });

    // ---- Change Domain modal ----
    // Submit button stays disabled until both fields are filled AND the
    // confirm-input matches the current domain (case-insensitive). Mirrors
    // the Factory-reset confirm pattern but with two inputs (new + confirm).
    var currentDomain = '{/literal}{$service_row.domain|escape:javascript}{literal}';
    function updateChangeDomainButton(){
        var nd = $.trim($('#puq-svc-new-domain').val() || '').toLowerCase();
        var cd = $.trim($('#puq-svc-confirm-domain').val() || '').toLowerCase();
        var ok = (nd !== '' && cd === currentDomain.toLowerCase() && nd !== currentDomain.toLowerCase());
        $('#puq-svc-change-domain-submit').prop('disabled', !ok);
    }
    $('#puq-svc-change-domain').on('click', function(){
        $('#puq-svc-new-domain').val('');
        $('#puq-svc-confirm-domain').val('');
        $('#puq-svc-change-domain-submit').prop('disabled', true);
        $('#puq-svc-change-domain-modal').modal('show');
        setTimeout(function(){ $('#puq-svc-new-domain').focus(); }, 200);
    });
    $('#puq-svc-new-domain, #puq-svc-confirm-domain').on('input', updateChangeDomainButton);
    $('#puq-svc-change-domain-submit').on('click', function(){
        var btn = this;
        var newDomain = $.trim($('#puq-svc-new-domain').val() || '').toLowerCase();
        if (!newDomain) { showNotification('Enter the new domain', 'error'); return; }
        btnSpinner(btn, 'Changing & redeploying...');
        $.ajax({
            url: baseUrl + 'changeDomain', type: 'POST',
            data: { service_id: whmcsId, domain: newDomain }, dataType: 'json',
            success: function(r){
                showNotification(r.msg || (r.status === 'success' ? 'Domain change started.' : 'Failed.'), r.status === 'success' ? 'success' : 'error');
                if (r.status === 'success') {
                    $('#puq-svc-change-domain-modal').modal('hide');
                    setTimeout(function(){ location.reload(); }, 1200);
                }
            },
            error: function(){ showNotification('Request failed.', 'error'); },
            complete: function(){ btnRestore(btn, 'exchange-alt', 'Change &amp; redeploy'); }
        });
    });

    $('#puq-svc-factory-reset').on('click', function(){
        var dom = currentDomain;
        puqPrompt({
            type: 'danger', icon: 'fa-bomb',
            title: 'Factory reset?',
            text: '<strong>Destructive.</strong> Wipes everything for <code>' + esc(dom) + '</code> and re-runs provisioning from scratch.<br><br>Type the domain exactly to confirm:',
            placeholder: dom, ok: 'Factory reset',
            validator: function(v){ return $.trim(v).toLowerCase() === dom.toLowerCase(); },
            validatorMsg: 'Domain does not match.',
            onSubmit: function(val){
                return new Promise(function(resolve, reject){
                    $.ajax({
                        url: baseUrl + 'factoryReset', type: 'POST',
                        data: { service_id: whmcsId, confirm_domain: val }, dataType: 'json',
                        success: function(r) {
                            if (r.status === 'success') { showNotification(r.msg || 'Reset queued.', 'success'); resolve(); }
                            else reject(new Error(r.msg || 'Reset failed'));
                        },
                        error: function(){ reject(new Error('Request failed')); }
                    });
                });
            }
        });
    });

    // Light counts query — populates tab badges (mailboxes: 5 etc.) AND
    // the per-role usage bars in the card header.
    function fmtMb(mb) {
        mb = Number(mb || 0);
        if (mb >= 1024) return (mb / 1024).toFixed(1) + ' GB';
        return Math.round(mb) + ' MB';
    }
    function pctColor(pct) {
        if (pct >= 95) return 'bg-danger';
        if (pct >= 80) return 'bg-warning';
        return 'bg-success';
    }
    /**
     * Render a single usage cell.
     * @param o.category  CSS suffix → cat-web|cat-mail|cat-bw|cat-dns|cat-db|cat-ftp|cat-people
     * @param o.icon      Font Awesome class (e.g. "fa-server")
     * @param o.label     Display label
     * @param o.used      Current consumption
     * @param o.limit     Limit (0/null/undefined → "no limit")
     * @param o.unit      'mb' → format as MB/GB, '' → plain integer
     */
    function renderUsageBar(o) {
        var u = Number(o.used || 0);
        var l = Number(o.limit || 0);
        var hasLimit = l > 0;
        var pct = hasLimit ? Math.min(100, Math.round((u / l) * 100)) : 0;
        var num = (o.unit === 'mb') ? fmtMb(u) : u;
        var lim = hasLimit
            ? ((o.unit === 'mb') ? fmtMb(l) : l)
            : '∞';
        var width = hasLimit ? pct : (u > 0 ? 100 : 0);
        var color = hasLimit ? pctColor(pct) : (u > 0 ? 'bg-success' : 'bg-empty');
        return '<div class="puq-usage-cell cat-' + (o.category || 'web') + '">' +
            '<div class="puq-usage-head">' +
              '<span class="puq-usage-name"><i class="fa ' + o.icon + '"></i> ' + o.label + '</span>' +
              '<span class="puq-usage-value">' +
                '<span class="num">' + num + '</span>' +
                ' <span class="lim">/ ' + lim + '</span>' +
              '</span>' +
            '</div>' +
            '<div class="progress"><div class="progress-bar ' + color + '" style="width:' + width + '%;"></div></div>' +
        '</div>';
    }
    window.puqSvc = window.puqSvc || {};
    window.puqSvc.refresh_counts = function () { loadResourceCounts(); };
    window.puqSvc.refresh_ssl = function () { loadSslStatus(); };

    function fmtRelativeDays(iso) {
        if (!iso) return '';
        var t = Date.parse(iso); if (isNaN(t)) return esc(iso);
        var days = Math.floor((t - Date.now()) / 86400000);
        if (days < 0)  return Math.abs(days) + 'd ago';
        if (days === 0) return 'today';
        return 'in ' + days + 'd';
    }
    function hostChip(h, port, ok) {
        // Always http:// without port — user-facing browser links.
        // Even for mail hosts (mail./webmail.) HTTP is what admin wants
        // to test (Roundcube/RoundcubePro lives under webmail.*).
        var url = 'http://' + h + '/';
        var cls = 'puq-host-chip' + (ok === false ? ' puq-host-miss' : '');
        return '<a class="' + cls + '" href="' + esc(url) + '" target="_blank" rel="noopener" title="Open ' + esc(url) + ' in new tab">' + esc(h) + '</a>';
    }
    function renderSslRow(kind, row) {
        var iconKind = (kind === 'web') ? 'fa-lock' : 'fa-envelope';
        var probePort = (kind === 'web') ? 443 : 993;
        var statusCls, statusText, info, btnLabel;
        if (!row) {
            statusCls = 'puq-ssl-status-pending';
            statusText = 'pending init';
            info = '<span class="v">no tracker yet</span> — runs after deploy';
            btnLabel = 'Issue';
            return '<div class="puq-ssl-row">' +
                '<span class="puq-ssl-label"><i class="fa ' + iconKind + '"></i> SSL</span>' +
                '<span class="label ' + statusCls + '">' + esc(statusText) + '</span>' +
                '<span class="puq-ssl-info">' + info + '</span>' +
                '<button class="btn btn-default btn-xs puq-ssl-btn" data-kind="' + esc(kind) + '"><i class="fa fa-bolt"></i> ' + esc(btnLabel) + '</button>' +
                '</div>';
        }
        var dnsOk  = parseInt(row.last_check_ok, 10) === 1;
        var cert   = (row.cert_status || 'none').toLowerCase();
        var frozen = row.issue_frozen_until && Date.parse(row.issue_frozen_until) > Date.now();
        if (cert === 'active') {
            statusCls = 'puq-ssl-status-active';
            statusText = 'active';
            info = '<span class="v">expires ' + esc(row.cert_expires_at || '?') + '</span> <span class="text-muted">(' + esc(fmtRelativeDays(row.cert_expires_at)) + ')</span>';
            btnLabel = 'Renew';
        } else if (cert === 'expired') {
            statusCls = 'puq-ssl-status-expired';
            statusText = 'expired';
            info = '<span class="v">expired ' + esc(row.cert_expires_at || '?') + '</span>';
            btnLabel = 'Issue';
        } else if (frozen) {
            statusCls = 'puq-ssl-status-frozen';
            statusText = 'frozen';
            info = '<span class="v">frozen until ' + esc(row.issue_frozen_until) + '</span> · ' + esc(row.consecutive_issue_failures || 0) + ' fails';
            btnLabel = 'Retry';
        } else if (cert === 'wrong_subject') {
            statusCls = 'puq-ssl-status-installing';
            statusText = 'installing…';
            info = '<span class="v">server returned panel default cert</span> — issue queued';
            btnLabel = 'Issue now';
        } else if (!dnsOk) {
            statusCls = 'puq-ssl-status-pending';
            statusText = 'waiting DNS';
            info = '<span class="v">' + esc(row.consecutive_dns_fails || 0) + ' fails</span> · check #' + esc(row.checks_total || 0);
            btnLabel = 'Check';
        } else {
            statusCls = 'puq-ssl-status-installing';
            statusText = 'no cert';
            info = '<span class="v">DNS OK</span> — installing on next check';
            btnLabel = 'Issue now';
        }
        // Detail: cert info always (when probe data exists) + host chips
        var hostsArr = row.required_hosts || [];
        var sanArr   = row.cert_san || [];
        var sanLower = sanArr.map(function(s){ return String(s).toLowerCase(); });
        var hostChips = hostsArr.map(function(h){
            var inSan = sanLower.indexOf(String(h).toLowerCase()) !== -1;
            return hostChip(h, probePort, cert === 'active' ? inSan : null);
        }).join('');
        var sanChips = sanArr.map(function(h){ return hostChip(h, probePort, true); }).join('');
        var hasProbe = !!(row.cert_issuer || row.cert_expires_at || row.cert_subject);
        var detail =
            '<div class="puq-ssl-detail">' +
                '<div><i class="fa fa-globe"></i> hosts required: ' + hostChips + '</div>' +
                (hasProbe ?
                    '<div><i class="fa fa-certificate"></i> ' +
                        'CN <strong>' + esc(row.cert_subject || '—') + '</strong>' +
                        ' · issued by <strong>' + esc(row.cert_issuer || '—') + '</strong>' +
                        (row.cert_expires_at ? ' · valid until <strong>' + esc(row.cert_expires_at) + '</strong>' : '') +
                    '</div>' +
                    (sanChips ? '<div><i class="fa fa-tag"></i> SAN: ' + sanChips + '</div>' : '')
                    : '') +
                '<div><i class="fa fa-clock text-muted"></i> last check: ' + esc(row.last_check_at || 'never') +
                    ' — ' + esc((row.last_check_detail || '').substring(0, 220)) + '</div>' +
                (row.last_issue_error ? '<div class="text-danger"><i class="fa fa-exclamation-triangle"></i> last error: ' + esc(String(row.last_issue_error).substring(0, 220)) + '</div>' : '') +
            '</div>';
        // In-progress task → replace the action button with a disabled
        // progress indicator so admin can't queue duplicates.
        var inProg = row.in_progress || null;
        var actionEl;
        if (inProg) {
            var stage = inProg.action === 'service.sslAutoInstall'
                ? 'installing'
                : 'checking';
            actionEl = '<button class="btn btn-default btn-xs" disabled title="task #' + inProg.task_id + ' ' + esc(inProg.task_status) + '">'
                + '<i class="fa fa-spinner fa-spin"></i> ' + esc(stage) + ' (' + esc(inProg.task_status) + ')'
                + '</button>';
        } else {
            actionEl = '<button class="btn btn-default btn-xs puq-ssl-btn" data-kind="' + esc(kind) + '" title="Run DNS check + (re)issue if eligible — Let\'s Encrypt rate-limit protected"><i class="fa fa-bolt"></i> ' + esc(btnLabel) + '</button>';
        }
        return '<div class="puq-ssl-row">' +
            '<span class="puq-ssl-label"><i class="fa ' + iconKind + '"></i> SSL</span>' +
            '<span class="label ' + statusCls + '">' + esc(statusText) + '</span>' +
            '<span class="puq-ssl-info">' + info + '</span>' +
            actionEl +
            '</div>' + detail;
    }
    var sslAutoPollTimer = null;
    function loadSslStatus() {
        $('.puq-ssl-block').each(function(){ $(this).html('<div class="puq-ssl-loading"><i class="fa fa-spinner fa-spin"></i> SSL…</div>'); });
        var sslErr = function(msg){
            $('.puq-ssl-block').html('<div class="text-danger" style="padding:8px;font-size:12px;"><i class="fa fa-exclamation-triangle"></i> ' + esc(msg) + '</div>');
        };
        $.ajax({
            url: baseUrl + 'getServiceSslStatus&id=' + puqId, dataType: 'json',
            success: function(r) {
                if (r.status !== 'success' || !r.data) { sslErr((r && r.msg) ? r.msg : 'Failed to load SSL status.'); return; }
                $('.puq-ssl-block[data-kind="web"]').html(renderSslRow('web', r.data.web));
                $('.puq-ssl-block[data-kind="mail"]').html(renderSslRow('mail', r.data.mail));
                // Auto-poll every 10s while any SSL task is in flight so
                // the spinner / status updates without manual refresh.
                if (sslAutoPollTimer) { clearTimeout(sslAutoPollTimer); sslAutoPollTimer = null; }
                var busy = (r.data.web  && r.data.web.in_progress)
                        || (r.data.mail && r.data.mail.in_progress);
                if (busy) sslAutoPollTimer = setTimeout(loadSslStatus, 10000);
            },
            error: function(){ sslErr('Request failed — could not reach the server.'); }
        });
    }
    // Manual Issue / Renew / Check button — ASYNC enqueue. Server returns
    // immediately ("queued"); loadSslStatus() pulls the new task row + flips
    // the button to a disabled "checking/installing" spinner, then the
    // built-in auto-poll keeps the row fresh until cert flips active.
    $(document).on('click', '.puq-ssl-btn', function(){
        var btn = this;
        var kind = $(btn).data('kind');
        $(btn).prop('disabled', true).html('<i class="fa fa-spinner fa-spin"></i> Queuing...');
        $.ajax({ url: baseUrl + 'issueSslNow', type: 'POST', data: { id: puqId, kind: kind }, dataType: 'json',
            success: function(r){
                showNotification(r.msg || 'Done', r.status === 'success' ? 'success' : 'error');
                loadSslStatus(); // picks up the new in_progress task immediately
            },
            error: function(){ showNotification('Request failed.', 'error'); loadSslStatus(); }
        });
    });
    function renderOpsBanner(ops) {
        var $b = $('#puq-svc-ops-banner');
        if (!ops) { $b.empty(); return false; }
        var parts = [];
        ['web', 'mail'].forEach(function(role){
            var op = ops[role];
            if (!op) return;
            var Role = role.charAt(0).toUpperCase() + role.slice(1);
            var kind = op.kind === 'restore'
                ? 'is being restored from a backup'
                : 'backup is being created';
            var since = op.started_at ? ' (since ' + esc((op.started_at || '').substring(0, 16)) + ')' : '';
            var locks = op.kind === 'restore'
                ? ' — client mutations on the ' + role + ' role are locked until the probe sees the queue drain.'
                : '';
            parts.push(
                '<div class="alert alert-warning" style="padding:6px 10px;font-size:12px;margin-bottom:6px;">'
              + '<i class="fas fa-circle-notch fa-spin"></i> <strong>' + Role + '</strong> ' + kind + since + locks
              + '</div>'
            );
        });
        $b.html(parts.join(''));
        return parts.length > 0;
    }

    function loadResourceCounts() {
        // Dim existing cards in each group's grid while AJAX is in flight.
        $('#puq-svc-usage-bars .puq-usage-grid').addClass('is-loading');
        var gridsErr = function(msg){
            $('#puq-svc-usage-bars .puq-usage-grid').removeClass('is-loading puq-usage-grid-loading')
                .html('<div class="text-danger" style="padding:8px;font-size:12px;"><i class="fa fa-exclamation-triangle"></i> ' + esc(msg) + '</div>');
        };
        $.ajax({
            url: baseUrl + 'getResourceCounts&service_id=' + whmcsId, dataType: 'json',
            success: function(r) {
                if (r.status !== 'success' || !r.data || !r.data.counts) { gridsErr((r && r.msg) ? r.msg : 'Failed to load usage.'); return; }
                renderOpsBanner(r.data.ops || null);
                var c = r.data.counts;
                ['mailboxes','databases','ftp','dns_records','cron','backups','tasks_total','tasks_open'].forEach(function(k){
                    var $el = $('#puq-cnt-' + k);
                    if (!$el.length) return;
                    var v = parseInt(c[k] || 0, 10);
                    if (v <= 0) {
                        if (k === 'tasks_open') {
                            $el.removeClass('puq-warn puq-zero').addClass('puq-zero').text(0).show();
                        } else {
                            $el.hide();
                        }
                    } else {
                        $el.removeClass('puq-warn puq-zero').show().text(v);
                        if (k === 'tasks_open') $el.addClass('puq-warn');
                    }
                });

                // Usage cards. Limits come from getServiceOverview (one
                // extra request — cheap, since it also drives the Overview
                // tab); the live usage snapshot is already in counts.usage.
                var u = r.data.usage || {};
                $.ajax({
                    url: baseUrl + 'getServiceOverview&id=' + puqId, dataType: 'json',
                    success: function(r2) {
                        if (r2.status !== 'success' || !r2.data) { gridsErr((r2 && r2.msg) ? r2.msg : 'Failed to load limits.'); return; }
                        var lim = r2.data.limits || {};
                        var webCells = [
                            { category:'web',    icon:'fa-server',          label:'Web disk',       used: u.disk_web_mb,                       limit: lim.web_disk_quota,   unit:'mb' },
                            { category:'bw',     icon:'fa-arrow-down',      label:'Web bandwidth',  used: u.bw_used_mb,                        limit: lim.web_bandwidth,    unit:'mb' },
                            { category:'dns',    icon:'fa-network-wired',   label:'DNS records',    used: u.dns_records   || c.dns_records,    limit: lim.dns_records,      unit:''   },
                            { category:'db',     icon:'fa-database',        label:'Databases',      used: u.databases     || c.databases,      limit: lim.web_databases,    unit:''   },
                            { category:'ftp',    icon:'fa-folder-open',     label:'FTP',            used: u.ftp_accounts  || c.ftp,            limit: lim.web_ftp_accounts, unit:''   },
                            { category:'cron',   icon:'fa-clock',           label:'Cron jobs',      used: u.cron_jobs     || c.cron,           limit: lim.web_cron_jobs,    unit:''   },
                            { category:'backup', icon:'fa-archive',         label:'Web backups',    used: u.backups_web,                       limit: lim.web_backups,      unit:''   },
                        ];
                        var mailCells = [
                            { category:'mail',   icon:'fa-envelope',        label:'Mail disk',      used: u.disk_mail_mb,                      limit: lim.mail_disk_quota,  unit:'mb' },
                            { category:'people', icon:'fa-at',              label:'Mail accounts',  used: u.mail_accounts || c.mailboxes,      limit: lim.mail_accounts,    unit:''   },
                            { category:'backup', icon:'fa-archive',         label:'Mail backups',   used: u.backups_mail,                      limit: lim.mail_backups,     unit:''   },
                        ];
                        // Vanity: no per-service DNS zone (single provider-zone
                        // record) and no per-customer mail backup (shared mail
                        // user) → drop those tiles to match the client view.
                        if (isVanity) {
                            webCells  = webCells.filter(function(x){ return x.category !== 'dns'; });
                            mailCells = mailCells.filter(function(x){ return x.label !== 'Mail backups'; });
                        }
                        // Inject ONLY the cards into existing .puq-usage-grid
                        // containers — preserves the identity rows above
                        // (server-rendered with status pills, probed by JS).
                        var $grids = $('#puq-svc-usage-bars .puq-usage-grid');
                        $grids.eq(0).html(webCells.map(renderUsageBar).join('')).removeClass('is-loading puq-usage-grid-loading');
                        $grids.eq(1).html(mailCells.map(renderUsageBar).join('')).removeClass('is-loading puq-usage-grid-loading');
                    },
                    error: function(){ gridsErr('Request failed — could not load limits.'); }
                });
            },
            error: function(){ gridsErr('Request failed — could not reach the server.'); }
        });
    }

    // Real-time SSH probe — replaces the "checking…" pills with actual
    // state from the panels. Slower (one SSH cmd per server) so it fires
    // after first paint, not blocking initial render.
    function probeRealState() {
        var probeFailed = function(detail){
            $('.puq-real.puq-checking')
                .removeClass('label-default puq-checking')
                .addClass('label-danger')
                .html('<i class="fa fa-exclamation-triangle"></i> probe failed')
                .attr('title', detail || '');
        };
        $.ajax({
            url: baseUrl + 'probeServiceState&service_id=' + whmcsId, dataType: 'json',
            success: function(r) {
                if (r.status !== 'success' || !r.data) { probeFailed((r && r.msg) ? r.msg : 'probe error'); return; }
                var d = r.data;
                function setPill($el, role, st) {
                    if (!$el || !$el.length) return;
                    $el.removeClass('label-success label-warning label-danger label-default puq-checking');
                    var cls, text, icon;
                    if (st.status === 'ok')       { cls = 'label-success'; icon = 'fa-check';  text = role; }
                    else if (st.status === 'missing') { cls = 'label-warning'; icon = 'fa-times';  text = 'no ' + role; }
                    else if (st.status === 'skipped') { cls = 'label-default'; icon = 'fa-minus';  text = role + ': skipped'; }
                    else                          { cls = 'label-danger';  icon = 'fa-exclamation-triangle'; text = role + ' probe error'; }
                    $el.addClass(cls)
                       .html('<i class="fa ' + icon + '"></i> ' + esc(text))
                       .attr('title', st.detail || '');
                }
                setPill($('#puq-real-web'),  'domain', d.web  || {status:'error', detail:'no data'});
                setPill($('#puq-real-mail'), 'domain', d.mail || {status:'error', detail:'no data'});
                $('.puq-real-dns').each(function(){
                    var sid = parseInt($(this).data('server-id'), 10);
                    var match = (d.dns || []).find(function(x){ return x.server_id === sid; });
                    setPill($(this), 'zone', match || {status:'error', detail:'no probe result for this server'});
                });
            },
            error: function() { probeFailed('AJAX request failed'); }
        });
    }

    $(document).ready(function(){
        $('#puq-svc-tabs a').on('shown.bs.tab', function(){
            var tab = $(this).data('tab'); if (tab) loadTab(tab);
        });
        loadTab(initialTab || 'overview');

        // Tab badges + real-time pills. Counts is cheap (local SQL),
        // probe is slower (SSH per server). Fire all three immediately.
        loadResourceCounts();
        probeRealState();
        loadSslStatus();
        // Refresh counts every 30s while page is open so admin sees
        // deploys land without manual reload. Probe is heavier so
        // we don't auto-refresh it — admin can hit "Redeploy" panel
        // or reload the page when they want a fresh probe. SSL is
        // cheap (one local SELECT) — refresh on the slower 60s cadence.
        setInterval(loadResourceCounts, 30000);
        setInterval(loadSslStatus, 60000);
    });
})();
</script>
{/literal}
