update 2026-05-27 23:25:57

This commit is contained in:
action committed 2026-05-27 23:25:57 +08:00
1 parent 84f707b52b
commit d0159b49a5
759 files changed
+362365

No files matched your search

@@ -0,0 +1,168 @@
'use strict';
'require fs';
'require rpc';
'require ui';
'require view.log-viewer.log-widget as widget';
return widget.view.extend({
viewName : 'dmesg',
title : _('Kernel Log'),
enableAutoRefresh : true,
enableConvertTimestamp: true,
entryRegexp : new RegExp(/^<(\d{1,2})>\[([\d\s.]+)\]\s+(.*)$/),
entriesHandler : null,
facilityName : [
'kern',
'user',
'mail',
'daemon',
'auth',
'syslog',
'lpr',
'news',
],
localtime : null,
uptime : null,
days : {
0: 'Sun',
1: 'Mon',
2: 'Tue',
3: 'Wed',
4: 'Thu',
5: 'Fri',
6: 'Sat',
7: 'Sun',
},
months : {
1: 'Jan',
2: 'Feb',
3: 'Mar',
4: 'Apr',
5: 'May',
6: 'Jun',
7: 'Jul',
8: 'Aug',
9: 'Sep',
10: 'Oct',
11: 'Nov',
12: 'Dec',
},
logCols : [
'#',
_('Timestamp'),
null,
_('Facility'),
_('Level'),
_('Message'),
],
callLogHash : rpc.declare({
object: 'luci.log-viewer',
method: 'getDmesgHash',
expect: { '': {} }
}),
callSystemInfo : rpc.declare({
object: 'system',
method: 'info'
}),
getLogHash() {
return this.callLogHash().then(data => {
return data.hash || '';
});
},
convertTimestampFunc(t) {
if(!this.convertTimestampValue || !this.localtime || !this.uptime) {
return t;
};
let date = new Date((this.localtime - this.uptime + Math.round(Number(t))) * 1000);
return '%s %s %2d %02d:%02d:%02d %d'.format(
this.days[ date.getUTCDay() ],
this.months[ date.getUTCMonth() + 1 ],
date.getUTCDate(),
date.getUTCHours(),
date.getUTCMinutes(),
date.getUTCSeconds(),
date.getUTCFullYear()
);
},
async getLogData(tail) {
await this.callSystemInfo().then(s => {
this.localtime = s.localtime;
this.uptime = s.uptime;
}).catch(err => {});
return fs.exec_direct('/bin/dmesg', [ '-r' ], 'text').catch(err => {
throw new Error(_('Unable to load log data:') + ' ' + err.message);
});
},
parseLogData(logdata, tail) {
if(!logdata) {
return [];
};
let unsupportedLog = false;
this.logTimestampFlag = true;
this.logFacilitiesFlag = true;
this.logLevelsFlag = true;
this.logHostsFlag = false;
let strings = logdata.trim().split(/\n/);
if(tail && tail > 0 && strings) {
strings = strings.slice(-tail);
};
this.totalLogLines = strings.length;
let entriesArray = strings.map((e, i) => {
let logLevelsTranslate = Object.keys(this.logLevels);
let strArray = e.match(this.entryRegexp);
if(strArray) {
let level = 0;
let facility = 0;
if(strArray[1].length > 1) {
let fieldArray = Number(strArray[1]).toString(8).split('');
level = logLevelsTranslate[Number(fieldArray[1])];
facility = Number(fieldArray[0]);
} else {
level = logLevelsTranslate[Number(strArray[1]).toString(8)];
};
return [
i + 1, // # (Number)
this.convertTimestampFunc(strArray[2].trim()), // Timestamp (String)
null, // Host (String)
this.facilityName[ facility ], // Facility (String)
level, // Level (String)
this.htmlEntities(strArray[3]) || ' ', // Message (String)
];
} else {
unsupportedLog = true;
return;
};
});
if(unsupportedLog) {
throw new Error(_('Unable to load log data:') + ' ' + _('Unsupported log format'));
} else {
if(this.logSortingValue == 'desc') {
entriesArray.reverse();
};
return entriesArray;
};
},
});
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,179 @@
'use strict';
'require baseclass';
'require fs';
'require view.log-viewer.log-widget as widget';
return baseclass.extend({
view: widget.view.extend({
enableAutoRefresh: true,
/**
* Pattern for picking application-specific entries from the log.
*
* @property {string} appPattern
*/
appPattern : '^',
/**
* Enable "tail" option for the logread (logread -l).
* Must be disabled for application-specific log.
*
* @property {bool} loggerTail
*/
loggerTail : false,
logdRegexp : new RegExp(/^([^\s]{3}\s+[^\s]{3}\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2}\s+\d{4})\s+([a-z0-9]+)\.([a-z]+)\s+(.*)$/),
syslog_ngRegexp : new RegExp(/^([^\s]{3}\s+\d{1,2}\s+\d{1,2}:\d{1,2}:\d{1,2})\s+([^\s]+)\s+(.*)$/),
entryRegexp : null,
loggerChecked : false,
entriesHandler : null,
logger : null,
getLogHash() {
return this.getLogData(1, true).then(data => {
return data || '';
});
},
// logd
logdHandler(strArray, lineNum) {
return [
lineNum, // # (Number)
strArray[1], // Timestamp (String)
null, // Host (String)
strArray[2], // Facility (String)
strArray[3], // Level (String)
this.htmlEntities(strArray[4]) || ' ', // Message (String)
];
},
// syslog-ng
syslog_ngHandler(strArray, lineNum) {
return [
lineNum, // # (Number)
strArray[1], // Timestamp (String)
strArray[2], // Host (String)
null, // Facility (String)
null, // Level (String)
this.htmlEntities(strArray[3]) || ' ', // Message (String)
];
},
// unsupported log handler
unsupportedLogHandler(line, lineNum) {
return [
lineNum, // # (Number)
null, // Timestamp (String)
null, // Host (String)
null, // Facility (String)
null, // Level (String)
this.htmlEntities(line) || ' ', // Message (String)
];
},
checkLogread() {
return Promise.all([
L.resolveDefault(fs.stat('/sbin/logread'), null),
L.resolveDefault(fs.stat('/usr/sbin/logread'), null),
]).then(stat => {
let logger = (stat[0]) ? stat[0].path : (stat[1]) ? stat[1].path : null;
if(logger) {
this.logger = logger;
} else {
throw new Error(_('Logread not found'));
};
});
},
async getLogData(tail, extraTstamp=false) {
if(!this.logger) {
await this.checkLogread();
};
let loggerArgs = [];
if(this.loggerTail && tail) {
loggerArgs.push('-l', String(tail));
};
loggerArgs.push('-e', this.appPattern);
if(extraTstamp) {
loggerArgs.push('-t');
};
return fs.exec_direct(this.logger, loggerArgs, 'text').catch(err => {
throw new Error(_('Unable to load log data:') + ' ' + err.message);
});
},
parseLogData(logdata, tail) {
if(!logdata) {
return [];
};
let strings = logdata.trim().split(/\n/);
if(!this.loggerTail && tail && tail > 0 && strings) {
strings = strings.slice(-tail);
};
this.totalLogLines = strings.length;
let entriesArray = strings.map((e, i) => {
if(!this.loggerChecked) {
if(this.logdRegexp.test(e)) {
this.entryRegexp = this.logdRegexp;
this.logTimestampFlag = true;
this.logFacilitiesFlag = true;
this.logLevelsFlag = true;
this.logHostFlag = false;
this.entriesHandler = this.logdHandler;
this.logCols = [
'#',
_('Timestamp'),
null,
_('Facility'),
_('Level'),
_('Message'),
];
this.loggerChecked = true;
}
else if(this.syslog_ngRegexp.test(e)) {
this.entryRegexp = this.syslog_ngRegexp;
this.logTimestampFlag = true;
this.logFacilitiesFlag = false;
this.logLevelsFlag = false;
this.logHostFlag = true;
this.logFacilities = {};
this.logLevels = {};
this.entriesHandler = this.syslog_ngHandler;
this.logCols = [
'#',
_('Timestamp'),
_('Host'),
null,
null,
_('Message'),
];
this.loggerChecked = true;
} else {
return this.unsupportedLogHandler(e, i + 1);
};
};
let strArray = e.match(this.entryRegexp);
if(strArray) {
return this.entriesHandler(strArray, i + 1);
} else {
return this.unsupportedLogHandler(e, i + 1);
};
});
if(this.logSortingValue == 'desc') {
entriesArray.reverse();
};
return entriesArray;
},
}),
});
@@ -0,0 +1,117 @@
'use strict';
'require baseclass';
'require ui';
'require view.log-viewer.log-base as base';
document.head.append(E('style', {'type': 'text/css'},
`
#logTable {
width: calc(100% - 4px);
}
.log-entry-time-cell {
min-width: 14em !important;
white-space: pre-wrap !important;
}
.log-entry-host-cell {
min-width: 10em !important;
overflow-wrap: anywhere !important;
}
.log-entry-message-cell {
min-width: 20em !important;
white-space: pre-wrap !important;
overflow-wrap: anywhere !important;
}
.log-entry-text-nowrap {
white-space: nowrap !important;
};
`));
return baseclass.extend({
view: base.view.extend({
filterHighlightFunc(match) {
return `<span class="log-highlight-item">${match}</span>`;
},
makeLogArea(logdataArray) {
let lines = `<tr class="tr"><td class="td center">${_('No entries available...')}</td></tr>`;
let logTable = E('table', { 'id': 'logTable', 'class': 'table' });
for(let level of Object.keys(this.logLevels)) {
this.logLevelsStat[level] = 0;
};
if(logdataArray.length > 0) {
lines = [];
logdataArray.forEach((e, i) => {
if(e[4] in this.logLevels) {
this.logLevelsStat[e[4]] = this.logLevelsStat[e[4]] + 1;
};
let line = [ `<tr class="tr log-${(e[1]) ? e[4] || 'empty' : (this.entriesHandler) ? 'raw' : 'empty'}">` ];
this.logCols.forEach((c, i) => {
if(c) {
let cellClass = '';
switch(i) {
case 0:
case 3:
case 4:
cellClass = 'log-entry-text-nowrap';
break;
case 1:
cellClass = 'log-entry-time-cell';
break;
case 2:
cellClass = 'log-entry-host-cell';
break;
case 5:
cellClass = 'log-entry-message-cell';
break;
};
line.push(`<td class="td left ${cellClass}" data-title="${c}">${e[i] || '&#160;'}</td>`);
};
});
line.push('</tr>');
lines.push(line.join(''));
});
lines = lines.join('');
let logTableHeader = E('tr', { 'class': 'tr table-titles' });
this.logCols.forEach(e => {
if(e) {
logTableHeader.append(
E('th', { 'class': 'th left log-entry-text-nowrap' }, e)
);
};
});
logTable.append(logTableHeader);
};
try {
logTable.insertAdjacentHTML('beforeend', lines);
} catch(err) {
if(err.name == 'SyntaxError') {
ui.addNotification(null,
E('p', {}, _('HTML/XML error') + ': ' + err.message), 'error');
};
throw err;
};
let levelsStatString = '';
if((Object.values(this.logLevelsStat).reduce((s,c) => s + c, 0)) > 0) {
Object.entries(this.logLevelsStat).forEach(e => {
if(e[0] in this.logLevels && e[1] > 0) {
levelsStatString += `<span class="log-entries-count-level log-${e[0]}" title="${e[0]}">${e[1]}</span>`;
};
});
};
return E([
E('div', { 'class': 'log-entries-count' },
`${_('Entries')}: ${logdataArray.length} / ${this.totalLogLines}${levelsStatString}`
),
logTable,
]);
},
}),
});
@@ -0,0 +1,10 @@
'use strict';
'require view.log-viewer.log-system as abc';
return abc.view.extend({
viewName : 'syslog',
title : _('System Log'),
enableAutoRefresh: true,
appPattern : '^',
loggerTail : true,
});