mirror of
https://github.com/openwrt/luci.git
synced 2026-04-15 10:51:51 +00:00
luci-app-adblock: release 4.5.3-1
* sync with base package Signed-off-by: Dirk Brenken <dev@brenken.org>
This commit is contained in:
@@ -6,8 +6,8 @@ include $(TOPDIR)/rules.mk
|
||||
LUCI_TITLE:=LuCI support for Adblock
|
||||
LUCI_DEPENDS:=+luci-base +luci-lib-uqr +adblock
|
||||
|
||||
PKG_VERSION:=4.5.2
|
||||
PKG_RELEASE:=3
|
||||
PKG_VERSION:=4.5.3
|
||||
PKG_RELEASE:=1
|
||||
PKG_LICENSE:=Apache-2.0
|
||||
PKG_MAINTAINER:=Dirk Brenken <dev@brenken.org>
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
'require ui';
|
||||
|
||||
const localFile = '/etc/adblock/adblock.allowlist';
|
||||
let notMsg = false, errMsg = false;
|
||||
const maxSize = 100000;
|
||||
let notMsg = false;
|
||||
|
||||
const resetScroll = () => {
|
||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||
@@ -12,19 +13,24 @@ const resetScroll = () => {
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return L.resolveDefault(fs.stat(localFile), "")
|
||||
return L.resolveDefault(fs.stat(localFile), null)
|
||||
.then(function (stat) {
|
||||
if (!stat) {
|
||||
return fs.write(localFile, "");
|
||||
}
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat(localFile), ""),
|
||||
L.resolveDefault(fs.read_direct(localFile), "")
|
||||
]);
|
||||
});
|
||||
if (!stat) {
|
||||
return fs.write(localFile, "").then(() => [{ size: 0 }, ""]);
|
||||
}
|
||||
return Promise.all([
|
||||
Promise.resolve(stat),
|
||||
L.resolveDefault(fs.read_direct(localFile), "")
|
||||
]);
|
||||
});
|
||||
},
|
||||
|
||||
render: function (allowlist) {
|
||||
if (allowlist[0] && allowlist[0].size >= 100000) {
|
||||
const size = allowlist[0] ? allowlist[0].size : 0;
|
||||
const content = allowlist[1] != null ? allowlist[1] : '';
|
||||
const tooBig = size >= maxSize;
|
||||
|
||||
if (tooBig) {
|
||||
resetScroll();
|
||||
ui.addNotification(null, E('p', _('The allowlist is too big, unable to save modifications.')), 'error');
|
||||
}
|
||||
@@ -35,28 +41,29 @@ return view.extend({
|
||||
'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em',
|
||||
'spellcheck': 'false',
|
||||
'wrap': 'off',
|
||||
'rows': 25
|
||||
}, [allowlist[1] != null ? allowlist[1] : ''])
|
||||
'rows': 25,
|
||||
'readonly': tooBig ? 'readonly' : null,
|
||||
'input': function () { notMsg = false; }
|
||||
}, [content])
|
||||
]);
|
||||
},
|
||||
handleSave: function (ev) {
|
||||
let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/[^a-z0-9\.\-# \r\n]/g, '').replace(/\r\n?/g, '\n'));
|
||||
|
||||
handleSave: function (_ev) {
|
||||
const value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/[^a-z0-9.\-# \r\n]/g, '').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 adblock that changes take effect.')), 'info');
|
||||
notMsg = true;
|
||||
}
|
||||
}).catch(function (e) {
|
||||
resetScroll();
|
||||
if (!errMsg) {
|
||||
.then(function () {
|
||||
document.querySelector('textarea').value = value + "\n";
|
||||
resetScroll();
|
||||
if (!notMsg) {
|
||||
notMsg = true;
|
||||
ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload adblock that changes take effect.')), 'info');
|
||||
}
|
||||
}).catch(function (e) {
|
||||
resetScroll();
|
||||
ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
|
||||
errMsg = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,8 @@
|
||||
'require ui';
|
||||
|
||||
const localFile = '/etc/adblock/adblock.blocklist';
|
||||
let notMsg = false, errMsg = false;
|
||||
const maxSize = 100000;
|
||||
let notMsg = false;
|
||||
|
||||
const resetScroll = () => {
|
||||
document.body.scrollTop = document.documentElement.scrollTop = 0;
|
||||
@@ -12,19 +13,24 @@ const resetScroll = () => {
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return L.resolveDefault(fs.stat(localFile), "")
|
||||
return L.resolveDefault(fs.stat(localFile), null)
|
||||
.then(function (stat) {
|
||||
if (!stat) {
|
||||
return fs.write(localFile, "");
|
||||
}
|
||||
return Promise.all([
|
||||
L.resolveDefault(fs.stat(localFile), ""),
|
||||
L.resolveDefault(fs.read_direct(localFile), "")
|
||||
]);
|
||||
});
|
||||
if (!stat) {
|
||||
return fs.write(localFile, "").then(() => [{ size: 0 }, ""]);
|
||||
}
|
||||
return Promise.all([
|
||||
Promise.resolve(stat),
|
||||
L.resolveDefault(fs.read_direct(localFile), "")
|
||||
]);
|
||||
});
|
||||
},
|
||||
|
||||
render: function (blocklist) {
|
||||
if (blocklist[0] && blocklist[0].size >= 100000) {
|
||||
const size = blocklist[0] ? blocklist[0].size : 0;
|
||||
const content = blocklist[1] != null ? blocklist[1] : '';
|
||||
const tooBig = size >= maxSize;
|
||||
|
||||
if (tooBig) {
|
||||
resetScroll();
|
||||
ui.addNotification(null, E('p', _('The blocklist is too big, unable to save modifications.')), 'error');
|
||||
}
|
||||
@@ -32,31 +38,32 @@ return view.extend({
|
||||
E('p', _('This is the local adblock blocklist to always-block certain domains.<br /> \
|
||||
<em><b>Please note:</b></em> add only one domain per line. Comments introduced with \'#\' are allowed - ip addresses, wildcards and regex are not.')),
|
||||
E('textarea', {
|
||||
'style': 'min-height: 500px; max-height: 90vh; width: 100%; padding: 5px; font-family: monospace; resize: vertical;',
|
||||
'style': 'width: 100% !important; padding: 5px; font-family: monospace; margin-top: .4em',
|
||||
'spellcheck': 'false',
|
||||
'wrap': 'off',
|
||||
'rows': 25
|
||||
}, [blocklist[1] != null ? blocklist[1] : ''])
|
||||
'rows': 25,
|
||||
'readonly': tooBig ? 'readonly' : null,
|
||||
'input': function () { notMsg = false; }
|
||||
}, [content])
|
||||
]);
|
||||
},
|
||||
handleSave: function (ev) {
|
||||
let value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/[^a-z0-9\.\-# \r\n]/g, '').replace(/\r\n?/g, '\n'));
|
||||
|
||||
handleSave: function (_ev) {
|
||||
const value = ((document.querySelector('textarea').value || '').trim().toLowerCase().replace(/[^a-z0-9.\-# \r\n]/g, '').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 adblock that changes take effect.')), 'info');
|
||||
notMsg = true;
|
||||
}
|
||||
}).catch(function (e) {
|
||||
resetScroll();
|
||||
if (!errMsg) {
|
||||
.then(function () {
|
||||
document.querySelector('textarea').value = value + "\n";
|
||||
resetScroll();
|
||||
if (!notMsg) {
|
||||
notMsg = true;
|
||||
ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload adblock that changes take effect.')), 'info');
|
||||
}
|
||||
}).catch(function (e) {
|
||||
resetScroll();
|
||||
ui.addNotification(null, E('p', _('Unable to save modifications: %s').format(e.message)), 'error');
|
||||
errMsg = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleReset: null
|
||||
});
|
||||
});
|
||||
@@ -4,7 +4,9 @@
|
||||
'require ui';
|
||||
'require uci';
|
||||
|
||||
var notMsg = false, errMsg = false;
|
||||
/* separate flags per notification context */
|
||||
let listNotMsg = false;
|
||||
let mapNotMsg = false;
|
||||
|
||||
/*
|
||||
button handling
|
||||
@@ -29,13 +31,13 @@ function handleAction(ev) {
|
||||
'click': ui.createHandlerFn(this, function (ev) {
|
||||
L.resolveDefault(fs.read_direct('/etc/adblock/adblock.blocklist'), '')
|
||||
.then(function (res) {
|
||||
var domain = document.getElementById('blocklist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g, '');
|
||||
var pattern = new RegExp('^' + domain.replace(/[\.]/g, '\\.') + '$', 'm');
|
||||
const domain = document.getElementById('blocklist').value.trim().toLowerCase().replace(/[^a-z0-9.\-]/g, '');
|
||||
const pattern = new RegExp('^' + domain.replace(/[.]/g, '\\.') + '$', 'm');
|
||||
if (res.search(pattern) === -1) {
|
||||
var blocklist = res + domain + '\n';
|
||||
const blocklist = res + domain + '\n';
|
||||
fs.write('/etc/adblock/adblock.blocklist', blocklist);
|
||||
if (!notMsg) {
|
||||
notMsg = true;
|
||||
if (!listNotMsg) {
|
||||
listNotMsg = true;
|
||||
ui.addNotification(null, E('p', _('Blocklist modifications have been saved, reload adblock that changes take effect.')), 'info');
|
||||
}
|
||||
}
|
||||
@@ -67,13 +69,13 @@ function handleAction(ev) {
|
||||
'click': ui.createHandlerFn(this, function (ev) {
|
||||
L.resolveDefault(fs.read_direct('/etc/adblock/adblock.allowlist'), '')
|
||||
.then(function (res) {
|
||||
var domain = document.getElementById('allowlist').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g, '');
|
||||
var pattern = new RegExp('^' + domain.replace(/[\.]/g, '\\.') + '$', 'm');
|
||||
const domain = document.getElementById('allowlist').value.trim().toLowerCase().replace(/[^a-z0-9.\-]/g, '');
|
||||
const pattern = new RegExp('^' + domain.replace(/[.]/g, '\\.') + '$', 'm');
|
||||
if (res.search(pattern) === -1) {
|
||||
var allowlist = res + domain + '\n';
|
||||
const allowlist = res + domain + '\n';
|
||||
fs.write('/etc/adblock/adblock.allowlist', allowlist);
|
||||
if (!notMsg) {
|
||||
notMsg = true;
|
||||
if (!listNotMsg) {
|
||||
listNotMsg = true;
|
||||
ui.addNotification(null, E('p', _('Allowlist modifications have been saved, reload adblock that changes take effect.')), 'info');
|
||||
}
|
||||
}
|
||||
@@ -120,7 +122,7 @@ function handleAction(ev) {
|
||||
E('button', {
|
||||
'class': 'btn cbi-button-action',
|
||||
'click': ui.createHandlerFn(this, function (ev) {
|
||||
const domain = document.getElementById('search').value.trim().toLowerCase().replace(/[^a-z0-9\.\-]/g, '');
|
||||
const domain = document.getElementById('search').value.trim().toLowerCase().replace(/[^a-z0-9.\-]/g, '');
|
||||
if (domain) {
|
||||
document.getElementById('run').classList.add("spinning");
|
||||
document.getElementById('search').value = domain;
|
||||
@@ -134,7 +136,7 @@ function handleAction(ev) {
|
||||
}
|
||||
document.getElementById('run').classList.remove("spinning");
|
||||
document.getElementById('search').value = '';
|
||||
})
|
||||
});
|
||||
}
|
||||
document.getElementById('search').focus();
|
||||
})
|
||||
@@ -173,8 +175,7 @@ function handleAction(ev) {
|
||||
])
|
||||
]),
|
||||
E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
|
||||
E('input', { 'class': 'cbi-input-text', 'spellcheck': 'false', 'id': 'search' }, [
|
||||
]),
|
||||
E('input', { 'class': 'cbi-input-text', 'spellcheck': 'false', 'id': 'search' }, []),
|
||||
'\xa0\xa0\xa0',
|
||||
_('Filter criteria like date, domain or client (optional)')
|
||||
]),
|
||||
@@ -190,16 +191,16 @@ function handleAction(ev) {
|
||||
'click': function () {
|
||||
document.querySelectorAll('.cbi-page-actions button').forEach(function (btn) {
|
||||
btn.disabled = true;
|
||||
})
|
||||
});
|
||||
this.blur();
|
||||
this.classList.add('spinning');
|
||||
const top_count = document.getElementById('top_count').value;
|
||||
const res_count = document.getElementById('res_count').value;
|
||||
const search = document.getElementById('search').value.trim().replace(/[^\w\.\-\:]/g, '') || '+';
|
||||
const search = document.getElementById('search').value.trim().replace(/[^\w.\-:]/g, '') || '+';
|
||||
L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['report', 'gen', top_count, res_count, search]), '')
|
||||
.then(function () {
|
||||
location.reload();
|
||||
})
|
||||
});
|
||||
}
|
||||
}, _('Refresh'))
|
||||
])
|
||||
@@ -284,30 +285,17 @@ return view.extend({
|
||||
let a_cnt = '\xa0', a_addr = '\xa0', b_cnt = '\xa0', b_addr = '\xa0', c_cnt = '\xa0', c_addr = '\xa0';
|
||||
if (content[0].top_clients[i]) {
|
||||
a_cnt = content[0].top_clients[i].count;
|
||||
}
|
||||
if (content[0].top_clients[i]) {
|
||||
a_addr = content[0].top_clients[i].address;
|
||||
}
|
||||
if (content[0].top_domains[i]) {
|
||||
b_cnt = content[0].top_domains[i].count;
|
||||
}
|
||||
if (content[0].top_domains[i]) {
|
||||
b_addr = '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].top_domains[i].address) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].top_domains[i].address + '</a>';
|
||||
}
|
||||
if (content[0].top_blocked[i]) {
|
||||
c_cnt = content[0].top_blocked[i].count;
|
||||
}
|
||||
if (content[0].top_blocked[i]) {
|
||||
c_addr = '<a href="https://ip-api.com/#' + encodeURIComponent(content[0].top_blocked[i].address) + '" target="_blank" rel="noreferrer noopener" title="Domain Lookup">' + content[0].top_blocked[i].address + '</a>';
|
||||
}
|
||||
rows_top.push([
|
||||
a_cnt,
|
||||
a_addr,
|
||||
b_cnt,
|
||||
b_addr,
|
||||
c_cnt,
|
||||
c_addr
|
||||
]);
|
||||
rows_top.push([a_cnt, a_addr, b_cnt, b_addr, c_cnt, c_addr]);
|
||||
}
|
||||
cbi_update_table(tbl_top, rows_top);
|
||||
|
||||
@@ -325,11 +313,10 @@ return view.extend({
|
||||
])
|
||||
]);
|
||||
|
||||
max = 0;
|
||||
if (content[0].requests) {
|
||||
let button;
|
||||
max = content[0].requests.length;
|
||||
for (let i = 0; i < max; i++) {
|
||||
let button;
|
||||
if (content[0].requests[i].rc === 'NX') {
|
||||
button = E('button', {
|
||||
'class': 'btn cbi-button cbi-button-positive',
|
||||
@@ -365,7 +352,7 @@ return view.extend({
|
||||
|
||||
const page = E('div', { 'class': 'cbi-map', 'id': 'map' }, [
|
||||
E('div', { 'class': 'cbi-section' }, [
|
||||
E('p', _('This tab displays the most recently generated DNS report. Use the \‘Refresh\’ button to update it.')),
|
||||
E('p', _('This tab displays the most recently generated DNS report. Use the \'Refresh\' button to update it.')),
|
||||
E('div', { 'class': 'cbi-value', 'style': 'position:relative;min-height:220px' }, [
|
||||
E('div', {
|
||||
'style': 'position:absolute; top:0; right:0; text-align:center'
|
||||
@@ -408,7 +395,7 @@ return view.extend({
|
||||
'style': 'float:none;margin-right:.4em;',
|
||||
'id': 'btnTest',
|
||||
'title': 'Adblock Test',
|
||||
'click': function() {
|
||||
'click': function () {
|
||||
window.open('https://adblock.turtlecute.org/', '_blank', 'noopener,noreferrer');
|
||||
}
|
||||
}, [_('Adblock Test')]),
|
||||
@@ -419,13 +406,12 @@ return view.extend({
|
||||
'title': 'Map',
|
||||
'disabled': 'disabled',
|
||||
'click': ui.createHandlerFn(this, function () {
|
||||
if (content[1] && content[1].length > 1) {
|
||||
if (Array.isArray(content[1]) && content[1].length > 1) {
|
||||
sessionStorage.setItem('mapData', JSON.stringify(content[1]));
|
||||
return handleAction('map');
|
||||
}
|
||||
else {
|
||||
if (!notMsg) {
|
||||
notMsg = true;
|
||||
} else {
|
||||
if (!mapNotMsg) {
|
||||
mapNotMsg = true;
|
||||
return ui.addNotification(null, E('p', _('No GeoIP Map data!')), 'info');
|
||||
}
|
||||
}
|
||||
@@ -449,40 +435,38 @@ return view.extend({
|
||||
}, [_('Refresh...')])
|
||||
])
|
||||
]);
|
||||
|
||||
if (uci.get('adblock', 'global', 'adb_map') === '1') {
|
||||
const btn = page.querySelector('#btnMap');
|
||||
if (btn) {
|
||||
btn.removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/* Draw Pie Chart with Tooltip */
|
||||
const tooltip = E('div', {
|
||||
const tooltipEl = E('div', {
|
||||
id: 'dnsPieTooltip',
|
||||
style: 'position:absolute; padding:6px 10px; background:#333; color:#fff; border-radius:4px; font-size:12px; pointer-events:none; opacity:0; transition:opacity .15s; z-index:9999'
|
||||
});
|
||||
document.body.appendChild(tooltip);
|
||||
setTimeout(function() {
|
||||
document.body.appendChild(tooltipEl);
|
||||
|
||||
setTimeout(function () {
|
||||
const total = Number(content[0].total || 0);
|
||||
const blocked = Number(content[0].blocked || 0);
|
||||
const allowed = Math.max(total - blocked, 0);
|
||||
|
||||
const canvas = document.getElementById('dnsPie');
|
||||
if (!canvas || total <= 0)
|
||||
return;
|
||||
if (!canvas || total <= 0) return;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
const colors = {
|
||||
blocked: '#b04a4a',
|
||||
allowed: '#6a8f6a'
|
||||
};
|
||||
const colors = { blocked: '#b04a4a', allowed: '#6a8f6a' };
|
||||
let finalRot = 0;
|
||||
|
||||
function drawPie(rotation = 0) {
|
||||
function drawPie(rotation) {
|
||||
const w = canvas.clientWidth;
|
||||
canvas.width = w;
|
||||
canvas.height = w;
|
||||
const cx = w / 2;
|
||||
const cy = w / 2;
|
||||
const r = (w / 2) - 4;
|
||||
const cx = w / 2, cy = w / 2, r = (w / 2) - 4;
|
||||
const blockedAngle = (blocked / total) * 2 * Math.PI;
|
||||
const allowedAngle = (allowed / total) * 2 * Math.PI;
|
||||
|
||||
@@ -505,43 +489,48 @@ return view.extend({
|
||||
ctx.lineWidth = 2;
|
||||
ctx.stroke();
|
||||
}
|
||||
|
||||
let rot = 0;
|
||||
function animate() {
|
||||
rot += 0.10;
|
||||
drawPie(rot);
|
||||
if (rot < Math.PI * 2)
|
||||
if (rot < Math.PI * 2) {
|
||||
requestAnimationFrame(animate);
|
||||
} else {
|
||||
finalRot = rot % (2 * Math.PI);
|
||||
drawPie(finalRot);
|
||||
}
|
||||
}
|
||||
animate();
|
||||
window.addEventListener('resize', function() {
|
||||
drawPie(rot);
|
||||
|
||||
window.addEventListener('resize', function () {
|
||||
drawPie(finalRot);
|
||||
});
|
||||
|
||||
const tooltip = document.getElementById('dnsPieTooltip');
|
||||
canvas.addEventListener('mousemove', function(ev) {
|
||||
const blockedAngle = (blocked / total) * 2 * Math.PI;
|
||||
|
||||
canvas.addEventListener('mousemove', function (ev) {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const x = ev.clientX - rect.left;
|
||||
const y = ev.clientY - rect.top;
|
||||
const cx = canvas.width / 2;
|
||||
const cy = canvas.height / 2;
|
||||
const dx = x - cx;
|
||||
const dy = y - cy;
|
||||
const dist = Math.sqrt(dx*dx + dy*dy);
|
||||
if (dist > canvas.width/2 - 4) {
|
||||
const cx = canvas.width / 2, cy = canvas.height / 2;
|
||||
const dx = x - cx, dy = y - cy;
|
||||
const dist = Math.sqrt(dx * dx + dy * dy);
|
||||
if (dist > canvas.width / 2 - 4) {
|
||||
tooltip.style.opacity = 0;
|
||||
return;
|
||||
}
|
||||
let angle = Math.atan2(dy, dx);
|
||||
/* normalise angle relative to finalRot so it matches the drawn slices */
|
||||
let angle = Math.atan2(dy, dx) - finalRot;
|
||||
if (angle < 0) angle += 2 * Math.PI;
|
||||
|
||||
const blockedAngle = (blocked / total) * 2 * Math.PI;
|
||||
let label, abs, pct;
|
||||
if (angle < blockedAngle) {
|
||||
label = 'Blocked';
|
||||
abs = blocked;
|
||||
label = 'Blocked'; abs = blocked;
|
||||
pct = ((blocked / total) * 100).toFixed(1) + '%';
|
||||
} else {
|
||||
label = 'Allowed';
|
||||
abs = allowed;
|
||||
label = 'Allowed'; abs = allowed;
|
||||
pct = ((allowed / total) * 100).toFixed(1) + '%';
|
||||
}
|
||||
tooltip.textContent = `${label}: ${abs} (${pct})`;
|
||||
@@ -549,13 +538,15 @@ return view.extend({
|
||||
tooltip.style.top = ev.pageY + 12 + 'px';
|
||||
tooltip.style.opacity = 1;
|
||||
});
|
||||
canvas.addEventListener('mouseleave', function() {
|
||||
|
||||
canvas.addEventListener('mouseleave', function () {
|
||||
tooltip.style.opacity = 0;
|
||||
});
|
||||
}, 0);
|
||||
|
||||
return page;
|
||||
},
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
});
|
||||
@@ -13,6 +13,26 @@ document.querySelector('head').appendChild(E('link', {
|
||||
'href': L.resource('view/adblock/custom.css')
|
||||
}));
|
||||
|
||||
/*
|
||||
module-level file size set during render, read by observer
|
||||
*/
|
||||
let fileSize = 0;
|
||||
|
||||
/*
|
||||
button state helper
|
||||
*/
|
||||
function updateButtons() {
|
||||
const buttons = document.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload');
|
||||
if (fileSize === 0) {
|
||||
if (buttons[1]) buttons[1].removeAttribute('disabled');
|
||||
if (buttons[2]) buttons[2].removeAttribute('disabled');
|
||||
} else {
|
||||
if (buttons[0]) buttons[0].removeAttribute('disabled');
|
||||
if (buttons[3]) buttons[3].removeAttribute('disabled');
|
||||
if (buttons[4]) buttons[4].removeAttribute('disabled');
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
observe DOM changes
|
||||
*/
|
||||
@@ -29,17 +49,7 @@ const observer = new MutationObserver(function (mutations) {
|
||||
labels.forEach(function (label) {
|
||||
label.setAttribute("style", "font-weight: bold !important; color: #595 !important;");
|
||||
})
|
||||
L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), '').then(function (stat) {
|
||||
const buttons = document.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload');
|
||||
if (buttons[1] && buttons[2] && stat.size === 0) {
|
||||
buttons[1].removeAttribute('disabled');
|
||||
buttons[2].removeAttribute('disabled');
|
||||
} else if (buttons[0] && buttons[3] && buttons[4] && stat.size > 0) {
|
||||
buttons[0].removeAttribute('disabled');
|
||||
buttons[3].removeAttribute('disabled');
|
||||
buttons[4].removeAttribute('disabled');
|
||||
}
|
||||
});
|
||||
updateButtons();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -59,28 +69,21 @@ function handleEdit(ev) {
|
||||
if (ev === 'upload') {
|
||||
return ui.uploadFile('/etc/adblock/adblock.custom.feeds').then(function () {
|
||||
L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "").then(function (data) {
|
||||
if (data) {
|
||||
let dataLength = Object.keys(data).length || 0;
|
||||
if (dataLength > 0) {
|
||||
for (let i = 0; i < dataLength; i++) {
|
||||
let feed = Object.keys(data)[i];
|
||||
let descr = data[feed].descr;
|
||||
if (feed && descr) {
|
||||
continue;
|
||||
}
|
||||
fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
|
||||
return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
|
||||
});
|
||||
if (data && Object.keys(data).length > 0) {
|
||||
for (let i = 0; i < Object.keys(data).length; i++) {
|
||||
let feed = Object.keys(data)[i];
|
||||
let descr = data[feed].descr;
|
||||
if (feed && descr) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
|
||||
return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
|
||||
return fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
|
||||
ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
|
||||
});
|
||||
}
|
||||
location.reload();
|
||||
} else {
|
||||
fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
|
||||
return ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
|
||||
return fs.write('/etc/adblock/adblock.custom.feeds', null).then(function () {
|
||||
ui.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -118,9 +121,9 @@ function handleEdit(ev) {
|
||||
}
|
||||
}
|
||||
/*
|
||||
gather all input data
|
||||
gather all input data and fall through from 'save'
|
||||
*/
|
||||
let sumSubElements = [];
|
||||
const exportObj = {};
|
||||
const nodeKeys = document.querySelectorAll('[id^="widget.cbid.json"][id$="name"]');
|
||||
for (const keyNode of nodeKeys) {
|
||||
const keyValue = keyNode.value?.trim();
|
||||
@@ -142,40 +145,39 @@ function handleEdit(ev) {
|
||||
sub[key] = value;
|
||||
}
|
||||
}
|
||||
if (sub.descr) {
|
||||
sumSubElements.push(keyValue, sub);
|
||||
/* require at least descr and url to produce a valid feed entry */
|
||||
if (sub.descr && sub.url) {
|
||||
exportObj[keyValue] = sub;
|
||||
}
|
||||
}
|
||||
/*
|
||||
construct json object
|
||||
*/
|
||||
let exportObj = {};
|
||||
for (let i = 0; i < sumSubElements.length; i += 2) {
|
||||
const key = sumSubElements[i];
|
||||
const value = sumSubElements[i + 1];
|
||||
exportObj[key] = value;
|
||||
}
|
||||
const exportJson = JSON.stringify(exportObj, null, 4);
|
||||
/*
|
||||
save to file and reload
|
||||
*/
|
||||
const exportJson = JSON.stringify(exportObj, null, 4);
|
||||
return fs.write('/etc/adblock/adblock.custom.feeds', exportJson)
|
||||
.then(() => location.reload());
|
||||
}
|
||||
|
||||
return view.extend({
|
||||
load: function () {
|
||||
return L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), "")
|
||||
return L.resolveDefault(fs.stat('/etc/adblock/adblock.custom.feeds'), null)
|
||||
.then(function (stat) {
|
||||
if (!stat) {
|
||||
return fs.write('/etc/adblock/adblock.custom.feeds', "");
|
||||
}
|
||||
return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "");
|
||||
});
|
||||
if (!stat) {
|
||||
return fs.write('/etc/adblock/adblock.custom.feeds', "").then(function () {
|
||||
return { size: 0, data: null };
|
||||
});
|
||||
}
|
||||
return L.resolveDefault(fs.read_direct('/etc/adblock/adblock.custom.feeds', 'json'), "")
|
||||
.then(function (data) {
|
||||
return { size: stat.size, data: data };
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
render: function (data) {
|
||||
render: function (result) {
|
||||
let m, s, o, feed, url, rule, size, descr;
|
||||
const data = result.data;
|
||||
fileSize = result.size;
|
||||
|
||||
m = new form.JSONMap(data, null, _('With this editor you can upload your local custom feed file or fill up an initial one (a 1:1 copy of the version shipped with the package). \
|
||||
The file is located at \'/etc/adblock/adblock.custom.feeds\'. \
|
||||
@@ -209,7 +211,7 @@ return view.extend({
|
||||
if (!value) {
|
||||
return true;
|
||||
}
|
||||
if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-\?\&\+_@%=:~#]+$/)) {
|
||||
if (!value.match(/^https?:\/\/[A-Za-z0-9\[\]\/.\-?&+_@%=:~#]+$/)) {
|
||||
return _('Protocol/URL format not supported');
|
||||
}
|
||||
return true;
|
||||
@@ -219,7 +221,7 @@ return view.extend({
|
||||
o.value('feed 1', _('<Domain>'));
|
||||
o.value('feed 127.0.0.1 2', _('127.0.0.1 <Domain>'));
|
||||
o.value('feed 0.0.0.0 2', _('0.0.0.0 <Domain>'));
|
||||
o.value('feed 3 [|^]', _('<Adblock Plus Syntax>'));
|
||||
o.value('feed || 3 [|^]', _('<Adblock Plus Syntax>'));
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
@@ -294,11 +296,11 @@ return view.extend({
|
||||
return handleEdit('save');
|
||||
})
|
||||
}, [_('Save')]),
|
||||
])
|
||||
]);
|
||||
});
|
||||
return m.render();
|
||||
},
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
});
|
||||
@@ -12,37 +12,39 @@ function Logview(logtag, name) {
|
||||
return L.view.extend({
|
||||
load: () => Promise.resolve(),
|
||||
|
||||
render: () => {
|
||||
L.Poll.add(() => {
|
||||
render: function () {
|
||||
const pollFn = () => {
|
||||
return callLogRead(1000, false, true).then(res => {
|
||||
const logEl = document.getElementById('logfile');
|
||||
if (!logEl) return;
|
||||
|
||||
const filtered = (res?.log ?? [])
|
||||
.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'
|
||||
.filter(entry => !logtag || entry.msg.includes(logtag))
|
||||
.map(entry => {
|
||||
const d = new Date(entry.time * 1000);
|
||||
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}`;
|
||||
});
|
||||
const time = d.toLocaleTimeString([], {
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hour12: false
|
||||
});
|
||||
return `[${date}-${time}] ${entry.msg}`;
|
||||
});
|
||||
if (filtered.length > 0) {
|
||||
logEl.value = filtered.join('\n');
|
||||
} else {
|
||||
logEl.value = _('No %s related logs yet!').format(name);
|
||||
}
|
||||
|
||||
logEl.value = filtered.length > 0
|
||||
? filtered.join('\n')
|
||||
: _('No %s related logs yet!').format(name);
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
this._pollFn = pollFn;
|
||||
L.Poll.add(pollFn);
|
||||
|
||||
return E('div', { class: 'cbi-map' }, [
|
||||
E('div', { class: 'cbi-section' }, [
|
||||
@@ -58,10 +60,17 @@ function Logview(logtag, name) {
|
||||
]);
|
||||
},
|
||||
|
||||
unload: function () {
|
||||
if (this._pollFn) {
|
||||
L.Poll.remove(this._pollFn);
|
||||
this._pollFn = null;
|
||||
}
|
||||
},
|
||||
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
}
|
||||
|
||||
return L.Class.extend({ Logview });
|
||||
return L.Class.extend({ Logview });
|
||||
@@ -24,7 +24,7 @@ function handleAction(ev) {
|
||||
btn.blur();
|
||||
});
|
||||
return fs.exec_direct('/etc/init.d/adblock', [ev]);
|
||||
})
|
||||
});
|
||||
} else {
|
||||
if (ev !== 'stop') {
|
||||
document.querySelectorAll('.cbi-page-actions button').forEach(function (btn) {
|
||||
@@ -49,6 +49,7 @@ return view.extend({
|
||||
`https://${window.location.hostname}/cgi-bin/adblock`
|
||||
]);
|
||||
},
|
||||
|
||||
render: function (result) {
|
||||
/*
|
||||
config check
|
||||
@@ -78,6 +79,7 @@ return view.extend({
|
||||
/*
|
||||
poll runtime information
|
||||
*/
|
||||
let parseErrCount = 0;
|
||||
poll.add(function () {
|
||||
return L.resolveDefault(fs.stat('/var/run/adb_runtime.json'), null).then(function (stat) {
|
||||
if (!stat) {
|
||||
@@ -89,15 +91,17 @@ return view.extend({
|
||||
let info = null;
|
||||
try {
|
||||
info = JSON.parse(res);
|
||||
parseErrCount = 0;
|
||||
} catch (e) {
|
||||
info = null;
|
||||
parseErrCount++;
|
||||
if (status) {
|
||||
status.textContent = '-';
|
||||
if (status.classList.contains('spinning')) {
|
||||
buttons.forEach(function (btn) {
|
||||
btn.disabled = false;
|
||||
})
|
||||
status.classList.remove('spinning');
|
||||
buttons.forEach(function (btn) {
|
||||
btn.disabled = false;
|
||||
});
|
||||
status.classList.remove('spinning');
|
||||
if (parseErrCount >= 3) {
|
||||
ui.addNotification(null, E('p', _('Unable to parse the adblock runtime information!')), 'error');
|
||||
poll.stop();
|
||||
}
|
||||
@@ -110,7 +114,7 @@ return view.extend({
|
||||
buttons.forEach(function (btn) {
|
||||
btn.disabled = true;
|
||||
btn.blur();
|
||||
})
|
||||
});
|
||||
if (!status.classList.contains("spinning")) {
|
||||
status.classList.add("spinning");
|
||||
}
|
||||
@@ -126,7 +130,7 @@ return view.extend({
|
||||
}
|
||||
buttons.forEach(function (btn) {
|
||||
btn.disabled = false;
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
if (info) {
|
||||
@@ -180,7 +184,7 @@ return view.extend({
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('Last Run')),
|
||||
E('div', { 'class': 'cbi-value-field', 'id': 'last', 'style': 'margin-bottom:- 5px; color:#37c; ' }, ' - ')
|
||||
E('div', { 'class': 'cbi-value-field', 'id': 'last', 'style': 'margin-bottom:-5px;color:#37c;' }, '-')
|
||||
]),
|
||||
E('div', { 'class': 'cbi-value' }, [
|
||||
E('label', { 'class': 'cbi-value-title', 'style': 'margin-bottom:-5px;padding-top:0rem;' }, _('System Info')),
|
||||
@@ -224,7 +228,7 @@ return view.extend({
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('general', form.Flag, 'adb_tld', _('TLD Compression'), _('The top level domain compression removes thousands of needless host entries from the final DNS blocklist.'));
|
||||
o.default = 1
|
||||
o.default = 1;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('general', form.Flag, 'adb_safesearch', _('Enable SafeSearch'), _('Enforcing SafeSearch for google, bing, brave, duckduckgo, yandex, youtube and pixabay.'));
|
||||
@@ -303,13 +307,13 @@ return view.extend({
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('additional', form.Flag, 'adb_fetchinsecure', _('Download Insecure'), _('Don\'t check SSL server certificates during download.'));
|
||||
o.default = 0
|
||||
o.default = 0;
|
||||
o.rmempty = true;
|
||||
|
||||
/*
|
||||
firewall settings tab
|
||||
*/
|
||||
o = s.taboption('firewall', form.DummyValue, '_sub');
|
||||
o = s.taboption('firewall', form.DummyValue, '_fw_sub1');
|
||||
o.rawhtml = true;
|
||||
o.default = '<em style="color:#37c;font-weight:bold;">' + _('Changes on this tab needs an adblock service restart to take effect.') + '</em>'
|
||||
+ '<hr style="width: 200px; height: 1px;" />'
|
||||
@@ -357,7 +361,7 @@ return view.extend({
|
||||
o.default = '2a13:1001::86:54:11:100';
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('firewall', form.DummyValue, '_sub');
|
||||
o = s.taboption('firewall', form.DummyValue, '_fw_sub2');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External Filtered DNS Policy (MAC-/Interface‑based DNS bypass)') + '</em>';
|
||||
|
||||
@@ -421,7 +425,7 @@ return view.extend({
|
||||
o.default = '2a13:1001::86:54:11:13';
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('firewall', form.DummyValue, '_sub');
|
||||
o = s.taboption('firewall', form.DummyValue, '_fw_sub3');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External Remote DNS Policy (temporary MAC‑based remote DNS bypass)') + '</em>';
|
||||
|
||||
@@ -481,12 +485,12 @@ return view.extend({
|
||||
blackColor: 'black'
|
||||
};
|
||||
const svg = uqr.renderSVG(url, options);
|
||||
o = s.taboption('firewall', form.DummyValue, '_sub', _('QRCode for Remote Access'));
|
||||
o = s.taboption('firewall', form.DummyValue, '_fw_qr', _('QRCode for Remote Access'));
|
||||
o.rawhtml = true;
|
||||
o.default = svg;
|
||||
}
|
||||
|
||||
o = s.taboption('firewall', form.DummyValue, '_sub');
|
||||
o = s.taboption('firewall', form.DummyValue, '_fw_sub4');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('External DNS Bridge (Zero‑Downtime during DNS Restarts)') + '</em>';
|
||||
|
||||
@@ -547,7 +551,7 @@ return view.extend({
|
||||
o.default = '2a13:1001::86:54:11:13';
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('firewall', form.DummyValue, '_sub');
|
||||
o = s.taboption('firewall', form.DummyValue, '_fw_sub5');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('Local DNS Enforcement') + '</em>';
|
||||
|
||||
@@ -721,12 +725,9 @@ return view.extend({
|
||||
/*
|
||||
prepare category data
|
||||
*/
|
||||
var code, category, list, path, categories = [];
|
||||
if (result[2]) {
|
||||
categories = result[2].trim().split('\n');
|
||||
}
|
||||
const categories = result[2] ? result[2].trim().split('\n') : [];
|
||||
|
||||
o = s.taboption('feeds', form.DummyValue, '_sub');
|
||||
o = s.taboption('feeds', form.DummyValue, '_feeds1');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('1Hosts List Selection') + '</em>';
|
||||
|
||||
@@ -734,19 +735,14 @@ return view.extend({
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
|
||||
if (!cat) continue;
|
||||
|
||||
const code = cat[1].trim();
|
||||
const list = cat[2].trim();
|
||||
const path = cat[3].trim();
|
||||
|
||||
if (code === 'hst') {
|
||||
o.value(path, list);
|
||||
if (cat[1].trim() === 'hst') {
|
||||
o.value(cat[3].trim(), cat[2].trim());
|
||||
}
|
||||
}
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('feeds', form.DummyValue, '_sub');
|
||||
o = s.taboption('feeds', form.DummyValue, '_feeds2');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('Hagezi List Selection') + '</em>';
|
||||
|
||||
@@ -754,19 +750,14 @@ return view.extend({
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
|
||||
if (!cat) continue;
|
||||
|
||||
const code = cat[1].trim();
|
||||
const list = cat[2].trim();
|
||||
const path = cat[3].trim();
|
||||
|
||||
if (code === 'hag') {
|
||||
o.value(path, list);
|
||||
if (cat[1].trim() === 'hag') {
|
||||
o.value(cat[3].trim(), cat[2].trim());
|
||||
}
|
||||
}
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('feeds', form.DummyValue, '_sub');
|
||||
o = s.taboption('feeds', form.DummyValue, '_feeds3');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('IPFire List Selection') + '</em>';
|
||||
|
||||
@@ -774,19 +765,14 @@ return view.extend({
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
|
||||
if (!cat) continue;
|
||||
|
||||
const code = cat[1].trim();
|
||||
const list = cat[2].trim();
|
||||
const path = cat[3].trim();
|
||||
|
||||
if (code === 'ipf') {
|
||||
o.value(path, list);
|
||||
if (cat[1].trim() === 'ipf') {
|
||||
o.value(cat[3].trim(), cat[2].trim());
|
||||
}
|
||||
}
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('feeds', form.DummyValue, '_sub');
|
||||
o = s.taboption('feeds', form.DummyValue, '_feeds4');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('StevenBlack List Selection') + '</em>';
|
||||
|
||||
@@ -794,19 +780,14 @@ return view.extend({
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const cat = categories[i].match(/^(\w+);(.*?);(.*)$/);
|
||||
if (!cat) continue;
|
||||
|
||||
const code = cat[1].trim();
|
||||
const list = cat[2].trim();
|
||||
const path = cat[3].trim();
|
||||
|
||||
if (code === 'stb') {
|
||||
o.value(path, list);
|
||||
if (cat[1].trim() === 'stb') {
|
||||
o.value(cat[3].trim(), cat[2].trim());
|
||||
}
|
||||
}
|
||||
o.optional = true;
|
||||
o.rmempty = true;
|
||||
|
||||
o = s.taboption('feeds', form.DummyValue, '_sub');
|
||||
o = s.taboption('feeds', form.DummyValue, '_feeds5');
|
||||
o.rawhtml = true;
|
||||
o.default = '<hr style="width: 200px; height: 1px;" /><em style="color:#37c;font-weight:bold;">' + _('UTCapitole Archive Selection') + '</em>';
|
||||
|
||||
@@ -814,12 +795,8 @@ return view.extend({
|
||||
for (let i = 0; i < categories.length; i++) {
|
||||
const cat = categories[i].match(/^(\w+);(.*)$/);
|
||||
if (!cat) continue;
|
||||
|
||||
const code = cat[1].trim();
|
||||
const category = cat[2].trim();
|
||||
|
||||
if (code === 'utc') {
|
||||
o.value(category);
|
||||
if (cat[1].trim() === 'utc') {
|
||||
o.value(cat[2].trim());
|
||||
}
|
||||
}
|
||||
o.optional = true;
|
||||
@@ -861,14 +838,14 @@ return view.extend({
|
||||
'style': 'float:none',
|
||||
'title': 'Save & Restart',
|
||||
'click': function () {
|
||||
handleAction('restart');
|
||||
return handleAction('restart');
|
||||
}
|
||||
}, [_('Save & Restart')])
|
||||
])
|
||||
]);
|
||||
});
|
||||
return m.render();
|
||||
},
|
||||
handleSaveApply: null,
|
||||
handleSave: null,
|
||||
handleReset: null
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user