<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Ansible Execution — Live Output</title>
    <link rel="stylesheet"
          href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
          crossorigin="anonymous" referrerpolicy="no-referrer">
    <style>
        * { box-sizing: border-box; margin: 0; padding: 0; }

        body {
            background: #1e1e1e;
            color: #d4d4d4;
            font-family: 'Segoe UI', system-ui, sans-serif;
            display: flex;
            flex-direction: column;
            height: 100vh;
            overflow: hidden;
        }

        /* ── Top bar ─────────────────────────────────────────────── */
        #topBar {
            background: #252526;
            border-bottom: 1px solid #3c3c3c;
            padding: 10px 16px;
            display: flex;
            align-items: center;
            gap: 10px;
            flex-shrink: 0;
        }
        #topBar .title {
            font-size: .85rem;
            font-weight: 600;
            color: #ccc;
            flex: 1;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }
        #statusBadge {
            font-size: .75rem;
            padding: 3px 10px;
            border-radius: 12px;
            background: #555;
            color: #fff;
            flex-shrink: 0;
        }
        #statusBadge.running    { background: #0d6efd; }
        #statusBadge.completed  { background: #198754; }
        #statusBadge.failed     { background: #dc3545; }
        #statusBadge.cancelled  { background: #ffc107; color: #000; }
        #statusBadge.timeout    { background: #ffc107; color: #000; }

        .pulse-dot {
            width: 9px; height: 9px; border-radius: 50%;
            background: #4ec9b0;
            animation: pulse 1.2s infinite;
            display: inline-block;
            flex-shrink: 0;
        }
        .pulse-dot.done  { background: #6a9955; animation: none; }
        .pulse-dot.error { background: #f44747; animation: none; }
        @keyframes pulse { 0%,100%{opacity:1;} 50%{opacity:.3;} }

        /* ── Info bar ────────────────────────────────────────────── */
        #infoBar {
            background: #2d2d2d;
            border-bottom: 1px solid #3c3c3c;
            padding: 6px 16px;
            display: flex;
            gap: 20px;
            flex-wrap: wrap;
            font-size: .75rem;
            color: #9d9d9d;
            flex-shrink: 0;
        }
        #infoBar span strong { color: #ccc; }

        /* ── Terminal ────────────────────────────────────────────── */
        #terminal {
            flex: 1;
            overflow-y: auto;
            background: #1e1e1e;
            padding: 12px 16px;
            font-family: 'Courier New', 'Consolas', monospace;
            font-size: .78rem;
            line-height: 1.55;
            white-space: pre-wrap;
            word-break: break-all;
        }
        #terminal .ansi-ok          { color: #4ec9b0; }
        #terminal .ansi-changed     { color: #dcdcaa; }
        #terminal .ansi-fail        { color: #f44747; }
        #terminal .ansi-unreachable { color: #ce9178; }
        #terminal .ansi-task        { color: #9cdcfe; font-weight: bold; }
        #terminal .ansi-play        { color: #c586c0; font-weight: bold; }
        #terminal .ansi-recap       { color: #569cd6; font-weight: bold; }
        #terminal .ansi-skipped     { color: #808080; }
        #terminal .ansi-warning     { color: #ff8c00; }

        #placeholder {
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            height: 100%;
            gap: 12px;
            color: #555;
        }
        #placeholder .spinner {
            width: 36px; height: 36px;
            border: 3px solid #333;
            border-top-color: #4ec9b0;
            border-radius: 50%;
            animation: spin .8s linear infinite;
        }
        @keyframes spin { to { transform: rotate(360deg); } }

        /* ── Footer ─────────────────────────────────────────────── */
        #footer {
            background: #252526;
            border-top: 1px solid #3c3c3c;
            padding: 7px 16px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            flex-shrink: 0;
            font-size: .75rem;
            gap: 10px;
        }
        #hostSummary { color: #9d9d9d; }
        #elapsed     { color: #6a9955; }

        .btn-sm {
            padding: 3px 10px;
            font-size: .73rem;
            border-radius: 4px;
            border: 1px solid #555;
            background: transparent;
            color: #ccc;
            cursor: pointer;
            display: inline-flex;
            align-items: center;
            gap: 5px;
            text-decoration: none;
        }
        .btn-sm:hover { background: #3c3c3c; }
        .btn-sm.danger { border-color: #dc3545; color: #f44747; }
        .btn-sm.danger:hover { background: #3c1a1a; }
        .btn-sm.primary { border-color: #0d6efd; color: #6ea8fe; }
        .btn-sm.primary:hover { background: #1a2a3c; }

        .btn-group { display: flex; gap: 6px; }
    </style>
</head>
<body>

<!-- ── Top bar ──────────────────────────────────────────────────── -->
<div id="topBar">
    <span class="pulse-dot" id="pulseDot"></span>
    <span class="title" id="titleText">
        <i class="fas fa-terminal" style="color:#4ec9b0;margin-right:6px;"></i>
        Connecting to execution <code style="color:#9cdcfe;">{{ execution_id[:8] }}…</code>
    </span>
    <span id="statusBadge">Waiting…</span>
</div>

<!-- ── Info bar ─────────────────────────────────────────────────── -->
<div id="infoBar">
    <span><strong>Playbook:</strong> <span id="infoPlaybook">—</span></span>
    <span><strong>Hosts:</strong> <span id="infoHosts">—</span></span>
    <span><strong>Elapsed:</strong> <span id="elapsed">0s</span></span>
    <span id="infoExtra"></span>
</div>

<!-- ── Terminal output ─────────────────────────────────────────── -->
<div id="terminal">
    <div id="placeholder">
        <div class="spinner"></div>
        <div>Waiting for execution output…</div>
    </div>
</div>

<!-- ── Footer ───────────────────────────────────────────────────── -->
<div id="footer">
    <span id="hostSummary"></span>
    <div class="btn-group">
        <button class="btn-sm" id="autoScrollBtn" onclick="toggleAutoScroll()">
            <i class="fas fa-arrow-down" id="autoScrollIcon"></i> Auto-scroll
        </button>
        <a id="detailsLink" class="btn-sm primary"
           href="/ansible/executions/{{ execution_id }}" target="_blank">
            <i class="fas fa-external-link-alt"></i> Full Details
        </a>
        <button class="btn-sm" id="saveReportBtn" onclick="saveFailureReport()"
                style="display:none;background:#c0392b;color:#fff;border:none;">
            <i class="fas fa-save"></i> Save Report
        </button>
        <button class="btn-sm danger" id="stopBtn" onclick="stopPolling()">
            <i class="fas fa-stop"></i> Stop polling
        </button>
    </div>
</div>

<script>
const EXECUTION_ID = "{{ execution_id }}";
const API_URL      = `/api/ansible/executions/${EXECUTION_ID}/live`;

let pollTimer   = null;
let autoScroll  = true;
let startTime   = Date.now();
let firstOutput = false;
let elapsedInterval = null;

// ── Start immediately ────────────────────────────────────────────
startPolling();
elapsedInterval = setInterval(() => {
    const sec = Math.round((Date.now() - startTime) / 1000);
    document.getElementById('elapsed').textContent =
        sec < 60 ? `${sec}s` : `${Math.floor(sec/60)}m ${sec%60}s`;
}, 1000);

function startPolling() {
    pollTimer = setInterval(poll, 2000);
    poll();
}

function stopPolling() {
    clearInterval(pollTimer);
    pollTimer = null;
    clearInterval(elapsedInterval);
    elapsedInterval = null;
    document.getElementById('stopBtn').style.display = 'none';
    const dot = document.getElementById('pulseDot');
    dot.className = 'pulse-dot done';
    appendLine('\n— Polling stopped by user —', '#808080');
}

function poll() {
    fetch(API_URL)
        .then(r => r.json())
        .then(data => {
            if (!data.success) { renderError(data.error || 'Unknown error'); return; }
            renderData(data);
            const done = ['completed','failed','cancelled','timeout'].includes(data.status);
            if (done) {
                clearInterval(pollTimer);
                clearInterval(elapsedInterval);
                pollTimer = null;
                document.getElementById('stopBtn').style.display = 'none';
            }
        })
        .catch(err => appendLine('Poll error: ' + err, '#f44747'));
}

function renderData(data) {
    // Title bar
    document.getElementById('titleText').innerHTML =
        `<i class="fas fa-terminal" style="color:#4ec9b0;margin-right:6px;"></i>`+
        `<strong style="color:#9cdcfe;">${data.playbook_name || EXECUTION_ID.slice(0,8)}</strong>`+
        ` — ${(data.target_hosts || []).join(', ') || 'all hosts'}`;

    // Status badge
    const badge = document.getElementById('statusBadge');
    badge.textContent = (data.status || 'unknown').charAt(0).toUpperCase() + (data.status||'').slice(1);
    badge.className = data.status || '';

    // Pulse dot
    const dot = document.getElementById('pulseDot');
    dot.className = data.status === 'running'    ? 'pulse-dot' :
                    data.status === 'completed'   ? 'pulse-dot done' : 'pulse-dot error';

    // Info bar
    document.getElementById('infoPlaybook').textContent = data.playbook_name || '—';
    document.getElementById('infoHosts').textContent = (data.target_hosts || []).join(', ') || '—';
    if (data.summary_message) {
        document.getElementById('infoExtra').innerHTML =
            `<strong>Status:</strong> ${escHtml(data.summary_message)}`;
    }

    // Host summary (when done)
    if (!['running','queued'].includes(data.status)) {
        document.getElementById('hostSummary').innerHTML =
            `<span style="color:#4ec9b0">✓ ${data.successful_hosts||0} ok</span>` +
            ` &nbsp; <span style="color:#f44747">✗ ${data.failed_hosts||0} failed</span>` +
            ` &nbsp; <span style="color:#ce9178">⚠ ${data.unreachable_hosts||0} unreachable</span>`;

        // Show "Save Report" button only when there are failures/unreachable hosts
        const hasFailures = (data.failed_hosts > 0 || data.unreachable_hosts > 0);
        const saveBtn = document.getElementById('saveReportBtn');
        if (hasFailures && !window._reportSaved) {
            saveBtn.style.display = '';
        }
    }

    // Terminal output
    const log = data.log || '';
    if (log) {
        if (!firstOutput) {
            document.getElementById('placeholder').remove();
            firstOutput = true;
        }
        const term = document.getElementById('terminal');
        term.innerHTML = colorize(escHtml(log));
        if (autoScroll) term.scrollTop = term.scrollHeight;
    }
}

function renderError(msg) {
    document.getElementById('pulseDot').className = 'pulse-dot error';
    document.getElementById('statusBadge').textContent = 'Error';
    document.getElementById('statusBadge').className = 'failed';
    if (!firstOutput) {
        document.getElementById('placeholder').remove();
        firstOutput = true;
    }
    document.getElementById('terminal').innerHTML =
        `<span style="color:#f44747">Error: ${escHtml(msg)}</span>`;
}

function appendLine(text, color) {
    const term = document.getElementById('terminal');
    const span = document.createElement('span');
    span.style.color = color || '#d4d4d4';
    span.textContent = text + '\n';
    term.appendChild(span);
    if (autoScroll) term.scrollTop = term.scrollHeight;
}

function colorize(html) {
    return html
        .replace(/(PLAY\s+\[.*?\](?:\s*\*+)?)/g,     '<span class="ansi-play">$1</span>')
        .replace(/(TASK\s+\[.*?\](?:\s*\*+)?)/g,     '<span class="ansi-task">$1</span>')
        .replace(/(PLAY RECAP(?:\s*\*+)?)/g,          '<span class="ansi-recap">$1</span>')
        .replace(/(ok:\s+\[.*?\].*)/g,                '<span class="ansi-ok">$1</span>')
        .replace(/(changed:\s+\[.*?\].*)/g,           '<span class="ansi-changed">$1</span>')
        .replace(/(skipping:\s+\[.*?\].*)/g,          '<span class="ansi-skipped">$1</span>')
        .replace(/(fatal:.*)/g,                       '<span class="ansi-fail">$1</span>')
        .replace(/(FAILED!.*)/g,                      '<span class="ansi-fail">$1</span>')
        .replace(/(UNREACHABLE!.*)/g,                 '<span class="ansi-unreachable">$1</span>')
        .replace(/(\[WARNING\].*)/g,                  '<span class="ansi-warning">$1</span>');
}

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

function toggleAutoScroll() {
    autoScroll = !autoScroll;
    document.getElementById('autoScrollIcon').style.opacity = autoScroll ? '1' : '0.35';
    document.getElementById('autoScrollBtn').style.borderColor = autoScroll ? '#555' : '#ffc107';
}

async function saveFailureReport() {
    const btn = document.getElementById('saveReportBtn');
    btn.disabled = true;
    btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Saving…';
    try {
        const r = await fetch('/api/ansible/failure-reports', {
            method: 'POST',
            headers: {'Content-Type': 'application/json'},
            body: JSON.stringify({execution_id: EXECUTION_ID})
        });
        const d = await r.json();
        if (d.success) {
            btn.style.background = '#27ae60';
            btn.innerHTML = '<i class="fas fa-check"></i> Saved';
            window._reportSaved = true;
        } else if (r.status === 409) {
            btn.style.background = '#7f8c8d';
            btn.innerHTML = '<i class="fas fa-info-circle"></i> Already saved';
            window._reportSaved = true;
        } else {
            btn.disabled = false;
            btn.style.background = '#c0392b';
            btn.innerHTML = '<i class="fas fa-exclamation-circle"></i> Failed — retry';
            appendLine('Error saving report: ' + (d.error || 'unknown'), '#f44747');
        }
    } catch(e) {
        btn.disabled = false;
        btn.innerHTML = '<i class="fas fa-exclamation-circle"></i> Error';
        appendLine('Network error: ' + e, '#f44747');
    }
}

// Close popup when parent window unloads (optional: keep open)
// window.addEventListener('beforeunload', stopPolling);
</script>
</body>
</html>
