update 2026-07-27 22:20:34

This commit is contained in:
action committed 2026-07-27 22:20:34 +08:00
1 parent 079ea4f182
commit 20b786ab77
6243 files changed
-2850540

No files matched your search

-21
View File
@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2021 gSpotx2f
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
-21
View File
@@ -1,21 +0,0 @@
#
# Copyright (C) 2026 gSpot (https://github.com/gSpotx2f/luci-app-log)
#
# This is free software, licensed under the MIT License.
#
include $(TOPDIR)/rules.mk
PKG_NAME:=luci-app-log-viewer
PKG_VERSION:=1.5.0
PKG_RELEASE:=3
LUCI_TITLE:=Advanced syslog and kernel log (tail, search, etc)
LUCI_DEPENDS:=+ucode +ucode-mod-fs
LUCI_PKGARCH:=all
#LUCI_PKGARCH:=noarch
PKG_LICENSE:=MIT
#include ../../luci.mk
include $(TOPDIR)/feeds/luci/luci.mk
# call BuildPackage - OpenWrt buildroot signature
-66
View File
@@ -1,66 +0,0 @@
# luci-app-log-viewer
Advanced syslog and kernel log (tail, search, etc) for LuCI (OpenWrt webUI).
OpenWrt >= 22.03.
Supported LuCI themes: luci-theme-bootstrap, luci-theme-material, luci-theme-openwrt-2020.
**Dependences:** ucode, ucode-mod-fs.
## Installation notes
**OpenWrt >= 25.12:**
wget --no-check-certificate -O /tmp/luci-app-log-viewer-1.5.0-r3.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-app-log-viewer-1.5.0-r3.apk
apk --allow-untrusted add /tmp/luci-app-log-viewer-1.5.0-r3.apk
rm /tmp/luci-app-log-viewer-1.5.0-r3.apk
service rpcd restart
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-log-viewer-ru-1.5.0-r3.apk https://github.com/gSpotx2f/packages-openwrt/raw/master/25.12/luci-i18n-log-viewer-ru-1.5.0-r3.apk
apk --allow-untrusted add /tmp/luci-i18n-log-viewer-ru-1.5.0-r3.apk
rm /tmp/luci-i18n-log-viewer-ru-1.5.0-r3.apk
**OpenWrt <= 24.10:**
wget --no-check-certificate -O /tmp/luci-app-log-viewer_1.5.0-r3_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-app-log-viewer_1.5.0-r3_all.ipk
opkg install /tmp/luci-app-log-viewer_1.5.0-r3_all.ipk
rm /tmp/luci-app-log-viewer_1.5.0-r3_all.ipk
service rpcd restart
i18n-ru:
wget --no-check-certificate -O /tmp/luci-i18n-log-viewer-ru_1.5.0-r3_all.ipk https://github.com/gSpotx2f/packages-openwrt/raw/master/24.10/luci-i18n-log-viewer-ru_1.5.0-r3_all.ipk
opkg install /tmp/luci-i18n-log-viewer-ru_1.5.0-r3_all.ipk
rm /tmp/luci-i18n-log-viewer-ru_1.5.0-r3_all.ipk
## Screenshots:
Kernel log:
![](https://github.com/gSpotx2f/luci-app-log/blob/master/screenshots/01.jpg)
System log (logd):
![](https://github.com/gSpotx2f/luci-app-log/blob/master/screenshots/02.jpg)
Filter settings:
![](https://github.com/gSpotx2f/luci-app-log/blob/master/screenshots/03.jpg)
System log (syslog-ng):
![](https://github.com/gSpotx2f/luci-app-log/blob/master/screenshots/04.jpg)
## Mod lines:
wget --no-check-certificate -O /www/luci-static/resources/view/log-viewer/log-widget.js https://github.com/gSpotx2f/luci-app-log/raw/master/dev-mods-examples/mod-lines/htdocs/luci-static/resources/view/log-viewer/log-widget.js
![](https://github.com/gSpotx2f/luci-app-log/blob/master/dev-mods-examples/mod-lines/screenshots/01.jpg)
## Mod textarea:
wget --no-check-certificate -O /www/luci-static/resources/view/log-viewer/log-widget.js https://github.com/gSpotx2f/luci-app-log/raw/master/dev-mods-examples/mod-textarea/htdocs/luci-static/resources/view/log-viewer/log-widget.js
![](https://github.com/gSpotx2f/luci-app-log/blob/master/dev-mods-examples/mod-textarea/screenshots/01.jpg)
@@ -1,9 +0,0 @@
'use strict';
'require view.log-viewer.log-system as abc';
return abc.view.extend({
viewName : 'dropbear',
title : _('Dropbear'),
enableAutoRefresh: false,
appPattern : 'dropbear\[[0-9]*\]:',
});
@@ -1,10 +0,0 @@
{
"admin/status/log-viewer/dropbear": {
"title": "Dropbear",
"order": 3,
"action": {
"type": "view",
"path": "log-viewer/dropbear"
}
}
}
@@ -1,113 +0,0 @@
'use strict';
'require baseclass';
'require ui';
'require view.log-viewer.log-base as base';
document.head.append(E('style', {'type': 'text/css'},
`
:root {
--app-log-entry-outline-color: #ccc;
}
:root[data-darkmode="true"] {
--app-log-entry-outline-color: #555;
}
#log-area {
width: 100%;
height: 100%;
margin-bottom: 1em;
}
.log-entry-line {
display: inline-block;
text-indent: 8px;
margin: 0 0 1px 0;
padding: 0 4px;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
border: 1px solid var(--app-log-entry-outline-color);
font-weight: normal;
/*font-size: 12px !important;*/
font-family: monospace !important;
white-space: pre-wrap !important;
overflow-wrap: anywhere !important;
}
`));
return baseclass.extend({
view: base.view.extend({
filterHighlightFunc(match) {
return `<span class="log-highlight-item">${match}</span>`;
},
padNumber(number, lengthFirst, lengthLast) {
let length = Math.max(lengthFirst, lengthLast);
try {
number = String(number).padStart(length, ' ');
} catch(e) {
if(e.name != 'TypeError') {
throw e;
};
};
return number;
},
makeLogArea(logdataArray) {
let lines = `<span class="log-entry-line center" style="width:100%">${_('No entries available...')}</span>`;
let logArea = E('div', { 'id': 'log-area' });
for(let level of Object.keys(this.logLevels)) {
this.logLevelsStat[level] = 0;
};
let logdataArrayLen = logdataArray.length;
if(logdataArray.length > 0) {
lines = [];
let firstNumLength = String(logdataArray[0][0]).length;
let lastNumLength = String(logdataArray[logdataArrayLen - 1][0]).length;
logdataArray.forEach((e, i) => {
if(e[4] in this.logLevels) {
this.logLevelsStat[e[4]] = this.logLevelsStat[e[4]] + 1;
};
e[0] = this.padNumber(e[0], firstNumLength, lastNumLength);
if(e[5]) {
e[5] = `&#9;${e[5]}`;
};
lines.push(
`<span class="log-entry-line log-${(e[1]) ? e[4] || 'empty' : (this.entriesHandler) ? 'raw' : 'empty'}">` +
e.filter(i => (i)).join(' ') +
'</span>'
);
});
lines = lines.join('<br />');
};
try {
logArea.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}`
),
logArea,
]);
},
}),
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 297 KiB

@@ -1,9 +0,0 @@
'use strict';
'require view.log-viewer.multilog.log-multilog as abc';
return abc.view.extend({
viewName : 'multilog-boot',
title : _('Log') + ' - ' + _('boot.log'),
autoRefresh: false,
logFile : '/var/log/boot.log',
});
@@ -1,9 +0,0 @@
'use strict';
'require view.log-viewer.multilog.log-multilog as abc';
return abc.view.extend({
viewName : 'multilog-cron',
title : _('Log') + ' - ' + _('cron'),
autoRefresh: false,
logFile : '/var/log/cron',
});
@@ -1,25 +0,0 @@
'use strict';
'require baseclass';
'require fs';
'require view.log-viewer.log-system as abc';
return baseclass.extend({
view: abc.view.extend({
/**
* Log file.
*
* @property {string} logFile
*/
logFile: null,
getLogHash() {
return fs.stat(this.logFile).then((data) => {
return data.mtime || '';
}).catch(e => {});
},
getLogData(tail) {
return L.resolveDefault(fs.read_direct(this.logFile, 'text'), '');
},
}),
});
@@ -1,9 +0,0 @@
'use strict';
'require view.log-viewer.multilog.log-multilog as abc';
return abc.view.extend({
viewName : 'multilog-maillog',
title : _('Log') + ' - ' + _('maillog'),
autoRefresh: false,
logFile : '/var/log/maillog',
});
@@ -1,9 +0,0 @@
'use strict';
'require view.log-viewer.multilog.log-multilog as abc';
return abc.view.extend({
viewName : 'multilog-messages',
title : _('Log') + ' - ' + _('messages'),
autoRefresh: true,
logFile : '/var/log/messages',
});
@@ -1,9 +0,0 @@
'use strict';
'require view.log-viewer.multilog.log-multilog as abc';
return abc.view.extend({
viewName : 'multilog-secure',
title : _('Log') + ' - ' + _('secure'),
autoRefresh: false,
logFile : '/var/log/secure',
});
@@ -1,67 +0,0 @@
{
"admin/status/log-viewer": {
"title": "Log Viewer",
"order": 5,
"action": {
"type": "alias",
"path": "admin/status/log-viewer/messages"
},
"depends": {
"acl": [ "luci-app-log-viewer" ]
}
},
"admin/status/log-viewer/messages": {
"title": "messages",
"order": 1,
"action": {
"type": "view",
"path": "log-viewer/multilog/messages"
}
},
"admin/status/log-viewer/secure": {
"title": "secure",
"order": 2,
"action": {
"type": "view",
"path": "log-viewer/multilog/secure"
}
},
"admin/status/log-viewer/maillog": {
"title": "maillog",
"order": 3,
"action": {
"type": "view",
"path": "log-viewer/multilog/maillog"
}
},
"admin/status/log-viewer/cron": {
"title": "cron",
"order": 4,
"action": {
"type": "view",
"path": "log-viewer/multilog/cron"
}
},
"admin/status/log-viewer/boot": {
"title": "boot",
"order": 5,
"action": {
"type": "view",
"path": "log-viewer/multilog/boot"
}
},
"admin/status/log-viewer/dmesg": {
"title": "kernel",
"order": 6,
"action": {
"type": "view",
"path": "log-viewer/dmesg"
}
}
}
@@ -1,22 +0,0 @@
{
"luci-app-log-viewer": {
"description": "Grant access to log-viewer procedures",
"read": {
"cgi-io": [ "exec" ],
"file": {
"/sbin/logread*": [ "exec" ],
"/usr/sbin/logread*": [ "exec" ],
"/bin/dmesg -r": [ "exec" ],
"/var/log/messages": [ "read" ],
"/var/log/secure": [ "read" ],
"/var/log/maillog": [ "read" ],
"/var/log/cron": [ "read" ],
"/var/log/boot.log": [ "read" ]
},
"ubus": {
"system": [ "info" ],
"luci.log-viewer": [ "getSyslogSize", "getSyslogHash", "getDmesgSize", "getDmesgHash", "getLogfileSize" ]
}
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 290 KiB

@@ -1,85 +0,0 @@
'use strict';
'require baseclass';
'require ui';
'require view.log-viewer.log-base as base';
return baseclass.extend({
view: base.view.extend({
rowsDefault: 20,
htmlEntities(str) {
return str;
},
filterHighlightFunc(match) {
return `►${match}◄`;
},
padNumber(number, lengthFirst, lengthLast) {
let length = Math.max(lengthFirst, lengthLast);
try {
number = String(number).padStart(length, ' ');
} catch(e) {
if(e.name != 'TypeError') {
throw e;
};
};
return number;
},
makeLogArea(logdataArray) {
let lines = _('No entries available...');
let logTextarea = E('textarea', {
'id' : 'syslog',
'class' : 'cbi-input-textarea',
'style' : 'width:100% !important; margin-bottom:1em; resize:none; font-size:12px; font-family:monospace !important',
'readonly' : 'readonly',
'wrap' : 'off',
'rows' : this.rowsDefault,
'spellcheck': 'false',
});
for(let level of Object.keys(this.logLevels)) {
this.logLevelsStat[level] = 0;
};
let logdataArrayLen = logdataArray.length;
if(logdataArrayLen > 0) {
lines = [];
let firstNumLength = String(logdataArray[0][0]).length;
let lastNumLength = String(logdataArray[logdataArrayLen - 1][0]).length;
logdataArray.forEach((e, i) => {
if(e[4] in this.logLevels) {
this.logLevelsStat[e[4]] = this.logLevelsStat[e[4]] + 1;
};
e[0] = this.padNumber(e[0], firstNumLength, lastNumLength);
if(e[5]) {
e[5] = `\t${e[5]}`;
};
lines.push(e.filter(i => (i)).join(' '));
});
lines = lines.join('\r\n');
};
logTextarea.value = lines;
logTextarea.rows = ((logdataArrayLen + 1) < this.rowsDefault) ? this.rowsDefault : logdataArrayLen + 1;
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')}: ${logdataArrayLen} / ${this.totalLogLines}${levelsStatString}`
),
logTextarea,
]);
},
}),
});
Binary file not shown.

Before

Width:  |  Height:  |  Size: 235 KiB

@@ -1,168 +0,0 @@
'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
@@ -1,179 +0,0 @@
'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;
},
}),
});
@@ -1,117 +0,0 @@
'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,
]);
},
}),
});
@@ -1,10 +0,0 @@
'use strict';
'require view.log-viewer.log-system as abc';
return abc.view.extend({
viewName : 'syslog',
title : _('System Log'),
enableAutoRefresh: true,
appPattern : '^',
loggerTail : true,
});
-129
View File
@@ -1,129 +0,0 @@
msgid ""
msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Project-Id-Version: \n"
"POT-Creation-Date: \n"
"PO-Revision-Date: \n"
"Last-Translator: \n"
"Language-Team: \n"
"MIME-Version: 1.0\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ru\n"
"X-Generator: Poedit 2.0.6\n"
msgid "<code>!pattern</code> - entries that do not match the pattern."
msgstr "<code>!шаблон</code> - записи не совпадающие с шаблоном."
msgid "All"
msgstr "Все"
msgid "All entries"
msgstr "Все записи"
msgid "Apply"
msgstr "Применить"
msgid "Apply pattern as regular expression"
msgstr "Применять фильтр как регулярное выражение"
msgid "Auto refresh"
msgstr "Автообновление"
msgid "Convert timestamps to a human readable date."
msgstr "Конвертировать временные метки в удобочитаемую дату."
msgid "Date"
msgstr "Дата"
msgid "Dismiss"
msgstr "Закрыть"
msgid "Download log"
msgstr "Скачать журнал"
msgid "Edit"
msgstr "Изменить"
msgid "Entries"
msgstr "Записи"
msgid "Facility"
msgstr "Категория"
msgid "Facilities"
msgstr "Категории"
msgid "Filter settings"
msgstr "Настройки фильтра"
msgid "Host"
msgstr "Хост"
msgid "Hosts"
msgstr "Хосты"
msgid "Invalid regular expression"
msgstr "Неправильное регулярное выражение"
msgid "Kernel Log"
msgstr "Журнал ядра"
msgid "Last entries"
msgstr "Последние записи"
msgid "Level"
msgstr "Уровень"
msgid "Log"
msgstr "Лог"
msgid "Log Viewer"
msgstr "Просмотр лога"
msgid "Logging levels"
msgstr "Уровни логирования"
msgid "Logread not found"
msgstr "Logread не найден"
msgid "Message"
msgstr "Сообщение"
msgid "Message filter"
msgstr "Фильтр сообщений"
msgid "More entries"
msgstr "Больше записей"
msgid "No entries available..."
msgstr "Нет доступных записей..."
msgid "Refresh log"
msgstr "Обновить лог"
msgid "Sorting entries"
msgstr "Сортировка записей"
msgid "System Log"
msgstr "Системный журнал"
msgid "Timestamp"
msgstr "Время"
msgid "Timestamp filter"
msgstr "Фильтр даты"
msgid "Type a search pattern..."
msgstr "Введите шаблон для поиска..."
msgid "Unable to load log data:"
msgstr "Невозможно загрузить данные лога:"
msgid "Unsupported log format"
msgstr "Неподдерживаемый формат лога"
msgid "ascending"
msgstr "по возрастанию"
msgid "descending"
msgstr "по убыванию"
-119
View File
@@ -1,119 +0,0 @@
msgid ""
msgstr "Content-Type: text/plain; charset=UTF-8"
msgid "<code>!pattern</code> - entries that do not match the pattern."
msgstr ""
msgid "All"
msgstr ""
msgid "All entries"
msgstr ""
msgid "Apply"
msgstr ""
msgid "Apply pattern as regular expression"
msgstr ""
msgid "Auto refresh"
msgstr ""
msgid "Convert timestamps to a human readable date."
msgstr ""
msgid "Date"
msgstr ""
msgid "Dismiss"
msgstr ""
msgid "Download log"
msgstr ""
msgid "Edit"
msgstr ""
msgid "Entries"
msgstr ""
msgid "Facility"
msgstr ""
msgid "Facilities"
msgstr ""
msgid "Filter settings"
msgstr ""
msgid "Host"
msgstr ""
msgid "Hosts"
msgstr ""
msgid "Invalid regular expression"
msgstr ""
msgid "Kernel Log"
msgstr ""
msgid "Last entries"
msgstr ""
msgid "Level"
msgstr ""
msgid "Log"
msgstr ""
msgid "Log Viewer"
msgstr ""
msgid "Logging levels"
msgstr ""
msgid "Logread not found"
msgstr ""
msgid "Message"
msgstr ""
msgid "Message filter"
msgstr ""
msgid "More entries"
msgstr ""
msgid "No entries available..."
msgstr ""
msgid "Refresh log"
msgstr ""
msgid "Sorting entries"
msgstr ""
msgid "System Log"
msgstr ""
msgid "Timestamp"
msgstr ""
msgid "Timestamp filter"
msgstr ""
msgid "Type a search pattern..."
msgstr ""
msgid "Unable to load log data:"
msgstr ""
msgid "Unsupported log format"
msgstr ""
msgid "ascending"
msgstr ""
msgid "descending"
msgstr ""
@@ -1,31 +0,0 @@
{
"admin/status/log-viewer": {
"title": "Log Viewer",
"order": 5,
"action": {
"type": "alias",
"path": "admin/status/log-viewer/syslog"
},
"depends": {
"acl": [ "luci-app-log-viewer" ]
}
},
"admin/status/log-viewer/syslog": {
"title": "System Log",
"order": 1,
"action": {
"type": "view",
"path": "log-viewer/syslog"
}
},
"admin/status/log-viewer/dmesg": {
"title": "Kernel Log",
"order": 2,
"action": {
"type": "view",
"path": "log-viewer/dmesg"
}
}
}
@@ -1,17 +0,0 @@
{
"luci-app-log-viewer": {
"description": "Grant access to log-viewer procedures",
"read": {
"cgi-io": [ "exec" ],
"file": {
"/sbin/logread*": [ "exec" ],
"/usr/sbin/logread*": [ "exec" ],
"/bin/dmesg -r": [ "exec" ]
},
"ubus": {
"system": [ "info" ],
"luci.log-viewer": [ "getSyslogSize", "getSyslogHash", "getDmesgSize", "getDmesgHash", "getLogfileSize" ]
}
}
}
}
@@ -1,67 +0,0 @@
'use strict';
import { popen, stat } from 'fs';
const dmesgCmd = '/bin/dmesg';
const logreadCmd = (
(stat('/sbin/logread')?.perm?.user_exec) ? '/sbin/logread' :
((stat('/usr/sbin/logread')?.perm?.user_exec) ? '/usr/sbin/logread' : null));
if(!logreadCmd) {
die('Error! Logread not found!');
}
function getCmdOutput(cmd, number) {
let ret = '';
const fd = popen(cmd, 'r');
if(fd) {
let c = fd.read('all');
fd.close();
if(c) {
c = trim(c);
ret = number ? int(c) : c;
}
}
return ret;
}
const methods = {
getSyslogSize: {
call: function() {
return { 'bytes': getCmdOutput(logreadCmd + ' | wc -c', true) };
},
},
getSyslogHash: {
call: function() {
return { 'hash': getCmdOutput(logreadCmd + ' -t -l 1') };
},
},
getDmesgSize: {
call: function() {
return { 'bytes': getCmdOutput(dmesgCmd + ' | wc -c', true) };
},
},
getDmesgHash: {
call: function() {
return { 'hash': getCmdOutput(dmesgCmd + ' | tail -n 1') };
},
},
getLogfileSize: {
args: { fpath: 'fpath' },
call: function(request) {
let ret = '';
const path = request.args?.fpath;
if(path) {
ret = stat(path)?.size;
}
return { 'bytes': ret ?? '' };
},
},
};
return { 'luci.log-viewer': methods };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 369 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 264 KiB