diff --git a/autoinstall/autoinstall.sh b/autoinstall/autoinstall.sh index 5869b4d..321a24a 100755 --- a/autoinstall/autoinstall.sh +++ b/autoinstall/autoinstall.sh @@ -11,8 +11,8 @@ 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-6" -BASE_URL="https://raw.githubusercontent.com/gSpotx2f/ruantiblock_openwrt/master" +RUAB_LUCI_APP_VERSION="0.9.0-7" +BASE_URL="https://github.com/gSpotx2f/ruantiblock_openwrt/raw/master" PKG_DIR="/tmp" if [ -n "$1" ]; then @@ -29,8 +29,8 @@ URL_LUCI_APP_RU_PKG="${BASE_URL}/packages/${OWRT_VERSION}/luci-i18n-ruantiblock- ### tor URL_TORRC="${BASE_URL}/tor/etc/tor/torrc" ### ruantiblock-mod-lua -URL_LUA_IPTOOL="https://raw.githubusercontent.com/gSpotx2f/iptool-lua/master/5.1/iptool.lua" -URL_LUA_IDN="https://raw.githubusercontent.com/haste/lua-idn/master/idn.lua" +URL_LUA_IPTOOL="https://github.com/gSpotx2f/iptool-lua/raw/master/5.1/iptool.lua" +URL_LUA_IDN="https://github.com/haste/lua-idn/raw/master/idn.lua" ### Local files diff --git a/images/01.jpg b/images/01.jpg deleted file mode 100644 index e96e480..0000000 Binary files a/images/01.jpg and /dev/null differ diff --git a/images/02.jpg b/images/02.jpg deleted file mode 100644 index 3dff941..0000000 Binary files a/images/02.jpg and /dev/null differ diff --git a/images/03.jpg b/images/03.jpg deleted file mode 100644 index b7ed136..0000000 Binary files a/images/03.jpg and /dev/null differ diff --git a/luci-app-ruantiblock/Makefile b/luci-app-ruantiblock/Makefile index 5a6ed70..527dd81 100644 --- a/luci-app-ruantiblock/Makefile +++ b/luci-app-ruantiblock/Makefile @@ -5,11 +5,12 @@ include $(TOPDIR)/rules.mk PKG_VERSION:=0.9.0 -PKG_RELEASE:=6 +PKG_RELEASE:=7 LUCI_TITLE:=LuCI support for ruantiblock -LUCI_DEPENDS:=+ruantiblock +luci-mod-admin-full +LUCI_DEPENDS:=+ruantiblock LUCI_PKGARCH:=all -include ../../luci.mk +#include ../../luci.mk +include $(TOPDIR)/feeds/luci/luci.mk # call BuildPackage - OpenWrt buildroot signature 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 1c0e098..cf2fb8f 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 @@ -139,15 +139,15 @@ return L.Class.extend({ * View name (for local storage and downloads). * Must be overridden by a subclass! */ - viewName: null, + viewName : null, /** * Page title. * Must be overridden by a subclass! */ - title: null, + title : null, - logLevels: { + 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'))), @@ -158,25 +158,25 @@ return L.Class.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, + isLevels : false, - logHosts: {}, + logHosts : {}, - logLevelsStat: {}, + logLevelsStat : {}, - logHostsDropdown: null, + logHostsDropdown : null, logLevelsDropdown: null, - totalLogLines: 0, + totalLogLines : 0, - htmlEntities: function(str) { + htmlEntities : function(str) { return String(str).replace( /&/g, '&').replace( / { @@ -455,27 +455,27 @@ return L.Class.extend({ 'style': 'max-width:4em !important', }); - let logHostsDropdownElem = ''; + let logHostsDropdownElem = ''; let logLevelsDropdownElem = ''; if(this.isLevels) { logLevelsDropdownElem = this.makeLogLevelsDropdownSection(); }; if(this.isHosts) { - logHostsDropdownElem = this.makeLogHostsDropdownSection(); + logHostsDropdownElem = this.makeLogHostsDropdownSection(); }; let logFilter = E('input', { - 'id': 'logFilter', - 'name': 'logFilter', - 'type': 'text', - 'form': 'logForm', - 'class': 'cbi-input-text', + 'id' : 'logFilter', + 'name' : 'logFilter', + 'type' : 'text', + 'form' : 'logForm', + 'class' : 'cbi-input-text', 'placeholder': _('Type an expression...'), }); let logFormSubmitBtn = E('input', { - 'type': 'submit', - 'form': 'logForm', + 'type' : 'submit', + 'form' : 'logForm', 'class': 'cbi-button btn cbi-button-action', 'value': _('Apply'), 'click': ev => ev.target.blur(), @@ -483,9 +483,9 @@ return L.Class.extend({ }); let logSorting = E('select', { - 'id': 'logSorting', - 'name': 'logSorting', - 'form': 'logForm', + 'id' : 'logSorting', + 'name' : 'logSorting', + 'form' : 'logForm', 'class': "cbi-input-select", }, [ E('option', { 'value': 'asc' }, _('ascending')), @@ -494,8 +494,8 @@ return L.Class.extend({ logSorting.value = this.logSortingValue; let logDownloadBtn = E('button', { - 'id': 'logDownloadBtn', - 'name': 'logDownloadBtn', + 'id' : 'logDownloadBtn', + 'name' : 'logDownloadBtn', 'class': 'cbi-button btn', 'click': ui.createHandlerFn(this, this.downloadLog), }, _('Download log')); @@ -509,7 +509,7 @@ return L.Class.extend({ E('div', { 'id': 'tailInputSection', 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', - 'for': 'tailInput', + 'for' : 'tailInput', }, _('Last entries')), E('div', { 'class': 'cbi-value-field' }, [ tailInput, @@ -523,7 +523,7 @@ return L.Class.extend({ E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', - 'for': 'logFilter', + 'for' : 'logFilter', }, _('Message filter')), E('div', { 'class': 'cbi-value-field' }, logFilter), ]), @@ -531,7 +531,7 @@ return L.Class.extend({ E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', - 'for': 'logSorting', + 'for' : 'logSorting', }, _('Sorting entries')), E('div', { 'class': 'cbi-value-field' }, logSorting,), ]), @@ -539,14 +539,14 @@ return L.Class.extend({ E('div', { 'class': 'cbi-value' }, [ E('label', { 'class': 'cbi-value-title', - 'for': 'logFilter', + 'for' : 'logFilter', }, _('Refresh log')), E('div', { 'class': 'cbi-value-field' }, [ logFormSubmitBtn, E('form', { - 'id': 'logForm', - 'name': 'logForm', - 'style': 'display:inline-block; margin-top:0.5em', + 'id' : 'logForm', + 'name' : 'logForm', + 'style' : 'display:inline-block; margin-top:0.5em', 'submit': ui.createHandlerFn(this, function(ev) { ev.preventDefault(); let formElems = Array.from(document.forms.logForm.elements); @@ -633,8 +633,8 @@ return L.Class.extend({ ]); }, + handleSave : null, handleSaveApply: null, - handleSave: null, - handleReset: null, + handleReset : null, }), }) diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/cron.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/cron.js index 7652f8f..7484db6 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/cron.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/cron.js @@ -3,221 +3,234 @@ 'require ui'; 'require view.ruantiblock.tools as tools'; -let crontab_regexp = new RegExp(`^(\\*?\\/?(\\d){0,2}\\s){5}${tools.exec_path} update(\n)?`, 'gm'); -let current_crontab_content; - -function to_dd(n){ - return String(n).replace(/^(\d)$/, "0$1"); -}; - -function cron_status_string(s) { - return s || _('No Shedule'); -} - -function pick_cron_task(content) { - if(!content){ - return; - }; - let current_tasks = content.match(crontab_regexp) || []; - return current_tasks.join(''); -}; - -function set_cron_status(value) { - document.getElementById('cron_status').value = cron_status_string(value); - document.getElementById("btn_cron_del").style.visibility = (value) ? 'visible' : 'hidden'; -} - -function write_cron_file() { - let btn_cron_add = document.getElementById('btn_cron_add'); - let btn_cron_del = document.getElementById('btn_cron_del'); - - if(!current_crontab_content) { - ui.addNotification(null, E('p', _('No changes to save.'))); - btn_cron_add.disabled = false; - return; - }; - - return fs.write(tools.crontab_file, current_crontab_content).then(rc => { - ui.addNotification(null, E('p',_('Changes have been saved.')), 'info'); - set_cron_status(pick_cron_task(current_crontab_content)); - }).then(() => { - return fs.exec('/etc/init.d/cron', [ 'enabled' ]).then(res => { - if(res.code !== 0) { - return fs.exec('/etc/init.d/cron', [ 'enable' ]); - }; - }).catch(e => { - ui.addNotification(null, E('p', _('Unable to execute or read contents') - + ': %s
[ %s ]'.format(e.message, '/etc/init.d/cron'))); - }); - }).finally(() => { - return fs.exec('/etc/init.d/cron', [ 'restart' ]).catch(e => { - ui.addNotification(null, E('p', _('Unable to execute or read contents') - + ': %s
[ %s ]'.format(e.message, '/etc/init.d/cron'))); - }); - }).catch(e => { - ui.addNotification(null, E('p', _('Unable to save the changes') - + ': %s
[ %s ]'.format( - e.message, tools.crontab_file - ))); - }); -} - -function del_cron_schedule(ev) { - if(current_crontab_content) { - current_crontab_content = current_crontab_content.replace(crontab_regexp, ""); - }; - return write_cron_file(); -}; - -function set_cron_schedule(ev) { - let hour_interval = document.getElementById('cron_hour_interval').value; - let day_interval = document.getElementById('cron_day_interval').value; - let hour = document.getElementById('cron_hour').value; - let min = document.getElementById('cron_min').value; - let task_string = '%s %s %s * * %s update\n'.format( - min, - (!hour_interval) ? hour : (hour_interval == "1") ? '*' : '*/' + hour_interval, - (hour_interval || day_interval == "1") ? '*' : '*/' + day_interval, - tools.exec_path - ); - if(current_crontab_content) { - current_crontab_content = current_crontab_content.replace(crontab_regexp, "") + task_string; - }; - return write_cron_file(); -}; - -function onchange_hour_interval(e) { - let value = e.target.value; - let bool = (value != ''); - let cron_hour = document.getElementById('cron_hour'); - let cron_day_interval = document.getElementById('cron_day_interval'); - cron_hour.disabled = bool; - cron_day_interval.disabled = bool; - - if(bool) { - cron_hour.style.opacity = '50%'; - cron_day_interval.style.opacity = '50%'; - } else { - cron_hour.style.opacity = '100%'; - cron_day_interval.style.opacity = '100%'; - }; -} - return L.view.extend({ - load: function() { - return fs.read(tools.crontab_file).catch(e => { - ui.addNotification(null, E('p', _('Unable to read the contents') - + ': %s
[ %s ]'.format( - e.message, tools.crontab_file - ))); - }); - }, + crontabRegexp: new RegExp( + `^(\\*?\\/?(\\d){0,2}\\s){5}${tools.execPath} update(\n)?`, 'gm'), - render: function(content) { - current_crontab_content = content; - let current_task = pick_cron_task(content); + currentCrontabContent: null, - let cron_status = E('textarea', { - 'id': 'cron_status', - 'name': 'cron_status', - 'style': 'width:100% !important; padding:5px 10px 5px 10px !important; resize:none !important;', - 'readonly': 'readonly', - 'wrap': 'off', - 'rows': 2, - }, cron_status_string(current_task)); + toDD: function(n){ + return String(n).replace(/^(\d)$/, "0$1"); + }, - let btn_cron_del = E('button', { - 'class': 'cbi-button btn cbi-button-reset', - 'id': 'btn_cron_del', - 'name': 'btn_cron_del', - }, _('Reset')); - btn_cron_del.onclick = ui.createHandlerFn(this, del_cron_schedule); - btn_cron_del.style.visibility = (current_task) ? 'visible' : 'hidden'; + cronStatusString: function(s) { + return s || _('No Shedule'); + }, - let status_header = E('div', { 'class': 'cbi-section-node' }, [ - E('div', { 'class': 'cbi-value' }, [ - E('label', { 'class': 'cbi-value-title', 'for': 'cron_status' }, - _('Current schedule')), - E('div', { 'class': 'cbi-value-field' }, cron_status), - ]), - E('div', { 'class': 'cbi-value' }, [ - E('label', { 'class': 'cbi-value-title', 'for': 'btn_cron_del' }), - E('div', { 'class': 'cbi-value-field' }, btn_cron_del), - ]) - ]); + pickCronTask: function(content) { + if(!content){ + return; + }; + let current_tasks = content.match(this.crontabRegexp) || []; + return current_tasks.join(''); + }, - let layout = E('div', { 'class': 'cbi-section-node' }); + setCronStatus: function(value) { + document.getElementById('cron_status').value = this.cronStatusString(value); + document.getElementById("btn_cron_del").style.visibility = (value) ? + 'visible' : 'hidden'; + }, - function layout_append(elem, title, descr) { - descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : ''; - layout.append( - E('div', { 'class': 'cbi-value' }, [ - E('label', { 'class': 'cbi-value-title', 'for': elem.id || null }, - title), - E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]), - ]) - ) - }; + writeCronFile: function() { + let btn_cron_add = document.getElementById('btn_cron_add'); + let btn_cron_del = document.getElementById('btn_cron_del'); - layout_append(E('b', {}, _('Interval'))); + if(!this.currentCrontabContent) { + ui.addNotification(null, E('p', _('No changes to save.'))); + btn_cron_add.disabled = false; + return; + }; - let cron_hour_interval = E('select', - { 'id': 'cron_hour_interval', 'style': 'width:60px !important; min-width:60px !important' }, [ - E('option', { 'value': '' }, ''), - E('option', { 'value': '1' }, '∗') - ]); - for(let i = 2; i <= 12 ; i += 2) { - cron_hour_interval.append(E('option', { 'value': String(i) }, '∗/' + i)); - }; - layout_append(cron_hour_interval, _('Hour')); - cron_hour_interval.onchange = onchange_hour_interval; + return fs.write(tools.crontabFile, this.currentCrontabContent).then(rc => { + ui.addNotification(null, E('p',_('Changes have been saved.')), 'info'); + this.setCronStatus(this.pickCronTask(this.currentCrontabContent)); + }).then(() => { + return tools.getInitStatus('cron').then(res => { + if(!res) { + return tools.handleServiceAction('cron', 'enable'); + }; + }); + }).finally(() => { + return tools.handleServiceAction('cron', 'restart'); + }).catch(e => { + ui.addNotification(null, E('p', _('Unable to save the changes') + + ': %s [ %s ]'.format( + e.message, tools.crontabFile + ))); + }); + }, - let cron_day_interval = E('select', - { 'id': 'cron_day_interval', 'style': 'width:60px !important; min-width:60px !important' }, - E('option', { 'value': '1' }, '∗') - ); - for(let i = 2; i < 8 ; i++) { - cron_day_interval.append(E('option', { 'value': String(i) }, '∗/' + i)); - }; - cron_day_interval.append(E('option', { 'value': '14' }, '∗/14')); - cron_day_interval.append(E('option', { 'value': '28' }, '∗/28')); - layout_append(cron_day_interval, _('Day')); + delCronSchedule: function(ev) { + if(this.currentCrontabContent) { + this.currentCrontabContent = this.currentCrontabContent.replace( + this.crontabRegexp, ""); + }; + return this.writeCronFile(); + }, - layout_append(E('b', {}, _('Time'))); + setCronSchedule: function(ev) { + let hour_interval = document.getElementById('cron_hour_interval').value; + let day_interval = document.getElementById('cron_day_interval').value; + let hour = document.getElementById('cron_hour').value; + let min = document.getElementById('cron_min').value; + let task_string = '%s %s %s * * %s update\n'.format( + min, + (!hour_interval) ? hour : (hour_interval == "1") ? '*' : '*/' + hour_interval, + (hour_interval || day_interval == "1") ? '*' : '*/' + day_interval, + tools.execPath + ); + if(this.currentCrontabContent) { + this.currentCrontabContent = this.currentCrontabContent.replace( + this.crontabRegexp, "") + task_string; + }; + return this.writeCronFile(); + }, - let cron_hour = E('select', - { 'id': 'cron_hour', 'style': 'width:60px !important; min-width:60px !important' }); - for(let i = 0; i < 24 ; i++) { - cron_hour.append(E('option', { 'value': String(i) }, to_dd(i))); - }; - layout_append(cron_hour, _('Hour')); + onchangeHourInterval: function(e) { + let value = e.target.value; + let bool = (value != ''); + let cron_hour = document.getElementById('cron_hour'); + let cron_day_interval = document.getElementById('cron_day_interval'); + cron_hour.disabled = bool; + cron_day_interval.disabled = bool; - let cron_min = E('select', - { 'id': 'cron_min', 'style': 'width:60px !important; min-width:60px !important' }); - for(let i = 0; i < 60 ; i++) { - cron_min.append(E('option', { 'value': String(i) }, to_dd(i))); - }; - layout_append(cron_min, _('Minute')); + // For luci-theme-material + if(bool) { + cron_hour.style.opacity = '50%'; + cron_day_interval.style.opacity = '50%'; + } else { + cron_hour.style.opacity = '100%'; + cron_day_interval.style.opacity = '100%'; + }; + }, - let btn_cron_add = E('button', { - 'class': 'btn cbi-button-save', - 'id': 'btn_cron_add', - 'name': 'btn_cron_add' - }, _('Set')); - btn_cron_add.onclick = ui.createHandlerFn(this, set_cron_schedule); - layout_append(btn_cron_add); + load: function() { + return fs.read(tools.crontabFile).catch(e => { + ui.addNotification(null, E('p', _('Unable to read the contents') + + ': %s [ %s ]'.format( + e.message, tools.crontabFile + ))); + }); + }, - return E([ - E('h2', - { 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Blacklist updates') + ' (cron)'), - E('div', { 'class': 'cbi-section-descr fade-in' }), - E('div', { 'class': 'cbi-section fade-in' }, status_header), - E('div', { 'class': 'cbi-section fade-in' }, layout), - ]); + render: function(content) { + this.currentCrontabContent = content; + let current_task = this.pickCronTask(content); - }, + let cron_status = E('textarea', { + 'id': 'cron_status', + 'name': 'cron_status', + 'style': 'width:100% !important; padding:5px 10px 5px 10px !important; resize:none !important;', + 'readonly': 'readonly', + 'wrap': 'off', + 'rows': 2, + }, this.cronStatusString(current_task)); - handleSave: null, - handleSaveApply: null, - handleReset: null, + let btn_cron_del = E('button', { + 'class': 'cbi-button btn cbi-button-reset', + 'id': 'btn_cron_del', + 'name': 'btn_cron_del', + }, _('Reset')); + btn_cron_del.onclick = ui.createHandlerFn(this, this.delCronSchedule); + btn_cron_del.style.visibility = (current_task) ? 'visible' : 'hidden'; + + let status_header = E('div', { 'class': 'cbi-section-node' }, [ + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title', 'for': 'cron_status' }, + _('Current schedule')), + E('div', { 'class': 'cbi-value-field' }, + cron_status), + ]), + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title', 'for': 'btn_cron_del' }), + E('div', { 'class': 'cbi-value-field' }, + btn_cron_del), + ]) + ]); + + let layout = E('div', { 'class': 'cbi-section-node' }); + + function layout_append(elem, title, descr) { + descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : ''; + layout.append( + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title', 'for': elem.id || null }, + title), + E('div', { 'class': 'cbi-value-field' }, + [ elem, descr ]), + ]) + ) + }; + + layout_append(E('b', {}, _('Interval'))); + + let cron_hour_interval = E('select', + { 'id': 'cron_hour_interval', 'style': 'width:60px !important; min-width:60px !important' }, [ + E('option', { 'value': '' }, ''), + E('option', { 'value': '1' }, '∗') + ]); + for(let i = 2; i <= 12 ; i += 2) { + cron_hour_interval.append( + E('option', { 'value': String(i) }, '∗/' + i) + ); + }; + layout_append(cron_hour_interval, _('Hour')); + cron_hour_interval.onchange = this.onchangeHourInterval; + + let cron_day_interval = E('select', + { 'id': 'cron_day_interval', 'style': 'width:60px !important; min-width:60px !important' }, + E('option', { 'value': '1' }, '∗') + ); + for(let i = 2; i < 8 ; i++) { + cron_day_interval.append( + E('option', { 'value': String(i) }, '∗/' + i) + ); + }; + cron_day_interval.append(E('option', { 'value': '14' }, '∗/14')); + cron_day_interval.append(E('option', { 'value': '28' }, '∗/28')); + layout_append(cron_day_interval, _('Day')); + + layout_append(E('b', {}, _('Time'))); + + let cron_hour = E('select', + { 'id': 'cron_hour', 'style': 'width:60px !important; min-width:60px !important' }); + for(let i = 0; i < 24 ; i++) { + cron_hour.append( + E('option', { 'value': String(i) }, this.toDD(i)) + ); + }; + layout_append(cron_hour, _('Hour')); + + let cron_min = E('select', + { 'id': 'cron_min', 'style': 'width:60px !important; min-width:60px !important' }); + for(let i = 0; i < 60 ; i++) { + cron_min.append( + E('option', { 'value': String(i) }, this.toDD(i)) + ); + }; + layout_append(cron_min, _('Minute')); + + let btn_cron_add = E('button', { + 'class': 'btn cbi-button-save', + 'id': 'btn_cron_add', + 'name': 'btn_cron_add' + }, _('Set')); + btn_cron_add.onclick = ui.createHandlerFn(this, this.setCronSchedule); + layout_append(btn_cron_add); + + return E([ + E('h2', + { 'class': 'fade-in' }, + _('Ruantiblock') + ' - ' + _('Blacklist updates') + ' (cron)' + ), + E('div', { 'class': 'cbi-section-descr fade-in' }), + E('div', { 'class': 'cbi-section fade-in' }, status_header), + E('div', { 'class': 'cbi-section fade-in' }, layout), + ]); + + }, + + handleSave : null, + handleSaveApply: null, + handleReset : null, }); diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/info.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/info.js index 2e3b047..f209831 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/info.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/info.js @@ -4,230 +4,238 @@ 'require view.ruantiblock.tools as tools'; return L.view.extend({ - poll_info: function() { - return fs.exec_direct(tools.exec_path, [ 'html-info' ], 'json').catch(e => { - ui.addNotification(null, E('p', _('Unable to execute or read contents') - + ': %s
[ %s ]'.format(e.message, tools.exec_path) - )); - L.Poll.stop(); - }).then(data => { - if(!data) { - return; - }; + infoPoll: function() { + return fs.exec_direct(tools.execPath, [ 'html-info' ], 'json').catch(e => { + ui.addNotification(null, E('p', _('Unable to execute or read contents') + + ': %s [ %s ]'.format(e.message, tools.execPath) + )); + L.Poll.stop(); + }).then(data => { + if(!data) { + return; + }; - try { - data = JSON.parse(data); - } catch(err) {}; + try { + data = JSON.parse(data); + } catch(err) {}; - if(data.status === 'enabled') { - let date = document.getElementById('last_blacklist_update.date'); + if(data.status === 'enabled') { + let date = document.getElementById('last_blacklist_update.date'); - if(data.last_blacklist_update.status) { - if(date) { - date.textContent = data.last_blacklist_update.date; - }; + if(data.last_blacklist_update.status) { + if(date) { + date.textContent = data.last_blacklist_update.date; + }; - let ip = document.getElementById('last_blacklist_update.ip'); - if(ip) { - ip.textContent = data.last_blacklist_update.ip; - }; + let ip = document.getElementById('last_blacklist_update.ip'); + if(ip) { + ip.textContent = data.last_blacklist_update.ip; + }; - let cidr = document.getElementById('last_blacklist_update.cidr'); - if(cidr) { - cidr.textContent = data.last_blacklist_update.cidr; - }; + let cidr = document.getElementById('last_blacklist_update.cidr'); + if(cidr) { + cidr.textContent = data.last_blacklist_update.cidr; + }; - let fqdn = document.getElementById('last_blacklist_update.fqdn'); - if(fqdn) { - fqdn.textContent = data.last_blacklist_update.fqdn; - }; - } else { - if(date) { - date.textContent = _('No data'); - }; - }; + let fqdn = document.getElementById('last_blacklist_update.fqdn'); + if(fqdn) { + fqdn.textContent = data.last_blacklist_update.fqdn; + }; + } else { + if(date) { + date.textContent = _('No data'); + }; + }; - if(data.iptables) { - for(let [k, v] of Object.entries(data.iptables)) { - if(k === '_dummy') continue; + if(data.iptables) { + for(let [k, v] of Object.entries(data.iptables)) { + if(k === '_dummy') continue; - let elem = document.getElementById('iptables.' + k); - if(elem) { - elem.textContent = v; - }; - }; - }; + let elem = document.getElementById('iptables.' + k); + if(elem) { + elem.textContent = v; + }; + }; + }; - if(data.ipset) { - for(let [k, v] of Object.entries(data.ipset)) { - if(k === '_dummy') continue; + if(data.ipset) { + for(let [k, v] of Object.entries(data.ipset)) { + if(k === '_dummy') continue; - let elem0 = document.getElementById('ipset.' + k + '.' + '0'); - let elem1 = document.getElementById('ipset.' + k + '.' + '1'); - if(elem0 && elem1) { - elem0.textContent = v[0]; - elem1.textContent = v[1]; - }; - }; - }; - } else { - if(L.Poll.active()) { - L.Poll.stop(); - }; - }; - }); - }, + let elem0 = document.getElementById('ipset.' + k + '.' + '0'); + let elem1 = document.getElementById('ipset.' + k + '.' + '1'); + if(elem0 && elem1) { + elem0.textContent = v[0]; + elem1.textContent = v[1]; + }; + }; + }; + } else { + if(L.Poll.active()) { + L.Poll.stop(); + }; + }; + }); + }, - load: function() { - return fs.exec_direct(tools.exec_path, [ 'html-info' ], 'json').catch(e => { - ui.addNotification(null, E('p', _('Unable to execute or read contents') - + ': %s
[ %s ]'.format(e.message, tools.exec_path) - )); - }) - }, + load: function() { + return fs.exec_direct(tools.execPath, [ 'html-info' ], 'json').catch(e => { + ui.addNotification(null, E('p', _('Unable to execute or read contents') + + ': %s [ %s ]'.format(e.message, tools.execPath) + )); + }) + }, - render: function(data) { - if(!data) { - return; - }; + render: function(data) { + if(!data) { + return; + }; - try { - data = JSON.parse(data); - } catch(err) {}; + try { + data = JSON.parse(data); + } catch(err) {}; - let update_status = null, - iptables = null, - ipset = null; - if(data) { - if(data.status === 'enabled') { - update_status = E('div', { 'class': 'table' }); + let update_status = null, + iptables = null, + ipset = null; + if(data) { + if(data.status === 'enabled') { + update_status = E('div', { 'class': 'table' }); - if(data.last_blacklist_update.status) { - update_status.append( - E('div', { 'class': 'tr' }, [ - E('div', { 'class': 'td left', 'style': 'min-width:33%' }, - _('Last blacklist update') + ':'), - E('div', { 'class': 'td left', 'id': 'last_blacklist_update.date' }, - data.last_blacklist_update.date), - ]), - E('div', { 'class': 'tr' }, [ - E('div', { 'class': 'td left' }, 'IP:'), - E('div', { 'class': 'td left', 'id': 'last_blacklist_update.ip' }, - data.last_blacklist_update.ip), - ]), - E('div', { 'class': 'tr' }, [ - E('div', { 'class': 'td left' }, 'CIDR:'), - E('div', { 'class': 'td left', 'id': 'last_blacklist_update.cidr' }, - data.last_blacklist_update.cidr), - ]), - E('div', { 'class': 'tr' }, [ - E('div', { 'class': 'td left' }, 'FQDN:'), - E('div', { 'class': 'td left', 'id': 'last_blacklist_update.fqdn' }, - data.last_blacklist_update.fqdn), - ]) - ); - } else { - update_status.append( - E('div', { 'class': 'tr' }, [ - E('div', { 'class': 'td left' }, - _('Last blacklist update')), - E('div', { 'class': 'td left' }, _('No data')), - ]) - ); - }; + if(data.last_blacklist_update.status) { + update_status.append( + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left', 'style': 'min-width:33%' }, + _('Last blacklist update') + ':'), + E('div', { 'class': 'td left', + 'id': 'last_blacklist_update.date' }, + data.last_blacklist_update.date), + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left' }, 'IP:'), + E('div', { 'class': 'td left', + 'id': 'last_blacklist_update.ip' }, + data.last_blacklist_update.ip), + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left' }, 'CIDR:'), + E('div', { 'class': 'td left', + 'id': 'last_blacklist_update.cidr' }, + data.last_blacklist_update.cidr), + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left' }, 'FQDN:'), + E('div', { 'class': 'td left', + 'id': 'last_blacklist_update.fqdn' }, + data.last_blacklist_update.fqdn), + ]) + ); + } else { + update_status.append( + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td left' }, + _('Last blacklist update')), + E('div', { 'class': 'td left' }, _('No data')), + ]) + ); + }; - if(data.iptables) { - let table_iptables = E('div', { 'class': 'table' }, [ - E('div', { 'class': 'tr table-titles' }, [ - E('div', { 'class': 'th left', 'style': 'min-width:33%' }, - _('Match-set')), - E('div', { 'class': 'th left' }, _('Bytes')), - ]), - ]); + if(data.iptables) { + let table_iptables = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th left', 'style': 'min-width:33%' }, + _('Match-set')), + E('div', { 'class': 'th left' }, _('Bytes')), + ]), + ]); - for(let [k, v] of Object.entries(data.iptables)) { - if(k === '_dummy') continue; + for(let [k, v] of Object.entries(data.iptables)) { + if(k === '_dummy') continue; + table_iptables.append( + E('div', { 'class': 'tr' }, [ + E('div', { + 'class' : 'td left', + 'data-title': _('Match-set'), + }, k), + E('div', { + 'class' : 'td left', + 'id' : 'iptables.' + k, + 'data-title': _('Bytes'), + }, v), + ]) + ); + }; - table_iptables.append( - E('div', { 'class': 'tr' }, [ - E('div', { - 'class': 'td left', - 'data-title': _('Match-set'), - }, k), - E('div', { - 'class': 'td left', - 'id': 'iptables.' + k, - 'data-title': _('Bytes'), - }, v), - ]) - ); - }; + iptables = E([ + E('h3', {}, _('Iptables rules')), + table_iptables, + ]); + }; - iptables = E([ - E('h3', {}, _('Iptables rules')), - table_iptables, - ]); - }; + if(data.ipset) { + let table_ipset = E('div', { 'class': 'table' }, + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th left', 'style': 'min-width:33%' }, + _('Name')), + E('div', { 'class': 'th left' }, + _('Size in memory')), + E('div', { 'class': 'th left' }, + _('Number of entries')), + ]) + ); - if(data.ipset) { - let table_ipset = E('div', { 'class': 'table' }, - E('div', { 'class': 'tr table-titles' }, [ - E('div', { 'class': 'th left', 'style': 'min-width:33%' }, _('Name')), - E('div', { 'class': 'th left' }, _('Size in memory')), - E('div', { 'class': 'th left' }, _('Number of entries')), - ]) - ); + for(let [k, v] of Object.entries(data.ipset)) { + if(k === '_dummy') continue; - for(let [k, v] of Object.entries(data.ipset)) { - if(k === '_dummy') continue; + table_ipset.append( + E('div', { 'class': 'tr' }, [ + E('div', { + 'class' : 'td left', + 'data-title': _('Name'), + }, k), + E('div', { + 'class' : 'td left', + 'id' : 'ipset.' + k + '.' + '0', + 'data-title': _('Size in memory'), + }, v[0]), + E('div', { + 'class' : 'td left', + 'id' : 'ipset.' + k + '.' + '1', + 'data-title': _('Number of entries'), + }, v[1]), + ]) + ); + }; - table_ipset.append( - E('div', { 'class': 'tr' }, [ - E('div', { - 'class': 'td left', - 'data-title': _('Name'), - }, k), - E('div', { - 'class': 'td left', - 'id': 'ipset.' + k + '.' + '0', - 'data-title': _('Size in memory'), - }, v[0]), - E('div', { - 'class': 'td left', - 'id': 'ipset.' + k + '.' + '1', - 'data-title': _('Number of entries'), - }, v[1]), - ]) - ); - }; + ipset = E([ + E('h3', {}, _('Ipset')), + table_ipset, + ]); + }; - ipset = E([ - E('h3', {}, _('Ipset')), - table_ipset, - ]); - }; + L.Poll.add(this.infoPoll); + } else { + update_status = E('em', {}, _('Status') + ' : ' + _('disabled')); + }; + }; + return E([ + E('h2', { 'class': 'fade-in' }, + _('Ruantiblock') + ' - ' + _('Statistics') + ), + E('div', { 'class': 'cbi-section-descr fade-in' }), + E('div', { 'class': 'cbi-section fade-in' }, + E('div', { 'class': 'cbi-section-node' }, update_status) + ), + E('div', { 'class': 'cbi-section fade-in' }, + E('div', { 'class': 'cbi-section-node' }, iptables) + ), + E('div', { 'class': 'cbi-section fade-in' }, + E('div', { 'class': 'cbi-section-node' }, ipset) + ), + ]); + }, - L.Poll.add(this.poll_info); - } else { - update_status = E('em', {}, _('Status') + ' : ' + _('disabled')); - }; - }; - return E([ - E('h2', { 'class': 'fade-in' }, _('Ruantiblock') + ' - ' + _('Statistics')), - E('div', { 'class': 'cbi-section-descr fade-in' }), - E('div', { 'class': 'cbi-section fade-in' }, - E('div', { 'class': 'cbi-section-node' }, update_status) - ), - E('div', { 'class': 'cbi-section fade-in' }, - E('div', { 'class': 'cbi-section-node' }, iptables) - ), - E('div', { 'class': 'cbi-section fade-in' }, - E('div', { 'class': 'cbi-section-node' }, ipset) - ), - ]); - }, - - handleSave: null, - handleSaveApply: null, - handleReset: null, + handleSave : null, + handleSaveApply: null, + handleReset : null, }); 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 a24e352..cc592d0 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 @@ -8,13 +8,13 @@ return abc.view.extend({ title: _('Ruantiblock') + ' - ' + _('Log'), - appRegexp: new RegExp(`^.*${tools.app_name}\[[0-9]+\].*$`, 'gm'), + appRegexp : new RegExp(`^.*${tools.appName}\[[0-9]+\].*$`, 'gm'), - testRegexp: new RegExp(/([0-9]{2}:){2}[0-9]{2}/), + testRegexp : new RegExp(/([0-9]{2}:){2}[0-9]{2}/), isLoggerChecked: false, - entriesHandler: null, + entriesHandler : null, // logd logdHandler: function(strArray, lineNum) { @@ -53,8 +53,9 @@ 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.app_name ]).catch(err => { - ui.addNotification(null, E('p', {}, _('Unable to load log data:') + ' ' + err.message)); + return fs.exec_direct(logger, [ '-e', tools.appName ]).catch(err => { + ui.addNotification( + null, E('p', {}, _('Unable to load log data:') + ' ' + err.message)); return ''; }); }; diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/service.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/service.js index 25730f2..08866df 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/service.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/service.js @@ -4,342 +4,349 @@ 'require ui'; 'require view.ruantiblock.tools as tools'; -const btn_style_neutral = 'btn' -const btn_style_action = 'btn cbi-button-action'; -const btn_style_save = 'btn cbi-button-save important'; -const btn_style_reset = 'btn cbi-button-reset important'; -const btn_style_warning = 'btn cbi-button-negative important' -let status_token_value; - -function disable_buttons(bool, btn, elems=[]) { - let btn_start = elems[1] || document.getElementById("btn_start"); - let btn_destroy = elems[5] || document.getElementById("btn_destroy"); - let btn_enable = elems[2] || document.getElementById("btn_enable"); - let btn_update = elems[4] || document.getElementById("btn_update"); - let btn_tp = elems[3] || document.getElementById("btn_tp"); - - btn_start.disabled = bool; - btn_update.disabled = bool; - btn_destroy.disabled = bool; - if(btn === btn_update) { - btn_enable.disabled = false; - } else { - btn_enable.disabled = bool; - }; - if(btn_tp) { - btn_tp.disabled = bool - }; -} - -function get_app_status() { - return Promise.all([ - fs.exec(tools.exec_path, [ 'raw-status' ]), - fs.exec(tools.exec_path, [ 'total-proxy-status' ]), - fs.exec(tools.exec_path, [ 'vpn-route-status' ]), - fs.exec(tools.init_path, [ 'enabled' ]), - L.resolveDefault(fs.read(tools.token_file), 0), - uci.load(tools.app_name), - ]).catch(e => { - ui.addNotification(null, E('p', _('Unable to execute or read contents') - + ': %s
[ %s | %s | %s ]'.format( - e.message, tools.exec_path, tools.init_path, 'uci.ruantiblock' - ))); - }); -} - -function set_app_status(status_array, elems=[], force_app_code) { - let section = uci.get(tools.app_name, 'config'); - if(!status_array || typeof(section) !== 'object') { - (elems[0] || document.getElementById("status")).innerHTML = tools.make_status_string(1); - ui.addNotification(null, E('p', _('Unable to read the contents') - + ': set_app_status()')); - disable_buttons(true, null, elems); - return; - }; - - let app_status_code = (force_app_code) ? force_app_code : status_array[0].code; - let tp_status_code = status_array[1].code; - let vpn_route_status_code = status_array[2].code; - let enabled_flag = status_array[3].code; - - let proxy_local_clients = section.proxy_local_clients; - let proxy_mode = section.proxy_mode; - let bllist_mode = section.bllist_mode; - let bllist_module = section.bllist_module; - let bllist_source = section.bllist_source; - - let btn_enable = elems[2] || document.getElementById('btn_enable'); - if(enabled_flag == 0) { - btn_enable.onclick = ui.createHandlerFn(this, button_action, 'disable'); - btn_enable.textContent = _('Disable'); - btn_enable.className = btn_style_reset; - } else { - btn_enable.onclick = ui.createHandlerFn(this, button_action, 'enable'); - btn_enable.textContent = _('Enable'); - btn_enable.className = btn_style_save; - }; - - let btn_tp = elems[3] || document.getElementById('btn_tp'); - if(btn_tp) { - if(tp_status_code == 0) { - btn_tp.onclick = ui.createHandlerFn(this, button_action, 'total-proxy-off'); - btn_tp.textContent = _('Disable'); - btn_tp.className = btn_style_reset; - } else { - btn_tp.onclick = ui.createHandlerFn(this, button_action, 'total-proxy-on'); - btn_tp.textContent = _('Enable'); - btn_tp.className = btn_style_save; - }; - }; - - let btn_start = elems[1] || document.getElementById("btn_start"); - let btn_update = elems[4] || document.getElementById("btn_update"); - let btn_destroy = elems[5] || document.getElementById("btn_destroy"); - - function btn_start_state_on() { - btn_start.onclick = ui.createHandlerFn(this, button_action, 'stop'); - btn_start.textContent = _('Disable'); - btn_start.className = btn_style_reset; - } - - function btn_start_state_off() { - btn_start.onclick = ui.createHandlerFn(this, button_action, 'start'); - btn_start.textContent = _('Enable'); - btn_start.className = btn_style_action; - } - - if(app_status_code == 0) { - disable_buttons(false, null, elems); - btn_start_state_on(); - btn_destroy.disabled = false; - btn_update.disabled = false; - if(btn_tp) { - btn_tp.disabled = false; - }; - } - else if(app_status_code == 2) { - disable_buttons(false, null, elems); - btn_start_state_off(); - btn_update.disabled = true; - if(btn_tp) { - btn_tp.disabled = true; - }; - } - else if(app_status_code == 3) { - btn_start_state_off(); - disable_buttons(true, btn_start, elems); - } - else if(app_status_code == 4) { - btn_start_state_on(); - disable_buttons(true, btn_update, elems); - } - else { - ui.addNotification(null, E('p', _('Error') - + ' %s: return code = %s'.format(tools.exec_path, app_status_code))); - disable_buttons(true, null, elems); - }; - - (elems[0] || document.getElementById("status")).innerHTML = tools.make_status_string( - app_status_code, - proxy_mode, - bllist_mode, - bllist_module, - bllist_source, - tp_status_code, - vpn_route_status_code); - - if(!L.Poll.active()) { - L.Poll.start(); - }; -} - -function button_action(action) { - let btn, - cmd = tools.exec_path; - - switch(action) { - case 'start': - case 'stop': - btn = document.getElementById('btn_start'); - break; - case 'destroy': - btn = document.getElementById('btn_destroy'); - break; - case 'update': - btn = document.getElementById('btn_update'); - break; - case 'enable': - case 'disable': - btn = document.getElementById('btn_enable'); - cmd = tools.init_path; - break; - case 'total-proxy-on': - case 'total-proxy-off': - btn = document.getElementById('btn_tp'); - break; - } - - disable_buttons(true, btn); - L.Poll.stop(); - - if(action === 'update') { - get_app_status().then(status_array => { - set_app_status(status_array, [], 4); - }); - }; - - return fs.exec_direct(cmd, [ action ]).then(res => { - return get_app_status().then( - (status_array) => { - set_app_status(status_array); - ui.hideModal(); - }); - }); -} +const btn_style_neutral = 'btn' +const btn_style_action = 'btn cbi-button-action'; +const btn_style_positive = 'btn cbi-button-save important'; +const btn_style_negative = 'btn cbi-button-reset important'; +const btn_style_warning = 'btn cbi-button-negative important' return L.view.extend({ - poll_status: function() { - return fs.read(tools.token_file).then(v => { - v = tools.normalize_value(v); - if(v != status_token_value) { - get_app_status().then(set_app_status); - } - status_token_value = v; - }).catch(e => { - status_token_value = 0; - }); - }, + statusTokenValue: null, - dialog_destroy: function(ev) { - ev.target.blur(); - let cancel_button = E('button', { - 'class': btn_style_neutral, - 'click': ui.hideModal, - }, _('Cancel')); + disableButtons: function(bool, btn, elems=[]) { + let btn_start = elems[1] || document.getElementById("btn_start"); + let btn_destroy = elems[5] || document.getElementById("btn_destroy"); + let btn_enable = elems[2] || document.getElementById("btn_enable"); + let btn_update = elems[4] || document.getElementById("btn_update"); + let btn_tp = elems[3] || document.getElementById("btn_tp"); - let shutdown_btn = E('button', { - 'class': btn_style_warning, - }, _('Shutdown')); - shutdown_btn.onclick = ui.createHandlerFn(this, function() { - cancel_button.disabled = true; - return button_action('destroy'); - }); + btn_start.disabled = bool; + btn_update.disabled = bool; + btn_destroy.disabled = bool; + if(btn === btn_update) { + btn_enable.disabled = false; + } else { + btn_enable.disabled = bool; + }; + if(btn_tp) { + btn_tp.disabled = bool; + }; + }, - ui.showModal(_('Shutdown'), [ - E('div', { 'class': 'cbi-section' }, [ - E('p', _('The service will be disabled and all blacklist data will be deleted. Continue?')), - ]), - E('div', { 'class': 'right' }, [ - shutdown_btn, - ' ', - cancel_button, - ]) - ]); - }, + getAppStatus: function() { + return Promise.all([ + fs.exec(tools.execPath, [ 'raw-status' ]), + fs.exec(tools.execPath, [ 'total-proxy-status' ]), + fs.exec(tools.execPath, [ 'vpn-route-status' ]), + tools.getInitStatus(tools.appName), + L.resolveDefault(fs.read(tools.tokenFile), 0), + uci.load(tools.appName), + ]).catch(e => { + ui.addNotification(null, E('p', _('Unable to execute or read contents') + + ': %s [ %s | %s | %s ]'.format( + e.message, tools.execPath, 'tools.getInitStatus', 'uci.ruantiblock' + ))); + }); + }, - load: function() { - return get_app_status(); - }, + setAppStatus: function(status_array, elems=[], force_app_code) { + let section = uci.get(tools.appName, 'config'); + if(!status_array || typeof(section) !== 'object') { + (elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString(1); + ui.addNotification(null, E('p', _('Unable to read the contents') + + ': setAppStatus()')); + this.disableButtons(true, null, elems); + return; + }; - render: function(status_array) { - if(!status_array) { - return; - }; + let app_status_code = (force_app_code) ? force_app_code : status_array[0].code; + let tp_status_code = status_array[1].code; + let vpn_route_status_code = status_array[2].code; + let enabled_flag = status_array[3]; + let proxy_local_clients = section.proxy_local_clients; + let proxy_mode = section.proxy_mode; + let bllist_mode = section.bllist_mode; + let bllist_module = section.bllist_module; + let bllist_source = section.bllist_source; - let section = uci.get(tools.app_name, 'config'); - 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; + let btn_enable = elems[2] || document.getElementById('btn_enable'); + if(enabled_flag == true) { + btn_enable.onclick = ui.createHandlerFn( + this, this.serviceAction, 'disable', 'btn_enable'); + btn_enable.textContent = _('Enabled'); + btn_enable.className = btn_style_positive; + } else { + btn_enable.onclick = ui.createHandlerFn( + this, this.serviceAction, 'enable', 'btn_enable'); + btn_enable.textContent = _('Disabled'); + btn_enable.className = btn_style_negative; + }; - let status_string = E('div', { - 'id': 'status', - 'name': 'status', - 'class': 'cbi-section-node', - }); + let btn_tp = elems[3] || document.getElementById('btn_tp'); + if(btn_tp) { + if(tp_status_code == 0) { + btn_tp.onclick = ui.createHandlerFn( + this, this.appAction, 'total-proxy-off', 'btn_tp'); + btn_tp.textContent = _('Enabled'); + btn_tp.className = btn_style_positive; + } else { + btn_tp.onclick = ui.createHandlerFn( + this, this.appAction, 'total-proxy-on', 'btn_tp'); + btn_tp.textContent = _('Disabled'); + btn_tp.className = btn_style_negative; + }; + }; - let layout = E('div', { 'class': 'cbi-section-node' }); + let btn_start = elems[1] || document.getElementById('btn_start'); + let btn_update = elems[4] || document.getElementById('btn_update'); + let btn_destroy = elems[5] || document.getElementById('btn_destroy'); - function layout_append(elem, title, descr) { - descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : ''; - layout.append( - E('div', { 'class': 'cbi-value' }, [ - E('label', { 'class': 'cbi-value-title' }, title), - E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]), - ]) - ) - }; + let btnStartStateOn = () => { + btn_start.onclick = ui.createHandlerFn( + this, this.serviceAction, 'stop', 'btn_start'); + btn_start.textContent = _('Enabled'); + btn_start.className = btn_style_positive; + } - let btn_start = E('button', { - 'id': 'btn_start', - 'name': 'btn_start', - 'class': btn_style_action, - }, _('Enable')); - layout_append(btn_start, _('Service')); + let btnStartStateOff = () => { + btn_start.onclick = ui.createHandlerFn( + this, this.serviceAction,'start', 'btn_start'); + btn_start.textContent = _('Disabled'); + btn_start.className = btn_style_negative; + } - let btn_enable = E('button', { - 'id': 'btn_enable', - 'name': 'btn_enable', - 'class': btn_style_save, - }, _('Enable')); - layout_append(btn_enable, _('Run at startup')); + if(app_status_code == 0) { + this.disableButtons(false, null, elems); + btnStartStateOn(); + btn_destroy.disabled = false; + btn_update.disabled = false; + if(btn_tp) { + btn_tp.disabled = false; + }; + } + else if(app_status_code == 2) { + this.disableButtons(false, null, elems); + btnStartStateOff(); + btn_update.disabled = true; + if(btn_tp) { + btn_tp.disabled = true; + }; + } + else if(app_status_code == 3) { + btnStartStateOff(); + this.disableButtons(true, btn_start, elems); + } + else if(app_status_code == 4) { + btnStartStateOn(); + this.disableButtons(true, btn_update, elems); + } + else { + ui.addNotification(null, E('p', _('Error') + + ' %s: return code = %s'.format(tools.execPath, app_status_code))); + this.disableButtons(true, null, elems); + }; - let btn_tp = E('button', { - 'id': 'btn_tp', - 'name': 'btn_tp', - 'class': btn_style_save, - }, _('Enable')); - if(proxy_local_clients == '0') { - layout_append(btn_tp, _('Total-proxy'), - _('All traffic goes through the proxy without applying rules')); - }; + (elems[0] || document.getElementById("status")).innerHTML = tools.makeStatusString( + app_status_code, + proxy_mode, + bllist_mode, + bllist_module, + bllist_source, + tp_status_code, + vpn_route_status_code); - let btn_update = E('button', { - 'id': 'btn_update', - 'name': 'btn_update', - 'class': btn_style_action, - }, _('Update')); - btn_update.onclick = ui.createHandlerFn(this, () => { button_action('update') }); - layout_append(btn_update, _('Update blacklist')); + if(!L.Poll.active()) { + L.Poll.start(); + }; + }, - let btn_destroy = E('button', { - 'id': 'btn_destroy', - 'name': 'btn_destroy', - 'class': btn_style_reset, - }, _('Shutdown')); - btn_destroy.onclick = this.dialog_destroy; + serviceAction: function(action, button) { + if(button) { + let elem = document.getElementById(button); + this.disableButtons(true, elem); + }; - layout_append(btn_destroy, _('Shutdown'), - _('Complete service shutdown, as well as deleting ipsets and blacklist data')); + L.Poll.stop(); - set_app_status(status_array, [ - status_string, - btn_start, - btn_enable, - btn_tp, - btn_update, - btn_destroy, - ]); + return tools.handleServiceAction(tools.appName, action).then(() => { + return this.getAppStatus().then( + (status_array) => { + this.setAppStatus(status_array); + } + ); + }); + }, - L.Poll.add(this.poll_status); + appAction: function(action, button) { + if(button) { + let elem = document.getElementById(button); + this.disableButtons(true, elem); + }; - return E([ - E('h2', { 'class': 'fade-in' }, _('Ruantiblock')), - E('div', { 'class': 'cbi-section-descr fade-in' }, - E('a', { - 'href': 'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki', - 'target': '_blank' }, - 'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki') - ), - E('div', { 'class': 'cbi-section fade-in' }, [ - status_string, - E('hr'), - ]), - E('div', { 'class': 'cbi-section fade-in' }, - layout - ), - ]); - }, + L.Poll.stop(); - handleSave: null, - handleSaveApply: null, - handleReset: null, + if(action === 'update') { + this.getAppStatus().then(status_array => { + this.setAppStatus(status_array, [], 4); + }); + }; + + return fs.exec_direct(tools.execPath, [ action ]).then(res => { + return this.getAppStatus().then( + (status_array) => { + this.setAppStatus(status_array); + ui.hideModal(); + } + ); + }); + }, + + statusPoll: function() { + return fs.read(tools.tokenFile).then(v => { + v = tools.normalizeValue(v); + if(v != this.statusTokenValue) { + this.getAppStatus().then( + L.bind(this.setAppStatus, this) + ); + } + this.statusTokenValue = v; + }).catch(e => { + this.statusTokenValue = 0; + }); + }, + + dialogDestroy: function(ev) { + ev.target.blur(); + let cancel_button = E('button', { + 'class': btn_style_neutral, + 'click': ui.hideModal, + }, _('Cancel')); + + let shutdown_btn = E('button', { + 'class': btn_style_warning, + }, _('Shutdown')); + shutdown_btn.onclick = ui.createHandlerFn(this, () => { + cancel_button.disabled = true; + return this.appAction('destroy'); + }); + + ui.showModal(_('Shutdown'), [ + E('div', { 'class': 'cbi-section' }, [ + E('p', _('The service will be disabled and all blacklist data will be deleted. Continue?')), + ]), + E('div', { 'class': 'right' }, [ + shutdown_btn, + ' ', + cancel_button, + ]) + ]); + }, + + load: function() { + return this.getAppStatus(); + }, + + render: function(status_array) { + if(!status_array) { + return; + }; + + let section = uci.get(tools.appName, 'config'); + let proxy_local_clients = (typeof(section) === 'object') ? + section.proxy_local_clients : null; + this.statusTokenValue = (Array.isArray(status_array)) ? + tools.normalizeValue(status_array[4]) : null; + + let status_string = E('div', { + 'id' : 'status', + 'name' : 'status', + 'class': 'cbi-section-node', + }); + + let layout = E('div', { 'class': 'cbi-section-node' }); + + function layout_append(elem, title, descr) { + descr = (descr) ? E('div', { 'class': 'cbi-value-description' }, descr) : ''; + layout.append( + E('div', { 'class': 'cbi-value' }, [ + E('label', { 'class': 'cbi-value-title' }, title), + E('div', { 'class': 'cbi-value-field' }, [ elem, descr ]), + ]) + ) + }; + + let btn_start = E('button', { + 'id' : 'btn_start', + 'name' : 'btn_start', + 'class': btn_style_action, + }, _('Enable')); + layout_append(btn_start, _('Service')); + + let btn_enable = E('button', { + 'id' : 'btn_enable', + 'name' : 'btn_enable', + 'class': btn_style_positive, + }, _('Enable')); + layout_append(btn_enable, _('Run at startup')); + + let btn_tp = E('button', { + 'id' : 'btn_tp', + 'name' : 'btn_tp', + 'class': btn_style_positive, + }, _('Enable')); + if(proxy_local_clients == '0') { + layout_append(btn_tp, _('Total-proxy'), + _('All traffic goes through the proxy without applying rules')); + }; + + let btn_update = E('button', { + 'id' : 'btn_update', + 'name' : 'btn_update', + 'class': btn_style_action, + }, _('Update')); + btn_update.onclick = ui.createHandlerFn(this, () => { this.appAction('update', 'btn_update') }); + layout_append(btn_update, _('Update blacklist')); + + let btn_destroy = E('button', { + 'id' : 'btn_destroy', + 'name' : 'btn_destroy', + 'class': btn_style_negative, + }, _('Shutdown')); + btn_destroy.onclick = L.bind(this.dialogDestroy, this); + + layout_append(btn_destroy, _('Shutdown'), + _('Complete service shutdown, as well as deleting ipsets and blacklist data')); + + this.setAppStatus(status_array, [ + status_string, + btn_start, + btn_enable, + btn_tp, + btn_update, + btn_destroy, + ]); + + L.Poll.add(L.bind(this.statusPoll, this)); + + return E([ + E('h2', { 'class': 'fade-in' }, _('Ruantiblock')), + E('div', { 'class': 'cbi-section-descr fade-in' }, + E('a', { + 'href': 'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki', + 'target': '_blank' }, + 'https://github.com/gSpotx2f/ruantiblock_openwrt/wiki') + ), + E('div', { 'class': 'cbi-section fade-in' }, [ + status_string, + E('hr'), + ]), + E('div', { 'class': 'cbi-section fade-in' }, + layout + ), + ]); + }, + + handleSave : null, + handleSaveApply: null, + handleReset : null, }); diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/settings.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/settings.js index c278fe3..3ffd645 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/settings.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/settings.js @@ -6,393 +6,354 @@ 'require tools.widgets as widgets'; 'require view.ruantiblock.tools as tools'; -let available_parsers = []; - -function depends(elem, key, array, empty=true) { - if(empty && array.length === 0) { - elem.depends(key, '_dummy'); - } else { - array.forEach(e => elem.depends(key, e)); - }; -}; - -function depends_bllist_module(elem) { - depends(elem, 'bllist_module', available_parsers); -}; - -function validate_ip_port(section, value) { - return (/^$|^([0-9]{1,3}\.){3}[0-9]{1,3}(#[\d]{2,5})?$/.test(value)) ? true : _('Expecting:') - + ` ${_('One of the following:')}\n - ${_('valid IP address')}\n - ${_('valid address#port')}\n`; -}; - -let CBIBlockTitle = form.DummyValue.extend({ - string: null, - - renderWidget: function(section_id, option_index, cfgvalue) { - this.title = this.description = null; - return E([ - E('label', { 'class': 'cbi-value-title' }), - E('div', { 'class': 'cbi-value-field' }, - E('b', {}, this.string) - ), - ]); - }, -}); - -let ip_filter_edit = new tools.file_edit_dialog( - tools.ip_filter_file, - _('IP filter'), - _('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol # in the first position of the line - comments on the line.
Examples (dot is a special character):') + '
128[.]199[.]0[.]0/16
34[.]217[.]90[.]52
162[.]13[.]190[.]
' -); - -let fqdn_filter_edit = new tools.file_edit_dialog( - tools.fqdn_filter_file, - _('FQDN filter'), - _('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol # in the first position of the line - comments on the line.
Examples:') + '
poker
[ck]?a[sz]ino?
[vw]ulkan
slots?
' -); - -let user_entries_edit = new tools.file_edit_dialog( - tools.user_entries_file, - _('User entries'), - _('One entry (IP, CIDR or FQDN) per line. In the FQDN records, you can specify the DNS server for resolving this domain (separated by a space). You can also comment on lines (# is the first character of a line).
Examples:') + '
#comment
domain.net
sub.domain.com 8.8.8.8
sub.domain.com 8.8.8.8#53
74.125.131.19
74.125.0.0/16
' -); - -let torrc_edit = new tools.file_edit_dialog( - tools.torrc_file, - _('Tor configuration file'), - null, - function(rc) { - return fs.exec('/etc/init.d/tor', [ 'enabled' ]).then(res => { - if(res.code === 0) { - return fs.exec('/etc/init.d/tor', [ 'restart' ]); - }; - }).catch(e => { - ui.addNotification(null, E('p', _('Unable to execute or read contents') - + ': %s
[ %s ]'.format(e.message, '/etc/init.d/tor'))); - }); - } -); - return L.view.extend({ - app_status_code: null, + availableParsers: {}, - load: function() { - return Promise.all([ - L.resolveDefault(fs.exec(tools.exec_path, [ 'raw-status' ]), 1), - fs.list(tools.parsers_dir), - uci.load('network'), - ]).catch(e => { - ui.addNotification(null, E('p', _('Unable to read the contents') - + ': %s
[ %s ]'.format( - e.message, tools.parsers_dir - ))); - }); - }, + appStatusCode : null, - render: function(data) { - if(!data) { - return; - }; - this.app_status_code = data[0].code; - let p_dir_arr = data[1]; - let lan_iface = uci.get('network', 'lan', 'ifname') || 'eth0'; - let vpn_iface = uci.get('network', 'VPN', 'ifname') || 'tun0'; + depends: function(elem, key, array, empty=true) { + if(empty && array.length === 0) { + elem.depends(key, '_dummy'); + } else { + array.forEach(e => elem.depends(key, e)); + }; + }, - if(p_dir_arr) { - p_dir_arr.forEach(e => { - let fname = e.name; - if(fname.startsWith('ruab_parser')) { - available_parsers.push(tools.parsers_dir + '/' + fname); - }; - }); - }; + dependsBllistModule: function(elem) { + this.depends(elem, 'bllist_module', Object.values(this.availableParsers)); + }, - let m, s, o; + validateIpPort: function(section, value) { + return (/^$|^([0-9]{1,3}\.){3}[0-9]{1,3}(#[\d]{2,5})?$/.test(value)) ? true : _('Expecting:') + + ` ${_('One of the following:')}\n - ${_('valid IP address')}\n - ${_('valid address#port')}\n`; + }, - m = new form.Map(tools.app_name, _('Ruantiblock') + ' - ' + _('Settings')); + CBIBlockTitle: form.DummyValue.extend({ + string: null, - s = m.section(form.NamedSection, 'config'); - s.anonymous = true; - s.addremove = false; + renderWidget: function(section_id, option_index, cfgvalue) { + this.title = this.description = null; + return E([ + E('label', { 'class': 'cbi-value-title' }), + E('div', { 'class': 'cbi-value-field' }, + E('b', {}, this.string) + ), + ]); + }, + }), - /* Main settings tab */ + load: function() { + return Promise.all([ + L.resolveDefault(fs.exec(tools.execPath, [ 'raw-status' ]), 1), + fs.list(tools.parsersDir), + uci.load('network'), + ]).catch(e => { + ui.addNotification(null, E('p', _('Unable to read the contents') + + ': %s [ %s ]'.format( + e.message, tools.parsersDir + ))); + }); + }, - s.tab('main_settings', _('Main settings')); + render: function(data) { + if(!data) { + return; + }; + this.appStatusCode = data[0].code; + let p_dir_arr = data[1]; + let lan_iface = uci.get('network', 'lan', 'ifname') || 'eth0'; + let vpn_iface = uci.get('network', 'VPN', 'ifname') || 'tun0'; - // PROXY_MODE - if(this.app_status_code == 1 || this.app_status_code == 2) { - o = s.taboption('main_settings', form.ListValue, 'proxy_mode', - _('Proxy mode')); - o.value('1', 'Tor'); - o.value('2', 'VPN'); - }; + if(p_dir_arr) { + p_dir_arr.forEach(e => { + let fname = e.name; + if(fname.startsWith('ruab_parser')) { + this.availableParsers[fname] = tools.parsersDir + '/' + fname; + }; + }); + }; - // PROXY_LOCAL_CLIENTS - let proxy_local_clients = s.taboption('main_settings', form.Flag, 'proxy_local_clients', - _("Apply proxy rules to router application traffic")); - proxy_local_clients.rmempty = false; - proxy_local_clients.default = proxy_local_clients.enabled; + let ip_filter_edit = new tools.fileEditDialog( + tools.ipFilterFile, + _('IP filter'), + _('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol # in the first position of the line - comments on the line.
Examples (dot is a special character):') + + '
128[.]199[.]0[.]0/16
34[.]217[.]90[.]52
162[.]13[.]190[.]
' + ); - // USE_LOGGER - o = s.taboption('main_settings', form.Flag, 'use_logger', - _('Logging events')); - o.rmempty = false; - o.default = 1; + let fqdn_filter_edit = new tools.fileEditDialog( + tools.fqdnFilterFile, + _('FQDN filter'), + _('Patterns can be strings or regular expressions. Each pattern in a separate line, the symbol # in the first position of the line - comments on the line.
Examples:') + + '
poker
[ck]?a[sz]ino?
[vw]ulkan
slots?
' + ); - // DEF_TOTAL_PROXY - o = s.taboption('main_settings', form.Flag, 'def_total_proxy', - _("Enable the 'total-proxy' option at startup")); - o.rmempty = false; - o.default = 0; - o.depends('proxy_local_clients', '0'); + let user_entries_edit = new tools.fileEditDialog( + tools.userEntriesFile, + _('User entries'), + _('One entry (IP, CIDR or FQDN) per line. In the FQDN records, you can specify the DNS server for resolving this domain (separated by a space). You can also comment on lines (# is the first character of a line).
Examples:') + + '
#comment
domain.net
sub.domain.com 8.8.8.8
sub.domain.com 8.8.8.8#53
74.125.131.19
74.125.0.0/16
' + ); - // IPSET_CLEAR_SETS - o = s.taboption('main_settings', form.Flag, 'ipset_clear_sets', - _('Clean up ipsets before updating blacklist')); - o.description = _('Reduces RAM consumption during update'); - o.rmempty = false; - o.default = 0; + let torrc_edit = new tools.fileEditDialog( + tools.torrcFile, + _('Tor configuration file'), + null, + function(rc) { + return tools.getInitStatus('tor').then(res => { + if(res) { + return tools.handleServiceAction('tor', 'restart'); + }; + }); + } + ); + + let m, s, o; + + m = new form.Map(tools.appName, _('Ruantiblock') + ' - ' + _('Settings')); + + s = m.section(form.NamedSection, 'config'); + s.anonymous = true; + s.addremove = false; + + /* Main settings tab */ + + s.tab('main_settings', _('Main settings')); + + // PROXY_MODE + if(this.appStatusCode == 1 || this.appStatusCode == 2) { + o = s.taboption('main_settings', form.ListValue, 'proxy_mode', + _('Proxy mode')); + o.value('1', 'Tor'); + o.value('2', 'VPN'); + }; + + // PROXY_LOCAL_CLIENTS + let proxy_local_clients = s.taboption('main_settings', form.Flag, 'proxy_local_clients', + _("Apply proxy rules to router application traffic")); + proxy_local_clients.rmempty = false; + + // USE_LOGGER + o = s.taboption('main_settings', form.Flag, 'use_logger', + _('Logging events')); + o.rmempty = false; + + // DEF_TOTAL_PROXY + o = s.taboption('main_settings', form.Flag, 'def_total_proxy', + _("Enable the 'total-proxy' option at startup")); + o.rmempty = false; + o.default = 0; + o.depends('proxy_local_clients', '0'); + + // IPSET_CLEAR_SETS + o = s.taboption('main_settings', form.Flag, 'ipset_clear_sets', + _('Clean up ipsets before updating blacklist')); + o.description = _('Reduces RAM consumption during update'); + o.rmempty = false; - if(this.app_status_code == 1 || this.app_status_code == 2) { - /* Tor tab */ + if(this.appStatusCode == 1 || this.appStatusCode == 2) { + /* Tor tab */ - s.tab('tor_settings', _('Tor mode')); + s.tab('tor_settings', _('Tor mode')); - // IF_LAN - o = s.taboption('tor_settings', widgets.DeviceSelect, 'if_lan', - _('LAN interface')); - o.multiple = false; - o.noaliases = true; - o.rmempty = false; - o.default = lan_iface; + // IF_LAN + o = s.taboption('tor_settings', widgets.DeviceSelect, 'if_lan', + _('LAN interface')); + o.multiple = false; + o.noaliases = true; + o.rmempty = false; + o.default = lan_iface; - // TOR_TRANS_PORT - o = s.taboption('tor_settings', form.Value, 'tor_trans_port', - _('Transparent proxy port for iptables rules')); - o.rmempty = false; - o.datatype = "port"; - o.default = '9040'; + // TOR_TRANS_PORT + o = s.taboption('tor_settings', form.Value, 'tor_trans_port', + _('Transparent proxy port for iptables rules')); + o.rmempty = false; + o.datatype = "port"; - // ONION_DNS_ADDR - o = s.taboption('tor_settings', form.Value, 'onion_dns_addr', - _("Optional DNS resolver for '.onion' zone"), 'ipaddress#port'); - o.rmempty = false; - o.default = '127.0.0.1#9053'; - o.validate = validate_ip_port; + // ONION_DNS_ADDR + o = s.taboption('tor_settings', form.Value, 'onion_dns_addr', + _("Optional DNS resolver for '.onion' zone"), 'ipaddress#port'); + o.rmempty = false; + o.validate = this.validateIpPort; - // Torrc edit dialog - o = s.taboption('tor_settings', form.Button, '_torrc_btn', - _('Tor configuration file')); - o.onclick = () => torrc_edit.show(); - o.inputtitle = _('Edit'); - o.inputstyle = 'edit btn'; + // Torrc edit dialog + o = s.taboption('tor_settings', form.Button, '_torrc_btn', + _('Tor configuration file')); + o.onclick = () => torrc_edit.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; - /* VPN tab */ + /* VPN tab */ - s.tab('vpn_settings', _('VPN mode')); + s.tab('vpn_settings', _('VPN mode')); - // IF_VPN - o = s.taboption('vpn_settings', widgets.DeviceSelect, 'if_vpn', - _('VPN interface')); - o.multiple = false; - o.noaliases = true; - o.rmempty = false; - o.default = vpn_iface; - }; + // IF_VPN + o = s.taboption('vpn_settings', widgets.DeviceSelect, 'if_vpn', + _('VPN interface')); + o.multiple = false; + o.noaliases = true; + o.rmempty = false; + o.default = vpn_iface; + }; - /* Parser settings tab */ + /* Parser settings tab */ - s.tab('parser_settings', _('Blacklist settings')); + s.tab('parser_settings', _('Blacklist settings')); - // BLLIST_MODULE - let bllist_module = s.taboption('parser_settings', form.ListValue, - 'bllist_module', _('Blacklist module')); - bllist_module.value("", _("user entries only")); - available_parsers.forEach(e => bllist_module.value(e)); + // BLLIST_MODULE + let bllist_module = s.taboption('parser_settings', form.ListValue, + 'bllist_module', _('Blacklist module')); + bllist_module.value('', _('none (user entries only)')); + Object.entries(this.availableParsers).forEach( + e => bllist_module.value(e[1], e[0])); - // BLLIST_MODE - let bllist_mode = s.taboption('parser_settings', form.ListValue, - 'bllist_mode', _('Module operation mode')); - bllist_mode.value('ip'); - bllist_mode.value('fqdn'); - depends_bllist_module(bllist_mode); + // BLLIST_MODE + let bllist_mode = s.taboption('parser_settings', form.ListValue, + 'bllist_mode', _('Module operation mode')); + bllist_mode.value('ip'); + bllist_mode.value('fqdn'); - // BLLIST_SOURCE - let bllist_source = s.taboption('parser_settings', form.ListValue, - 'bllist_source', _('Blacklist source')); - bllist_source.description = _("Options") + ':'; - for(let [k, v] of Object.entries(tools.blacklist_sources)) { - bllist_source.value(k); - bllist_source.description += `
${k} - ${v}`; - }; - depends_bllist_module(bllist_source); + // BLLIST_SOURCE + let bllist_source = s.taboption('parser_settings', form.ListValue, + 'bllist_source', _('Blacklist source')); + bllist_source.description = _("Options") + ':'; + for(let [k, v] of Object.entries(tools.blacklistSources)) { + bllist_source.value(k); + bllist_source.description += `
${k} - ${v}`; + }; - o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_ip'); - o.string = _('IP configuration') + ':'; - depends_bllist_module(o); + o = s.taboption('parser_settings', this.CBIBlockTitle, '_dummy_ip'); + o.string = _('IP configuration') + ':'; - // IP_LIMIT - o = s.taboption('parser_settings', form.Value, 'ip_limit', _("IP limit")); - o.description = _("The number of IP addresses in the subnet, upon reaching which the entire '/24' subnet is added to the list"); - o.datatype = 'uinteger'; - o.default = '0'; - depends_bllist_module(o); + // IP_LIMIT + o = s.taboption('parser_settings', form.Value, 'ip_limit', _("IP limit")); + o.description = _("The number of IP addresses in the subnet, upon reaching which the entire '/24' subnet is added to the list"); + o.rmempty = false; + o.datatype = 'uinteger'; - // OPT_EXCLUDE_NETS - o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_nets'); - o.title = _('IP subnet patterns (/24) that are excluded from optimization'); - o.description = _('e.g:') + ' 192.168.1.'; - o.placeholder = _('e.g:') + ' 192.168.1.'; - o.default = ''; + // OPT_EXCLUDE_NETS + o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_nets'); + o.title = _('IP subnet patterns (/24) that are excluded from optimization'); + o.description = _('e.g:') + ' 192.168.1.'; + o.placeholder = _('e.g:') + ' 192.168.1.'; + o.validate = (section, value) => { + return (/^$|^([0-9]{1,3}[.]){3}$/.test(value)) ? true : _('Expecting:') + + ' ' + _('net pattern') + ' (' + _('e.g:') + ' 192.168.3.)\n'; + }; - depends_bllist_module(o); + // SUMMARIZE_IP + o = s.taboption('parser_settings', form.Flag, 'summarize_ip', + _("Summarize IP ranges")); + o.rmempty = false; - // SUMMARIZE_IP - o = s.taboption('parser_settings', form.Flag, 'summarize_ip', - _("Summarize IP ranges")); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); + // SUMMARIZE_CIDR + o = s.taboption('parser_settings', form.Flag, 'summarize_cidr', + _("Summarize '/24' networks")); + o.rmempty = false; - // SUMMARIZE_CIDR - o = s.taboption('parser_settings', form.Flag, 'summarize_cidr', - _("Summarize '/24' networks")); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); + o = s.taboption('parser_settings', this.CBIBlockTitle, '_dummy_fqdn'); + o.string = _('FQDN configuration') + ':'; - o = s.taboption('parser_settings', CBIBlockTitle, '_dummy_fqdn'); - o.string = _('FQDN configuration') + ':'; - depends_bllist_module(o); + // SD_LIMIT + o = s.taboption('parser_settings', form.Value, 'sd_limit', + _("Subdomains limit")); + o.description = _('The number of subdomains in the domain, upon reaching which the entire 2nd level domain is added to the list'); + o.rmempty = false; + o.datatype = 'uinteger'; - // SD_LIMIT - o = s.taboption('parser_settings', form.Value, 'sd_limit', - _("Subdomains limit")); - o.description = _('The number of subdomains in the domain, upon reaching which the entire 2nd level domain is added to the list'); - o.datatype = 'uinteger'; - o.default = '16'; - depends_bllist_module(o); + // OPT_EXCLUDE_SLD + o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_sld', + _('2nd level domains that are excluded from optimization')); + o.description = _('e.g:') + ' livejournal.com'; + o.placeholder = _('e.g:') + ' livejournal.com'; + o.datatype = "hostname"; - // OPT_EXCLUDE_SLD - o = s.taboption('parser_settings', form.DynamicList, 'opt_exclude_sld', - _('2nd level domains that are excluded from optimization')); - o.datatype = "hostname"; - o.default = [ - 'livejournal.com', - 'facebook.com', - 'vk.com', - 'blog.jp', - 'msk.ru', - 'net.ru', - 'org.ru', - 'net.ua', - 'com.ua', - 'org.ua', - 'co.uk', - 'amazonaws.com', - ]; - depends_bllist_module(o); + // USE_IDN + o = s.taboption('parser_settings', form.Flag, 'use_idn', + _("Convert cyrillic domains to punycode")); + o.rmempty = false; - // USE_IDN - o = s.taboption('parser_settings', form.Flag, 'use_idn', - _("Convert cyrillic domains to punycode")); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); + // ALT_NSLOOKUP + o = s.taboption('parser_settings', form.Flag, 'alt_nslookup', + _('Use optional DNS resolver')); + o.rmempty = false; - // ALT_NSLOOKUP - o = s.taboption('parser_settings', form.Flag, 'alt_nslookup', - _('Use optional DNS resolver')); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); - - // ALT_DNS_ADDR - o = s.taboption('parser_settings', form.Value, 'alt_dns_addr', - _("Optional DNS resolver"), 'ipaddress[#port]'); - o.rmempty = false; - o.depends('alt_nslookup', '1'); - o.validate = validate_ip_port; - o.default = '8.8.8.8'; + // ALT_DNS_ADDR + o = s.taboption('parser_settings', form.Value, 'alt_dns_addr', + _("Optional DNS resolver"), 'ipaddress[#port]'); + o.rmempty = false; + o.validate = this.validateIpPort; - /* Entries filters tab */ + /* Blacklist entry filters tab */ - s.tab('entries_filter_tab', _('Entries filters')); + s.tab('entries_filter_tab', _('Blacklist entry filters')); - // IP_FILTER - o = s.taboption('entries_filter_tab', form.Flag, 'ip_filter', - _("Enable IP filter")); - o.description = _('Exclude IP addresses from blacklist by IP filter patterns'); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); + // IP_FILTER + o = s.taboption('entries_filter_tab', form.Flag, 'ip_filter', + _("Enable IP filter")); + o.description = _('Exclude IP addresses from blacklist by IP filter patterns'); + o.rmempty = false; - // IP_FILTER edit dialog - o = s.taboption('entries_filter_tab', form.Button, '_ip_filter_btn', - _("IP filter")); - o.onclick = () => ip_filter_edit.show(); - o.inputtitle = _('Edit'); - o.inputstyle = 'edit btn'; - depends_bllist_module(o); + // IP_FILTER edit dialog + o = s.taboption('entries_filter_tab', form.Button, '_ip_filter_btn', + _("IP filter")); + o.onclick = () => ip_filter_edit.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; - // FQDN_FILTER - o = s.taboption('entries_filter_tab', form.Flag, 'fqdn_filter', - _("Enable FQDN filter")); - o.description = _('Exclude domains from blacklist by FQDN filter patterns'); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); + // FQDN_FILTER + o = s.taboption('entries_filter_tab', form.Flag, 'fqdn_filter', + _("Enable FQDN filter")); + o.description = _('Exclude domains from blacklist by FQDN filter patterns'); + o.rmempty = false; - // FQDN_FILTER edit dialog - o = s.taboption('entries_filter_tab', form.Button, '_fqdn_filter_btn', - _("FQDN filter")); - o.onclick = () => fqdn_filter_edit.show(); - o.inputtitle = _('Edit'); - o.inputstyle = 'edit btn'; - depends_bllist_module(o); + // FQDN_FILTER edit dialog + o = s.taboption('entries_filter_tab', form.Button, '_fqdn_filter_btn', + _("FQDN filter")); + o.onclick = () => fqdn_filter_edit.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; - /* User entries tab */ + /* User entries tab */ - s.tab('user_entries_tab', _('User entries')); + s.tab('user_entries_tab', _('User entries')); - // ADD_USER_ENTRIES - o = s.taboption('user_entries_tab', form.Flag, 'add_user_entries', - _('Enable'), _("Add user entries to the blacklist when updating")); - o.rmempty = false; - o.default = 0; - depends_bllist_module(o); + // ADD_USER_ENTRIES + o = s.taboption('user_entries_tab', form.Flag, 'add_user_entries', + _('Enable'), _("Add user entries to the blacklist when updating")); + o.rmempty = false; + o.default = 0; + this.dependsBllistModule(o); - // USER_ENTRIES_DNS - o = s.taboption('user_entries_tab', form.Value, 'user_entries_dns', - _("DNS server that is used for FQDN entries"), 'ipaddress[#port]'); - o.validate = validate_ip_port; + // USER_ENTRIES_DNS + o = s.taboption('user_entries_tab', form.Value, 'user_entries_dns', + _("DNS server that is used for FQDN entries"), 'ipaddress[#port]'); + o.validate = this.validateIpPort; - // USER_ENTRIES edit dialog - o = s.taboption('user_entries_tab', form.Button, '_user_entries_btn', - _('User entries')); - o.onclick = () => user_entries_edit.show(); - o.inputtitle = _('Edit'); - o.inputstyle = 'edit btn'; + // USER_ENTRIES edit dialog + o = s.taboption('user_entries_tab', form.Button, '_user_entries_btn', + _('User entries')); + o.onclick = () => user_entries_edit.show(); + o.inputtitle = _('Edit'); + o.inputstyle = 'edit btn'; + let map_promise = m.render(); + map_promise.then(node => node.classList.add('fade-in')); + return map_promise; + }, - let map_promise = m.render(); - map_promise.then(node => node.classList.add('fade-in')); + handleSaveApply: function(ev, mode) { + return this.handleSave(ev).then(() => { + ui.changes.apply(mode == '0'); - return map_promise; - }, - - handleSaveApply: function(ev, mode) { - return this.handleSave(ev).then(() => { - ui.changes.apply(mode == '0'); - - if(this.app_status_code != 1 && this.app_status_code != 2) { - window.setTimeout(() => fs.exec(tools.init_path, [ 'restart' ]), 3000); - }; - }); - }, + if(this.appStatusCode != 1 && this.appStatusCode != 2) { + window.setTimeout(() => tools.handleServiceAction( + tools.appName, 'restart'), 3000); + }; + }); + }, }); diff --git a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/tools.js b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/tools.js index 48444c4..bc5896c 100644 --- a/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/tools.js +++ b/luci-app-ruantiblock/htdocs/luci-static/resources/view/ruantiblock/tools.js @@ -1,252 +1,291 @@ 'use strict'; 'require fs'; +'require rpc'; 'require ui'; document.head.append(E('style', {'type': 'text/css'}, ` .label-status { - display: inline; - margin: 0px 2px 0px 0 !important; - padding: 1px 4px 2px 4px; - -webkit-border-radius: 3px; - -moz-border-radius: 3px; - border-radius: 3px; - font-weight: bold; - color: #fff !important; + display: inline; + margin: 0px 2px 0px 0 !important; + padding: 1px 4px 2px 4px; + -webkit-border-radius: 3px; + -moz-border-radius: 3px; + border-radius: 3px; + font-weight: bold; + color: #fff !important; } .starting { - background-color: #a7b668 !important; + background-color: #a7b668 !important; } .running { - background-color: #2ea256 !important; + background-color: #2ea256 !important; } .updating { - background-color: #1e82ff !important; + background-color: #1e82ff !important; } .stopped { - background-color: #acacac !important; + background-color: #acacac !important; } .error { - background-color: #ff4e54 !important; + background-color: #ff4e54 !important; } .total-proxy { - background-color: #ffb937 !important; + 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: '' + _('Starting') + '', - info_label_running: '' + _('Enabled') + '', - info_label_updating: '' + _('Updating') + '', - info_label_stopped: '' + _('Disabled') + '', - info_label_error: '' + _('Error') + '', + appName : 'ruantiblock', + execPath : '/usr/bin/ruantiblock', + tokenFile : '/var/run/ruantiblock.token', + parsersDir : '/usr/bin', + torrcFile : '/etc/tor/torrc', + userEntriesFile : '/etc/ruantiblock/user_entries', + fqdnFilterFile : '/etc/ruantiblock/fqdn_filter', + ipFilterFile : '/etc/ruantiblock/ip_filter', + crontabFile : '/etc/crontabs/root', + infoLabelStarting: '' + _('Starting') + '', + infoLabelRunning : '' + _('Enabled') + '', + infoLabelUpdating: '' + _('Updating') + '', + infoLabelStopped : '' + _('Disabled') + '', + infoLabelError : '' + _('Error') + '', - blacklist_sources: { - 'rublacklist': 'https://rublacklist.net', - 'zapret-info': 'https://github.com/zapret-info/z-i', - 'antifilter': 'https://antifilter.download', - }, + blacklistSources: { + '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; - }, + callInitStatus: rpc.declare({ + object: 'luci', + method: 'getInitList', + params: [ 'name' ], + expect: { '': {} } + }), - make_status_string: function( - app_status_code, - proxy_mode, - bllist_mode, - bllist_module, - bllist_source, - tp_status_code, - vpn_route_status_code) { - let app_status_label; - let spinning = ''; + callInitAction: rpc.declare({ + object: 'luci', + method: 'setInitAction', + params: [ 'name', 'action' ], + expect: { result: false } + }), - switch(app_status_code) { - case 0: - app_status_label = this.info_label_running; - break; - case 2: - app_status_label = this.info_label_stopped; - break; - case 3: - app_status_label = this.info_label_starting; - spinning = ' spinning'; - break; - case 4: - app_status_label = this.info_label_updating; - spinning = ' spinning'; - break; - default: - app_status_label = this.info_label_error; - return `
-
-
- ${_('Status')}: -
-
- ${app_status_label} -
-
-
` - }; + getInitStatus: function(name) { + return this.callInitStatus(name).then(res => { + if(res) { + return res[name].enabled; + } else { + throw _('Command failed'); + } + }).catch(e => { + ui.addNotification(null, + E('p', _('Failed to get %s init status: %s').format(name, e))); + }); + }, - return `
-
-
- ${_('Status')}: -
-
- %s %s %s -
-
-
-
- ${_('Proxy mode')}: -
-
- %s -
-
-
-
- ${_('Blacklist update mode')}: -
-
- %s -
-
- %s -
- `.format( - spinning, - app_status_label, - (tp_status_code == 0) ? '' - + _('Total-proxy is on') + '' : '', - (app_status_code != 2 && proxy_mode == 2 && vpn_route_status_code != 0) - ? '' - + _('VPN routing error! Need restart') + '' : '', - (proxy_mode == 1) ? 'Tor' : 'VPN', - (!bllist_module || bllist_module === '') ? _('user entries only') : bllist_mode, - (!bllist_module || bllist_module === '') ? '' : - `
-
- ${_('Blacklist source')}: -
-
- - ${bllist_source} - -
-
` - ); - }, + handleServiceAction: function(name, action) { + return this.callInitAction(name, action).then(success => { + if(!success) { + throw _('Command failed'); + }; + return true; + }).catch(e => { + ui.addNotification(null, + E('p', _('Service action failed "%s %s": %s').format(name, action, e))); + }); + }, - file_edit_dialog: L.Class.extend({ - __init__: function(file, title, description, callback, file_exists=false) { - this.file = file; - this.title = title; - this.description = description; - this.callback = callback; - this.file_exists = file_exists; - }, + normalizeValue: function(v) { + return (v && typeof(v) === 'string') ? v.trim().replace(/\r?\n/g, '') : v; + }, - load: function() { - return fs.read(this.file); - }, + makeStatusString: function( + app_status_code, + proxy_mode, + bllist_mode, + bllist_module, + bllist_source, + tp_status_code, + vpn_route_status_code) { + let app_status_label; + let spinning = ''; - render: function(content) { - ui.showModal(this.title, [ - E('div', { 'class': 'cbi-section' }, [ - E('div', { 'class': 'cbi-section-descr' }, this.description), - E('div', { 'class': 'cbi-section' }, - E('p', {}, - E('textarea', { - 'id': 'widget.modal_content', - 'class': 'cbi-input-textarea', - 'style': 'width:100% !important', - 'rows': 10, - 'wrap': 'off', - 'spellcheck': 'false', - }, - content || '') - ) - ), - ]), - E('div', { 'class': 'right' }, [ - E('button', { - 'class': 'btn', - 'click': ui.hideModal, - }, _('Dismiss')), - ' ', - E('button', { - 'id': 'btn_save', - 'class': 'btn cbi-button-positive important', - 'click': ui.createHandlerFn(this, this.handleSave), - }, _('Save')), - ]), - ]); - }, + switch(app_status_code) { + case 0: + app_status_label = this.infoLabelRunning; + break; + case 2: + app_status_label = this.infoLabelStopped; + break; + case 3: + app_status_label = this.infoLabelStarting; + spinning = ' spinning'; + break; + case 4: + app_status_label = this.infoLabelUpdating; + spinning = ' spinning'; + break; + default: + app_status_label = this.infoLabelError; + return `
+
+
+ ${_('Status')}: +
+
+ ${app_status_label} +
+
+
` + }; - handleSave: function(ev) { - let textarea = document.getElementById('widget.modal_content'); - let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n'; + return `
+
+
+ ${_('Status')}: +
+
+ %s %s %s +
+
+
+
+ ${_('Proxy mode')}: +
+
+ %s +
+
+
+
+ ${_('Blacklist update mode')}: +
+
+ %s +
+
+ %s +
+ `.format( + spinning, + app_status_label, + (tp_status_code == 0) ? '' + + _('Total-proxy is on') + '' : '', + (app_status_code != 2 && proxy_mode == 2 && vpn_route_status_code != 0) + ? '' + + _('VPN routing error! Need restart') + '' : '', + (proxy_mode == 1) ? 'Tor' : 'VPN', + (!bllist_module || bllist_module === '') ? _('user entries only') : bllist_mode, + (!bllist_module || bllist_module === '') ? '' : + `
+
+ ${_('Blacklist source')}: +
+
+ + ${bllist_source} + +
+
` + ); + }, - return fs.write(this.file, value).then(async rc => { - textarea.value = value; - ui.addNotification(null, E('p', _('Contents have been saved.')), - 'info'); - if(this.callback) { - return this.callback(rc); - }; - }).catch(e => { - ui.addNotification(null, E('p', _('Unable to save the contents') - + ': %s'.format(e.message))); - }).finally(() => { - ui.hideModal(); - }); - }, + fileEditDialog: L.Class.extend({ + __init__: function(file, title, description, callback, file_exists=false) { + this.file = file; + this.title = title; + this.description = description; + this.callback = callback; + this.file_exists = file_exists; + }, - error: function(e) { - if(!this.file_exists && e instanceof Error && e.name === 'NotFoundError') { - return this.render(); - } else { - ui.showModal(this.title, [ - E('div', { 'class': 'cbi-section' }, - E('p', {}, _('Unable to read the contents') - + ': %s'.format(e.message)) - ), - E('div', { 'class': 'right' }, - E('button', { - 'class': 'btn', - 'click': ui.hideModal, - }, _('Dismiss')) - ), - ]); - }; - }, + load: function() { + return fs.read(this.file); + }, - show: function() { - ui.showModal(null, - E('p', { 'class': 'spinning' }, _('Loading')) - ); - this.load().then(content => { - ui.hideModal(); - return this.render(content); - }).catch(e => { - ui.hideModal(); - return this.error(e); - }) - }, - }), + render: function(content) { + ui.showModal(this.title, [ + E('div', { 'class': 'cbi-section' }, [ + E('div', { 'class': 'cbi-section-descr' }, this.description), + E('div', { 'class': 'cbi-section' }, + E('p', {}, + E('textarea', { + 'id': 'widget.modal_content', + 'class': 'cbi-input-textarea', + 'style': 'width:100% !important', + 'rows': 10, + 'wrap': 'off', + 'spellcheck': 'false', + }, + content || '') + ) + ), + ]), + E('div', { 'class': 'right' }, [ + E('button', { + 'class': 'btn', + 'click': ui.hideModal, + }, _('Dismiss')), + ' ', + E('button', { + 'id': 'btn_save', + 'class': 'btn cbi-button-positive important', + 'click': ui.createHandlerFn(this, this.handleSave), + }, _('Save')), + ]), + ]); + }, + + handleSave: function(ev) { + let textarea = document.getElementById('widget.modal_content'); + let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n'; + + return fs.write(this.file, value).then(async rc => { + textarea.value = value; + ui.addNotification(null, E('p', _('Contents have been saved.')), + 'info'); + if(this.callback) { + return this.callback(rc); + }; + }).catch(e => { + ui.addNotification(null, E('p', _('Unable to save the contents') + + ': %s'.format(e.message))); + }).finally(() => { + ui.hideModal(); + }); + }, + + error: function(e) { + if(!this.file_exists && e instanceof Error && e.name === 'NotFoundError') { + return this.render(); + } else { + ui.showModal(this.title, [ + E('div', { 'class': 'cbi-section' }, + E('p', {}, _('Unable to read the contents') + + ': %s'.format(e.message)) + ), + E('div', { 'class': 'right' }, + E('button', { + 'class': 'btn', + 'click': ui.hideModal, + }, _('Dismiss')) + ), + ]); + }; + }, + + show: function() { + ui.showModal(null, + E('p', { 'class': 'spinning' }, _('Loading')) + ); + this.load().then(content => { + ui.hideModal(); + return this.render(content); + }).catch(e => { + ui.hideModal(); + return this.error(e); + }) + }, + }), }); diff --git a/luci-app-ruantiblock/po/ru/ruantiblock.po b/luci-app-ruantiblock/po/ru/ruantiblock.po index da57d17..da8b4ab 100644 --- a/luci-app-ruantiblock/po/ru/ruantiblock.po +++ b/luci-app-ruantiblock/po/ru/ruantiblock.po @@ -19,6 +19,12 @@ msgstr "Домены 2-го уровня не подлежащие оптими msgid "Add user entries to the blacklist when updating" msgstr "Добавлять записи пользователя в блэклист при обновлении" +msgid "Alert" +msgstr "Тревога" + +msgid "All" +msgstr "Все" + msgid "All traffic goes through the proxy without applying rules" msgstr "Весь трафик отправляется в прокси, без применения правил" @@ -28,6 +34,9 @@ msgstr "Применить" msgid "Apply proxy rules to router application traffic" msgstr "Применять правила прокси к трафику приложений роутера" +msgid "Blacklist entry filters" +msgstr "Фильтры записей блэклиста" + msgid "Blacklist module" msgstr "Модуль блэклиста" @@ -55,10 +64,12 @@ msgstr "Изменения сохранены." msgid "Clean up ipsets before updating blacklist" msgstr "Очищать ipset'ы перед обновлением блэклиста" +msgid "Command failed" +msgstr "Команда не выполнена" + msgid "" "Complete service shutdown, as well as deleting ipsets and blacklist data" -msgstr "" -"Полное выключение службы, а также удаление ipset'ов и данных блэклиста" +msgstr "Полное выключение службы, а также удаление ipset'ов и данных блэклиста" msgid "Contents have been saved." msgstr "Содержимое сохранено." @@ -66,6 +77,9 @@ msgstr "Содержимое сохранено." msgid "Convert cyrillic domains to punycode" msgstr "Конвертировать кириллические домены в punycode" +msgid "Critical" +msgstr "Критическая ситуация" + msgid "Current schedule" msgstr "Текущее расписание" @@ -75,18 +89,27 @@ msgstr "DNS сервер для FQDN записей" msgid "Day" msgstr "День" -msgid "Disable" -msgstr "Отключить" +msgid "Debug" +msgstr "Отладка" msgid "Disabled" msgstr "Отключено" msgid "Dismiss" -msgstr "Закрыть" +msgstr "Отмена" + +msgid "Download error" +msgstr "Ошибка загрузки" + +msgid "Download log" +msgstr "Скачать лог" msgid "Edit" msgstr "Изменить" +msgid "Emergency" +msgstr "Чрезвычайная ситуация" + msgid "Enable" msgstr "Включить" @@ -102,18 +125,18 @@ msgstr "Включать 'total-proxy' при старте" msgid "Enabled" msgstr "Включено" -msgid "Entries filters" -msgstr "Фильтры записей" +msgid "Entries" +msgstr "Записи" msgid "Error" msgstr "Ошибка" -msgid "Exclude domains from blacklist by FQDN filter patterns" -msgstr "Исключение доменов из блэклиста по шаблонам фильтра FQDN" - msgid "Exclude IP addresses from blacklist by IP filter patterns" msgstr "Исключение IP адресов из блэклиста по шаблонам фильтра IP" +msgid "Exclude domains from blacklist by FQDN filter patterns" +msgstr "Исключение доменов из блэклиста по шаблонам фильтра FQDN" + msgid "Expecting:" msgstr "Ожидается:" @@ -123,12 +146,24 @@ msgstr "Конфигурация FQDN" msgid "FQDN filter" msgstr "Фильтр FQDN" +msgid "Facility" +msgstr "Категория" + +msgid "Failed to get %s init status: %s" +msgstr "Не удалось получить статус инициализации %s: %s" + +msgid "HTML/XML error" +msgstr "Ошибка HTML/XML" + +msgid "Host" +msgstr "Хост" + +msgid "Hosts" +msgstr "Хосты" + msgid "Hour" msgstr "Час" -msgid "Interval" -msgstr "Интервал" - msgid "IP configuration" msgstr "Конфигурация IP" @@ -141,6 +176,15 @@ msgstr "Лимит IP адресов" msgid "IP subnet patterns (/24) that are excluded from optimization" msgstr "Шаблоны IP подсетей (/24) не подлежащих оптимизации" +msgid "Info" +msgstr "Информация" + +msgid "Interval" +msgstr "Интервал" + +msgid "Invalid regular expression" +msgstr "Неправильное регулярное выражение" + msgid "Ipset" msgstr "Ipset" @@ -153,6 +197,12 @@ msgstr "LAN интерфейс" msgid "Last blacklist update" msgstr "Последнее обновление блэклиста" +msgid "Last entries" +msgstr "Последние записи" + +msgid "Level" +msgstr "Уровень" + msgid "Loading" msgstr "Загрузка" @@ -162,12 +212,21 @@ msgstr "Лог" msgid "Logging events" msgstr "Записывать события в лог" +msgid "Logging levels" +msgstr "Уровни логирования" + msgid "Main settings" msgstr "Основные настройки" msgid "Match-set" msgstr "Правило" +msgid "Message" +msgstr "Сообщение" + +msgid "Message filter" +msgstr "Фильтр сообщений" + msgid "Minute" msgstr "Минута" @@ -177,14 +236,20 @@ msgstr "Режим работы модуля" msgid "Name" msgstr "Имя" +msgid "No Shedule" +msgstr "Нет расписания" + msgid "No changes to save." msgstr "Нет изменений для сохранения." msgid "No data" msgstr "Нет данных" -msgid "No Shedule" -msgstr "Нет расписания" +msgid "No entries available..." +msgstr "Нет доступных записей..." + +msgid "Notice" +msgstr "Сообщение" msgid "Number of entries" msgstr "Кол-во записей" @@ -202,15 +267,12 @@ msgstr "" msgid "One of the following:" msgstr "Одно из следующих значений:" -msgid "Only messages that include the specified string will be displayed" -msgstr "Будут показаны только сообщения включающие указанную строку" +msgid "Optional DNS resolver" +msgstr "Альтернативный DNS резолвер" msgid "Optional DNS resolver for '.onion' zone" msgstr "Дополнительный DNS резолвер для '.onion' зоны" -msgid "Optional DNS resolver" -msgstr "Альтернативный DNS резолвер" - msgid "Options" msgstr "Опции" @@ -238,15 +300,27 @@ msgstr "Режим прокси" msgid "Reduces RAM consumption during update" msgstr "Уменьшает потребление оперативной памяти при обновлении" +msgid "Refresh log" +msgstr "Обновить лог" + msgid "Reset" msgstr "Сбросить" +msgid "Ruantiblock" +msgstr "Ruantiblock" + msgid "Run at startup" msgstr "Запуск при старте системы" +msgid "Save" +msgstr "Сохранить" + msgid "Service" msgstr "Служба" +msgid "Service action failed \"%s %s\": %s" +msgstr "Не удалось выполнить действие службы \"%s %s\": %s" + msgid "Set" msgstr "Установить" @@ -259,6 +333,9 @@ msgstr "Выключение" msgid "Size in memory" msgstr "Размер в памяти" +msgid "Sorting entries" +msgstr "Сортировка записей" + msgid "Starting" msgstr "Запускается" @@ -300,21 +377,33 @@ msgstr "" msgid "Time" msgstr "Время" +msgid "Timestamp" +msgstr "Время" + msgid "Tor configuration file" msgstr "Конфигурационный файл Tor" msgid "Tor mode" msgstr "Режим Tor" +msgid "Total-proxy" +msgstr "Total-proxy" + msgid "Total-proxy is on" msgstr "Total-proxy включен" msgid "Transparent proxy port for iptables rules" msgstr "Порт прозрачного прокси для правил iptables" +msgid "Type an expression..." +msgstr "Введите выражение..." + msgid "Unable to execute or read contents" msgstr "Невозможно выполнить или прочитать содержимое" +msgid "Unable to load log data:" +msgstr "Невозможно загрузить данные лога:" + msgid "Unable to read the contents" msgstr "Невозможно прочитать содержимое" @@ -348,9 +437,27 @@ msgstr "Режим VPN" msgid "VPN routing error! Need restart" msgstr "Ошибка маршрутизации VPN! Необходим перезапуск" +msgid "Warning" +msgstr "Внимание" + +msgid "ascending" +msgstr "по возрастанию" + +msgid "descending" +msgstr "по убыванию" + +msgid "disabled" +msgstr "отключен" + msgid "e.g:" msgstr "прим:" +msgid "net pattern" +msgstr "шаблон сети" + +msgid "none (user entries only)" +msgstr "нет (только записи пользователя)" + msgid "user entries only" msgstr "только записи пользователя" @@ -359,86 +466,3 @@ msgstr "верный IP-адрес" msgid "valid address#port" msgstr "верный IP-адрес#порт" - - - -msgid "Alert" -msgstr "Тревога" - -msgid "All" -msgstr "Все" - -msgid "Critical" -msgstr "Критическая ситуация" - -msgid "Debug" -msgstr "Отладка" - -msgid "Download log" -msgstr "Скачать лог" - -msgid "Emergency" -msgstr "Чрезвычайная ситуация" - -msgid "Entries" -msgstr "Записи" - -msgid "Facility" -msgstr "Категория" - -msgid "Host" -msgstr "Хост" - -msgid "Hosts" -msgstr "Хосты" - -msgid "Info" -msgstr "Информация" - -msgid "Invalid regular expression" -msgstr "Неправильное регулярное выражение" - -msgid "Last entries" -msgstr "Последние записи" - -msgid "Level" -msgstr "Уровень" - -msgid "Logging levels" -msgstr "Уровни логирования" - -msgid "Message" -msgstr "Сообщение" - -msgid "Message filter" -msgstr "Фильтр сообщений" - -msgid "No entries available..." -msgstr "Нет доступных записей..." - -msgid "Notice" -msgstr "Сообщение" - -msgid "Refresh log" -msgstr "Обновить лог" - -msgid "Sorting entries" -msgstr "Сортировка записей" - -msgid "Timestamp" -msgstr "Время" - -msgid "Type an expression..." -msgstr "Введите выражение..." - -msgid "Unable to load log data:" -msgstr "Невозможно загрузить данные лога:" - -msgid "Warning" -msgstr "Внимание" - -msgid "ascending" -msgstr "по возрастанию" - -msgid "descending" -msgstr "по убыванию" diff --git a/luci-app-ruantiblock/po/templates/ruantiblock.pot b/luci-app-ruantiblock/po/templates/ruantiblock.pot index f3a858d..fa66d6a 100644 --- a/luci-app-ruantiblock/po/templates/ruantiblock.pot +++ b/luci-app-ruantiblock/po/templates/ruantiblock.pot @@ -7,6 +7,12 @@ msgstr "" msgid "Add user entries to the blacklist when updating" msgstr "" +msgid "Alert" +msgstr "" + +msgid "All" +msgstr "" + msgid "All traffic goes through the proxy without applying rules" msgstr "" @@ -16,6 +22,9 @@ msgstr "" msgid "Apply proxy rules to router application traffic" msgstr "" +msgid "Blacklist entry filters" +msgstr "" + msgid "Blacklist module" msgstr "" @@ -43,6 +52,9 @@ msgstr "" msgid "Clean up ipsets before updating blacklist" msgstr "" +msgid "Command failed" +msgstr "" + msgid "" "Complete service shutdown, as well as deleting ipsets and blacklist data" msgstr "" @@ -53,6 +65,9 @@ msgstr "" msgid "Convert cyrillic domains to punycode" msgstr "" +msgid "Critical" +msgstr "" + msgid "Current schedule" msgstr "" @@ -62,43 +77,47 @@ msgstr "" msgid "Day" msgstr "" -msgid "Disable" +msgid "Debug" msgstr "" - msgid "Disabled" msgstr "" msgid "Dismiss" msgstr "" +msgid "Download error" +msgstr "" + +msgid "Download log" +msgstr "" msgid "Edit" msgstr "" +msgid "Emergency" +msgstr "" msgid "Enable" msgstr "" msgid "Enable FQDN filter" msgstr "" -msgid "Enable ip filter" +msgid "Enable IP filter" msgstr "" msgid "Enable the 'total-proxy' option at startup" msgstr "" - msgid "Enabled" msgstr "" -msgid "Entries filters" +msgid "Entries" msgstr "" - msgid "Error" msgstr "" -msgid "Exclude domains from blacklist by FQDN filter patterns" +msgid "Exclude IP addresses from blacklist by IP filter patterns" msgstr "" -msgid "Exclude IP addresses from blacklist by IP filter patterns" +msgid "Exclude domains from blacklist by FQDN filter patterns" msgstr "" msgid "Expecting:" @@ -110,15 +129,24 @@ msgstr "" msgid "FQDN filter" msgstr "" -msgid "Hour" +msgid "Facility" +msgstr "" + +msgid "Failed to get %s init status: %s" +msgstr "" + +msgid "HTML/XML error" +msgstr "" + +msgid "Host" +msgstr "" + +msgid "Hosts" msgstr "" msgid "Hour" msgstr "" -msgid "Interval" -msgstr "" - msgid "IP configuration" msgstr "" @@ -131,6 +159,15 @@ msgstr "" msgid "IP subnet patterns (/24) that are excluded from optimization" msgstr "" +msgid "Info" +msgstr "" + +msgid "Interval" +msgstr "" + +msgid "Invalid regular expression" +msgstr "" + msgid "Ipset" msgstr "" @@ -143,6 +180,12 @@ msgstr "" msgid "Last blacklist update" msgstr "" +msgid "Last entries" +msgstr "" + +msgid "Level" +msgstr "" + msgid "Loading" msgstr "" @@ -152,12 +195,21 @@ msgstr "" msgid "Logging events" msgstr "" +msgid "Logging levels" +msgstr "" + msgid "Main settings" msgstr "" msgid "Match-set" msgstr "" +msgid "Message" +msgstr "" + +msgid "Message filter" +msgstr "" + msgid "Minute" msgstr "" @@ -167,13 +219,19 @@ msgstr "" msgid "Name" msgstr "" +msgid "No Shedule" +msgstr "" + msgid "No changes to save." msgstr "" msgid "No data" msgstr "" -msgid "No Shedule" +msgid "No entries available..." +msgstr "" + +msgid "Notice" msgstr "" msgid "Number of entries" @@ -189,10 +247,10 @@ msgstr "" msgid "One of the following:" msgstr "" -msgid "Optional DNS resolver for '.onion' zone" +msgid "Optional DNS resolver" msgstr "" -msgid "Optional DNS resolver" +msgid "Optional DNS resolver for '.onion' zone" msgstr "" msgid "Options" @@ -216,33 +274,46 @@ msgstr "" msgid "Reduces RAM consumption during update" msgstr "" +msgid "Refresh log" +msgstr "" + msgid "Reset" msgstr "" +msgid "Ruantiblock" +msgstr "" + msgid "Run at startup" msgstr "" +msgid "Save" +msgstr "" + msgid "Service" msgstr "" +msgid "Service action failed \"%s %s\": %s" +msgstr "" + msgid "Set" msgstr "" msgid "Settings" msgstr "" - msgid "Shutdown" msgstr "" msgid "Size in memory" msgstr "" +msgid "Sorting entries" +msgstr "" + msgid "Starting" msgstr "" msgid "Statistics" msgstr "" - msgid "Status" msgstr "" @@ -273,21 +344,31 @@ msgstr "" msgid "Time" msgstr "" +msgid "Timestamp" +msgstr "" + msgid "Tor configuration file" msgstr "" msgid "Tor mode" msgstr "" +msgid "Total-proxy" +msgstr "" + msgid "Total-proxy is on" msgstr "" msgid "Transparent proxy port for iptables rules" msgstr "" +msgid "Type an expression..." +msgstr "" msgid "Unable to execute or read contents" msgstr "" +msgid "Unable to load log data:" +msgstr "" msgid "Unable to read the contents" msgstr "" @@ -308,7 +389,6 @@ msgstr "" msgid "Use optional DNS resolver" msgstr "" - msgid "User entries" msgstr "" @@ -321,9 +401,27 @@ msgstr "" msgid "VPN routing error! Need restart" msgstr "" +msgid "Warning" +msgstr "" + +msgid "ascending" +msgstr "" + +msgid "descending" +msgstr "" + +msgid "disabled" +msgstr "" + msgid "e.g:" msgstr "" +msgid "net pattern" +msgstr "" + +msgid "none (user entries only)" +msgstr "" + msgid "user entries only" msgstr "" @@ -332,86 +430,3 @@ msgstr "" msgid "valid address#port" msgstr "" - - - -msgid "Alert" -msgstr "" - -msgid "All" -msgstr "" - -msgid "Critical" -msgstr "" - -msgid "Debug" -msgstr "" - -msgid "Download log" -msgstr "" - -msgid "Emergency" -msgstr "" - -msgid "Entries" -msgstr "" - -msgid "Facility" -msgstr "" - -msgid "Host" -msgstr "" - -msgid "Hosts" -msgstr "" - -msgid "Info" -msgstr "" - -msgid "Invalid regular expression" -msgstr "" - -msgid "Last entries" -msgstr "" - -msgid "Level" -msgstr "" - -msgid "Logging levels" -msgstr "" - -msgid "Message" -msgstr "" - -msgid "Message filter" -msgstr "" - -msgid "No entries available..." -msgstr "" - -msgid "Notice" -msgstr "" - -msgid "Refresh log" -msgstr "" - -msgid "Sorting entries" -msgstr "" - -msgid "Timestamp" -msgstr "" - -msgid "Type an expression..." -msgstr "" - -msgid "Unable to load log data:" -msgstr "" - -msgid "Warning" -msgstr "" - -msgid "ascending" -msgstr "" - -msgid "descending" -msgstr "" diff --git a/luci-app-ruantiblock/root/usr/share/rpcd/acl.d/luci-app-ruantiblock.json b/luci-app-ruantiblock/root/usr/share/rpcd/acl.d/luci-app-ruantiblock.json index 7c239e0..d401b2c 100644 --- a/luci-app-ruantiblock/root/usr/share/rpcd/acl.d/luci-app-ruantiblock.json +++ b/luci-app-ruantiblock/root/usr/share/rpcd/acl.d/luci-app-ruantiblock.json @@ -1,37 +1,34 @@ { - "luci-app-ruantiblock": { - "description": "Grant access to ruantiblock procedures", - "read": { - "cgi-io": [ "exec" ], - "file": { - "/usr/bin": [ "list" ], - "/etc/ruantiblock/fqdn_filter": [ "read" ], - "/etc/ruantiblock/ip_filter": [ "read" ], - "/etc/ruantiblock/user_entries": [ "read" ], - "/var/run/ruantiblock.token": [ "read" ], - "/etc/tor/torrc": [ "read" ], - "/etc/crontabs/root": [ "read" ], - "/usr/bin/ruantiblock": [ "exec" ], - "/etc/init.d/ruantiblock": [ "exec" ], - "/etc/init.d/tor enabled": [ "exec" ], - "/etc/init.d/tor restart": [ "exec" ], - "/etc/init.d/cron enabled": [ "exec" ], - "/etc/init.d/cron enable": [ "exec" ], - "/etc/init.d/cron restart": [ "exec" ], - "/sbin/logread -e ruantiblock": [ "exec" ], - "/usr/sbin/logread -e ruantiblock": [ "exec" ] - }, - "uci": [ "network", "ruantiblock" ] - }, - "write": { - "file": { - "/etc/ruantiblock/fqdn_filter": [ "write" ], - "/etc/ruantiblock/ip_filter": [ "write" ], - "/etc/ruantiblock/user_entries": [ "write" ], - "/etc/tor/torrc": [ "write" ], - "/etc/crontabs/root": [ "write" ] - }, - "uci": [ "ruantiblock" ] - } - } + "luci-app-ruantiblock": { + "description": "Grant access to ruantiblock procedures", + "read": { + "cgi-io": [ "exec" ], + "file": { + "/usr/bin": [ "list" ], + "/etc/ruantiblock/fqdn_filter": [ "read" ], + "/etc/ruantiblock/ip_filter": [ "read" ], + "/etc/ruantiblock/user_entries": [ "read" ], + "/var/run/ruantiblock.token": [ "read" ], + "/etc/tor/torrc": [ "read" ], + "/etc/crontabs/root": [ "read" ], + "/usr/bin/ruantiblock": [ "exec" ], + "/sbin/logread -e ruantiblock": [ "exec" ], + "/usr/sbin/logread -e ruantiblock": [ "exec" ] + }, + "uci": [ "network", "ruantiblock" ], + "ubus": { + "luci": [ "getInitList", "setInitAction" ] + } + }, + "write": { + "file": { + "/etc/ruantiblock/fqdn_filter": [ "write" ], + "/etc/ruantiblock/ip_filter": [ "write" ], + "/etc/ruantiblock/user_entries": [ "write" ], + "/etc/tor/torrc": [ "write" ], + "/etc/crontabs/root": [ "write" ] + }, + "uci": [ "ruantiblock" ] + } + } } diff --git a/packages/19.07/luci-app-ruantiblock_0.9.0-6_all.ipk b/packages/19.07/luci-app-ruantiblock_0.9.0-6_all.ipk deleted file mode 100644 index fb04f95..0000000 Binary files a/packages/19.07/luci-app-ruantiblock_0.9.0-6_all.ipk and /dev/null differ diff --git a/packages/19.07/luci-app-ruantiblock_0.9.0-7_all.ipk b/packages/19.07/luci-app-ruantiblock_0.9.0-7_all.ipk new file mode 100644 index 0000000..7e82cee Binary files /dev/null and b/packages/19.07/luci-app-ruantiblock_0.9.0-7_all.ipk differ diff --git a/packages/19.07/luci-i18n-ruantiblock-ru_0.9.0-6_all.ipk b/packages/19.07/luci-i18n-ruantiblock-ru_0.9.0-6_all.ipk deleted file mode 100644 index 6bd0e6b..0000000 Binary files a/packages/19.07/luci-i18n-ruantiblock-ru_0.9.0-6_all.ipk and /dev/null differ diff --git a/packages/19.07/luci-i18n-ruantiblock-ru_0.9.0-7_all.ipk b/packages/19.07/luci-i18n-ruantiblock-ru_0.9.0-7_all.ipk new file mode 100644 index 0000000..d2df2e0 Binary files /dev/null and b/packages/19.07/luci-i18n-ruantiblock-ru_0.9.0-7_all.ipk differ diff --git a/ruantiblock/files/etc/config/ruantiblock b/ruantiblock/files/etc/config/ruantiblock index d8301a4..be037a9 100644 --- a/ruantiblock/files/etc/config/ruantiblock +++ b/ruantiblock/files/etc/config/ruantiblock @@ -1,35 +1,35 @@ config main 'config' - option proxy_mode '1' - option proxy_local_clients '1' - option ipset_clear_sets '0' - option if_lan 'eth0' - option if_vpn 'tun0' - option tor_trans_port '9040' - option onion_dns_addr '127.0.0.1#9053' - option add_user_entries '0' - option use_logger '1' - option def_total_proxy '0' - option bllist_source 'zapret-info' - option bllist_mode 'ip' - option ip_limit '0' - option summarize_ip '0' - option summarize_cidr '0' - option ip_filter '0' - option sd_limit '16' - list opt_exclude_sld 'livejournal.com' - list opt_exclude_sld 'facebook.com' - list opt_exclude_sld 'vk.com' - list opt_exclude_sld 'blog.jp' - list opt_exclude_sld 'msk.ru' - list opt_exclude_sld 'net.ru' - list opt_exclude_sld 'org.ru' - list opt_exclude_sld 'net.ua' - list opt_exclude_sld 'com.ua' - list opt_exclude_sld 'org.ua' - list opt_exclude_sld 'co.uk' - list opt_exclude_sld 'amazonaws.com' - option fqdn_filter '0' - option use_idn '0' - option alt_nslookup '0' - option alt_dns_addr '8.8.8.8' + option proxy_mode '1' + option proxy_local_clients '1' + option ipset_clear_sets '0' + option if_lan 'eth0' + option if_vpn 'tun0' + option tor_trans_port '9040' + option onion_dns_addr '127.0.0.1#9053' + option add_user_entries '0' + option use_logger '1' + option def_total_proxy '0' + option bllist_source 'zapret-info' + option bllist_mode 'ip' + option ip_limit '0' + option summarize_ip '0' + option summarize_cidr '0' + option ip_filter '0' + option sd_limit '16' + list opt_exclude_sld 'livejournal.com' + list opt_exclude_sld 'facebook.com' + list opt_exclude_sld 'vk.com' + list opt_exclude_sld 'blog.jp' + list opt_exclude_sld 'msk.ru' + list opt_exclude_sld 'net.ru' + list opt_exclude_sld 'org.ru' + list opt_exclude_sld 'net.ua' + list opt_exclude_sld 'com.ua' + list opt_exclude_sld 'org.ua' + list opt_exclude_sld 'co.uk' + list opt_exclude_sld 'amazonaws.com' + option fqdn_filter '0' + option use_idn '0' + option alt_nslookup '0' + option alt_dns_addr '8.8.8.8' diff --git a/ruantiblock/files/etc/hotplug.d/iface/40-ruantiblock b/ruantiblock/files/etc/hotplug.d/iface/40-ruantiblock index a00d151..e9f4caf 100755 --- a/ruantiblock/files/etc/hotplug.d/iface/40-ruantiblock +++ b/ruantiblock/files/etc/hotplug.d/iface/40-ruantiblock @@ -4,5 +4,5 @@ IF_VPN=`uci get ruantiblock.config.if_vpn` PROXY_MODE=`uci get ruantiblock.config.proxy_mode` if [ "$ACTION" = "ifup" ] && [ "$PROXY_MODE" = "2" ] && [ "$DEVICE" = "$IF_VPN" ]; then - [ `/usr/bin/ruantiblock raw-status` -ne 2 ] && /usr/bin/ruantiblock reload + [ `/usr/bin/ruantiblock raw-status` -ne 2 ] && /usr/bin/ruantiblock reload fi diff --git a/screenshots/01.jpg b/screenshots/01.jpg new file mode 100644 index 0000000..a7d8554 Binary files /dev/null and b/screenshots/01.jpg differ diff --git a/screenshots/02.jpg b/screenshots/02.jpg new file mode 100644 index 0000000..3ce1e80 Binary files /dev/null and b/screenshots/02.jpg differ diff --git a/screenshots/03.jpg b/screenshots/03.jpg new file mode 100644 index 0000000..5dc5fed Binary files /dev/null and b/screenshots/03.jpg differ diff --git a/screenshots/04.jpg b/screenshots/04.jpg new file mode 100644 index 0000000..4d93fd7 Binary files /dev/null and b/screenshots/04.jpg differ diff --git a/screenshots/05.jpg b/screenshots/05.jpg new file mode 100644 index 0000000..ed233cb Binary files /dev/null and b/screenshots/05.jpg differ diff --git a/screenshots/06.jpg b/screenshots/06.jpg new file mode 100644 index 0000000..eab6a63 Binary files /dev/null and b/screenshots/06.jpg differ