update 2026-07-27 22:20:34
No files matched your search
@@ -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.
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||

|
||||
|
||||
System log (logd):
|
||||
|
||||

|
||||
|
||||
Filter settings:
|
||||
|
||||

|
||||
|
||||
System log (syslog-ng):
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
|
||||
## 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
|
||||
|
||||

|
||||
@@ -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] = `	${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,
|
||||
]);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
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" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
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,
|
||||
]);
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
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;
|
||||
};
|
||||
},
|
||||
});
|
||||
@@ -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] || ' '}</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,
|
||||
});
|
||||
@@ -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 "по убыванию"
|
||||
@@ -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 };
|
||||
|
Before Width: | Height: | Size: 369 KiB |
|
Before Width: | Height: | Size: 278 KiB |
|
Before Width: | Height: | Size: 155 KiB |
|
Before Width: | Height: | Size: 264 KiB |