diff --git a/autoinstall/current/autoinstall.sh b/autoinstall/current/autoinstall.sh index f57f40a..d012682 100755 --- a/autoinstall/current/autoinstall.sh +++ b/autoinstall/current/autoinstall.sh @@ -11,7 +11,7 @@ LUCI_APP=1 OWRT_VERSION="current" RUAB_VERSION="1.3-1" RUAB_MOD_LUA_VERSION="1.3-2" -RUAB_LUCI_APP_VERSION="1.3-0" +RUAB_LUCI_APP_VERSION="1.3-1" BASE_URL="https://raw.githubusercontent.com/gSpotx2f/packages-openwrt/master" PKG_DIR="/tmp" diff --git a/luci-app-ruantiblock/Makefile b/luci-app-ruantiblock/Makefile index 444c4a2..ab37433 100644 --- a/luci-app-ruantiblock/Makefile +++ b/luci-app-ruantiblock/Makefile @@ -4,7 +4,7 @@ include $(TOPDIR)/rules.mk -PKG_VERSION:=1.3-0 +PKG_VERSION:=1.3-1 LUCI_TITLE:=LuCI support for ruantiblock LUCI_DEPENDS:=+ruantiblock LUCI_PKGARCH:=all diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/abstract-log.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/abstract-log.js index 902688b..16c3785 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/abstract-log.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/abstract-log.js @@ -123,7 +123,6 @@ log-emerg td { } .log-info { background-color: var(--app-log-info) !important; - /*color: var(--app-log-dark-font-color) !important;*/ } .log-debug { background-color: var(--app-log-debug) !important; @@ -156,6 +155,8 @@ log-emerg td { } .log-host-dropdown-item { } +.log-facility-dropdown-item { +} `)); return baseclass.extend({ @@ -172,6 +173,31 @@ return baseclass.extend({ */ title : null, + logFacilities : { + 'kern' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'kern')), + 'user' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'user')), + 'mail' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'mail')), + 'daemon' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'daemon')), + 'auth' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'auth')), + 'syslog' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'syslog')), + 'lpr' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'lpr')), + 'news' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'news')), + 'uucp' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'uucp')), + 'authpriv': E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'authpriv')), + 'ftp' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'ftp')), + 'ntp' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'ntp')), + 'log' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'log')), + 'clock' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'clock')), + 'local0' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local0')), + 'local1' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local1')), + 'local2' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local2')), + 'local3' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local3')), + 'local4' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local4')), + 'local5' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local5')), + 'local6' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local6')), + 'local7' : E('span', { 'class': 'zonebadge log-facility-dropdown-item' }, E('strong', 'local7')), + }, + logLevels : { 'emerg' : E('span', { 'class': 'zonebadge log-emerg' }, E('strong', _('Emergency'))), 'alert' : E('span', { 'class': 'zonebadge log-alert' }, E('strong', _('Alert'))), @@ -183,25 +209,29 @@ return baseclass.extend({ 'debug' : E('span', { 'class': 'zonebadge log-debug' }, E('strong', _('Debug'))), }, - tailValue : 25, + tailValue : 25, - logSortingValue : 'asc', + logSortingValue : 'asc', - isHosts : false, + isHosts : false, - isLevels : false, + isFacilities : false, - logHosts : {}, + isLevels : false, - logLevelsStat : {}, + logHosts : {}, - logHostsDropdown : null, + logLevelsStat : {}, - logLevelsDropdown: null, + logHostsDropdown : null, - totalLogLines : 0, + logFacilitiesDropdown: null, - htmlEntities: function(str) { + logLevelsDropdown : null, + + totalLogLines : 0, + + htmlEntities(str) { return String(str).replace( /&/g, '&').replace( /} * Returns an array of values: [ #, Timestamp, Host, Level, Facility, Message ] */ - parseLogData: function(logdata, tail) { + parseLogData(logdata, tail) { throw new Error('parseLogData must be overridden by a subclass'); }, - setHostFilter: function(cArr) { + setDateFilter(entriesArray) { + let fPattern = document.getElementById('timeFilter').value; + if(!fPattern) { + return entriesArray; + }; + return this.setRegexpFilter(entriesArray, 1, fPattern); + }, + + setHostFilter(entriesArray) { let logHostsKeys = Object.keys(this.logHosts); if(logHostsKeys.length > 0 && this.logHostsDropdown) { let selectedHosts = this.logHostsDropdown.getValue(); this.logHostsDropdown.addChoices(logHostsKeys, this.logHosts); if(selectedHosts.length === 0 || logHostsKeys.length === selectedHosts.length) { - return cArr; + return entriesArray; }; - return cArr.filter(e => selectedHosts.includes(e[2])); + return entriesArray.filter(e => selectedHosts.includes(e[2])); }; - return cArr; + return entriesArray; }, - setLevelFilter: function(cArr) { + setFacilityFilter(entriesArray) { + let logFacilitiesKeys = Object.keys(this.logFacilities); + if(logFacilitiesKeys.length > 0 && this.logFacilitiesDropdown) { + let selectedFacilities = this.logFacilitiesDropdown.getValue(); + if(selectedFacilities.length === 0 || logFacilitiesKeys.length === selectedFacilities.length) { + return entriesArray; + }; + return entriesArray.filter(e => selectedFacilities.includes(e[3])); + }; + return entriesArray; + }, + + setLevelFilter(entriesArray) { let logLevelsKeys = Object.keys(this.logLevels); if(logLevelsKeys.length > 0 && this.logLevelsDropdown) { let selectedLevels = this.logLevelsDropdown.getValue(); if(selectedLevels.length === 0 || logLevelsKeys.length === selectedLevels.length) { - return cArr; + return entriesArray; }; - return cArr.filter(e => selectedLevels.includes(e[3])); + return entriesArray.filter(e => selectedLevels.includes(e[4])); }; - return cArr; + return entriesArray; }, - regexpFilterHighlightFunc: function(match) { + regexpFilterHighlightFunc(match) { return `${match}`; }, - setRegexpFilter: function(cArr) { - let fPattern = document.getElementById('logFilter').value; - if(!fPattern) { - return cArr; - }; + setRegexpFilter(entriesArray, fieldNum, pattern) { let fArr = []; try { - let regExp = new RegExp(fPattern, 'giu'); - cArr.forEach((e, i) => { - if(e[5] !== null && regExp.test(e[5])) { + let regExp = new RegExp(pattern, 'giu'); + entriesArray.forEach((e, i) => { + if(e[fieldNum] !== null && regExp.test(e[fieldNum])) { if(this.regexpFilterHighlightFunc) { - e[5] = e[5].replace(regExp, this.regexpFilterHighlightFunc); + e[fieldNum] = e[fieldNum].replace(regExp, this.regexpFilterHighlightFunc); }; fArr.push(e); }; @@ -340,7 +410,7 @@ return baseclass.extend({ if(err.name === 'SyntaxError') { ui.addNotification(null, E('p', {}, _('Invalid regular expression') + ': ' + err.message)); - return cArr; + return entriesArray; } else { throw err; }; @@ -348,7 +418,15 @@ return baseclass.extend({ return fArr; }, - makeLogArea: function(logdataArray) { + setMsgFilter(entriesArray) { + let fPattern = document.getElementById('msgFilter').value; + if(!fPattern) { + return entriesArray; + }; + return this.setRegexpFilter(entriesArray, 5, fPattern); + }, + + makeLogArea(logdataArray) { let lines = `${_('No entries available...')}`; let logTable = E('table', { 'id': 'logTable', 'class': 'table' }); @@ -359,16 +437,16 @@ return baseclass.extend({ if(logdataArray.length > 0) { lines = []; logdataArray.forEach((e, i) => { - if(e[3] in this.logLevels) { - this.logLevelsStat[e[3]] = this.logLevelsStat[e[3]] + 1; + if(e[4] in this.logLevels) { + this.logLevelsStat[e[4]] = this.logLevelsStat[e[4]] + 1; }; lines.push( - `${e[0]}` + - ((e[1]) ? `${e[1]}` : '') + + `${e[0]}` + + ((e[1]) ? `${e[1]}` : '') + ((e[2]) ? `${e[2]}` : '') + - ((e[3]) ? `${e[3]}` : '') + - ((e[4]) ? `${e[4]}` : '') + + ((e[3]) ? `${e[3]}` : '') + + ((e[4]) ? `${e[4]}` : '') + ((e[5]) ? `${e[5]}` : '') + `` ); @@ -380,8 +458,8 @@ return baseclass.extend({ E('th', { 'class': 'th left log-entry-number' }, '#'), (logdataArray[0][1]) ? E('th', { 'class': 'th left log-entry-time' }, _('Timestamp')) : '', (logdataArray[0][2]) ? E('th', { 'class': 'th left log-entry-host' }, _('Host')) : '', - (logdataArray[0][3]) ? E('th', { 'class': 'th left log-entry-log-level' }, _('Level')) : '', (logdataArray[0][4]) ? E('th', { 'class': 'th left log-entry-facility' }, _('Facility')) : '', + (logdataArray[0][3]) ? E('th', { 'class': 'th left log-entry-log-level' }, _('Level')) : '', (logdataArray[0][5]) ? E('th', { 'class': 'th left log-entry-message' }, _('Message')) : '', ]) ); @@ -414,7 +492,7 @@ return baseclass.extend({ ]); }, - downloadLog: function(ev) { + downloadLog(ev) { let formElems = Array.from(document.forms.logForm.elements); formElems.forEach(e => e.disabled = true); @@ -422,7 +500,7 @@ return baseclass.extend({ logdata = logdata || ''; let link = E('a', { 'download': this.viewName + '.log', - 'href': URL.createObjectURL( + 'href' : URL.createObjectURL( new Blob([ logdata ], { type: 'text/plain' })), }); link.click(); @@ -435,7 +513,7 @@ return baseclass.extend({ }); }, - load: function() { + load() { // Restoring settings from localStorage let tailValueLocal = localStorage.getItem(`luci-app-${this.viewName}-tailValue`); if(tailValueLocal) { @@ -448,7 +526,7 @@ return baseclass.extend({ return this.getLogData(this.tailValue); }, - render: function(logdata) { + render(logdata) { let logWrapper = E('div', { 'id' : 'logWrapper', 'style': 'width:100%; min-height:20em; padding: 0 0 0 45px; font-size:0.9em !important' @@ -479,18 +557,31 @@ return baseclass.extend({ 'style': 'max-width:4em !important', }); - let logHostsDropdownElem = ''; - let logLevelsDropdownElem = ''; + let logHostsDropdownElem = ''; + let logFacilitiesDropdownElem = ''; + let logLevelsDropdownElem = ''; if(this.isLevels) { logLevelsDropdownElem = this.makeLogLevelsDropdownSection(); }; + if(this.isFacilities) { + logFacilitiesDropdownElem = this.makeLogFacilitiesDropdownSection(); + }; if(this.isHosts) { logHostsDropdownElem = this.makeLogHostsDropdownSection(); }; - let logFilter = E('input', { - 'id' : 'logFilter', - 'name' : 'logFilter', + let timeFilter = E('input', { + 'id' : 'timeFilter', + 'name' : 'timeFilter', + 'type' : 'text', + 'form' : 'logForm', + 'class' : 'cbi-input-text', + 'placeholder': _('Type an expression...'), + }); + + let msgFilter = E('input', { + 'id' : 'msgFilter', + 'name' : 'msgFilter', 'type' : 'text', 'form' : 'logForm', 'class' : 'cbi-input-text', @@ -524,13 +615,68 @@ return baseclass.extend({ 'click': ui.createHandlerFn(this, this.downloadLog), }, _('Download log')); + let onSubmitForm = () => { + let formElems = Array.from(document.forms.logForm.elements); + formElems.forEach(e => e.disabled = true); + logDownloadBtn.disabled = true; + + // Saving settings to localStorage + if(this.tailValue != tailInput.value) { + this.tailValue = (/^[0-9]+$/.test(tailInput.value)) ? tailInput.value : 0; + localStorage.setItem( + `luci-app-${this.viewName}-tailValue`, String(this.tailValue)); + }; + if(this.logSortingValue != logSorting.value) { + this.logSortingValue = logSorting.value; + localStorage.setItem( + `luci-app-${this.viewName}-logSorting`, this.logSortingValue); + }; + + let tail = (tailInput.value && tailInput.value > 0) ? tailInput.value : 0 + return this.getLogData(tail).then(logdata => { + logdata = logdata || ''; + logWrapper.innerHTML = ''; + logWrapper.append( + this.makeLogArea( + this.setMsgFilter( + this.setFacilityFilter( + this.setLevelFilter( + this.setHostFilter( + this.setDateFilter( + this.parseLogData(logdata, tail) + ) + ) + ) + ) + ) + ) + ); + + if(logdata) { + let timeFilterSection = document.getElementById('timeFilterSection'); + if(this.isFacilities && !this.logFacilitiesDropdown) { + logFacilitiesDropdownElem = this.makeLogFacilitiesDropdownSection(); + }; + if(this.isLevels && !this.logLevelsDropdown) { + timeFilterSection.after(this.makeLogLevelsDropdownSection()); + }; + if(this.isHosts && !this.logHostsDropdown) { + timeFilterSection.after(this.makeLogHostsDropdownSection()); + }; + }; + }).finally(() => { + formElems.forEach(e => e.disabled = false); + logDownloadBtn.disabled = false; + }); + }; + return E([ E('h2', { 'id': 'logTitle', 'class': 'fade-in' }, this.title), E('div', { 'class': 'cbi-section-descr fade-in' }), E('div', { 'class': 'cbi-section fade-in' }, E('div', { 'class': 'cbi-section-node' }, [ - E('div', { 'id': 'tailInputSection', 'class': 'cbi-value' }, [ + E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', 'for' : 'tailInput', @@ -541,15 +687,24 @@ return baseclass.extend({ ]), ]), + E('div', { 'id': 'timeFilterSection', 'class': 'cbi-value' }, [ + E('label', { + 'class': 'cbi-value-title', + 'for' : 'timeFilter', + }, _('Timestamp filter')), + E('div', { 'class': 'cbi-value-field' }, timeFilter), + ]), + logHostsDropdownElem, + logFacilitiesDropdownElem, logLevelsDropdownElem, E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', - 'for' : 'logFilter', + 'for' : 'msgFilter', }, _('Message filter')), - E('div', { 'class': 'cbi-value-field' }, logFilter), + E('div', { 'class': 'cbi-value-field' }, msgFilter), ]), E('div', { 'class': 'cbi-value' }, [ @@ -577,51 +732,7 @@ return baseclass.extend({ 'style' : 'display:inline-block; margin-top:0.5em', 'submit': ui.createHandlerFn(this, function(ev) { ev.preventDefault(); - let formElems = Array.from(document.forms.logForm.elements); - formElems.forEach(e => e.disabled = true); - logDownloadBtn.disabled = true; - - // Saving settings to localStorage - if(this.tailValue != tailInput.value) { - this.tailValue = (/^[0-9]+$/.test(tailInput.value)) ? tailInput.value : 0; - localStorage.setItem( - `luci-app-${this.viewName}-tailValue`, String(this.tailValue)); - }; - if(this.logSortingValue != logSorting.value) { - this.logSortingValue = logSorting.value; - localStorage.setItem( - `luci-app-${this.viewName}-logSorting`, this.logSortingValue); - }; - - let tail = (tailInput.value && tailInput.value > 0) ? tailInput.value : 0 - return this.getLogData(tail).then(logdata => { - logdata = logdata || ''; - logWrapper.innerHTML = ''; - logWrapper.append( - this.makeLogArea( - this.setRegexpFilter( - this.setLevelFilter( - this.setHostFilter( - this.parseLogData(logdata, tail) - ) - ) - ) - ) - ); - - if(logdata) { - let tailInputSection = document.getElementById('tailInputSection'); - if(this.isLevels && !this.logLevelsDropdown) { - tailInputSection.after(this.makeLogLevelsDropdownSection()); - }; - if(this.isHosts && !this.logHostsDropdown) { - tailInputSection.after(this.makeLogHostsDropdownSection()); - }; - }; - }).finally(() => { - formElems.forEach(e => e.disabled = false); - logDownloadBtn.disabled = false; - }); + return onSubmitForm(); }), }, E('span', {}, ' ')), ]), diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/log.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/log.js index a8aabf9..54808b2 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/log.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/log.js @@ -15,35 +15,35 @@ return abc.view.extend({ entriesHandler : null, // logd - logdHandler: function(strArray, lineNum) { + logdHandler(strArray, lineNum) { let logLevel = strArray[5].split('.'); return [ lineNum, // # (Number) strArray.slice(0, 5).join(' '), // Timestamp (String) null, // Host (String) - logLevel[1], // Level (String) logLevel[0], // Facility (String) + logLevel[1], // Level (String) this.htmlEntities(strArray.slice(6).join(' ')), // Message (String) ]; }, // syslog-ng - syslog_ngHandler: function(strArray, lineNum) { + syslog_ngHandler(strArray, lineNum) { if(!(strArray[3] in this.logHosts)) { - this.logHosts[strArray[3]] = this.makelogHostsDropdownItem(strArray[3]); + 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) + null, // Level (String) this.htmlEntities(strArray.slice(4).join(' ')), // Message (String) ]; }, - getLogData: function(tail) { + getLogData(tail) { return Promise.all([ L.resolveDefault(fs.stat('/sbin/logread'), null), L.resolveDefault(fs.stat('/usr/sbin/logread'), null), @@ -51,7 +51,7 @@ return abc.view.extend({ let logger = (stat[0]) ? stat[0].path : (stat[1]) ? stat[1].path : null; if(logger) { - return fs.exec_direct(logger, [ '-e', tools.appName + ':' ]).catch(err => { + return fs.exec_direct(logger, [ '-e', tools.appName + ':' ], 'text').catch(err => { ui.addNotification( null, E('p', {}, _('Unable to load log data:') + ' ' + err.message)); return ''; @@ -60,12 +60,13 @@ return abc.view.extend({ }); }, - parseLogData: function(logdata, tail) { + parseLogData(logdata, tail) { if(!logdata) { return []; }; - let strings = logdata.trim().split(/\n/); + let unsupportedLog = false; + let strings = logdata.trim().split(/\n/); if(tail && tail > 0 && strings) { strings = strings.slice(-tail); @@ -73,21 +74,32 @@ return abc.view.extend({ this.totalLogLines = strings.length; - let entriesArray = strings.map((e, i) => { - let strArray = e.split(/\s+/); + let entriesArray = strings.map((e, i) => { + let strArray = e.split(/\s+/); if(!this.isLoggerChecked) { + /** + * Checking the fourth field of a line. + * If it contains time then logd. + */ + if(this.testRegexp.test(strArray[3])) { + this.isFacilities = true; + this.isLevels = true; + this.logHosts = {}; + this.entriesHandler = this.logdHandler; + } /** * Checking the third field of a line. * If it contains time then syslog-ng. */ - if(this.testRegexp.test(strArray[2])) { + else if(this.testRegexp.test(strArray[2])) { this.isHosts = true; + this.logFacilities = {}; this.logLevels = {}; this.entriesHandler = this.syslog_ngHandler; } else { - this.isLevels = true; - this.entriesHandler = this.logdHandler; + unsupportedLog = true; + return; }; this.isLoggerChecked = true; }; @@ -95,10 +107,18 @@ return abc.view.extend({ return this.entriesHandler(strArray, i + 1); }); - if(this.logSortingValue === 'desc') { - entriesArray.reverse(); - }; + if(unsupportedLog) { + ui.addNotification( + null, + E('p', {}, _('Unable to load log data:') + ' ' + _('Unsupported log format')) + ); + return []; + } else { + if(this.logSortingValue === 'desc') { + entriesArray.reverse(); + }; - return entriesArray; + return entriesArray; + }; }, }); diff --git a/luci-app-ruantiblock/po/ru/ruantiblock.po b/luci-app-ruantiblock/po/ru/ruantiblock.po index 3b1d7e6..d2ccb33 100644 --- a/luci-app-ruantiblock/po/ru/ruantiblock.po +++ b/luci-app-ruantiblock/po/ru/ruantiblock.po @@ -164,6 +164,9 @@ msgstr "Тип FQDN фильтра" msgid "Facility" msgstr "Категория" +msgid "Facilities" +msgstr "Категории" + msgid "Failed to get %s init status: %s" msgstr "Не удалось получить статус инициализации %s: %s" @@ -428,6 +431,9 @@ msgstr "Таймаут" msgid "Timestamp" msgstr "Время" +msgid "Timestamp filter" +msgstr "Фильтр даты" + msgid "Tor configuration file" msgstr "Конфигурационный файл Tor" @@ -470,6 +476,9 @@ msgstr "Невозможно сохранить изменения" msgid "Unable to save the contents" msgstr "Невозможно сохранить содержимое" +msgid "Unsupported log format" +msgstr "Неподдерживаемый формат лога" + msgid "Update" msgstr "Обновить" diff --git a/luci-app-ruantiblock/po/templates/ruantiblock.pot b/luci-app-ruantiblock/po/templates/ruantiblock.pot index 010d9ef..ae7a21f 100644 --- a/luci-app-ruantiblock/po/templates/ruantiblock.pot +++ b/luci-app-ruantiblock/po/templates/ruantiblock.pot @@ -148,6 +148,9 @@ msgstr "" msgid "Facility" msgstr "" +msgid "Facilities" +msgstr "" + msgid "Failed to get %s init status: %s" msgstr "" @@ -393,6 +396,9 @@ msgstr "" msgid "Timestamp" msgstr "" +msgid "Timestamp filter" +msgstr "" + msgid "Tor configuration file" msgstr "" @@ -424,6 +430,7 @@ msgstr "" msgid "Unable to load log data:" msgstr "" + msgid "Unable to read the contents" msgstr "" @@ -433,6 +440,9 @@ msgstr "" msgid "Unable to save the contents" msgstr "" +msgid "Unsupported log format" +msgstr "" + msgid "Update" msgstr ""