luci-app: Code fixes, updated log

This commit is contained in:
gSpot
2021-03-26 23:49:38 +03:00
parent 399ce9fb7f
commit 03eac5f427
16 changed files with 333 additions and 260 deletions
+1 -1
View File
@@ -11,7 +11,7 @@ LUCI_APP=1
OWRT_VERSION="19.07"
RUAB_VERSION="0.9.0-2"
RUAB_MOD_LUA_VERSION="0.9.0-2"
RUAB_LUCI_APP_VERSION="0.9.0-4"
RUAB_LUCI_APP_VERSION="0.9.0-5"
BASE_URL="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master"
PKG_DIR="/tmp"
+1 -1
View File
@@ -5,7 +5,7 @@
include $(TOPDIR)/rules.mk
PKG_VERSION:=0.9.0
PKG_RELEASE:=4
PKG_RELEASE:=5
LUCI_TITLE:=LuCI support for ruantiblock
LUCI_DEPENDS:=+ruantiblock +luci-mod-admin-full
LUCI_PKGARCH:=all
@@ -1,217 +1,7 @@
'use strict';
'require ui';
return L.Class.extend({
view: L.view.extend({
viewName: null,
title: null,
logFacilities: [
'kern',
'user',
'mail',
'daemon',
'auth',
'syslog',
'lpr',
'news',
],
logLevels: {
'emerg': E('span', { 'class': 'zonebadge log-emerg' }, E('strong', _('Emergency'))),
'alert': E('span', { 'class': 'zonebadge log-alert' }, E('strong', _('Alert'))),
'crit': E('span', { 'class': 'zonebadge log-crit' }, E('strong', _('Critical'))),
'err': E('span', { 'class': 'zonebadge log-err' }, E('strong', _('Error'))),
'warn': E('span', { 'class': 'zonebadge log-warn' }, E('strong', _('Warning'))),
'notice': E('span', { 'class': 'zonebadge log-notice' }, E('strong', _('Notice'))),
'info': E('span', { 'class': 'zonebadge log-info' }, E('strong', _('Info'))),
'debug': E('span', { 'class': 'zonebadge log-debug' }, E('strong', _('Debug'))),
},
tailValue: 25,
logSortingValue: 'asc',
logLevelsStat: {},
logLevelsDropdown: null,
totalLogLines: 0,
htmlEntities: function(str) {
return String(str).replace(
/&/g, '&').replace(
/</g, '&#60;').replace(
/>/g, '&#62;').replace(
/"/g, '&#34;').replace(
/'/g, '&#39;');
},
/**
*
* @param {number} tail
* @returns {string}
* Returns the raw content of the log
*
*/
getLogData: function(tail) {
throw new Error('getLogData needs to be reloaded in subclass');
},
/**
*
* @param {string} logdata
* @param {number} tail
* @returns {Array<number, string|null, string|null, string|null, string|null>}
* Returns an array of values: [ #, Timestamp, Level, Facility, Message ]
*
*/
parseLogData: function(logdata, tail) {
throw new Error('parseLogData needs to be reloaded in subclass');
},
setLevelFilter: function(cArr) {
let logLevelsKeys = Object.keys(this.logLevels);
if(logLevelsKeys.length > 0) {
let selectedLevels = this.logLevelsDropdown.getValue();
if(logLevelsKeys.length === selectedLevels.length) {
return cArr;
};
return cArr.filter(s => selectedLevels.length === 0 || selectedLevels.includes(s[2]));
};
return cArr;
},
setRegexpFilter: function(cArr) {
let fPattern = document.getElementById('logFilter').value;
if(!fPattern) {
return cArr;
};
let fArr = [];
try {
let regExp = new RegExp(`(${fPattern})`, 'giu');
cArr.forEach((e, i) => {
if(e[4] !== null && regExp.test(e[4])) {
e[4] = e[4].replace(regExp, '<span class="log-highlight-item">$1</span>');
fArr.push(e);
};
});
} catch(err) {
if(err.name === 'SyntaxError') {
ui.addNotification(null,
E('p', {}, _('Invalid regular expression') + ': ' + err.message));
return cArr;
} else {
throw err;
};
};
return fArr;
},
makeLogArea: function(logdataArray) {
let lines = `<div class="tr"><div class="td center log-entry-empty">${_('No entries available...')}</div></div>`;
let logTable = E('div', { '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[2] in this.logLevels) {
this.logLevelsStat[e[2]] = this.logLevelsStat[e[2]] + 1;
};
lines.push(
`<div class="tr log-${e[2] || 'empty'}"><div class="td left" data-title="#">${e[0]}</div>` +
((e[1]) ? `<div class="td left" data-title="${_('Timestamp')}">${e[1]}</div>` : '') +
((e[2]) ? `<div class="td left" data-title="${_('Level')}">${e[2]}</div>` : '') +
((e[3]) ? `<div class="td left" data-title="${_('Facility')}">${e[3]}</div>` : '') +
((e[4]) ? `<div class="td left log-entry-message-cell" data-title="${_('Message')}">${e[4]}</div>` : '') +
`</div>`
);
});
lines = lines.join('');
logTable.append(
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th left log-entry-number' }, '#'),
(logdataArray[0][1]) ? E('div', { 'class': 'th left log-entry-time' }, _('Timestamp')) : '',
(logdataArray[0][2]) ? E('div', { 'class': 'th left log-entry-log-level' }, _('Level')) : '',
(logdataArray[0][3]) ? E('div', { 'class': 'th left log-entry-facility' }, _('Facility')) : '',
(logdataArray[0][4]) ? E('div', { 'class': 'th left log-entry-message' }, _('Message')) : '',
])
);
};
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
]);
},
downloadLog: function(ev) {
let formElems = Array.from(document.forms.logForm.elements);
formElems.forEach(e => e.disabled = true);
return this.getLogData(0).then(logdata => {
logdata = logdata || '';
let link = E('a', {
'download': this.viewName + '.log',
'href': URL.createObjectURL(
new Blob([ logdata ], { type: 'text/plain' })),
});
link.click();
URL.revokeObjectURL(link.href);
}).catch(() => {
ui.addNotification(null,
E('p', {}, _('Download error') + ': ' + err.message));
}).finally(() => {
formElems.forEach(e => e.disabled = false);
});
},
load: function() {
// Restoring settings from localStorage
let tailValueLocal = localStorage.getItem(`luci-app-${this.viewName}-tailValue`);
if(tailValueLocal) {
this.tailValue = Number(tailValueLocal);
};
let logSortingLocal = localStorage.getItem(`luci-app-${this.viewName}-logSorting`);
if(logSortingLocal) {
this.logSortingValue = logSortingLocal;
};
return this.getLogData(this.tailValue);
},
render: function(logdata) {
document.head.append(E('style', {'type': 'text/css'},
document.head.append(E('style', {'type': 'text/css'},
`
.log-entry-empty {
}
@@ -221,6 +11,13 @@ return L.Class.extend({
.log-entry-time {
min-width: 14em !important;
}
.log-entry-host {
min-width: 10em !important;
}
.log-entry-host-cell {
word-break: break-all !important;
word-wrap: break-word !important;
}
.log-entry-log-level {
max-width: 5em !important;
}
@@ -291,9 +88,234 @@ log-emerg td {
border: 1px solid #ccc;
font-weight: normal;
}
`
));
.log-host-dropdown-item {
}
`));
return L.Class.extend({
view: L.view.extend({
viewName: null,
title: null,
logLevels: {
'emerg': E('span', { 'class': 'zonebadge log-emerg' }, E('strong', _('Emergency'))),
'alert': E('span', { 'class': 'zonebadge log-alert' }, E('strong', _('Alert'))),
'crit': E('span', { 'class': 'zonebadge log-crit' }, E('strong', _('Critical'))),
'err': E('span', { 'class': 'zonebadge log-err' }, E('strong', _('Error'))),
'warn': E('span', { 'class': 'zonebadge log-warn' }, E('strong', _('Warning'))),
'notice': E('span', { 'class': 'zonebadge log-notice' }, E('strong', _('Notice'))),
'info': E('span', { 'class': 'zonebadge log-info' }, E('strong', _('Info'))),
'debug': E('span', { 'class': 'zonebadge log-debug' }, E('strong', _('Debug'))),
},
tailValue: 25,
logSortingValue: 'asc',
logLevelsStat: {},
logHosts: {},
logHostsDropdown: null,
logLevelsDropdown: null,
totalLogLines: 0,
htmlEntities: function(str) {
return String(str).replace(
/&/g, '&#38;').replace(
/</g, '&#60;').replace(
/>/g, '&#62;').replace(
/"/g, '&#34;').replace(
/'/g, '&#39;');
},
makelogHostsDropdownItem: function(host) {
return E(
'span',
{ 'class': 'zonebadge log-host-dropdown-item' },
E('strong', host)
);
},
/**
*
* @param {number} tail
* @returns {string}
* Returns the raw content of the log
*
*/
getLogData: function(tail) {
throw new Error('getLogData must be overridden by a subclass');
},
/**
*
* @param {string} logdata
* @param {number} tail
* @returns {Array<number, string|null, string|null, string|null, string|null, string|null>}
* Returns an array of values: [ #, Timestamp, Host, Level, Facility, Message ]
*
*/
parseLogData: function(logdata, tail) {
throw new Error('parseLogData must be overridden by a subclass');
},
setHostFilter: function(cArr) {
let logHostsKeys = Object.keys(this.logHosts);
if(logHostsKeys.length > 0) {
let selectedHosts = this.logHostsDropdown.getValue();
this.logHostsDropdown.addChoices(Object.keys(this.logHosts), this.logHosts);
if(selectedHosts.length === 0 || logHostsKeys.length === selectedHosts.length) {
return cArr;
};
return cArr.filter(e => selectedHosts.includes(e[2]));
};
return cArr;
},
setLevelFilter: function(cArr) {
let logLevelsKeys = Object.keys(this.logLevels);
if(logLevelsKeys.length > 0) {
let selectedLevels = this.logLevelsDropdown.getValue();
if(selectedLevels.length === 0 || logLevelsKeys.length === selectedLevels.length) {
return cArr;
};
return cArr.filter(e => selectedLevels.includes(e[3]));
};
return cArr;
},
setRegexpFilter: function(cArr) {
let fPattern = document.getElementById('logFilter').value;
if(!fPattern) {
return cArr;
};
let fArr = [];
try {
let regExp = new RegExp(`(${fPattern})`, 'giu');
cArr.forEach((e, i) => {
if(e[5] !== null && regExp.test(e[5])) {
e[5] = e[5].replace(regExp, '<span class="log-highlight-item">$1</span>');
fArr.push(e);
};
});
} catch(err) {
if(err.name === 'SyntaxError') {
ui.addNotification(null,
E('p', {}, _('Invalid regular expression') + ': ' + err.message));
return cArr;
} else {
throw err;
};
};
return fArr;
},
makeLogArea: function(logdataArray) {
let lines = `<div class="tr"><div class="td center log-entry-empty">${_('No entries available...')}</div></div>`;
let logTable = E('div', { '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[3] in this.logLevels) {
this.logLevelsStat[e[3]] = this.logLevelsStat[e[3]] + 1;
};
lines.push(
`<div class="tr log-${e[3] || 'empty'}"><div class="td left" data-title="#">${e[0]}</div>` +
((e[1]) ? `<div class="td left" data-title="${_('Timestamp')}">${e[1]}</div>` : '') +
((e[2]) ? `<div class="td left log-entry-host-cell" data-title="${_('Host')}">${e[2]}</div>` : '') +
((e[3]) ? `<div class="td left" data-title="${_('Level')}">${e[3]}</div>` : '') +
((e[4]) ? `<div class="td left" data-title="${_('Facility')}">${e[4]}</div>` : '') +
((e[5]) ? `<div class="td left log-entry-message-cell" data-title="${_('Message')}">${e[5]}</div>` : '') +
`</div>`
);
});
lines = lines.join('');
logTable.append(
E('div', { 'class': 'tr table-titles' }, [
E('div', { 'class': 'th left log-entry-number' }, '#'),
(logdataArray[0][1]) ? E('div', { 'class': 'th left log-entry-time' }, _('Timestamp')) : '',
(logdataArray[0][2]) ? E('div', { 'class': 'th left log-entry-host' }, _('Host')) : '',
(logdataArray[0][3]) ? E('div', { 'class': 'th left log-entry-log-level' }, _('Level')) : '',
(logdataArray[0][4]) ? E('div', { 'class': 'th left log-entry-facility' }, _('Facility')) : '',
(logdataArray[0][5]) ? E('div', { 'class': 'th left log-entry-message' }, _('Message')) : '',
])
);
};
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
]);
},
downloadLog: function(ev) {
let formElems = Array.from(document.forms.logForm.elements);
formElems.forEach(e => e.disabled = true);
return this.getLogData(0).then(logdata => {
logdata = logdata || '';
let link = E('a', {
'download': this.viewName + '.log',
'href': URL.createObjectURL(
new Blob([ logdata ], { type: 'text/plain' })),
});
link.click();
URL.revokeObjectURL(link.href);
}).catch(() => {
ui.addNotification(null,
E('p', {}, _('Download error') + ': ' + err.message));
}).finally(() => {
formElems.forEach(e => e.disabled = false);
});
},
load: function() {
// Restoring settings from localStorage
let tailValueLocal = localStorage.getItem(`luci-app-${this.viewName}-tailValue`);
if(tailValueLocal) {
this.tailValue = Number(tailValueLocal);
};
let logSortingLocal = localStorage.getItem(`luci-app-${this.viewName}-logSorting`);
if(logSortingLocal) {
this.logSortingValue = logSortingLocal;
};
return this.getLogData(this.tailValue);
},
render: function(logdata) {
let logWrapper = E('div', {
'id': 'logWrapper',
'style': 'width:100%; min-height:20em; padding: 0 0 0 45px; font-size:0.9em !important'
@@ -324,6 +346,31 @@ log-emerg td {
'style': 'max-width:4em !important',
});
let logHostsDropdownElem = '';
let logHostsKeys = Object.keys(this.logHosts);
if(logHostsKeys.length > 0) {
this.logHostsDropdown = new ui.Dropdown(
null,
this.logHosts,
{
id: 'logHostsDropdown',
multiple: true,
select_placeholder: _('All'),
}
);
logHostsDropdownElem = E(
'div', { 'class': 'cbi-value' }, [
E('label', {
'class': 'cbi-value-title',
'for': 'logHostsDropdown',
}, _('Hosts')),
E('div', { 'class': 'cbi-value-field' },
this.logHostsDropdown.render()
),
]
);
};
let logLevelsDropdownElem = '';
let logLevelsKeys = Object.keys(this.logLevels);
if(logLevelsKeys.length > 0) {
@@ -335,8 +382,6 @@ log-emerg td {
sort: logLevelsKeys,
multiple: true,
select_placeholder: _('All'),
display_items: 3,
dropdown_items: -1,
}
);
logLevelsDropdownElem = E(
@@ -405,6 +450,7 @@ log-emerg td {
]),
]),
logHostsDropdownElem,
logLevelsDropdownElem,
E('div', { 'class': 'cbi-value' }, [
@@ -427,7 +473,7 @@ log-emerg td {
E('label', {
'class': 'cbi-value-title',
'for': 'logFilter',
}),
}, _('Refresh log')),
E('div', { 'class': 'cbi-value-field' }, [
logFormSubmitBtn,
E('form', {
@@ -455,18 +501,18 @@ log-emerg td {
let tail = (tailInput.value && tailInput.value > 0) ? tailInput.value : 0
return this.getLogData(tail).then(logdata => {
logdata = logdata || '';
let loglines = this.makeLogArea(
logWrapper.innerHTML = '';
logWrapper.append(
this.makeLogArea(
this.setRegexpFilter(
this.setLevelFilter(
this.setHostFilter(
this.parseLogData(logdata, tail)
)
)
)
)
);
logWrapper.innerHTML = '';
logWrapper.append(loglines);
}).finally(() => {
formElems.forEach(e => e.disabled = false);
logDownloadBtn.disabled = false;
@@ -1,9 +1,9 @@
'require fs';
'require ui';
'require view.log.baselog as baselog';
'require view.log.abstract-log as abc';
'require view.ruantiblock.tools as tools';
return baselog.view.extend({
return abc.view.extend({
viewName: 'ruantiblock',
title: _('Ruantiblock') + ' - ' + _('Log'),
@@ -22,6 +22,7 @@ return baselog.view.extend({
return [
lineNum, // # (Number)
strArray.slice(0, 5).join(' '), // Timestamp (String)
null, // Host (String)
logLevel[1], // Level (String)
logLevel[0], // Facility (String)
this.htmlEntities(strArray.slice(6).join(' ')), // Message (String)
@@ -30,9 +31,14 @@ return baselog.view.extend({
// syslog-ng
syslog_ngHandler: function(strArray, lineNum) {
if(!(strArray[3] in this.logHosts)) {
this.logHosts[strArray[3]] = this.makelogHostsDropdownItem(strArray[3]);
};
return [
lineNum, // # (Number)
strArray.slice(0, 3).join(' '), // Timestamp (String)
strArray[3], // Host (String)
null, // Level (String)
null, // Facility (String)
this.htmlEntities(strArray.slice(4).join(' ')), // Message (String)
@@ -48,7 +54,7 @@ return baselog.view.extend({
if(logger) {
return fs.exec_direct(logger, [ '-e', tools.app_name ]).catch(err => {
ui.addNotification(null, E('p', {}, _('Unable to load log data: ' + err.message)));
ui.addNotification(null, E('p', {}, _('Unable to load log data:') + ' ' + err.message));
return '';
});
};
@@ -250,8 +250,6 @@ return L.view.extend({
let proxy_local_clients = (typeof(section) === 'object') ? section.proxy_local_clients : null;
status_token_value = (Array.isArray(status_array)) ? tools.normalize_value(status_array[4]) : null;
document.head.append(E('style', {'type': 'text/css'}, tools.css));
let status_string = E('div', {
'id': 'status',
'name': 'status',
@@ -2,30 +2,8 @@
'require fs';
'require ui';
return L.Class.extend({
app_name: 'ruantiblock',
exec_path: '/usr/bin/ruantiblock',
init_path: '/etc/init.d/ruantiblock',
token_file: '/var/run/ruantiblock.token',
parsers_dir: '/usr/bin',
torrc_file: '/etc/tor/torrc',
user_entries_file: '/etc/ruantiblock/user_entries',
fqdn_filter_file: '/etc/ruantiblock/fqdn_filter',
ip_filter_file: '/etc/ruantiblock/ip_filter',
crontab_file: '/etc/crontabs/root',
info_label_starting: '<span class="label-status starting">' + _('Starting') + '</span>',
info_label_running: '<span class="label-status running">' + _('Enabled') + '</span>',
info_label_updating: '<span class="label-status updating">' + _('Updating') + '</span>',
info_label_stopped: '<span class="label-status stopped">' + _('Disabled') + '</span>',
info_label_error: '<span class="label-status error">' + _('Error') + '</span>',
blacklist_sources: {
'rublacklist': 'https://rublacklist.net',
'zapret-info': 'https://github.com/zapret-info/z-i',
'antifilter': 'https://antifilter.download',
},
css: `
document.head.append(E('style', {'type': 'text/css'},
`
.label-status {
display: inline;
margin: 0px 2px 0px 0 !important;
@@ -53,7 +31,31 @@ return L.Class.extend({
}
.total-proxy {
background-color: #ffb937 !important;
}`,
}
`));
return L.Class.extend({
app_name: 'ruantiblock',
exec_path: '/usr/bin/ruantiblock',
init_path: '/etc/init.d/ruantiblock',
token_file: '/var/run/ruantiblock.token',
parsers_dir: '/usr/bin',
torrc_file: '/etc/tor/torrc',
user_entries_file: '/etc/ruantiblock/user_entries',
fqdn_filter_file: '/etc/ruantiblock/fqdn_filter',
ip_filter_file: '/etc/ruantiblock/ip_filter',
crontab_file: '/etc/crontabs/root',
info_label_starting: '<span class="label-status starting">' + _('Starting') + '</span>',
info_label_running: '<span class="label-status running">' + _('Enabled') + '</span>',
info_label_updating: '<span class="label-status updating">' + _('Updating') + '</span>',
info_label_stopped: '<span class="label-status stopped">' + _('Disabled') + '</span>',
info_label_error: '<span class="label-status error">' + _('Error') + '</span>',
blacklist_sources: {
'rublacklist': 'https://rublacklist.net',
'zapret-info': 'https://github.com/zapret-info/z-i',
'antifilter': 'https://antifilter.download',
},
normalize_value: function(v) {
return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v;
@@ -386,6 +386,12 @@ msgstr "Записи"
msgid "Facility"
msgstr "Категория"
msgid "Host"
msgstr "Хост"
msgid "Hosts"
msgstr "Хосты"
msgid "Info"
msgstr "Информация"
@@ -413,6 +419,9 @@ msgstr "Нет доступных записей..."
msgid "Notice"
msgstr "Сообщение"
msgid "Refresh log"
msgstr "Обновить лог"
msgid "Sorting entries"
msgstr "Сортировка записей"
@@ -113,6 +113,9 @@ msgstr ""
msgid "Hour"
msgstr ""
msgid "Hour"
msgstr ""
msgid "Interval"
msgstr ""
@@ -356,6 +359,12 @@ msgstr ""
msgid "Facility"
msgstr ""
msgid "Host"
msgstr ""
msgid "Hosts"
msgstr ""
msgid "Info"
msgstr ""
@@ -383,6 +392,9 @@ msgstr ""
msgid "Notice"
msgstr ""
msgid "Refresh log"
msgstr ""
msgid "Sorting entries"
msgstr ""
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -317,8 +317,8 @@ function BlackListParser:fill_domain_tables(val)
end
function BlackListParser:sink()
-- Needs to be reloaded in subclass
error("Method BlackListParser:sink() needs to be reloaded in subclass!")
-- Must be overridden by a subclass
error("Method BlackListParser:sink() must be overridden by a subclass!")
end
function BlackListParser:optimize_ip_table()
@@ -294,7 +294,7 @@ class BlackListParser(Config):
raise FieldValueError()
def parser_func(self):
"""Needs to be reloaded in subclass"""
"""Must be overridden by a subclass"""
raise NotImplementedError()
def _check_sld_masks(self, sld):