luci-app-banip: sync with banIP 0.8.5-1
[project/luci.git] / applications / luci-app-banip / htdocs / luci-static / resources / view / banip / feeds.js
1 'use strict';
2 'require view';
3 'require form';
4 'require fs';
5 'require ui';
6
7 /*
8 include custom CSS
9 */
10 document.querySelector('head').appendChild(E('link', {
11 'rel': 'stylesheet',
12 'type': 'text/css',
13 'href': L.resource('view/banip/custom.css')
14 }));
15
16 /*
17 observe DOM changes
18 */
19 const observer = new MutationObserver(function (mutations) {
20 if (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)
27 })
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;");
31 })
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');
41 }
42 });
43 }
44 });
45
46 const targetNode = document.getElementById('view');
47 const observerConfig = {
48 childList: true,
49 subtree: true,
50 attributes: false,
51 characterData: false
52 };
53 observer.observe(targetNode, observerConfig);
54
55 /*
56 button handling
57 */
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) {
62 if (data) {
63 let dataLength = Object.keys(data).length || 0;
64 if (dataLength > 0) {
65 for (let i = 0; i < dataLength; i++) {
66 let feed = Object.keys(data)[i];
67 let descr = data[feed].descr;
68 if (feed && descr) {
69 continue;
70 }
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');
73 });
74 return;
75 }
76 } else {
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');
79 });
80 return;
81 }
82 location.reload();
83 } else {
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');
86 });
87 }
88 });
89 }).catch(function () { });
90 }
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),
94 date = new Date(),
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);
98 link.click();
99 document.body.removeChild(link);
100 window.URL.revokeObjectURL(url);
101 }).catch(function () { });
102 }
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 () {
106 location.reload();
107 });
108 });
109 }
110 if (ev === 'clear') {
111 return fs.write('/etc/banip/banip.custom.feeds', null).then(function () {
112 location.reload();
113 });
114 }
115 if (ev === 'save') {
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');
120 }
121 }
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 || "";
130 if (value === "") {
131 continue;
132 }
133 switch (key) {
134 case 'url_4':
135 subElements.url_4 = value;
136 break;
137 case 'rule_4':
138 subElements.rule_4 = value;
139 break;
140 case 'url_6':
141 subElements.url_6 = value;
142 break;
143 case 'rule_6':
144 subElements.rule_6 = value;
145 break;
146 case 'descr':
147 subElements.descr = value;
148 break;
149 case 'flag':
150 subElements.flag = value;
151 break;
152 }
153 }
154 if (nodeKeys[i].value !== "" && subElements.descr !== "") {
155 sumSubElements.push(nodeKeys[i].value, subElements);
156 }
157 }
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"');
160 }
161 return fs.write('/etc/banip/banip.custom.feeds', exportJson).then(function () {
162 location.reload();
163 });
164 }
165
166 return view.extend({
167 load: function () {
168 return L.resolveDefault(fs.read_direct('/etc/banip/banip.custom.feeds', 'json'), "");
169 },
170
171 render: function (data) {
172 let m, s, o, feed, url_4, url_6, rule_4, rule_6, descr, flag;
173
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;
185
186 s = m.section(form.TypedSection, feed, null);
187 s.addremove = true;
188 s.anonymous = true;
189
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) {
194 if (!value) {
195 return _('Empty field not allowed');
196 }
197 if (!value.match(/^[a-z0-9]+$/)) {
198 return _('Invalid characters');
199 }
200 return true;
201 }
202
203 o = s.option(form.Value, 'url_4', _('URLv4'));
204 o.validate = function (section_id, value) {
205 if (!value) {
206 return true;
207 }
208 if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-_\?\&\+=~#]+$/)) {
209 return _('Protocol/URL format not supported');
210 }
211 return true;
212 }
213
214 o = s.option(form.Value, 'rule_4', _('Rulev4'));
215
216 o = s.option(form.Value, 'url_6', _('URLv6'));
217 o.validate = function (section_id, value) {
218 if (!value) {
219 return true;
220 }
221 if (!value.match(/^(http:\/\/|https:\/\/)[A-Za-z0-9\/\.\-_\?\&\+=:~#]+$/)) {
222 return _('Protocol/URL format not supported');
223 }
224 return true;
225 }
226
227 o = s.option(form.Value, 'rule_6', _('Rulev6'));
228
229 o = s.option(form.Value, 'descr', _('Description'));
230 o.datatype = 'and(minlength(3),maxlength(30))';
231 o.validate = function (section_id, value) {
232 if (!value) {
233 return _('Empty field not allowed');
234 }
235 return true;
236 }
237
238 o = s.option(form.Value, 'flag', _('Flag'));
239 o.datatype = 'and(minlength(2),maxlength(2))';
240 o.validate = function (section_id, value) {
241 if (!value) {
242 return true;
243 }
244 if (!value.match(/^gz$/)) {
245 return _('Flag not supported');
246 }
247 return true;
248 }
249 }
250
251 s = m.section(form.NamedSection, 'global');
252 s.render = L.bind(function () {
253 return E('div', { class: 'right' }, [
254 E('button', {
255 'class': 'btn cbi-button cbi-button-action',
256 'id': 'btnDownload',
257 'disabled': 'disabled',
258 'click': ui.createHandlerFn(this, function () {
259 return handleEdit('download');
260 })
261 }, [_('Download Custom Feeds')]),
262 '\xa0\xa0\xa0',
263 E('button', {
264 'class': 'btn cbi-button cbi-button-action',
265 'id': 'btnUpload',
266 'disabled': 'disabled',
267 'click': ui.createHandlerFn(this, function () {
268 return handleEdit('upload');
269 })
270 }, [_('Upload Custom Feeds')]),
271 '\xa0\xa0\xa0\xa0\xa0\xa0',
272 E('button', {
273 'class': 'btn cbi-button cbi-button-action important',
274 'id': 'btnCreate',
275 'disabled': 'disabled',
276 'click': ui.createHandlerFn(this, function () {
277 return handleEdit('create');
278 })
279 }, [_('Fill Custom Feeds')]),
280 '\xa0\xa0\xa0',
281 E('button', {
282 'class': 'btn cbi-button cbi-button-negative important',
283 'id': 'btnClear',
284 'disabled': 'disabled',
285 'click': ui.createHandlerFn(this, function () {
286 return handleEdit('clear');
287 })
288 }, [_('Clear Custom Feeds')]),
289 '\xa0\xa0\xa0',
290 E('button', {
291 'class': 'btn cbi-button cbi-button-positive important',
292 'id': 'btnSave',
293 'disabled': 'disabled',
294 'click': ui.createHandlerFn(this, function () {
295 return handleEdit('save');
296 })
297 }, [_('Save Custom Feeds')])
298 ])
299 });
300 return m.render();
301 },
302 handleSaveApply: null,
303 handleSave: null,
304 handleReset: null
305 });