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