{* Logs tab — live tail of real HestiaCP log files for this service.
   Top toolbar: log selector pills + controls. Viewer fills full width.

   Web logs are byte-range incremental tail.
   Mail logs are system-wide, grep-filtered by the service's domain. *}
<style>
/* Containment — without these the long apache lines (1500+ chars, no
   whitespace breaks) push the whole WHMCS clientsservices layout wider
   than the viewport. min-width:0 defeats the flex default of
   "intrinsic content width"; overflow:hidden on the tab-pane stops
   the pre from leaking horizontal bleed up to its ancestors. */
#tab-logs { min-width:0; max-width:100%; overflow:hidden; box-sizing:border-box; }
.puq-log-toolbar { display:flex; align-items:center; gap:6px; flex-wrap:wrap; padding:6px 0; margin-bottom:6px; border-bottom:1px solid #eee; min-width:0; max-width:100%; }
.puq-log-tabs { display:flex; gap:4px; flex-wrap:wrap; min-width:0; }
.puq-log-tab { padding:5px 10px; border:1px solid #ccc; background:#f5f5f5; border-radius:3px; cursor:pointer; font-size:12px; line-height:1.3; white-space:nowrap; user-select:none; }
.puq-log-tab:hover { background:#e8e8e8; }
.puq-log-tab.active { background:#3071a9; color:#fff; border-color:#286090; }
.puq-log-tab.missing { opacity:.45; cursor:not-allowed; border-style:dashed; }
.puq-log-tab .puq-log-mtime { font-size:10px; color:#888; }
.puq-log-tab.active .puq-log-mtime { color:rgba(255,255,255,.85); }
.puq-log-tab .puq-filter-chip { font-size:9px; padding:1px 4px; background:#999; color:#fff; border-radius:2px; vertical-align:middle; margin-left:4px; }
.puq-log-tab.active .puq-filter-chip { background:rgba(255,255,255,.3); }
.puq-log-controls { display:flex; align-items:center; gap:4px; margin-left:auto; flex-shrink:0; }
.puq-log-controls .text-muted { font-size:11px; }
.puq-log-controls select, .puq-log-controls input { height:24px; font-size:11px; padding:0 4px; }
/* Bulletproof containment for the log viewer.
   The .puq-log-frame is the visible box (relative + overflow:hidden).
   The .puq-log-pre is position:absolute filling the frame — taken out
   of normal flow, so its content (1500-char log lines) never
   contributes to ANY ancestor's intrinsic width. Internal overflow:auto
   gives horizontal+vertical scrollbars within the box. */
.puq-log-frame {
    position:relative; width:100%; max-width:100%; min-width:0;
    height:74vh; overflow:hidden; box-sizing:border-box;
    background:#1e1e1e; border-radius:3px;
}
.puq-log-pre {
    position:absolute; top:0; left:0; right:0; bottom:0;
    overflow:auto; margin:0; padding:8px; font-size:11px;
    background:transparent; color:#dcdcdc;
    white-space:pre;
}
.puq-log-pre .err  { color:#ff7b72; }
.puq-log-pre .warn { color:#f1c40f; }
.puq-log-pre .ok   { color:#7ee787; }
.puq-log-pre .dim  { color:#888; }
.puq-log-empty { color:#888; padding:32px; text-align:center; font-style:italic; }
.puq-log-rotated-mark { padding:2px 6px; background:#444; color:#dcdcdc; border-radius:3px; font-size:10px; margin:4px 0; display:inline-block; }
.puq-log-error-banner { color:#ff7b72; padding:6px 8px; background:#2b2b2b; border-bottom:1px solid #444; }
</style>

<div class="puq-log-toolbar" id="puq-log-toolbar">
    <div class="puq-log-tabs" id="puq-log-tabs">
        <span class="text-muted" style="font-size:12px;"><i class="fa fa-spinner fa-spin"></i> Loading…</span>
    </div>
    <div class="puq-log-controls">
        <span class="text-muted" id="puq-log-meta"></span>
        <label class="text-muted" style="margin:0;font-weight:normal;">
            <input type="checkbox" id="puq-log-autorefresh" checked> Auto
        </label>
        <select id="puq-log-interval" class="form-control input-sm" style="width:auto;" title="Poll interval">
            <option value="2000" selected>2s</option>
            <option value="5000">5s</option>
            <option value="15000">15s</option>
        </select>
        <select id="puq-log-tail-lines" class="form-control input-sm" style="width:auto;" title="Tail lines (filtered mode only)">
            <option value="100">100 lines</option>
            <option value="500" selected>500 lines</option>
            <option value="2000">2000 lines</option>
        </select>
        <button type="button" class="btn btn-default btn-xs" id="puq-log-refresh" title="Reload (clears view, fetches fresh)"><i class="fa fa-redo"></i></button>
        <button type="button" class="btn btn-default btn-xs" id="puq-log-clear-view" title="Clear viewer (server keeps log)"><i class="fa fa-eraser"></i></button>
        <button type="button" class="btn btn-default btn-xs" id="puq-log-download" title="Save what's shown to a file"><i class="fa fa-download"></i></button>
    </div>
</div>
<div class="puq-log-frame">
    <pre class="puq-log-pre" id="puq-log-content"><div class="puq-log-empty">Select a log above.</div></pre>
</div>

{literal}
<script>
(window.puqSvc = window.puqSvc || {}).init_logs = function(ctx){
    var esc = ctx.esc, baseUrl = ctx.baseUrl, puqId = ctx.puqId, fmtBytes = ctx.fmtBytes || function(n){
        n = parseInt(n,10)||0;
        if (n >= 1024*1024) return (n/1048576).toFixed(1) + ' MB';
        if (n >= 1024) return (n/1024).toFixed(1) + ' KB';
        return n + ' B';
    };
    function fmtMtime(ts){
        if (!ts) return '';
        var d = new Date(ts * 1000);
        return d.toISOString().replace('T',' ').substring(0,16);
    }

    var state = {
        currentRole: null,
        currentKey:  null,
        currentLabel:'',
        filterMode:  false,
        offset:      -1,        // -1 = "fetch last MAX bytes"
        size:        0,
        pollTimer:   null,
        autoActive:  true
    };

    function pillHtml(role, l){
        var cls = 'puq-log-tab';
        if (!l.exists || !l.readable) cls += ' missing';
        var title = l.path + (l.exists ? '' : ' — not present')
                  + (l.exists && !l.readable ? ' — sudo denied' : '');
        var sz = l.exists ? fmtBytes(l.size || 0) : 'n/a';
        var filterChip = l.filter_domain ? ' <span class="puq-filter-chip" title="System log, grep-filtered by domain">filtered</span>' : '';
        return '<div class="' + cls + '" data-role="' + esc(role) + '" data-key="' + esc(l.key) + '" data-filter="' + (l.filter_domain ? '1' : '0') + '" title="' + esc(title) + '">' +
            esc(l.label) + filterChip + ' <span class="puq-log-mtime">' + esc(sz) + '</span>' +
        '</div>';
    }

    function renderCatalog(d){
        var html = '';
        if (d.web && d.web.logs && d.web.logs.length) {
            d.web.logs.forEach(function(l){ html += pillHtml('web', l); });
        }
        if (d.mail && d.mail.logs && d.mail.logs.length) {
            d.mail.logs.forEach(function(l){ html += pillHtml('mail', l); });
        }
        if (html === '') html = '<span class="text-muted" style="font-size:12px;">No logs available for this service.</span>';
        if (d.errors && d.errors.length) {
            html = '<span class="text-warning" style="font-size:11px;margin-right:8px;">' + esc(d.errors.join(' / ')) + '</span>' + html;
        }
        $('#puq-log-tabs').html(html);

        // Auto-select the first available log if nothing chosen yet.
        if (!state.currentKey) {
            var $first = $('#puq-log-tabs .puq-log-tab').not('.missing').first();
            if ($first.length) selectLog($first);
        }
    }

    function loadCatalog(){
        $.ajax({
            url: baseUrl + 'getServiceLogsCatalog&id=' + puqId,
            dataType: 'json',
            success: function(r){
                if (r.status !== 'success' || !r.data) {
                    $('#puq-log-tabs').html('<span class="text-danger" style="font-size:12px;">' + esc(r.msg || 'Load failed') + '</span>');
                    return;
                }
                renderCatalog(r.data);
            },
            error: function(){
                $('#puq-log-tabs').html('<span class="text-danger" style="font-size:12px;">Request failed.</span>');
            }
        });
    }

    function colorize(line){
        var l = (line || '').toString();
        if (/\[error\]|\bERROR\b|\bFATAL\b|" 5\d\d /.test(l))      return '<span class="err">'  + esc(l) + '</span>';
        if (/\[warn\]|\bWARNING\b|" 4\d\d /.test(l))               return '<span class="warn">' + esc(l) + '</span>';
        if (/\b200\b|\bOK\b|" 2\d\d /.test(l))                     return '<span class="ok">'   + esc(l) + '</span>';
        return esc(l);
    }

    function appendToView(chunk, rotated){
        var $pre = $('#puq-log-content');
        var nearBottom = ($pre[0].scrollTop + $pre[0].clientHeight + 24) >= $pre[0].scrollHeight;
        if ($pre.find('.puq-log-empty').length) $pre.empty();
        if (rotated) $pre.append('<div class="puq-log-rotated-mark">↻ log rotated — restarting from end</div>');
        if (!chunk || chunk === '') return;
        var lines = chunk.split('\n');
        var html = lines.map(colorize).join('\n');
        $pre.append(html);
        if (nearBottom) $pre.scrollTop($pre[0].scrollHeight);
        var txtLen = $pre[0].textContent.length;
        if (txtLen > 400000) {
            $pre[0].innerHTML = $pre[0].innerHTML.substring($pre[0].innerHTML.length - 380000);
        }
    }

    function replaceView(chunk){
        var $pre = $('#puq-log-content');
        $pre.empty();
        if (!chunk) {
            $pre.html('<div class="puq-log-empty">(empty — no matching lines for this domain)</div>');
            return;
        }
        var html = chunk.split('\n').map(colorize).join('\n');
        $pre.html(html);
        $pre.scrollTop($pre[0].scrollHeight);
    }

    function poll(){
        if (!state.currentKey) return;
        var data = {
            id:         puqId,
            role:       state.currentRole,
            key:        state.currentKey,
            offset:     state.filterMode ? 0 : state.offset,
            max_bytes:  state.filterMode ? 524288 : 65536,
            tail_lines: parseInt($('#puq-log-tail-lines').val(), 10) || 500
        };
        $.ajax({
            url: baseUrl + 'tailServiceLog',
            type: 'POST',
            data: data,
            dataType: 'json',
            success: function(r){
                if (r.status !== 'success') {
                    $('#puq-log-content').html('<div class="puq-log-error-banner">' + esc(r.msg || 'Tail failed') + '</div>');
                    state.autoActive = false;
                    $('#puq-log-autorefresh').prop('checked', false);
                    stopPoll();
                    return;
                }
                var d = r.data;
                $('#puq-log-meta').text('size ' + fmtBytes(d.size) + (d.mode === 'filtered' ? ' · filtered' : ' · offset ' + d.new_offset));
                if (state.filterMode) {
                    replaceView(d.content);
                } else {
                    appendToView(d.content, !!d.rotated);
                    state.offset = d.new_offset;
                }
                state.size = d.size;
            },
            error: function(){
                $('#puq-log-content').html('<div class="puq-log-error-banner">Request failed.</div>');
            }
        });
    }

    function stopPoll(){ if (state.pollTimer) { clearInterval(state.pollTimer); state.pollTimer = null; } }
    function startPoll(){
        stopPoll();
        if (!state.autoActive) return;
        var ms = parseInt($('#puq-log-interval').val(), 10) || 2000;
        state.pollTimer = setInterval(poll, ms);
    }

    function selectLog($el){
        if ($el.hasClass('missing')) return;
        $('#puq-log-tabs .puq-log-tab').removeClass('active');
        $el.addClass('active');
        state.currentRole = $el.data('role');
        state.currentKey  = $el.data('key');
        state.filterMode  = $el.data('filter') === 1 || $el.data('filter') === '1';
        state.offset      = -1;
        state.currentLabel = $el.text().trim();
        $('#puq-log-meta').text('loading…');
        $('#puq-log-content').html('<div class="puq-log-empty"><i class="fa fa-spinner fa-spin"></i> Reading…</div>');
        poll();
        startPoll();
    }

    $('#puq-log-tabs').off('click.puqlog').on('click.puqlog', '.puq-log-tab', function(){
        selectLog($(this));
    });

    $('#puq-log-refresh').off('click.puqlog').on('click.puqlog', function(e){
        e.preventDefault();
        if (!state.currentKey) { loadCatalog(); return; }
        state.offset = -1;
        $('#puq-log-content').html('<div class="puq-log-empty"><i class="fa fa-spinner fa-spin"></i> Refreshing…</div>');
        poll();
    });

    $('#puq-log-clear-view').off('click.puqlog').on('click.puqlog', function(e){
        e.preventDefault();
        $('#puq-log-content').html('<div class="puq-log-empty">(view cleared — next poll will append)</div>');
    });

    $('#puq-log-download').off('click.puqlog').on('click.puqlog', function(e){
        e.preventDefault();
        if (!state.currentKey) { showNotification('Select a log first.', 'info'); return; }
        // Save EXACTLY what's currently rendered in the viewer — no extra
        // SSH round-trip. Works in offline state too once content is loaded.
        var text = $('#puq-log-content')[0].textContent || '';
        if (!text || text.indexOf('Select a log') === 0) {
            showNotification('Nothing to download yet.', 'info');
            return;
        }
        var safeRole = (state.currentRole || 'log').replace(/[^a-z]/g, '');
        var safeKey  = (state.currentKey  || 'log').replace(/[^a-z0-9_]/g, '');
        var ts = new Date().toISOString().replace(/[:\-]/g, '').replace(/\..+/, '').replace('T', '_');
        var fname = safeRole + '_' + safeKey + '_' + ts + '.log';
        var blob = new Blob([text], { type: 'text/plain;charset=utf-8' });
        var url = URL.createObjectURL(blob);
        var a = document.createElement('a');
        a.href = url; a.download = fname;
        document.body.appendChild(a);
        a.click();
        setTimeout(function(){ document.body.removeChild(a); URL.revokeObjectURL(url); }, 100);
    });

    $('#puq-log-autorefresh').off('change.puqlog').on('change.puqlog', function(){
        state.autoActive = this.checked;
        if (state.autoActive) startPoll(); else stopPoll();
    });
    $('#puq-log-interval').off('change.puqlog').on('change.puqlog', function(){
        if (state.autoActive) startPoll();
    });

    $('#puq-svc-tabs a[data-tab="logs"]').off('hide.bs.tab.puqloglocal').on('hide.bs.tab.puqloglocal', stopPoll);

    loadCatalog();
};
</script>
{/literal}
