Files
ewoooc/web/static/js/page-logs.js
OoO 605250619c
All checks were successful
CD Pipeline / deploy (push) Successful in 1m3s
Frontend V3 responsive production update
2026-05-12 18:27:29 +08:00

192 lines
7.2 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* ═══════════════════════════════════════════════════════════
* page-logs.js — 系統日誌
* 從原 logs.html L645-L865 抽出,邏輯不動
* 僅將 active class 名稱統一為 is-active隨 CSS 對齊)
* ═══════════════════════════════════════════════════════════ */
let autoRefreshEnabled = true;
let autoScrollEnabled = true;
let currentFilter = 'all';
let searchKeyword = '';
let refreshInterval = null;
let rawLogs = '';
document.addEventListener('DOMContentLoaded', function () {
if (typeof showLoading === 'function') showLoading('正在載入系統日誌...', '請稍候');
fetchLogs();
startAutoRefresh();
});
function updateLastUpdateTime() {
const now = new Date();
document.getElementById('last-update').textContent = `最後更新: ${now.toLocaleTimeString('zh-TW')}`;
}
function fetchLogs() {
fetch('/api/logs')
.then(r => r.json())
.then(data => {
rawLogs = data.logs || '';
updateStats(rawLogs);
displayLogs();
updateLastUpdateTime();
document.getElementById('connection-status').textContent = '連接正常';
if (typeof hideLoading === 'function') hideLoading();
})
.catch(e => {
console.error('Error fetching logs:', e);
document.getElementById('connection-status').textContent = '連接失敗';
if (typeof showToast === 'function') showToast('載入日誌失敗', 'error');
if (typeof hideLoading === 'function') hideLoading();
});
}
function displayLogs() {
const container = document.getElementById('log-container');
if (!rawLogs || !rawLogs.trim()) {
container.innerHTML = '<div class="log-empty"><i class="fas fa-file-alt"></i><p>暫無日誌資料</p></div>';
return;
}
let lines = rawLogs.split('\n');
if (currentFilter !== 'all') {
lines = lines.filter(line => {
const u = line.toUpperCase();
if (currentFilter === 'error') return u.includes('ERROR');
if (currentFilter === 'warning') return u.includes('WARNING');
if (currentFilter === 'info') return u.includes('INFO');
return true;
});
}
if (searchKeyword) {
lines = lines.filter(line => line.toLowerCase().includes(searchKeyword.toLowerCase()));
}
const html = lines.map(formatLogLine).join('\n');
container.innerHTML = html || '<div class="log-empty"><i class="fas fa-search"></i><p>沒有符合的日誌</p></div>';
if (autoScrollEnabled) container.scrollTop = container.scrollHeight;
}
function formatLogLine(line) {
if (!line.trim()) return '';
let cls = '';
let f = line;
const u = line.toUpperCase();
if (u.includes('ERROR')) { cls = 'error'; f = line.replace(/ERROR/gi, '<span class="log-error">ERROR</span>'); }
else if (u.includes('WARNING')) { cls = 'warning'; f = line.replace(/WARNING/gi, '<span class="log-warning">WARNING</span>'); }
else if (u.includes('INFO')) { cls = 'info'; f = line.replace(/INFO/gi, '<span class="log-info">INFO</span>'); }
f = f.replace(/(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})/g, '<span class="log-timestamp">$1</span>');
if (searchKeyword) {
const re = new RegExp(`(${searchKeyword.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
f = f.replace(re, '<span class="highlight">$1</span>');
}
return `<div class="log-line ${cls}">${f}</div>`;
}
function updateStats(logs) {
const lines = logs.split('\n').filter(l => l.trim());
document.getElementById('total-lines').textContent = lines.length;
document.getElementById('error-count').textContent = lines.filter(l => l.toUpperCase().includes('ERROR')).length;
document.getElementById('warning-count').textContent = lines.filter(l => l.toUpperCase().includes('WARNING')).length;
document.getElementById('info-count').textContent = lines.filter(l => l.toUpperCase().includes('INFO')).length;
}
function refreshLogs() {
const btn = event.target.closest('.btn-control');
btn.classList.add('spinning');
btn.disabled = true;
fetchLogs();
setTimeout(() => {
btn.classList.remove('spinning');
btn.disabled = false;
if (typeof showToast === 'function') showToast('日誌已刷新', 'success');
}, 600);
}
function toggleAutoRefresh() {
autoRefreshEnabled = !autoRefreshEnabled;
const btn = document.getElementById('pause-btn');
if (autoRefreshEnabled) {
btn.className = 'btn-control btn-control--pause';
btn.innerHTML = '<i class="fas fa-pause"></i><span>暫停自動刷新</span>';
startAutoRefresh();
if (typeof showToast === 'function') showToast('已啟用自動刷新', 'success');
} else {
btn.className = 'btn-control btn-control--resume';
btn.innerHTML = '<i class="fas fa-play"></i><span>繼續自動刷新</span>';
stopAutoRefresh();
if (typeof showToast === 'function') showToast('已暫停自動刷新', 'info');
}
}
function startAutoRefresh() {
if (refreshInterval) clearInterval(refreshInterval);
refreshInterval = setInterval(fetchLogs, 5000);
}
function stopAutoRefresh() {
if (refreshInterval) { clearInterval(refreshInterval); refreshInterval = null; }
}
function clearLogs() {
if (!confirm('確定要清除日誌顯示嗎?(不會刪除實際日誌檔案)')) return;
rawLogs = '';
updateStats('');
displayLogs();
if (typeof showToast === 'function') showToast('已清除日誌顯示', 'success');
}
function downloadLogs() {
if (!rawLogs) { if (typeof showToast === 'function') showToast('沒有可下載的日誌', 'error'); return; }
const blob = new Blob([rawLogs], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
const ts = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
a.download = `momo_system_logs_${ts}.txt`;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
if (typeof showToast === 'function') showToast('日誌已下載', 'success');
}
function filterByLevel(level) {
currentFilter = level;
document.querySelectorAll('.btn-filter').forEach(b => {
b.classList.toggle('is-active', b.dataset.level === level);
});
displayLogs();
}
function searchLogs() {
const input = document.getElementById('search-input');
const box = document.getElementById('search-box');
searchKeyword = input.value.trim();
box.classList.toggle('has-text', !!searchKeyword);
displayLogs();
}
function clearSearch() {
const input = document.getElementById('search-input');
input.value = '';
searchKeyword = '';
document.getElementById('search-box').classList.remove('has-text');
displayLogs();
input.focus();
}
function changeFontSize(size) {
const c = document.getElementById('log-container');
c.classList.remove('font-small', 'font-medium', 'font-large');
c.classList.add(`font-${size}`);
document.querySelectorAll('.btn-font-size').forEach(b => {
b.classList.toggle('is-active', b.dataset.size === size);
});
}
function toggleAutoScroll() {
autoScrollEnabled = document.getElementById('auto-scroll-toggle').checked;
if (autoScrollEnabled) {
const c = document.getElementById('log-container');
c.scrollTop = c.scrollHeight;
}
}