mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-07-28 03:01:54 +08:00
239 lines
6.5 KiB
JavaScript
239 lines
6.5 KiB
JavaScript
// SPDX-License-Identifier: Apache-2.0
|
|
/*
|
|
|
|
* Copyright (C) 2022-2026 sirpdboy <herboy2008@gmail.com>
|
|
*/
|
|
'use strict';
|
|
'require dom';
|
|
'require fs';
|
|
'require poll';
|
|
'require uci';
|
|
'require view';
|
|
'require form';
|
|
|
|
return view.extend({
|
|
render: function () {
|
|
var css = `
|
|
#log_textarea pre {
|
|
padding: 10px; /* 内边距 */
|
|
border-bottom: 1px solid #ddd; /* 边框颜色 */
|
|
font-size: small;
|
|
line-height: 1.3; /* 行高 */
|
|
white-space: pre-wrap;
|
|
word-wrap: break-word;
|
|
overflow-y: auto;
|
|
}
|
|
.cbi-section small {
|
|
margin-left: 1rem;
|
|
font-size: small;
|
|
}
|
|
.log-container {
|
|
display: flex;
|
|
flex-direction: column;
|
|
max-height: 1200px;
|
|
overflow-y: auto;
|
|
border-radius: 3px;
|
|
margin-top: 10px;
|
|
padding: 5px;
|
|
}
|
|
.log-line {
|
|
padding: 3px 0;
|
|
font-family: monospace;
|
|
font-size: 12px;
|
|
line-height: 1.4;
|
|
}
|
|
.log-line:last-child {
|
|
border-bottom: none;
|
|
}
|
|
.log-timestamp {
|
|
margin-right: 10px;
|
|
}
|
|
`;
|
|
|
|
var log_container = E('div', { 'class': 'log-container', 'id': 'log_container' },
|
|
E('img', {
|
|
'src': L.resource(['icons/loading.gif']),
|
|
'alt': _('Loading...'),
|
|
'style': 'vertical-align:middle'
|
|
}, _('Collecting data ...'))
|
|
);
|
|
|
|
var log_path = '/var/log/timecontrol.log';
|
|
var lastLogContent = '';
|
|
var lastScrollTop = 0;
|
|
var isScrolledToTop = true;
|
|
|
|
// 解析日志行的时间戳,用于排序
|
|
function parseLogTimestamp(logLine) {
|
|
// 假设日志格式为: [2024-01-01 12:00:00] INFO: some message
|
|
var timestampMatch = logLine.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/);
|
|
if (timestampMatch) {
|
|
return new Date(timestampMatch[1]).getTime();
|
|
}
|
|
return Date.now();
|
|
}
|
|
|
|
function reverseLogLines(logContent) {
|
|
if (!logContent || logContent.trim() === '') {
|
|
return logContent;
|
|
}
|
|
|
|
var lines = logContent.split('\n');
|
|
|
|
lines = lines.filter(function(line) {
|
|
return line.trim() !== '';
|
|
});
|
|
|
|
lines.sort(function(a, b) {
|
|
var timeA = parseLogTimestamp(a);
|
|
var timeB = parseLogTimestamp(b);
|
|
return timeB - timeA; // 降序排列
|
|
});
|
|
|
|
return lines.join('\n');
|
|
}
|
|
|
|
function formatLogLines(logContent, isNewContent) {
|
|
if (!logContent || logContent.trim() === '') {
|
|
return E('div', { 'class': 'log-line' }, _('Log is clean.'));
|
|
}
|
|
|
|
var lines = logContent.split('\n');
|
|
var formattedLines = [];
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var line = lines[i].trim();
|
|
if (line === '') continue;
|
|
|
|
var timestampMatch = line.match(/^\[(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2})\]/);
|
|
var timestampSpan = null;
|
|
var messageSpan = null;
|
|
var lineClass = 'log-line';
|
|
|
|
if (timestampMatch) {
|
|
timestampSpan = E('span', {
|
|
'class': 'log-timestamp',
|
|
'title': timestampMatch[1]
|
|
}, timestampMatch[0] + ' ');
|
|
messageSpan = E('span', {}, line.substring(timestampMatch[0].length + 1));
|
|
} else {
|
|
messageSpan = E('span', {}, line);
|
|
}
|
|
|
|
var lineDiv = E('div', { 'class': lineClass }, [
|
|
timestampSpan,
|
|
messageSpan
|
|
].filter(function(el) { return el !== null; }));
|
|
|
|
formattedLines.push(lineDiv);
|
|
}
|
|
|
|
return E('div', {}, formattedLines);
|
|
}
|
|
|
|
var clear_log_button = E('div', {}, [
|
|
E('button', {
|
|
'class': 'cbi-button cbi-button-remove',
|
|
'click': function (ev) {
|
|
ev.preventDefault();
|
|
var button = ev.target;
|
|
button.disabled = true;
|
|
button.textContent = _('Clear Logs...');
|
|
fs.exec_direct('/usr/libexec/timecontrol-call', ['clear_log'])
|
|
.then(function () {
|
|
button.textContent = _('Logs cleared successfully!');
|
|
button.disabled = false;
|
|
button.textContent = _('Clear Logs');
|
|
// 立即刷新日志显示框
|
|
var logContent = _('Log is clean.');
|
|
lastLogContent = logContent;
|
|
dom.content(log_container, formatLogLines(logContent, false));
|
|
isScrolledToTop = true; // 清空日志后,保持在顶部
|
|
})
|
|
.catch(function () {
|
|
button.textContent = _('Failed to clear log.');
|
|
button.disabled = false;
|
|
button.textContent = _('Clear Logs');
|
|
});
|
|
}
|
|
}, _('Clear Logs'))
|
|
]);
|
|
log_container.addEventListener('scroll', function() {
|
|
lastScrollTop = this.scrollTop;
|
|
isScrolledToTop = this.scrollTop <= 1;
|
|
});
|
|
|
|
poll.add(L.bind(function () {
|
|
return fs.read_direct(log_path, 'text')
|
|
.then(function (res) {
|
|
var logContent = res.trim();
|
|
if (logContent === '') {
|
|
logContent = _('Log is clean.');
|
|
}
|
|
|
|
// 检查内容是否有变化
|
|
if (logContent !== lastLogContent) {
|
|
var isNewContent = lastLogContent !== '' && lastLogContent !== _('Log is clean.');
|
|
var reversedLog = reverseLogLines(logContent);
|
|
// 格式化为HTML
|
|
var formattedLog = formatLogLines(reversedLog, isNewContent);
|
|
|
|
var prevScrollHeight = log_container.scrollHeight;
|
|
var prevScrollTop = log_container.scrollTop;
|
|
|
|
dom.content(log_container, formattedLog);
|
|
lastLogContent = logContent;
|
|
|
|
if (isScrolledToTop || isNewContent) {
|
|
log_container.scrollTop = 0;
|
|
} else {
|
|
var newScrollHeight = log_container.scrollHeight;
|
|
var heightDiff = newScrollHeight - prevScrollHeight;
|
|
log_container.scrollTop = prevScrollTop + heightDiff;
|
|
}
|
|
}
|
|
}).catch(function (err) {
|
|
var logContent;
|
|
if (err.toString().includes('NotFoundError')) {
|
|
logContent = _('Log file does not exist.');
|
|
} else {
|
|
logContent = _('Unknown error: %s').format(err);
|
|
}
|
|
|
|
if (logContent !== lastLogContent) {
|
|
dom.content(log_container, formatLogLines(logContent, false));
|
|
lastLogContent = logContent;
|
|
}
|
|
});
|
|
}));
|
|
|
|
// 启动轮询
|
|
poll.start();
|
|
return E('div', { 'class': 'cbi-map' }, [
|
|
E('style', [css]),
|
|
E('div', { 'class': 'cbi-section' }, [
|
|
clear_log_button,
|
|
log_container,
|
|
E('small', {}, _('Refresh every 5 seconds.').format(L.env.pollinterval)),
|
|
E('div', { 'class': 'cbi-section-actions cbi-section-actions-right' })
|
|
]),
|
|
E('div', { 'style': 'text-align: right; font-style: italic;' }, [
|
|
E('span', {}, [
|
|
_('© github '),
|
|
E('a', {
|
|
'href': 'https://github.com/sirpdboy',
|
|
'target': '_blank',
|
|
'style': 'text-decoration: none;'
|
|
}, 'by sirpdboy')
|
|
])
|
|
])
|
|
|
|
|
|
]);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|