10 document
.querySelector('head').appendChild(E('link', {
13 'href': L
.resource('view/banip/custom.css')
19 const observer
= new MutationObserver(function (mutations
) {
21 const inputs
= document
.querySelectorAll('input');
22 inputs
.forEach(function (input
) {
23 input
.setAttribute('autocomplete', 'off')
24 input
.setAttribute('autocorrect', 'off')
25 input
.setAttribute('autocapitalize', 'off')
26 input
.setAttribute('spellcheck', false)
28 const labels
= document
.querySelectorAll('label[for^="widget.cbid.json"][for$="name"]');
29 labels
.forEach(function (label
) {
30 label
.setAttribute("style", "font-weight: bold !important; color: #595 !important;");
32 L
.resolveDefault(fs
.stat('/etc/banip/banip.custom.feeds'), '').then(function (stat
) {
33 const buttons
= document
.querySelectorAll('#btnClear, #btnCreate, #btnSave, #btnUpload, #btnDownload');
34 if (buttons
[1] && buttons
[2] && stat
.size
=== 0) {
35 buttons
[1].removeAttribute('disabled');
36 buttons
[2].removeAttribute('disabled');
37 } else if (buttons
[0] && buttons
[3] && buttons
[4] && stat
.size
> 0) {
38 buttons
[0].removeAttribute('disabled');
39 buttons
[3].removeAttribute('disabled');
40 buttons
[4].removeAttribute('disabled');
46 const targetNode
= document
.getElementById('view');
47 const observerConfig
= {
53 observer
.observe(targetNode
, observerConfig
);
58 function handleEdit(ev
) {
59 if (ev
=== 'upload') {
60 return ui
.uploadFile('/etc/banip/banip.custom.feeds').then(function () {
61 L
.resolveDefault(fs
.read_direct('/etc/banip/banip.custom.feeds', 'json'), "").then(function (data
) {
63 let dataLength
= Object
.keys(data
).length
|| 0;
65 for (let i
= 0; i
< dataLength
; i
++) {
66 let feed
= Object
.keys(data
)[i
];
67 let descr
= data
[feed
].descr
;
71 fs
.write('/etc/banip/banip.custom.feeds', null).then(function () {
72 ui
.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
77 fs
.write('/etc/banip/banip.custom.feeds', null).then(function () {
78 ui
.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
84 fs
.write('/etc/banip/banip.custom.feeds', null).then(function () {
85 ui
.addNotification(null, E('p', _('Upload of the custom feed file failed.')), 'error');
89 }).catch(function () { });
91 if (ev
=== 'download') {
92 return fs
.read_direct('/etc/banip/banip.custom.feeds', 'blob').then(function (blob
) {
93 let url
= window
.URL
.createObjectURL(blob
),
95 name
= 'banip.custom.feeds_%04d-%02d-%02d.json'.format(date
.getFullYear(), date
.getMonth() + 1, date
.getDate()),
96 link
= E('a', { 'style': 'display:none', 'href': url
, 'download': name
});
97 document
.body
.appendChild(link
);
99 document
.body
.removeChild(link
);
100 window
.URL
.revokeObjectURL(url
);
101 }).catch(function () { });
103 if (ev
=== 'create') {
104 return fs
.read_direct('/etc/banip/banip.feeds', 'json').then(function (content
) {
105 fs
.write('/etc/banip/banip.custom.feeds', JSON
.stringify(content
)).then(function () {
110 if (ev
=== 'clear') {
111 return fs
.write('/etc/banip/banip.custom.feeds', null).then(function () {
116 const invalid
= document
.querySelectorAll('.cbi-input-invalid');
117 if (invalid
.length
> 0) {
118 document
.body
.scrollTop
= document
.documentElement
.scrollTop
= 0;
119 return ui
.addNotification(null, E('p', _('Invalid input values, unable to save modifications.')), 'error');
122 let sumSubElements
= [], exportJson
;
123 const nodeKeys
= document
.querySelectorAll('[id^="widget.cbid.json"][id$="name"]');
124 for (let i
= 0; i
< nodeKeys
.length
; i
++) {
125 let subElements
= {};
126 let elements
= document
.querySelectorAll('[id^="widget.cbid.json.' + nodeKeys
[i
].id
.split('.')[3] + '\."]');
127 for (const element
of elements
) {
128 let key
= element
.id
.split('.')[4];
129 let value
= element
.value
|| "";
135 subElements
.url_4
= value
;
138 subElements
.rule_4
= value
;
141 subElements
.url_6
= value
;
144 subElements
.rule_6
= value
;
147 subElements
.descr
= value
;
150 subElements
.flag
= value
;
154 if (nodeKeys
[i
].value
!== "" && subElements
.descr
!== "") {
155 sumSubElements
.push(nodeKeys
[i
].value
, subElements
);
158 if (sumSubElements
.length
> 0) {
159 exportJson
= JSON
.stringify(sumSubElements
).replace(/^\[/, '{\n').replace(/\}]$/, '\n\t}\n}\n').replace(/,{"/g, ':{\n\t"').replace(/"},"/g, '"\n\t},\n"').replace(/","/g, '",\n\t"');
161 return fs
.write('/etc/banip/banip.custom.feeds', exportJson
).then(function () {
168 return L
.resolveDefault(fs
.read_direct('/etc/banip/banip.custom.feeds', 'json'), "");
171 render: function (data
) {
172 let m
, s
, o
, feed
, url_4
, url_6
, rule_4
, rule_6
, descr
, flag
;
174 m
= new form
.JSONMap(data
, _('Custom Feed Editor'), _('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). \
175 The file is located at \'/etc/banip/banip.custom.feeds\'. \
176 Then you can edit this file, delete entries, add new ones or make a local backup. To go back to the maintainers version just empty the custom feed file again (do not delete it!).'));
177 for (let i
= 0; i
< Object
.keys(m
.data
.data
).length
; i
++) {
178 feed
= Object
.keys(m
.data
.data
)[i
];
179 url_4
= m
.data
.data
[feed
].url_4
;
180 rule_4
= m
.data
.data
[feed
].rule_4
;
181 url_6
= m
.data
.data
[feed
].url_6
;
182 rule_6
= m
.data
.data
[feed
].rule_6
;
183 descr
= m
.data
.data
[feed
].descr
;
184 flag
= m
.data
.data
[feed
].flag
;
186 s
= m
.section(form
.TypedSection
, feed
, null);
190 o
= s
.option(form
.Value
, 'name', _('Feed Name'));
191 o
.ucioption
= '.name';
192 o
.datatype
= 'and(minlength(3),maxlength(15))';
193 o
.validate = function (section_id
, value
) {
195 return _('Empty field not allowed');
197 if (!value
.match(/^[a-z0-9]+$/)) {
198 return _('Invalid characters');
203 o
= s
.option(form
.Value
, 'url_4', _('URLv4'));
204 o
.validate = function (section_id
, value
) {
208 if (!value
.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-_\?\&\+=~#]+$/)) {
209 return _('Protocol/URL format not supported');
214 o
= s
.option(form
.Value
, 'rule_4', _('Rulev4'));
216 o
= s
.option(form
.Value
, 'url_6', _('URLv6'));
217 o
.validate = function (section_id
, value
) {
221 if (!value
.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-_\?\&\+=:~#]+$/)) {
222 return _('Protocol/URL format not supported');
227 o
= s
.option(form
.Value
, 'rule_6', _('Rulev6'));
229 o
= s
.option(form
.Value
, 'descr', _('Description'));
230 o
.datatype
= 'and(minlength(3),maxlength(30))';
231 o
.validate = function (section_id
, value
) {
233 return _('Empty field not allowed');
238 o
= s
.option(form
.Value
, 'flag', _('Flag'));
239 o
.datatype
= 'and(minlength(2),maxlength(2))';
240 o
.validate = function (section_id
, value
) {
244 if (!value
.match(/^gz$/)) {
245 return _('Flag not supported');
251 s
= m
.section(form
.NamedSection
, 'global');
252 s
.render
= L
.bind(function () {
253 return E('div', { class: 'right' }, [
255 'class': 'btn cbi-button cbi-button-action',
257 'disabled': 'disabled',
258 'click': ui
.createHandlerFn(this, function () {
259 return handleEdit('download');
261 }, [_('Download Custom Feeds')]),
264 'class': 'btn cbi-button cbi-button-action',
266 'disabled': 'disabled',
267 'click': ui
.createHandlerFn(this, function () {
268 return handleEdit('upload');
270 }, [_('Upload Custom Feeds')]),
271 '\xa0\xa0\xa0\xa0\xa0\xa0',
273 'class': 'btn cbi-button cbi-button-action important',
275 'disabled': 'disabled',
276 'click': ui
.createHandlerFn(this, function () {
277 return handleEdit('create');
279 }, [_('Fill Custom Feeds')]),
282 'class': 'btn cbi-button cbi-button-negative important',
284 'disabled': 'disabled',
285 'click': ui
.createHandlerFn(this, function () {
286 return handleEdit('clear');
288 }, [_('Clear Custom Feeds')]),
291 'class': 'btn cbi-button cbi-button-positive important',
293 'disabled': 'disabled',
294 'click': ui
.createHandlerFn(this, function () {
295 return handleEdit('save');
297 }, [_('Save Custom Feeds')])
302 handleSaveApply
: null,