mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-08 13:14:12 +08:00
💋 Sync 2026-10-04 04:16:09
This commit is contained in:
1 parent
f6b2df73ec
commit
f62992fd80
745 files changed
+212416
-1173
No files matched your search
@@ -0,0 +1,290 @@
|
||||
'use strict';
|
||||
'require view';
|
||||
'require fs';
|
||||
'require ui';
|
||||
'require dom';
|
||||
|
||||
const localFile = '/etc/banip/banip.blocklist';
|
||||
const maxSize = 100000;
|
||||
|
||||
const resetScroll = () => {
|
||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||
};
|
||||
|
||||
/* Regex definitions for validation */
|
||||
const ipv4Regex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
const cidrRegex = /^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\/([0-9]|[12][0-9]|3[0-2]))$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){1,7}[0-9a-fA-F]{1,4}(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$|^::1$|^([0-9a-fA-F]{1,4}:){1,7}:(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$/;
|
||||
const macRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/;
|
||||
const domainRegex = /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/;
|
||||
const macIpBindingRegex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})\s+((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/;
|
||||
|
||||
function isValidRule(token) {
|
||||
return macRegex.test(token) || cidrRegex.test(token) || ipv4Regex.test(token) ||
|
||||
macIpBindingRegex.test(token) || ipv6Regex.test(token) || domainRegex.test(token);
|
||||
}
|
||||
|
||||
function parseBlocklist(text) {
|
||||
const lines = (text || '').split('\n');
|
||||
const items = [];
|
||||
lines.forEach(function (raw, idx) {
|
||||
const line = raw.trim();
|
||||
if (!line || line.startsWith('#')) return;
|
||||
const parts = line.split(/\s+#\s*(.*)$/);
|
||||
const main = parts[0].trim();
|
||||
const comment = parts[1] ? parts[1].trim() : '';
|
||||
items.push({ id: idx, value: main, comment: comment, raw: line });
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
function serializeBlocklist(items) {
|
||||
return items.map(function (item) {
|
||||
return item.comment ? `${item.value} # ${item.comment}` : item.value;
|
||||
}).join('\n') + '\n';
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
rawContent: '',
|
||||
isRawMode: false,
|
||||
items: [],
|
||||
|
||||
load: function () {
|
||||
return L.resolveDefault(fs.stat(localFile), null)
|
||||
.then(function (stat) {
|
||||
if (!stat) {
|
||||
return fs.write(localFile, "").then(() => [{ size: 0 }, ""]);
|
||||
}
|
||||
return Promise.all([
|
||||
Promise.resolve(stat),
|
||||
L.resolveDefault(fs.read_direct(localFile), "")
|
||||
]);
|
||||
});
|
||||
},
|
||||
|
||||
render: function (blocklist) {
|
||||
const size = blocklist[0] ? blocklist[0].size : 0;
|
||||
this.rawContent = blocklist[1] != null ? blocklist[1] : '';
|
||||
const tooBig = size >= maxSize;
|
||||
const self = this;
|
||||
|
||||
this.items = parseBlocklist(this.rawContent);
|
||||
|
||||
if (tooBig) {
|
||||
resetScroll();
|
||||
ui.addNotification(null, E('p', _('The blocklist is too big, raw editor mode enforced.')), 'error');
|
||||
this.isRawMode = true;
|
||||
}
|
||||
|
||||
const mainContainer = E('div', { 'class': 'cbi-map' });
|
||||
|
||||
function syncAndRebuild() {
|
||||
self.rawContent = serializeBlocklist(self.items);
|
||||
renderContainer();
|
||||
}
|
||||
|
||||
/*
|
||||
1. Unified Blocklist Table (Pure OpenWrt Native)
|
||||
*/
|
||||
function renderTable() {
|
||||
const rows = self.items.map(function (item, index) {
|
||||
return E('tr', { 'class': 'tr cbi-section-table-row cbi-rowstyle-' + ((index % 2) ? 2 : 1) }, [
|
||||
E('td', { 'class': 'td cbi-section-table-cell', 'style': 'text-align: left !important; font-family: monospace;' }, [item.value]),
|
||||
E('td', { 'class': 'td cbi-section-table-cell', 'style': 'text-align: left !important;' }, [item.comment || '-']),
|
||||
E('td', { 'class': 'td cbi-section-table-cell', 'style': 'text-align: right !important;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-remove',
|
||||
'title': _('Delete and unblock this rule'),
|
||||
'click': function () {
|
||||
self.items.splice(index, 1);
|
||||
syncAndRebuild();
|
||||
}
|
||||
}, [_('Delete')])
|
||||
])
|
||||
]);
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', _('Blocklist Entries')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('table', { 'class': 'table cbi-section-table' }, [
|
||||
E('tr', { 'class': 'tr cbi-section-table-titles' }, [
|
||||
E('th', { 'class': 'th cbi-section-table-cell', 'style': 'width: 45%; text-align: left !important;' }, [_('Blocked Address / Domain / Network')]),
|
||||
E('th', { 'class': 'th cbi-section-table-cell', 'style': 'width: 40%; text-align: left !important;' }, [_('Comment / Source')]),
|
||||
E('th', { 'class': 'th cbi-section-table-cell', 'style': 'width: 15%; text-align: right !important;' }, [_('Action')])
|
||||
]),
|
||||
E('tbody', {}, rows.length ? rows : [
|
||||
E('tr', { 'class': 'tr cbi-section-table-row' }, [
|
||||
E('td', { 'colspan': 3, 'class': 'td cbi-section-table-cell', 'style': 'text-align: center !important; color: #888; padding: 2em;' },
|
||||
[_('Currently no blocked entries. Add one below.')])
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
2. Quick Add Bar (Pure OpenWrt Native)
|
||||
*/
|
||||
function renderQuickAdd() {
|
||||
const valInput = E('input', {
|
||||
'type': 'text',
|
||||
'class': 'cbi-input-text',
|
||||
'style': 'width: 100%;',
|
||||
'placeholder': _('e.g. 198.51.100.1, 203.0.113.0/24, or malicious-site.com')
|
||||
});
|
||||
|
||||
const commentInput = E('input', {
|
||||
'type': 'text',
|
||||
'class': 'cbi-input-text',
|
||||
'style': 'width: 100%;',
|
||||
'placeholder': _('Optional comment (e.g. Malicious Bot)')
|
||||
});
|
||||
|
||||
function doAdd() {
|
||||
const val = valInput.value.trim();
|
||||
const cmt = commentInput.value.trim();
|
||||
if (!val) {
|
||||
ui.addNotification(null, E('p', _('Please specify a valid IP, CIDR, Domain or MAC address.')), 'warning');
|
||||
valInput.focus();
|
||||
return;
|
||||
}
|
||||
if (/[\s#]/.test(val)) {
|
||||
ui.addNotification(null, E('p', _('Format invalid: "%s" is not a valid address without spaces.').format(val)), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
self.items.unshift({ id: Date.now(), value: val, comment: cmt, raw: val });
|
||||
valInput.value = '';
|
||||
commentInput.value = '';
|
||||
syncAndRebuild();
|
||||
ui.addNotification(null, E('p', _('Added blocklist entry "%s".').format(val)), 'info');
|
||||
}
|
||||
|
||||
valInput.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Enter') doAdd();
|
||||
});
|
||||
commentInput.addEventListener('keydown', function (ev) {
|
||||
if (ev.key === 'Enter') doAdd();
|
||||
});
|
||||
|
||||
return E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', _('Quick Add Block Rule')),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('table', { 'class': 'table cbi-section-table' }, [
|
||||
E('tr', { 'class': 'tr cbi-section-table-titles' }, [
|
||||
E('th', { 'class': 'th cbi-section-table-cell', 'style': 'width: 45%; text-align: left !important;' }, [_('Address or Domain')]),
|
||||
E('th', { 'class': 'th cbi-section-table-cell', 'style': 'width: 40%; text-align: left !important;' }, [_('Comment / Source')]),
|
||||
E('th', { 'class': 'th cbi-section-table-cell', 'style': 'width: 15%; text-align: right !important;' }, [_('Action')])
|
||||
]),
|
||||
E('tr', { 'class': 'tr cbi-section-table-row' }, [
|
||||
E('td', { 'class': 'td cbi-section-table-cell', 'style': 'text-align: left !important;' }, [valInput]),
|
||||
E('td', { 'class': 'td cbi-section-table-cell', 'style': 'text-align: left !important;' }, [commentInput]),
|
||||
E('td', { 'class': 'td cbi-section-table-cell', 'style': 'text-align: right !important;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-add',
|
||||
'click': doAdd
|
||||
}, [_('Add Rule')])
|
||||
])
|
||||
])
|
||||
])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
3. Raw Textarea Editor
|
||||
*/
|
||||
function renderRawEditor() {
|
||||
return E('div', { 'class': 'cbi-section' }, [
|
||||
E('h3', _('Raw Text Editor')),
|
||||
E('div', { 'class': 'cbi-section-descr' }, [
|
||||
_('One record per line. Supports IPv4, IPv6, CIDR, MAC, Domain and comments starting with "#".')
|
||||
]),
|
||||
E('div', { 'class': 'cbi-section-node' }, [
|
||||
E('textarea', {
|
||||
'id': 'blocklist-raw-textarea',
|
||||
'class': 'cbi-input-textarea',
|
||||
'style': 'width: 100%; height: 480px; font-family: monospace; resize: vertical;',
|
||||
'spellcheck': 'false',
|
||||
'wrap': 'off',
|
||||
'input': function () {
|
||||
self.rawContent = this.value;
|
||||
}
|
||||
}, [self.rawContent || ''])
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
/*
|
||||
Assemble Container
|
||||
*/
|
||||
function renderContainer() {
|
||||
dom.content(mainContainer, [
|
||||
E('h2', { 'name': 'content' }, [_('SecBox Blocklist')]),
|
||||
E('div', { 'class': 'cbi-map-descr' }, [
|
||||
_('Manage locally blocked IP addresses, CIDR ranges, MAC addresses or domains.')
|
||||
]),
|
||||
E('div', { 'style': 'margin-bottom: 1em; text-align: right;' }, [
|
||||
E('button', {
|
||||
'class': 'btn cbi-button cbi-button-neutral',
|
||||
'click': function () {
|
||||
if (self.isRawMode) {
|
||||
self.items = parseBlocklist(self.rawContent);
|
||||
self.isRawMode = false;
|
||||
} else {
|
||||
self.rawContent = serializeBlocklist(self.items);
|
||||
self.isRawMode = true;
|
||||
}
|
||||
renderContainer();
|
||||
}
|
||||
}, [self.isRawMode ? _('Switch to Table View') : _('Switch to Raw Text Editor')])
|
||||
]),
|
||||
self.isRawMode
|
||||
? renderRawEditor()
|
||||
: E('div', {}, [
|
||||
renderQuickAdd(),
|
||||
renderTable()
|
||||
])
|
||||
]);
|
||||
}
|
||||
|
||||
renderContainer();
|
||||
return mainContainer;
|
||||
},
|
||||
|
||||
handleSaveApply: function (ev, mode) {
|
||||
return this.handleSave(ev).then(function () {
|
||||
ui.showIndicator('banip-action', _('Reloading SecBox service...'));
|
||||
return fs.exec_direct('/etc/init.d/banip', ['reload'])
|
||||
.then(function () {
|
||||
ui.hideIndicator('banip-action');
|
||||
ui.addNotification(null, E('p', _('Blocklist saved and SecBox service reloaded successfully!')), 'info');
|
||||
})
|
||||
.catch(function (e) {
|
||||
ui.hideIndicator('banip-action');
|
||||
ui.addNotification(null, E('p', _('Saved blocklist, but failed to reload SecBox: %s').format(e.message)), 'warning');
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleSave: function (ev) {
|
||||
const rawEl = document.getElementById('blocklist-raw-textarea');
|
||||
if (rawEl) {
|
||||
this.rawContent = rawEl.value;
|
||||
} else {
|
||||
this.rawContent = serializeBlocklist(this.items);
|
||||
}
|
||||
|
||||
return fs.write(localFile, this.rawContent || '')
|
||||
.then(function () {
|
||||
ui.addNotification(null, E('p', _('Blocklist changes saved.')), 'info');
|
||||
})
|
||||
.catch(function (e) {
|
||||
ui.addNotification(null, E('p', _('Unable to save changes: %s').format(e.message)), 'error');
|
||||
});
|
||||
},
|
||||
|
||||
handleReset: null
|
||||
});
|
||||
Reference in new issue
Block a user