<!DOCTYPE html>
<html lang="ru">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>TelePush — Логи</title>
    <link rel="icon" href="/static/favicon.ico" type="image/x-icon">
    <link rel="stylesheet" href="/static/bootstrap-icons.min.css">
    <link rel="stylesheet" href="/static/style.css">
    <style>
        :root {
            --lw-bg: #07070d;
            --lw-bg-elev: rgba(20, 20, 32, 0.55);
            --lw-border: rgba(139, 92, 246, 0.15);
            --lw-border-strong: rgba(139, 92, 246, 0.32);
            --lw-text: #e2e8f0;
            --lw-text-dim: #94a3b8;
            --lw-text-muted: #64748b;
            --lw-accent: #8b5cf6;
            --lw-accent-soft: rgba(139,92,246,0.10);
        }
        * { box-sizing: border-box; }
        body {
            margin: 0;
            background: #050509;
            color: var(--lw-text);
            /* Моноширный шрифт — терминал-стиль консоли. */
            font-family: "JetBrains Mono", "Cascadia Mono", Consolas, "Courier New", monospace;
            height: 100vh; overflow: hidden;
        }
        .lw-shell { display: flex; flex-direction: column; height: 100vh; }

        /* ── Header ── */
        .lw-header {
            display: flex; align-items: center; gap: 14px;
            padding: 14px 20px;
            background: rgba(12, 12, 20, 0.65);
            border-bottom: 1px solid var(--lw-border);
            backdrop-filter: blur(24px) saturate(140%);
            -webkit-backdrop-filter: blur(24px) saturate(140%);
            position: relative; z-index: 2;
        }
        .lw-title {
            display: flex; align-items: center; gap: 10px;
            font-size: 0.95rem; font-weight: 700; color: var(--lw-text);
            letter-spacing: 0.2px;
        }
        .lw-title-icon {
            display: inline-flex; align-items: center; justify-content: center;
            width: 28px; height: 28px;
            background: linear-gradient(135deg, rgba(139, 92, 246, 0.35), rgba(99, 102, 241, 0.18));
            border: 1px solid rgba(139, 92, 246, 0.4);
            border-radius: 8px;
            color: #c4b5fd; font-size: 0.85rem;
            box-shadow: 0 0 16px rgba(139, 92, 246, 0.18);
        }
        .lw-counter {
            font-size: 0.68rem; font-weight: 600; color: #c4b5fd;
            background: rgba(139, 92, 246, 0.12);
            border: 1px solid rgba(139, 92, 246, 0.22);
            padding: 3px 9px; border-radius: 11px;
            font-variant-numeric: tabular-nums;
        }
        .lw-pulse {
            display: inline-block; width: 6px; height: 6px;
            background: #64748b; border-radius: 50%;
            margin-right: 5px;
            transition: background 0.3s, box-shadow 0.3s;
        }
        .lw-pulse.active {
            background: #22c55e;
            box-shadow: 0 0 8px #22c55e;
            animation: lw-pulse 1.6s ease-in-out infinite;
        }
        @keyframes lw-pulse {
            0%, 100% { opacity: 1; transform: scale(1); }
            50% { opacity: 0.4; transform: scale(0.85); }
        }

        /* ── Tabs ── */
        .lw-tabs {
            display: flex; gap: 5px; flex-wrap: wrap; align-items: center;
            padding: 10px 16px;
            border-bottom: 1px solid rgba(255, 255, 255, 0.03);
            background: rgba(10, 10, 18, 0.5);
            backdrop-filter: blur(12px);
            -webkit-backdrop-filter: blur(12px);
        }
        .lw-tab {
            display: inline-flex; align-items: center; gap: 5px;
            padding: 4px 9px; font-size: 0.7rem; font-weight: 500;
            color: var(--lw-text-dim); background: rgba(255, 255, 255, 0.02);
            border: 1px solid rgba(255, 255, 255, 0.05); border-radius: 6px;
            cursor: pointer; transition: all 0.18s ease;
            font-family: inherit;
            white-space: nowrap;
        }
        .lw-tab i { font-size: 0.78rem; }
        .lw-tab:hover {
            background: rgba(139, 92, 246, 0.08);
            color: var(--lw-text);
            border-color: rgba(139, 92, 246, 0.2);
        }
        .lw-tab.active {
            background: linear-gradient(135deg, rgba(139, 92, 246, 0.22), rgba(99, 102, 241, 0.14));
            border-color: var(--lw-border-strong);
            color: #ddd6fe;
            box-shadow: 0 0 0 1px rgba(139, 92, 246, 0.15), 0 2px 8px rgba(139, 92, 246, 0.12);
        }
        .lw-badge {
            display: inline-flex; align-items: center; justify-content: center;
            min-width: 16px; height: 15px; padding: 0 5px;
            background: rgba(255, 255, 255, 0.06); color: var(--lw-text-muted);
            font-size: 0.6rem; font-weight: 700;
            border-radius: 8px;
            font-variant-numeric: tabular-nums;
        }
        .lw-badge:empty { display: none; }
        .lw-badge.has-warn { background: rgba(245, 158, 11, 0.22); color: #fcd34d; }
        .lw-badge.has-err  { background: rgba(239, 68, 68, 0.25); color: #fca5a5; }
        .lw-tab.active .lw-badge { background: rgba(139, 92, 246, 0.32); color: #ede9fe; }

        /* ── Actions ── */
        .lw-actions {
            margin-left: auto; display: flex; gap: 4px;
            flex-shrink: 0;  /* не переносится на новый ряд при wrap табов */
        }
        .lw-icon-btn {
            display: inline-flex; align-items: center; gap: 5px;
            background: rgba(255, 255, 255, 0.03);
            border: 1px solid rgba(255, 255, 255, 0.06);
            color: var(--lw-text-dim); border-radius: 6px;
            padding: 4px 9px; font-size: 0.7rem; font-weight: 500;
            cursor: pointer; transition: all 0.15s ease;
            font-family: inherit;
            white-space: nowrap;
        }
        .lw-icon-btn:hover {
            background: rgba(139, 92, 246, 0.14);
            color: #c4b5fd;
            border-color: rgba(139, 92, 246, 0.28);
        }
        .lw-icon-btn.active {
            background: rgba(34, 197, 94, 0.14);
            color: #86efac;
            border-color: rgba(34, 197, 94, 0.32);
            box-shadow: 0 0 0 1px rgba(34, 197, 94, 0.1);
        }
        .lw-icon-btn.active:hover {
            background: rgba(34, 197, 94, 0.2);
            color: #bbf7d0;
        }
        .lw-icon-btn i { font-size: 0.78rem; }

        /* ── Stream ── */
        .lw-stream {
            flex: 1; overflow-y: auto;
            padding: 14px 18px 32px;
            font-family: "JetBrains Mono", "SF Mono", "Cascadia Code", "Consolas", ui-monospace, monospace;
            font-size: 0.78rem; line-height: 1.6;
            color: var(--lw-text);
            font-feature-settings: "calt" 0;
        }
        .lw-stream::-webkit-scrollbar { width: 10px; }
        .lw-stream::-webkit-scrollbar-track { background: transparent; }
        .lw-stream::-webkit-scrollbar-thumb {
            background: rgba(139, 92, 246, 0.22);
            border-radius: 5px;
            border: 2px solid #050509;
        }
        .lw-stream::-webkit-scrollbar-thumb:hover { background: rgba(139, 92, 246, 0.42); }

        /* компактная плотность строк */
        .lw-row {
            display: flex; gap: 6px; padding: 0 8px;
            font-size: 0.68rem; line-height: 1.30;
            border-radius: 0; margin-bottom: 0;
            position: relative;
        }
        .lw-row:hover { background: rgba(255, 255, 255, 0.04); }
        /* яркие контрастные цвета уровней, не размытые */
        .lw-row.lvl-INFO    { color: #e2e8f0; }
        .lw-row.lvl-WARNING { color: #fbbf24; font-weight: 500; }
        .lw-row.lvl-ERROR   { color: #f87171; font-weight: 500; }
        .lw-row.lvl-SUCCESS { color: #4ade80; font-weight: 500; }
        .lw-row.lvl-DEBUG   { color: #64748b; }
        .lw-time {
            color: var(--lw-text-muted); flex-shrink: 0;
            font-variant-numeric: tabular-nums;
            min-width: 56px;
        }
        .lw-mod {
            flex-shrink: 0;
            min-width: 30px; text-align: left;
            opacity: 0.75;
        }
        .lw-icon { display: none; }
        /* перенос длинных сообщений с отступом под текст msg, не под [time] */
        .lw-msg {
            word-break: break-word; flex: 1;
            padding-left: 0;
            text-indent: 0;
        }

        /* ── Empty state ── */
        .lw-empty {
            text-align: center; padding: 80px 24px;
            color: var(--lw-text-muted);
            font-size: 0.85rem;
            font-family: -apple-system, "Inter", "Segoe UI", sans-serif;
        }
        .lw-empty-icon {
            display: inline-flex; align-items: center; justify-content: center;
            width: 56px; height: 56px;
            background: rgba(139, 92, 246, 0.06);
            border: 1px solid rgba(139, 92, 246, 0.14);
            border-radius: 14px;
            color: #a78bfa; font-size: 1.7rem;
            margin-bottom: 18px;
        }
        .lw-empty-title {
            font-weight: 600; color: var(--lw-text); font-size: 0.95rem;
            margin-bottom: 6px;
        }
        .lw-empty-sub { font-size: 0.78rem; color: var(--lw-text-muted); }

        /* подсветка phone-номеров в логах */
        .lw-phone {
            cursor: pointer;
            border-bottom: 1px dashed rgba(168, 139, 250, 0.4);
            transition: color 0.1s, background 0.1s;
        }
        .lw-phone:hover { color: #c4b5fd; background: rgba(139, 92, 246, 0.12); }
        .lw-row--highlighted {
            background: rgba(251, 191, 36, 0.10) !important;
            border-left: 2px solid #fbbf24;
            padding-left: 4px;
        }
        .lw-row--highlighted .lw-phone {
            background: rgba(251, 191, 36, 0.25);
            border-bottom-color: #fbbf24;
            color: #fbbf24;
            font-weight: 600;
        }
    </style>
</head>
<body>
<div class="lw-shell">
    <div class="lw-header">
        <span class="lw-title">
            <span class="lw-title-icon"><i class="bi bi-terminal-fill"></i></span>
            Логи работы
        </span>
        <span class="lw-counter" id="lw-counter" title="Сколько аккаунтов сейчас занято задачами"><span class="lw-pulse" id="lw-pulse"></span><span id="lw-counter-text">простой</span></span>
    </div>
    <div class="lw-tabs">
        <!-- табы-фильтры по модулям убраны: модуль виден на строке через цветной badge.
             Скрытый дефолт-фильтр data-mod="all" сохранён для совместимости с lwSetFilter(). -->
        <button class="lw-tab active" data-mod="all" style="display:none;"></button>
        <div class="lw-actions" style="margin-left:0;">
            <button class="lw-icon-btn lw-action-labeled" onclick="lwClear()" title="Очистить все записи"><i class="bi bi-trash"></i><span>Очистить</span></button>
            <button class="lw-icon-btn lw-action-labeled active" onclick="lwToggleAutoscroll(this)" title="Авто-скролл вниз при новых записях" id="lw-autoscroll-btn"><i class="bi bi-arrow-down-circle-fill"></i><span>Авто-скролл</span></button>
        </div>
    </div>
    <div class="lw-stream" id="lw-stream">
        <div class="lw-empty" id="lw-empty">
            <div class="lw-empty-icon"><i class="bi bi-hourglass-split"></i></div>
            <div class="lw-empty-title">Жду событий</div>
            <div class="lw-empty-sub">Запусти задачу — здесь будет видно что происходит в реальном времени</div>
        </div>
    </div>
</div>

<script>
var _lwFilter = 'all';
var _lwAutoScroll = true;
var _lwLastId = 0;
var _lwCount = 0;

var _MOD_LABELS = {spam:'РАС', dm:'ЛС', join:'ПОД', parser:'ПАР', monitor:'МОН', warmup:'ПРГ', autoreply:'АО', system:'СИС'};
var _MOD_COLORS = {spam:'#a78bfa', dm:'#22c55e', join:'#3b82f6', parser:'#ec4899', monitor:'#f97316', warmup:'#eab308', autoreply:'#06b6d4', system:'#64748b'};
var _LVL_ICONS = {
    INFO:    'bi-info-circle-fill',
    WARNING: 'bi-exclamation-triangle-fill',
    ERROR:   'bi-x-circle-fill',
    SUCCESS: 'bi-check-circle-fill',
    DEBUG:   'bi-dot',
};

function lwAdd(msg, level, mod, ts) {
    mod = mod || 'system';
    level = level || 'INFO';
    var el = document.getElementById('lw-stream');
    if (!el) return;
    // Убираем placeholder на первой записи
    var ph = document.getElementById('lw-empty');
    if (ph) ph.remove();

    var row = document.createElement('div');
    row.className = 'lw-row lvl-' + level;
    row.setAttribute('data-mod', mod);
    if (_lwFilter !== 'all' && _lwFilter !== mod) row.style.display = 'none';
    var clean = (msg || '')
        .replace(/[☀-➿]/g, '')
        .replace(/[\uD83C-\uDBFF][\uDC00-\uDFFF]/g, '')
        .replace(/[︎️]/g, '')
        .replace(/\s{2,}/g, ' ')
        .trim();
    // реальное время события из record.ts (UNIX seconds), а не момент показа в UI
    var d = (ts && ts > 0) ? new Date(ts * 1000) : new Date();
    var time = d.toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit', second: '2-digit'});
    var modColor = _MOD_COLORS[mod] || '#64748b';
    // подсвечиваем телефонные номера (10-15 цифр) — клик выделит все строки про этот акк
    var msgHtml = escapeHtmlLw(clean).replace(/\+?\d{10,15}/g, function(m) {
        var digits = m.replace(/\D/g, '');
        return '<span class="lw-phone" data-phone="' + digits + '" onclick="lwTogglePhone(\'' + digits + '\',event)">' + m + '</span>';
    });
    // data-phones для быстрого фильтра по клику без regex
    var phonesInRow = (clean.match(/\+?\d{10,15}/g) || []).map(function(p){return p.replace(/\D/g,'');});
    if (phonesInRow.length) row.setAttribute('data-phones', '|' + phonesInRow.join('|') + '|');
    // Формат строки: [time] [МОД] msg — без иконок, цвет всей строки по level
    row.innerHTML =
        '<span class="lw-time">[' + time + ']</span>' +
        '<span class="lw-mod" style="color:' + modColor + ';">[' + (_MOD_LABELS[mod] || mod.toUpperCase()) + ']</span>' +
        '<span class="lw-msg">' + msgHtml + '</span>';
    el.appendChild(row);
    // Если уже есть выбранный phone — подсвечиваем новую строку если она про него
    if (window._lwHighlightedPhone && phonesInRow.indexOf(window._lwHighlightedPhone) !== -1) {
        row.classList.add('lw-row--highlighted');
    }
    _lwCount++;
    var cn = document.getElementById('lw-counter-num');
    if (cn) cn.textContent = _lwCount;
    if (_lwAutoScroll) el.scrollTop = el.scrollHeight;
    // Не разрастаемся: храним последние 2000 строк
    while (el.children.length > 2000) el.removeChild(el.firstChild);
    // Badge на табе для не-активного фильтра
    if (mod !== _lwFilter && _lwFilter !== 'all') {
        var tab = document.querySelector('.lw-tab[data-mod="' + mod + '"]');
        if (tab) {
            var b = tab.querySelector('.lw-badge');
            if (b) {
                var n = parseInt(b.textContent || '0') + 1;
                b.textContent = n > 99 ? '99+' : n;
                if (level === 'ERROR') b.classList.add('has-err');
                else if (level === 'WARNING' && !b.classList.contains('has-err')) b.classList.add('has-warn');
            }
        }
    }
}

function escapeHtmlLw(s) {
    return String(s).replace(/[&<>"]/g, function(c) {
        return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];
    });
}

// подсветка всех логов с одинаковым phone-номером; повторный клик / клик в пустое — снимает
function lwTogglePhone(phone, ev) {
    if (ev) { ev.stopPropagation(); ev.preventDefault(); }
    var el = document.getElementById('lw-stream');
    if (!el) return;
    var current = window._lwHighlightedPhone || '';
    // Снимаем предыдущую подсветку
    Array.from(el.querySelectorAll('.lw-row--highlighted')).forEach(function(r) {
        r.classList.remove('lw-row--highlighted');
    });
    if (current === phone) {
        window._lwHighlightedPhone = '';
        return;
    }
    window._lwHighlightedPhone = phone;
    var marker = '|' + phone + '|';
    Array.from(el.children).forEach(function(r) {
        var dp = r.getAttribute && r.getAttribute('data-phones');
        if (dp && dp.indexOf(marker) !== -1) r.classList.add('lw-row--highlighted');
    });
}

// Снимаем подсветку при клике в пустое место стрима
document.addEventListener('click', function(e) {
    if (e.target && (e.target.classList.contains('lw-phone') || e.target.closest('.lw-phone'))) return;
    if (window._lwHighlightedPhone) lwTogglePhone(window._lwHighlightedPhone);
});

function lwSetFilter(btn, mod) {
    _lwFilter = mod;
    document.querySelectorAll('.lw-tab').forEach(function(t) { t.classList.remove('active'); });
    if (btn) btn.classList.add('active');
    var el = document.getElementById('lw-stream');
    Array.from(el.children).forEach(function(row) {
        var rm = row.getAttribute('data-mod') || 'system';
        row.style.display = (mod === 'all' || rm === mod) ? '' : 'none';
    });
    el.scrollTop = el.scrollHeight;
    if (btn) {
        var b = btn.querySelector('.lw-badge');
        if (b) { b.textContent = ''; b.classList.remove('has-err', 'has-warn'); }
    }
}

function lwClear() {
    var el = document.getElementById('lw-stream');
    el.innerHTML = '<div class="lw-empty" id="lw-empty">' +
        '<div class="lw-empty-icon"><i class="bi bi-check2-circle"></i></div>' +
        '<div class="lw-empty-title">Логи очищены</div>' +
        '<div class="lw-empty-sub">Новые события появятся здесь сразу как произойдут</div>' +
        '</div>';
    document.querySelectorAll('.lw-tab .lw-badge').forEach(function(b) {
        b.textContent = '';
        b.classList.remove('has-err', 'has-warn');
    });
    _lwCount = 0;
    var cn = document.getElementById('lw-counter-num');
    if (cn) cn.textContent = '0';
}

function lwToggleAutoscroll(btn) {
    _lwAutoScroll = !_lwAutoScroll;
    btn.classList.toggle('active', _lwAutoScroll);
    var icon = btn.querySelector('i');
    if (icon) icon.className = _lwAutoScroll ? 'bi bi-arrow-down-square-fill' : 'bi bi-arrow-down-square';
    btn.title = _lwAutoScroll ? 'Авто-скролл: вкл' : 'Авто-скролл: выкл';
}

var _lwBootToken = null;
function lwPoll() {
    fetch('/api/logs/recent?since=' + _lwLastId, { credentials: 'same-origin' })
        .then(function(r) { return r.json(); })
        .then(function(d) {
            // детект рестарта сервера: при смене boot-token сбрасываем _lwLastId и DOM,
            // иначе новые логи (id < старого since) отфильтруются и стрим будет пустым
            if (d && d.boot) {
                if (_lwBootToken === null) {
                    _lwBootToken = d.boot;
                } else if (_lwBootToken !== d.boot) {
                    _lwBootToken = d.boot;
                    _lwLastId = 0;
                    var _logEl = document.getElementById('lw-log');
                    if (_logEl) _logEl.innerHTML = '';
                }
            }
            if (d && d.logs) {
                d.logs.forEach(function(l) {
                    if (l.id > _lwLastId) _lwLastId = l.id;
                    lwAdd(l.message, l.level, l.module || 'system', l.ts);
                });
            }
            // Пульсирующая точка — только когда есть активная задача
            var pulse = document.getElementById('lw-pulse');
            if (pulse) pulse.classList.toggle('active', !!(d && d.active));
            // Counter: «N акков работают» + детализация модулей в tooltip
            var ctext = document.getElementById('lw-counter-text');
            var ctitle = document.getElementById('lw-counter');
            if (ctext) {
                var busy = (d && d.busy_accounts) || 0;
                if (busy === 0) {
                    ctext.textContent = 'простой';
                } else {
                    var noun = busy === 1 ? 'акк работает' : (busy < 5 ? 'акка работают' : 'акков работают');
                    // real-time прогресс: N акков, M из K отправлено
                    var sent = (d && d.rt_sent) || 0;
                    var total = (d && d.rt_total) || 0;
                    // глагол приходит с бэка (проверено/отправлено)
                    var verb = (d && d.rt_verb) || 'отправлено';
                    var statSuffix = '';
                    if (total > 0) {
                        statSuffix = ' · ' + sent + ' из ' + total + ' ' + verb;
                    } else if (sent > 0) {
                        statSuffix = ' · ' + sent + ' ' + verb;
                    }
                    ctext.textContent = busy + ' ' + noun + statSuffix;
                }
            }
            if (ctitle && d && d.active_modules) {
                var details = d.active_modules.map(function(m) { return m[0]; }).join(', ');
                ctitle.title = details ? 'Работают: ' + details : 'Сколько аккаунтов сейчас занято задачами';
            }
        })
        .catch(function() {});
}
setInterval(lwPoll, 1500);
lwPoll();
</script>
</body>
</html>
