LUCI_TITLE:=LuCI support for banIP
LUCI_DEPENDS:=+luci-base +banip
+PKG_VERSION:=1.6.0
+PKG_RELEASE:=1
PKG_LICENSE:=Apache-2.0
'require fs';
'require ui';
-let localFile = '/etc/banip/banip.allowlist';
-let notMsg, errMsg;
+const localFile = '/etc/banip/banip.allowlist';
+let notMsg = false, errMsg = false;
+
+const resetScroll = () => {
+ document.body.scrollTop = document.documentElement.scrollTop = 0;
+};
return view.extend({
load: function () {
return L.resolveDefault(fs.stat(localFile), "")
- .then(function (stat) {
- if (!stat) {
- return fs.write(localFile, "");
- }
- return Promise.all([
- L.resolveDefault(fs.stat(localFile), ""),
- L.resolveDefault(fs.read_direct(localFile), "")
- ]);
- });
+ .then(function (stat) {
+ if (!stat) {
+ return fs.write(localFile, "");
+ }
+ return Promise.all([
+ L.resolveDefault(fs.stat(localFile), ""),
+ L.resolveDefault(fs.read_direct(localFile), "")
+ ]);
+ });
},
render: function (allowlist) {
if (allowlist[0].size >= 100000) {
- document.body.scrollTop = document.documentElement.scrollTop = 0;
+ resetScroll();
ui.addNotification(null, E('p', _('The allowlist is too big, unable to save modifications.')), 'error');
}
return E('div', { 'class': 'cbi-section cbi-section-descr' }, [
]);
},
handleSave: function (ev) {
- let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n/g, '\n')) + '\n';
- return fs.write(localFile, value)
- .then(function () {
- document.querySelector('textarea').value = value;
- document.body.scrollTop = document.documentElement.scrollTop = 0;
- if (!notMsg) {
- ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload banIP that changes take effect.')), 'info');
- notMsg = true;
- }
- }).catch(function (e) {
- document.body.scrollTop = document.documentElement.scrollTop = 0;
- if (!errMsg) {
- ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
- errMsg = true;
- }
- });
+ let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n?/g, '\n'));
+ return fs.write(localFile, value + "\n")
+ .then(function () {
+ document.querySelector('textarea').value = value;
+ resetScroll();
+ if (!notMsg) {
+ ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload banIP that changes take effect.')), 'info');
+ notMsg = true;
+ }
+ }).catch(function (e) {
+ resetScroll();
+ if (!errMsg) {
+ ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
+ errMsg = true;
+ }
+ });
},
handleSaveApply: null,
handleReset: null
'require fs';
'require ui';
-let localFile = '/etc/banip/banip.blocklist';
-let notMsg, errMsg;
+const localFile = '/etc/banip/banip.blocklist';
+let notMsg = false, errMsg = false;
+
+const resetScroll = () => {
+ document.body.scrollTop = document.documentElement.scrollTop = 0;
+};
return view.extend({
load: function () {
return L.resolveDefault(fs.stat(localFile), "")
- .then(function (stat) {
- if (!stat) {
- return fs.write(localFile, "");
- }
- return Promise.all([
- L.resolveDefault(fs.stat(localFile), ""),
- L.resolveDefault(fs.read_direct(localFile), "")
- ]);
- });
+ .then(function (stat) {
+ if (!stat) {
+ return fs.write(localFile, "");
+ }
+ return Promise.all([
+ L.resolveDefault(fs.stat(localFile), ""),
+ L.resolveDefault(fs.read_direct(localFile), "")
+ ]);
+ });
},
render: function (blocklist) {
if (blocklist[0].size >= 100000) {
- document.body.scrollTop = document.documentElement.scrollTop = 0;
+ resetScroll();
ui.addNotification(null, E('p', _('The blocklist is too big, unable to save modifications.')), 'error');
}
return E('div', { 'class': 'cbi-section cbi-section-descr' }, [
]);
},
handleSave: function (ev) {
- let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n/g, '\n')) + '\n';
- return fs.write(localFile, value)
- .then(function () {
- document.querySelector('textarea').value = value;
- document.body.scrollTop = document.documentElement.scrollTop = 0;
- if (!notMsg) {
- ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload banIP that changes take effect.')), 'info');
- notMsg = true;
- }
- }).catch(function (e) {
- document.body.scrollTop = document.documentElement.scrollTop = 0;
- if (!errMsg) {
- ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
- errMsg = true;
- }
- });
+ let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/\r\n?/g, '\n'));
+ return fs.write(localFile, value + "\n")
+ .then(function () {
+ document.querySelector('textarea').value = value;
+ resetScroll();
+ if (!notMsg) {
+ ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload banIP that changes take effect.')), 'info');
+ notMsg = true;
+ }
+ }).catch(function (e) {
+ resetScroll();
+ if (!errMsg) {
+ ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
+ errMsg = true;
+ }
+ });
},
handleSaveApply: null,
handleReset: null
margin-bottom: -5px;
padding-top: 0rem;
}
-
.cbi-input-select {
margin-bottom: -5px;
padding-top: 0rem;
'use strict';
'require view.banip.logtemplate as LogTemplate';
-return LogTemplate.Logview('banIP/', 'banIP');
+return LogTemplate.Logview(' banIP/', 'banIP');
'use strict';
-'require fs';
+'require rpc';
+
+const callLogRead = rpc.declare({
+ object: 'log',
+ method: 'read',
+ params: ['lines', 'stream', 'oneshot'],
+ expect: {}
+});
function Logview(logtag, name) {
return L.view.extend({
- load: function() {
- return Promise.all([
- L.resolveDefault(fs.stat('/sbin/logread'), null),
- L.resolveDefault(fs.stat('/usr/sbin/logread'), null)
- ]);
- },
+ load: () => Promise.resolve(),
- render: function(stat) {
- let logger = stat[0]?.path || stat[1]?.path || null;
+ render: () => {
+ L.Poll.add(() => {
+ return callLogRead(1000, false, true).then(res => {
+ const logEl = document.getElementById('logfile');
+ if (!logEl) return;
- if (!logger) {
- return E('div', { class: 'error' }, _('logread not found on system.'));
- }
- L.Poll.add(function() {
- return L.resolveDefault(fs.exec_direct(logger, ['-e', logtag])).then(function(res) {
- var log = document.getElementById('logfile');
- if (log) {
- log.value = res ? res.trim() : _('No %s related logs yet!').format(name);
- log.scrollTop = log.scrollHeight;
+ const entries = res?.log ?? [];
+ if (entries.length > 0) {
+ const filtered = entries
+ .filter(entry => !logtag || entry.msg.includes(logtag))
+ .map(entry => {
+ const d = new Date(entry.time);
+ const date = d.toLocaleDateString([], {
+ year: 'numeric',
+ month: '2-digit',
+ day: '2-digit'
+ });
+ const time = d.toLocaleTimeString([], {
+ hour: '2-digit',
+ minute: '2-digit',
+ second: '2-digit',
+ hour12: false
+ });
+ return `[${date}-${time}] ${entry.msg}`;
+ });
+ logEl.value = filtered.join('\n');
+ } else {
+ logEl.value = _('No %s related logs yet!').format(name);
}
+ logEl.scrollTop = logEl.scrollHeight;
});
});
+
return E('div', { class: 'cbi-map' }, [
E('div', { class: 'cbi-section' }, [
- E('div', { class: 'cbi-section-descr' }, _('The syslog output, pre-filtered for messages related to: %s').format(name)),
+ E('div', { class: 'cbi-section-descr' },
+ _('The syslog output, pre-filtered for messages related to: %s').format(name)),
E('textarea', {
id: 'logfile',
style: 'min-height: 500px; max-height: 90vh; width: 100%; padding: 5px; font-family: monospace; resize: vertical;',
])
]);
},
+
handleSaveApply: null,
handleSave: null,
handleReset: null
});
}
-return L.Class.extend({
- Logview: Logview
-});
+return L.Class.extend({ Logview });
For further information please check the <a style="color:#37c;font-weight:bold;" href="https://github.com/openwrt/packages/blob/master/net/banip/files/README.md" target="_blank" rel="noreferrer noopener" >online documentation</a>'));
/*
- poll runtime information
+ set text content helper function
*/
- let buttons, rtRes, infStat, infVer, infElements, infFeeds, infDevices, infUplink, infSys, infNft, infRun, infFlags, infLast
+ const setText = (id, value) => {
+ const el = document.getElementById(id);
+ if (el) {
+ el.textContent = value || '-';
+ }
+ };
+ /*
+ poll runtime information
+ */
pollData: poll.add(function () {
- return L.resolveDefault(fs.stat('/var/run/banip.lock')).then(function (stat) {
- buttons = document.querySelectorAll('.cbi-button');
- infStat = document.getElementById('status');
- if (stat) {
- for (let i = 0; i < buttons.length; i++) {
- buttons[i].setAttribute('disabled', 'true');
- }
- if (infStat && !infStat.classList.contains('spinning')) {
- infStat.classList.add('spinning');
- }
- } else {
- for (let i = 0; i < buttons.length; i++) {
- buttons[i].removeAttribute('disabled');
- }
- if (infStat && infStat.classList.contains('spinning')) {
- infStat.classList.remove('spinning');
+ return L.resolveDefault(fs.read_direct('/var/run/banip_runtime.json'), 'null').then(function (res) {
+ const status = document.getElementById('status');
+ const buttons = document.querySelectorAll('.cbi-page-actions button');
+ try {
+ var info = JSON.parse(res);
+ } catch (e) {
+ status.textContent = '-';
+ poll.stop();
+ if (status.classList.contains('spinning')) {
+ buttons.forEach(function (btn) {
+ btn.disabled = false;
+ })
+ status.classList.remove('spinning');
}
+ ui.addNotification(null, E('p', _('Unable to parse the runtime information!')), 'error');
}
- L.resolveDefault(fs.exec_direct('/etc/init.d/banip', ['status'])).then(function (result) {
- if (result) {
- rtRes = result.trim().split('\n');
- if (rtRes) {
- for (let i = 0; i < rtRes.length; i++) {
- if (rtRes[i].match(/^\s+\+\sstatus\s+\:\s+(.*)$/)) {
- rtRes.status = rtRes[i].match(/^\s+\+\sstatus\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\sversion\s+\:\s+(.*)$/)) {
- rtRes.version = rtRes[i].match(/^\s+\+\sversion\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\selement_count\s+\:\s+(.*)$/)) {
- rtRes.elementCount = rtRes[i].match(/^\s+\+\selement_count\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\sactive_feeds\s+\:\s+(.*)$/)) {
- rtRes.activeFeeds = rtRes[i].match(/^\s+\+\sactive_feeds\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\sactive_devices\s+\:\s+(.*)$/)) {
- rtRes.activeDevices = rtRes[i].match(/^\s+\+\sactive_devices\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\sactive_uplink\s+\:\s+(.*)$/)) {
- rtRes.activeUplink = rtRes[i].match(/^\s+\+\sactive_uplink\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\snft_info\s+\:\s+(.*)$/)) {
- rtRes.nftInfo = rtRes[i].match(/^\s+\+\snft_info\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\srun_info\s+\:\s+(.*)$/)) {
- rtRes.runInfo = rtRes[i].match(/^\s+\+\srun_info\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\srun_flags\s+\:\s+(.*)$/)) {
- rtRes.runFlags = rtRes[i].match(/^\s+\+\srun_flags\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\slast_run\s+\:\s+(.*)$/)) {
- rtRes.lastRun = rtRes[i].match(/^\s+\+\slast_run\s+\:\s+(.*)$/)[1];
- } else if (rtRes[i].match(/^\s+\+\ssystem_info\s+\:\s+(.*)$/)) {
- rtRes.sysInfo = rtRes[i].match(/^\s+\+\ssystem_info\s+\:\s+(.*)$/)[1];
- }
- }
- }
- if (rtRes) {
- infStat = document.getElementById('status');
- if (infStat) {
- infStat.textContent = rtRes.status || '-';
- }
- infVer = document.getElementById('version');
- if (infVer) {
- infVer.textContent = rtRes.version || '-';
- }
- infElements = document.getElementById('elements');
- if (infElements) {
- infElements.textContent = rtRes.elementCount || '-';
- }
- infFeeds = document.getElementById('feeds');
- if (infFeeds) {
- infFeeds.textContent = rtRes.activeFeeds || '-';
- }
- infDevices = document.getElementById('devices');
- if (infDevices) {
- infDevices.textContent = rtRes.activeDevices || '-';
- }
- infUplink = document.getElementById('uplink');
- if (infUplink) {
- infUplink.textContent = rtRes.activeUplink || '-';
- }
- infNft = document.getElementById('nft');
- if (infNft) {
- infNft.textContent = rtRes.nftInfo || '-';
- }
- infRun = document.getElementById('run');
- if (infRun) {
- infRun.textContent = rtRes.runInfo || '-';
- }
- infFlags = document.getElementById('flags');
- if (infFlags) {
- infFlags.textContent = rtRes.runFlags || '-';
- }
- infLast = document.getElementById('last');
- if (infLast) {
- infLast.textContent = rtRes.lastRun || '-';
- }
- infSys = document.getElementById('sys');
- if (infSys) {
- infSys.textContent = rtRes.sysInfo || '-';
- }
+ if (status && info) {
+ status.textContent = `${info.status || '-'} (frontend: ${info.frontend_ver || '-'} / backend: ${info.backend_ver || '-'})`;
+ if (info.status === "processing") {
+ if (!status.classList.contains("spinning")) {
+ status.classList.add("spinning");
}
+ buttons.forEach(function (btn) {
+ btn.disabled = true;
+ btn.blur();
+ })
} else {
- infStat = document.getElementById('status');
- if (infStat) {
- infStat.textContent = '-';
- poll.stop();
- if (infStat.classList.contains('spinning')) {
- infStat.classList.remove('spinning');
- }
+ if (status.classList.contains("spinning")) {
+ buttons.forEach(function (btn) {
+ btn.disabled = false;
+ })
+ status.classList.remove("spinning");
}
}
- });
+ } else if (status) {
+ status.textContent = '-';
+ poll.stop();
+ if (status.classList.contains('spinning')) {
+ buttons.forEach(function (btn) {
+ btn.disabled = false;
+ })
+ status.classList.remove('spinning');
+ }
+ }
+ if (info) {
+ setText('elements', info.element_count);
+ setText('feeds', info.active_feeds?.join(', '));
+ setText('devices', `wan-dev: ${info.wan_devices?.join(', ') || '-'} /
+ wan-if: ${info.wan_interfaces?.join(', ') || '-'} /
+ vlan-allow: ${info.vlan_allow?.join(', ') || '-'} /
+ vlan-block: ${info.vlan_block?.join(', ') || '-'}`);
+ setText('uplink', info.active_uplink?.join(', ') || '-');
+ setText('nft', info.nft_info);
+ setText('run', info.run_info);
+ setText('flags', info.run_flags);
+ setText('last', info.last_run);
+ setText('sys', info.system_info);
+ }
});
}, 2);
E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Status')),
E('div', { 'class': 'cbi-value-field spinning', 'id': 'status', 'style': 'margin-bottom:-5px;color:#37c;' }, '\xa0')
]),
- E('div', { 'class': 'cbi-value' }, [
- E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Version')),
- E('div', { 'class': 'cbi-value-field', 'id': 'version', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
- ]),
E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Element Count')),
E('div', { 'class': 'cbi-value-field', 'id': 'elements', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
"description": "Grant access to LuCI app banIP",
"write": {
"file": {
- "/etc/banip/*": [
- "read",
- "write"
- ],
- "/etc/banip/banip.allowlist": [
- "write"
- ],
- "/etc/banip/banip.blocklist": [
- "write"
- ],
- "/etc/banip/banip.custom.feeds": [
- "read",
- "write"
- ]
+ "/etc/banip/*": [ "read", "write" ],
+ "/etc/banip/banip.allowlist": [ "write" ],
+ "/etc/banip/banip.blocklist": [ "write" ],
+ "/etc/banip/banip.custom.feeds": [ "read", "write" ]
},
- "uci": [
- "banip"
- ]
+ "uci": [ "banip" ]
},
"read": {
- "cgi-io": [
- "exec"
- ],
+ "cgi-io": [ "exec" ],
"file": {
- "/var/run/banip.lock": [
- "read"
- ],
- "/sbin/logread -e banIP-": [
- "exec"
- ],
- "/usr/sbin/logread -e banIP-": [
- "exec"
- ],
- "/sbin/logread -e banIP/": [
- "exec"
- ],
- "/usr/sbin/logread -e banIP/": [
- "exec"
- ],
- "/usr/sbin/nft -tj list sets": [
- "exec"
- ],
- "/etc/init.d/banip stop": [
- "exec"
- ],
- "/etc/init.d/banip reload": [
- "exec"
- ],
- "/etc/init.d/banip restart": [
- "exec"
- ],
- "/etc/init.d/banip report json": [
- "exec"
- ],
- "/etc/init.d/banip report gen": [
- "exec"
- ],
- "/etc/init.d/banip search [A-Za-z0-9:.]*": [
- "exec"
- ],
- "/etc/init.d/banip content [A-Za-z0-9]* *": [
- "exec"
- ],
- "/etc/init.d/banip status": [
- "exec"
- ]
+ "/var/run/banip.lock": [ "read" ],
+ "/var/run/banip_runtime.json": [ "read" ],
+ "/usr/sbin/nft -tj list sets": [ "exec" ],
+ "/etc/init.d/banip stop": [ "exec" ],
+ "/etc/init.d/banip reload": [ "exec" ],
+ "/etc/init.d/banip restart": [ "exec" ],
+ "/etc/init.d/banip report json": [ "exec" ],
+ "/etc/init.d/banip report gen": [ "exec" ],
+ "/etc/init.d/banip search [A-Za-z0-9:.]*": [ "exec" ],
+ "/etc/init.d/banip content [A-Za-z0-9]* *": [ "exec" ],
+ "/etc/init.d/banip status": [ "exec" ]
},
- "uci": [
- "banip"
- ]
+ "uci": [ "banip"],
+ "log": [ "read" ]
}
}
}
\ No newline at end of file