import { authFetch } from './authWrap'; import { log, error, xhrPost } from './logger'; import { timer } from './timers'; interface LogLine { created: number; level: string; module: string; facility: string; msg: string; } let logMonitorEl: HTMLElement | null = null; let logMonitorStatus = true; let logWarnings = 0; let logErrors = 0; let logConnected = false; function dateToStr(ts: number): string { const dt = new Date(1000 * ts); // const year = dt.getFullYear(); // const mo = String(dt.getMonth() + 1).padStart(2, '0'); // const day = String(dt.getDate()).padStart(2, '0'); const hour = String(dt.getHours()).padStart(2, '0'); const min = String(dt.getMinutes()).padStart(2, '0'); const sec = String(dt.getSeconds()).padStart(2, '0'); const ms = String(dt.getMilliseconds()).padStart(3, '0'); // const s = `${year}-${mo}-${day} ${hour}:${min}:${sec}.${ms}`; const s = `${hour}:${min}:${sec}.${ms}`; return s; } function htmlEscape(text: string): string { return text.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>'); } function parseLogLine(line: string): LogLine { let str = line.replaceAll('\n', ' ').replaceAll('\\', '\\\\'); const tracebackIndex = str.indexOf('Traceback'); if (tracebackIndex !== -1) str = str.substring(0, tracebackIndex); const parsed = JSON.parse(str) as Partial; return { created: Number(parsed.created ?? Date.now()), level: String(parsed.level ?? 'INFO'), module: String(parsed.module ?? 'logMonitor'), facility: String(parsed.facility ?? 'ui'), msg: String(parsed.msg ?? ''), }; } async function clearErrors(): Promise { logWarnings = 0; logErrors = 0; log('clearErrors'); } export async function initClearErrorsButton() { const btnServerClear = document.getElementById('btn_console_log_server_clear'); if (btnServerClear) { btnServerClear.onclick = async (evt) => { evt.preventDefault(); evt.stopPropagation(); clearErrors(); }; } } async function logMonitor() { const addLogLine = (line: string): void => { if (!logMonitorEl) logMonitorEl = document.getElementById('logMonitorData'); if (!logMonitorEl) return; try { const l = parseLogLine(line); const row = document.createElement('tr'); // row.style = 'padding: 10px; margin: 0;'; const level = `${l.level}`; if (l.level === 'WARNING') logWarnings++; if (l.level === 'ERROR') logErrors++; const module = `${l.module}`; const facilityText = l.facility.length > 20 ? `${l.facility.substring(0, 20)}...` : l.facility; const facility = l.facility !== 'sd' ? `${facilityText}` : ''; row.innerHTML = `${dateToStr(l.created)}${level}${facility}${module}${htmlEscape(l.msg)}`; logMonitorEl.appendChild(row); } catch (err) { error('logMonitor', { error: String(err), line }); } }; const cleanupLog = (atBottom: boolean): void => { if (!logMonitorEl) return; while (logMonitorEl.childElementCount > 100 && logMonitorEl.firstElementChild) { logMonitorEl.removeChild(logMonitorEl.firstElementChild); } if (atBottom) logMonitorEl.scrollTop = logMonitorEl.scrollHeight; else if (logMonitorEl.parentElement) logMonitorEl.parentElement.style.cssText = 'border-bottom: 2px solid var(--highlight-color);'; const elWarn = document.getElementById('logWarnings'); const elErr = document.getElementById('logErrors'); const modenUIBtn = document.getElementById('btn_console'); if (elWarn) elWarn.innerText = String(logWarnings); if (elErr) elErr.innerText = String(logErrors); if (modenUIBtn) { modenUIBtn.setAttribute('error-count', logErrors > 0 ? String(logErrors) : ''); modenUIBtn.style.backgroundColor = logErrors > 0 ? 'var(--color-error)' : ''; modenUIBtn.title = `Log\nErrors ${logErrors}\nWarnings ${logWarnings}`; } }; const txtGallery = document.getElementById('txt2img_gallery'); if (txtGallery) txtGallery.style.height = window.opts.logmonitor_show ? '50vh' : '55vh'; const imgGallery = document.getElementById('img2img_gallery'); if (imgGallery) imgGallery.style.height = window.opts.logmonitor_show ? '50vh' : '55vh'; if (!window.opts.logmonitor_show) { Array.from(document.getElementsByClassName('log-monitor')).forEach((el) => { if (el instanceof HTMLElement) el.style.display = 'none'; }); return; } if (logMonitorStatus) setTimeout(logMonitor, window.opts.logmonitor_refresh_period); else setTimeout(logMonitor, 10 * 1000); // on failure try to reconnect every 10sec logMonitorStatus = false; if (!logMonitorEl) { logMonitorEl = document.getElementById('logMonitorData'); if (logMonitorEl) { logMonitorEl.addEventListener('scroll', () => { const atBottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight); if (atBottom && logMonitorEl.parentElement) logMonitorEl.parentElement.style.cssText = ''; }); } } if (!logMonitorEl) return; const atBottom = logMonitorEl.scrollHeight <= (logMonitorEl.scrollTop + logMonitorEl.clientHeight); try { const res = await authFetch(`${window.api}/log?clear=True`); if (res?.ok) { logMonitorStatus = true; const lines = (await res.json()) as string[]; if (logMonitorEl && lines?.length > 0 && logMonitorEl.parentElement?.parentElement instanceof HTMLElement) { logMonitorEl.parentElement.parentElement.style.display = window.opts.logmonitor_show ? 'block' : 'none'; } if (!logConnected) { logConnected = true; xhrPost(`${window.api}/log`, { debug: 'connected' }); logErrors = 0; // reset error count on reconnect } for (const line of lines) addLogLine(line); } else { logConnected = false; logErrors++; if (res) addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Failed to fetch log: ${res?.status} ${res?.statusText}" }`); else addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Server unreachable" }`); } cleanupLog(atBottom); } catch { logConnected = false; logErrors++; addLogLine(`{ "created": ${Date.now()}, "level":"ERROR", "module":"logMonitor", "facility":"ui", "msg":"Server unreachable" }`); cleanupLog(atBottom); } } export async function initLogMonitor() { let el = document.getElementById('logMonitorPlaceholder'); const modernUi = Boolean(el); if (!el) el = document.getElementsByTagName('footer')[0]; if (!el) return; const t0 = performance.now(); el.classList.add('log-monitor'); const uiDisabled = Array.isArray(window.opts.ui_disabled) ? window.opts.ui_disabled : []; if (uiDisabled.includes('logs')) return; if (modernUi) { el.style.overflow = 'auto'; el.innerHTML = `
`; } else { el.innerHTML = `
Time Level Module Message Warnings 0 Errors 0
`; } el.style.display = 'none'; authFetch(`${window.api}/start?agent=${encodeURI(navigator.userAgent)}`); logMonitor(); initClearErrorsButton(); const t1 = performance.now(); log('initLogMonitor', { show: window.opts.logmonitor_show, time: Math.round(t1 - t0) }); timer('initLogMonitor', t1 - t0); }