mirror of
https://github.com/openwrt/luci.git
synced 2026-05-31 10:31:55 +08:00
0b9e8b4f52
Signed-off-by: Paul Donald <newtwen+github@gmail.com>
298 lines
8.1 KiB
JavaScript
298 lines
8.1 KiB
JavaScript
'use strict';
|
|
'require view';
|
|
'require fs';
|
|
'require poll';
|
|
'require ui';
|
|
|
|
return view.extend({
|
|
logFilterFrom: '0',
|
|
logFilterTo: '',
|
|
invertLogRangeFilter: false,
|
|
|
|
minSeverity: '',
|
|
invertMinSeverity: false,
|
|
|
|
sortLogsDescending: false,
|
|
|
|
logTextFilter: '',
|
|
invertLogTextSearch: false,
|
|
|
|
severity: [
|
|
['', 'KERN_DEFAULT', _('Default')],
|
|
// ['0', 'KERN_EMERG'], // unusable kernels tend to halt ergo no running system
|
|
['1', 'KERN_ALERT', _('Alert')],
|
|
['2', 'KERN_CRIT', _('Critical')],
|
|
['3', 'KERN_ERR', _('Error')],
|
|
['4', 'KERN_WARNING', _('Warning')],
|
|
['5', 'KERN_NOTICE', _('Notice')],
|
|
['6', 'KERN_INFO', _('Info')],
|
|
['7', 'KERN_DEBUG', _('Debug')],
|
|
// ['c', 'KERN_CONT'], // for follow-on printed lines lacking newline
|
|
/*
|
|
As of 24.10 there appear to be kernel log lines printed with severity 14-15
|
|
which seems like a bug in ubox. So we must structure the filter in an
|
|
'at least' fashion to include those.
|
|
*/
|
|
],
|
|
|
|
async retrieveLog() {
|
|
return fs.exec_direct('/bin/dmesg', [ '-r' ]).then(logdata => {
|
|
let loglines = [];
|
|
let lastSeverity = null;
|
|
let lastTime = null;
|
|
|
|
logdata.trim().split(/\n/).forEach(line => {
|
|
const priorityMatch = line.match(/^<(\w+)>/);
|
|
if (!priorityMatch) return;
|
|
|
|
const tag = priorityMatch[1];
|
|
const isCont = tag === 'c';
|
|
const cleanLine = line.replace(/^<\w+>/, '');
|
|
const timeMatch = cleanLine.match(/^\[\s*(\d+(?:\.\d+)?)\]/);
|
|
const time = timeMatch ? parseFloat(timeMatch[1]) : null;
|
|
if (time != null)
|
|
lastTime = time;
|
|
|
|
if (!isCont) {
|
|
lastSeverity = parseInt(tag, 10); // update severity
|
|
}
|
|
|
|
loglines.push({
|
|
severity: isCont ? lastSeverity : parseInt(tag, 10),
|
|
isCont,
|
|
time: time != null ? time : lastTime,
|
|
text: cleanLine
|
|
});
|
|
});
|
|
|
|
// Filter by time
|
|
const hasStart = this.logFilterFrom;
|
|
const hasEnd = this.logFilterTo;
|
|
|
|
if (hasStart || hasEnd) {
|
|
loglines = loglines.filter(({ time }) => {
|
|
if (time == null) return false;
|
|
|
|
let inRange = true;
|
|
if (hasStart && hasEnd)
|
|
inRange = time >= this.logFilterFrom && time <= this.logFilterTo;
|
|
else if (hasStart)
|
|
inRange = time >= this.logFilterFrom;
|
|
else if (hasEnd)
|
|
inRange = time <= this.logFilterTo;
|
|
|
|
return this.invertLogRangeFilter ? !inRange : inRange;
|
|
});
|
|
}
|
|
|
|
// Filter by severity
|
|
loglines = loglines.filter(entry => {
|
|
if (this.invertMinSeverity)
|
|
return entry.severity < this.minSeverity;
|
|
else
|
|
return entry.severity >= this.minSeverity;
|
|
});
|
|
|
|
// Filter by text
|
|
if (this.logTextFilter) {
|
|
loglines = loglines.filter(({ text }) => {
|
|
const match = text.includes(this.logTextFilter);
|
|
return this.invertLogTextSearch ? !match : match;
|
|
});
|
|
}
|
|
|
|
// Sort by time
|
|
if (this.sortLogsDescending) loglines.reverse();
|
|
|
|
return {
|
|
value: loglines.map(l => l.text).join('\n'),
|
|
rows: loglines.length + 1
|
|
};
|
|
}).catch(function(err) {
|
|
ui.addNotification(null, E('p', {}, _('Unable to load log data: ' + err.message)));
|
|
return '';
|
|
});
|
|
},
|
|
|
|
async pollLog() {
|
|
const element = document.getElementById('syslog');
|
|
if (element) {
|
|
const log = await this.retrieveLog();
|
|
element.value = log.value;
|
|
element.rows = log.rows;
|
|
}
|
|
},
|
|
|
|
async load() {
|
|
poll.add(this.pollLog.bind(this));
|
|
return await this.retrieveLog();
|
|
},
|
|
|
|
render(loglines) {
|
|
const scrollDownButton = E('button', {
|
|
'id': 'scrollDownButton',
|
|
'class': 'cbi-button cbi-button-neutral',
|
|
}, _('Scroll to tail', 'scroll to bottom (the tail) of the log file')
|
|
);
|
|
scrollDownButton.addEventListener('click', () => {
|
|
scrollUpButton.scrollIntoView();
|
|
scrollDownButton.blur();
|
|
});
|
|
|
|
const scrollUpButton = E('button', {
|
|
'id' : 'scrollUpButton',
|
|
'class': 'cbi-button cbi-button-neutral',
|
|
}, _('Scroll to head', 'scroll to top (the head) of the log file')
|
|
);
|
|
scrollUpButton.addEventListener('click', () => {
|
|
scrollDownButton.scrollIntoView();
|
|
scrollUpButton.blur();
|
|
});
|
|
|
|
const self = this;
|
|
|
|
|
|
// Create range invert checkbox
|
|
const rangeTimeInvert = E('input', {
|
|
'id': 'invertLogRangeTime',
|
|
'type': 'checkbox',
|
|
'class': 'cbi-input-checkbox',
|
|
});
|
|
|
|
// Create from time filter
|
|
const fromTimeFilter = E('input', {
|
|
'id': 'logFromTime',
|
|
'class': 'cbi-input-text',
|
|
'style': 'margin-bottom:10px',
|
|
'type': 'number',
|
|
'min': '0',
|
|
'step': '0.1',
|
|
'placeholder': '0.000000',
|
|
});
|
|
|
|
// Create to time filter
|
|
const toTimeFilter = E('input', {
|
|
'id': 'logToTime',
|
|
'class': 'cbi-input-text',
|
|
'style': 'margin-bottom:10px',
|
|
'type': 'number',
|
|
'min': '0',
|
|
'step': '0.1',
|
|
'placeholder': '0.000000',
|
|
});
|
|
|
|
|
|
// Create range invert checkbox
|
|
const severityInvert = E('input', {
|
|
'id': 'invertSeverity',
|
|
'type': 'checkbox',
|
|
'class': 'cbi-input-checkbox',
|
|
});
|
|
|
|
// Create severity select-dropdown from severity map
|
|
const severitySelect = E('select', {
|
|
'id': 'logSeveritySelect',
|
|
'class': 'cbi-input-select',
|
|
},
|
|
this.severity.map(([val, , label]) =>
|
|
E('option', { value: val }, label)
|
|
));
|
|
|
|
// Create range invert checkbox
|
|
const descendingSort = E('input', {
|
|
'id': 'invertAscendingSort',
|
|
'type': 'checkbox',
|
|
'class': 'cbi-input-checkbox',
|
|
});
|
|
|
|
// Create raw text search invert checkbox
|
|
const filterTextInvert = E('input', {
|
|
'id': 'invertLogTextSearch',
|
|
'type': 'checkbox',
|
|
'class': 'cbi-input-checkbox',
|
|
});
|
|
|
|
// Create raw text search text input
|
|
const filterTextInput = E('input', {
|
|
'id': 'logTextFilter',
|
|
'class': 'cbi-input-text',
|
|
});
|
|
|
|
function handleLogFilterChange() {
|
|
// time
|
|
self.invertLogRangeFilter = rangeTimeInvert.checked;
|
|
self.logFilterFrom = fromTimeFilter.value;
|
|
self.logFilterTo = toTimeFilter.value;
|
|
|
|
// severity
|
|
self.minSeverity = severitySelect.value;
|
|
self.invertMinSeverity = severityInvert.checked;
|
|
|
|
// sort
|
|
self.sortLogsDescending = descendingSort.checked;
|
|
|
|
// text
|
|
self.logTextFilter = filterTextInput.value;
|
|
self.invertLogTextSearch = filterTextInvert.checked;
|
|
self.pollLog();
|
|
}
|
|
|
|
// time
|
|
rangeTimeInvert.addEventListener('change', handleLogFilterChange);
|
|
fromTimeFilter.addEventListener('input', handleLogFilterChange);
|
|
toTimeFilter.addEventListener('input', handleLogFilterChange);
|
|
// severity
|
|
severitySelect.addEventListener('change', handleLogFilterChange);
|
|
severityInvert.addEventListener('change', handleLogFilterChange);
|
|
// sort
|
|
descendingSort.addEventListener('change', handleLogFilterChange);
|
|
// text
|
|
filterTextInput.addEventListener('input', handleLogFilterChange);
|
|
filterTextInvert.addEventListener('change', handleLogFilterChange);
|
|
|
|
return E([], [
|
|
E('h2', {}, [ _('Kernel Log') ]),
|
|
E('div', { 'id': 'content_syslog' }, [
|
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
|
E('label', { 'for': 'invertLogFacilitySearch', 'style': 'margin-right:5px' }, _('Not')),
|
|
rangeTimeInvert,
|
|
E('label', { 'for': 'logFacilitySelect', 'style': 'margin: 0 5px' }, _('between:')),
|
|
fromTimeFilter,
|
|
E('label', { 'for': 'logSeveritySelect', 'style': 'margin: 0 5px' }, _('and:')),
|
|
toTimeFilter,
|
|
]),
|
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
|
E('label', { 'for': 'invertLogSeveritySearch', 'style': 'margin-right:5px' }, _('Not')),
|
|
severityInvert,
|
|
'\xa0',
|
|
severitySelect,
|
|
E('label', { 'for': 'logSeveritySelect', 'style': 'margin: 0 5px' }, _('and above')),
|
|
]),
|
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
|
E('label', { 'for': 'invertAscendingSort', 'style': 'margin-right:5px' }, _('Reverse sort')),
|
|
descendingSort,
|
|
]),
|
|
E('div', { 'style': 'margin-bottom:10px' }, [
|
|
E('label', { 'for': 'invertLogTextSearch', 'style': 'margin-right:5px' }, _('Not')),
|
|
filterTextInvert,
|
|
E('label', { 'for': 'logTextFilter', 'style': 'margin: 0 5px' }, _('including:')),
|
|
filterTextInput,
|
|
]),
|
|
E('div', {'style': 'padding-bottom: 20px'}, [scrollDownButton]),
|
|
E('textarea', {
|
|
'id': 'syslog',
|
|
'style': 'font-size:12px',
|
|
'readonly': 'readonly',
|
|
'wrap': 'off',
|
|
'rows': loglines.rows
|
|
}, [ loglines.value ]),
|
|
E('div', {'style': 'padding-bottom: 20px'}, [scrollUpButton])
|
|
])
|
|
]);
|
|
},
|
|
|
|
handleSaveApply: null,
|
|
handleSave: null,
|
|
handleReset: null
|
|
});
|