5d4eca53a6d084a42d6ca77eeb71f26bfdfba1bd
[oweals/luci.git] / applications / luci-app-adblock / htdocs / luci-static / resources / view / adblock / overview.js
1 'use strict';
2 'require view';
3 'require poll';
4 'require fs';
5 'require ui';
6 'require uci';
7 'require form';
8 'require tools.widgets as widgets';
9
10 /*
11         button handling
12 */
13 async function handleAction(ev) {
14         if (ev === 'timer') {
15                 L.ui.showModal(_('Refresh Timer'), [
16                         E('p', _('To keep your adblock lists up-to-date, you should setup an automatic update job for these lists.')),
17                         E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
18                                 E('h5', _('Existing job(s)')),
19                                 E('textarea', {
20                                         'id': 'cronView',
21                                         'style': 'width: 100% !important; padding: 5px; font-family: monospace',
22                                         'readonly': 'readonly',
23                                         'wrap': 'off',
24                                         'rows': 5
25                                 })
26                         ]),
27                         E('div', { 'class': 'left', 'style': 'display:flex; flex-direction:column' }, [
28                                 E('label', { 'class': 'cbi-input-select', 'style': 'padding-top:.5em' }, [
29                                 E('h5', _('Set/Replace a new adblock job')),
30                                 E('select', { 'class': 'cbi-input-select', 'id': 'timerA' }, [
31                                         E('option', { 'value': 'start' }, 'Start'),
32                                         E('option', { 'value': 'reload' }, 'Reload'),
33                                         E('option', { 'value': 'restart' }, 'Restart')
34                                 ]),
35                                 '\xa0\xa0\xa0',
36                                 _('Adblock action')
37                                 ]),
38                                 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
39                                 E('input', { 'class': 'cbi-input-text', 'id': 'timerH', 'maxlength': '2' }, [
40                                 ]),
41                                 '\xa0\xa0\xa0',
42                                 _('The hours portition (req., range: 0-23)')
43                                 ]),
44                                 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
45                                 E('input', { 'class': 'cbi-input-text', 'id': 'timerM', 'maxlength': '2' }),
46                                 '\xa0\xa0\xa0',
47                                 _('The minutes portion (opt., range: 0-59)')
48                                 ]),
49                                 E('label', { 'class': 'cbi-input-text', 'style': 'padding-top:.5em' }, [
50                                 E('input', { 'class': 'cbi-input-text', 'id': 'timerD', 'maxlength': '13' }),
51                                 '\xa0\xa0\xa0',
52                                 _('The day of the week (opt., values: 1-7 possibly sep. by , or -)')
53                                 ])
54                         ]),
55                         E('div', { 'class': 'right' }, [
56                                 E('button', {
57                                         'class': 'btn',
58                                         'click': L.hideModal
59                                 }, _('Cancel')),
60                                 ' ',
61                                 E('button', {
62                                         'class': 'btn cbi-button-action',
63                                         'click': ui.createHandlerFn(this, function(ev) {
64                                                 var action  = document.getElementById('timerA').value;
65                                                 var hours   = document.getElementById('timerH').value;
66                                                 var minutes = document.getElementById('timerM').value || '0';
67                                                 var days    = document.getElementById('timerD').value || '*';
68                                                 if (hours) {
69                                                         L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['timer', action, hours, minutes, days]))
70                                                         .then(function(res) {
71                                                                 if (res) {
72                                                                         ui.addNotification(null, E('p', _('The Refresh Timer could not been updated.')), 'error');
73                                                                 } else {
74                                                                         ui.addNotification(null, E('p', _('The Refresh Timer has been updated.')), 'info');
75                                                                 }
76                                                         });
77                                                 } else {
78                                                         document.getElementById('timerH').focus();
79                                                         return
80                                                 }
81                                                 L.hideModal();
82                                         })
83                                 }, _('Save'))
84                         ])
85                 ]);
86                 L.resolveDefault(fs.read_direct('/etc/crontabs/root'), ' ')
87                 .then(function(res) {
88                         document.getElementById('cronView').value = res.trim();
89                 });
90                 document.getElementById('timerH').focus();
91                 return
92         }
93
94         if (ev === 'suspend') {
95                 if (document.getElementById('status') && document.getElementById('btn_suspend') && document.getElementById('status').textContent.substr(0,6) === 'paused') {
96                         document.querySelector('#btn_suspend').textContent = 'Suspend';
97                         ev = 'resume';
98                 } else if (document.getElementById('status') && document.getElementById('btn_suspend')) {
99                         document.querySelector('#btn_suspend').textContent = 'Resume';
100                 }
101         }
102
103         poll.start();
104         fs.exec_direct('/etc/init.d/adblock', [ev])
105         var running = 1;
106         while (running === 1) {
107                 await new Promise(r => setTimeout(r, 1000));
108                 L.resolveDefault(fs.read_direct('/var/run/adblock.pid')).then(function(res) {
109                         if (!res) {
110                                 running = 0;
111                         }
112                 })
113         }
114         poll.stop();
115 }
116
117 return view.extend({
118         load: function() {
119                 return Promise.all([
120                         L.resolveDefault(fs.exec_direct('/etc/init.d/adblock', ['list']), {}),
121                         uci.load('adblock')
122                 ]);
123         },
124
125         render: function(result) {
126                 var m, s, o;
127
128                 m = new form.Map('adblock', 'Adblock', _('Configuration of the adblock package to block ad/abuse domains by using DNS. \
129                         For further information <a href="https://github.com/openwrt/packages/blob/master/net/adblock/files/README.md" target="_blank" rel="noreferrer noopener" >check the online documentation</a>'));
130
131                 /*
132                         poll runtime information
133                 */
134                 pollData: poll.add(function() {
135                         return L.resolveDefault(fs.read_direct('/tmp/adb_runtime.json'), 'null').then(function(res) {
136                                 var info = JSON.parse(res);
137                                 var status = document.getElementById('status');
138                                 if (status && info) {
139                                         status.textContent = (info.data.adblock_status || '-') + ' / ' + (info.data.adblock_version || '-');
140                                         if (info.data.adblock_status === "running") {
141                                                 if (!status.classList.contains("spinning")) {
142                                                         status.classList.add("spinning");
143                                                 }
144                                         } else {
145                                                 if (status.classList.contains("spinning")) {
146                                                         status.classList.remove("spinning");
147                                                         poll.stop();
148                                                 }
149                                         }
150                                         if (status.textContent.substr(0,6) === 'paused' && document.getElementById('btn_suspend')) {
151                                                 document.querySelector('#btn_suspend').textContent = 'Resume';
152                                         }
153                                 } else if (status) {
154                                         status.textContent = '-';
155                                         if (status.classList.contains("spinning")) {
156                                                 status.classList.remove("spinning");
157                                         }
158                                 }
159                                 var domains = document.getElementById('domains');
160                                 if (domains && info) {
161                                         domains.textContent = parseInt(info.data.blocked_domains, 10).toLocaleString() || '-';
162                                 }
163                                 var sources = document.getElementById('sources');
164                                 var src_array = [];
165                                 if (sources && info) {
166                                         for (var i = 0; i < info.data.active_sources.length; i++) {
167                                                 if (i < info.data.active_sources.length-1) {
168                                                         src_array += info.data.active_sources[i].source + ', ';
169                                                 } else {
170                                                         src_array += info.data.active_sources[i].source
171                                                 }
172                                         }
173                                         sources.textContent = src_array || '-';
174                                 }
175                                 var backend = document.getElementById('backend');
176                                 if (backend && info) {
177                                         backend.textContent = info.data.dns_backend || '-';
178                                 }
179                                 var utils = document.getElementById('utils');
180                                 if (utils && info) {
181                                         utils.textContent = info.data.run_utils || '-';
182                                 }
183                                 var ifaces = document.getElementById('ifaces');
184                                 if (ifaces && info) {
185                                         ifaces.textContent = info.data.run_ifaces || '-';
186                                 }
187                                 var dirs = document.getElementById('dirs');
188                                 if (dirs && info) {
189                                         dirs.textContent = info.data.run_directories || '-';
190                                 }
191                                 var flags = document.getElementById('flags');
192                                 if (flags && info) {
193                                         flags.textContent = info.data.run_flags || '-';
194                                 }
195                                 var run = document.getElementById('run');
196                                 if (run && info) {
197                                         run.textContent = info.data.last_run || '-';
198                                 }
199                         });
200                 }, 1);
201
202                 /*
203                         runtime information and buttons
204                 */
205                 s = m.section(form.NamedSection, 'global');
206                 s.render = L.bind(function(view, section_id) {
207                         return E('div', { 'class': 'cbi-section' }, [
208                                 E('h3', _('Information')), 
209                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
210                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Status / Version')),
211                                 E('div', { 'class': 'cbi-value-field spinning', 'id': 'status', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'\xa0')]),
212                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
213                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Blocked Domains')),
214                                 E('div', { 'class': 'cbi-value-field', 'id': 'domains', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
215                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
216                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Active Sources')),
217                                 E('div', { 'class': 'cbi-value-field', 'id': 'sources', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
218                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
219                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('DNS Backend')),
220                                 E('div', { 'class': 'cbi-value-field', 'id': 'backend', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
221                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
222                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Utils')),
223                                 E('div', { 'class': 'cbi-value-field', 'id': 'utils', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
224                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
225                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Interfaces')),
226                                 E('div', { 'class': 'cbi-value-field', 'id': 'ifaces', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
227                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
228                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Directories')),
229                                 E('div', { 'class': 'cbi-value-field', 'id': 'dirs', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
230                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
231                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Run Flags')),
232                                 E('div', { 'class': 'cbi-value-field', 'id': 'flags', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
233                                 E('div', { 'class': 'cbi-value', 'style': 'margin-bottom:5px' }, [
234                                 E('label', { 'class': 'cbi-value-title', 'style': 'padding-top:0rem' }, _('Last Run')),
235                                 E('div', { 'class': 'cbi-value-field', 'id': 'run', 'style': 'font-weight: bold;margin-bottom:5px;color:#37c' },'-')]),
236                                 E('div', { class: 'right' }, [
237                                         E('button', {
238                                                 'class': 'cbi-button cbi-button-apply',
239                                                 'click': ui.createHandlerFn(this, function() {
240                                                         return handleAction('timer');
241                                                 })
242                                         }, [ _('Refresh Timer...') ]),
243                                         '\xa0\xa0\xa0',
244                                         E('button', {
245                                                 'class': 'cbi-button cbi-button-apply',
246                                                 'id': 'btn_suspend',
247                                                 'click': ui.createHandlerFn(this, function() {
248                                                         return handleAction('suspend');
249                                                 })
250                                         }, [ _('Suspend') ]),
251                                         '\xa0\xa0\xa0',
252                                         E('button', {
253                                                 'class': 'cbi-button cbi-button-apply',
254                                                 'click': ui.createHandlerFn(this, function() {
255                                                         return handleAction('start');
256                                                 })
257                                         }, [ _('Refresh') ])
258                                 ])
259                         ]);
260                 }, o, this);
261                 this.pollData;
262
263                 /*
264                         tabbed config section
265                 */
266                 s = m.section(form.NamedSection, 'global', 'adblock', _('Settings'));
267                 s.addremove = false;
268                 s.tab('general',  _('General Settings'));
269                 s.tab('additional', _('Additional Settings'));
270                 s.tab('adv_dns', _('Advanced DNS Settings'));
271                 s.tab('adv_report', _('Advanced Report Settings'));
272                 s.tab('adv_email', _('Advanced E-Mail Settings'));
273                 s.tab('sources', _('Blocklist Sources'), _('List of supported and fully pre-configured adblock sources, already active sources are pre-selected.<br /> \
274                         <b><em>To avoid OOM errors, please do not select too many lists!</em></b><br /> \
275                         List size information with the respective domain ranges as follows:<br /> \
276                         &#8226;&#xa0;<b>S</b> (-10k), <b>M</b> (10k-30k) and <b>L</b> (30k-80k) should work for 128 MByte devices,<br /> \
277                         &#8226;&#xa0;<b>XL</b> (80k-200k) should work for 256-512 MByte devices,<br /> \
278                         &#8226;&#xa0;<b>XXL</b> (200k-) needs more RAM and Multicore support, e.g. x86 or raspberry devices.<br /> \
279                         <p>&#xa0;</p>'));
280
281                 /*
282                         general settings tab
283                 */
284                 o = s.taboption('general', form.Flag, 'adb_enabled', _('Enabled'), _('Enable the adblock service.'));
285                 o.rmempty = false;
286
287                 o = s.taboption('general', widgets.NetworkSelect, 'adb_trigger', _('Startup Trigger Interface'), _('List of available network interfaces to trigger the adblock start. \
288                         Choose \'unspecified\' to use a classic startup timeout instead of a network trigger.'));
289                 o.unspecified = true;
290                 o.nocreate = true;
291                 o.rmempty = true;
292
293                 o = s.taboption('general', form.Flag, 'adb_forcedns', _('Force Local DNS'), _('Redirect all DNS queries from \'lan\' zone to the local DNS resolver, applies to UDP and TCP protocol.'));
294                 o.rmempty = false;
295
296                 o = s.taboption('general', form.Value, 'adb_portlist', _('Local DNS Ports'), _('Space separated list of DNS-related firewall ports which should be forced locally.'));
297                 o.depends('adb_forcedns', '1');
298                 o.placeholder = '53 853 5353';
299                 o.rmempty = true;
300
301                 o = s.taboption('general', form.Flag, 'adb_safesearch', _('Enable SafeSearch'), _('Enforcing SafeSearch for google, bing, duckduckgo, yandex, youtube and pixabay.'));
302                 o.rmempty = false;
303
304                 o = s.taboption('general', form.Flag, 'adb_safesearchmod', _('SafeSearch Moderate'), _('Enable moderate SafeSearch filters for youtube.'));
305                 o.depends('adb_safesearch', '1');
306                 o.rmempty = true;
307
308                 o = s.taboption('general', form.Flag, 'adb_report', _('DNS Report'), _('Gather DNS related network traffic via tcpdump and provide a DNS Report on demand. \
309                         Please note: this needs additional \'tcpdump-mini\' package installation and a full adblock service restart to take effect.'));
310                 o.rmempty = false;
311
312                 o = s.taboption('general', form.Flag, 'adb_mail', _('E-Mail Notification'), _('Send adblock related notification e-mails. \
313                         Please note: this needs additional \'msmtp\' package installation.'));
314                 o.rmempty = false;
315
316                 o = s.taboption('general', form.Value, 'adb_mailreceiver', _('E-Mail Receiver Address'), _('Receiver address for adblock notification e-mails.'));
317                 o.depends('adb_mail', '1');
318                 o.placeholder = 'name@example.com';
319                 o.rmempty = true;
320
321                 /*
322                         additional settings tab
323                 */
324                 o = s.taboption('additional', form.Flag, 'adb_debug', _('Verbose Debug Logging'), _('Enable verbose debug logging in case of any processing errors.'));
325                 o.rmempty = false;
326
327                 o = s.taboption('additional', form.Flag, 'adb_nice', _('Low Priority Service'), _('Reduce the priority of the adblock background processing to take fewer resources from the system. \
328                         Please note: This change requires a full adblock service restart to take effect.'));
329                 o.enabled = '10';
330                 o.rmempty = true;
331
332                 o = s.taboption('additional', form.Value, 'adb_triggerdelay', _('Trigger Delay'), _('Additional trigger delay in seconds before adblock processing begins.'));
333                 o.placeholder = '2';
334                 o.datatype = 'range(1,120)';
335                 o.rmempty = true;
336
337                 o = s.taboption('additional', form.ListValue, 'adb_maxqueue', _('Download Queue'), _('Size of the download queue for download processing (incl. sorting, merging etc.) in parallel.'));
338                 o.value('4');
339                 o.value('8');
340                 o.value('16');
341                 o.value('32');
342                 o.rmempty = false;
343
344                 o = s.taboption('additional', form.Value, 'adb_tmpbase', _('Base Temp Directory'), _('Base Temp Directory for all adblock related runtime operations, \
345                         e.g. downloading, sorting, merging etc.'));
346                 o.placeholder = '/tmp';
347                 o.rmempty = true;
348
349                 o = s.taboption('additional', form.Flag, 'adb_backup', _('Blocklist Backup'), _('Create compressed blocklist backups, they will be used in case of download errors or during startup.'));
350                 o.default = 1
351                 o.rmempty = false;
352
353                 o = s.taboption('additional', form.Value, 'adb_backupdir', _('Backup Directory'), _('Target directory for blocklist backups. \
354                         Default is \'/tmp\', please use preferably an usb stick or another local disk.'));
355                 o.depends('adb_backup', '1');
356                 o.placeholder = '/tmp';
357                 o.rmempty = true;
358
359                 o = s.taboption('additional', form.ListValue, 'adb_fetchutil', _('Download Utility'), _('List of supported and fully pre-configured download utilities.'));
360                 o.value('uclient-fetch');
361                 o.value('wget');
362                 o.value('curl');
363                 o.value('aria2c');
364                 o.rmempty = false;
365
366                 o = s.taboption('additional', form.Value, 'adb_fetchparm', _('Download Parameters'), _('Special config options for the selected download utility.'))
367                 o.rmempty = true;
368
369                 /*
370                         advanced dns settings tab
371                 */
372                 o = s.taboption('adv_dns', form.ListValue, 'adb_dns', _('DNS Backend'), _('List of supported DNS backends with their default list directory. \
373                         To overwrite the default path use the \'DNS Directory\' option.'));
374                 o.value('dnsmasq', _('dnsmasq (/tmp/dnsmasq.d)'));
375                 o.value('unbound', _('unbound (/var/lib/unbound)'));
376                 o.value('named', _('named (/var/lib/bind)'));
377                 o.value('kresd', _('kresd (/etc/kresd)'));
378                 o.value('raw', _('raw (/tmp)'));
379                 o.rmempty = false;
380
381                 o = s.taboption('adv_dns', form.Value, 'adb_dnsdir', _('DNS Directory'), _('Target directory for the generated blocklist \'adb_list.overall\'.'));
382                 o.placeholder = '/tmp';
383                 o.rmempty = true;
384
385                 o = s.taboption('adv_dns', form.Value, 'adb_dnstimeout', _('DNS Restart Timeout'), _('Timeout to wait for a successful DNS backend restart.'));
386                 o.placeholder = '20';
387                 o.datatype = 'range(1,60)';
388                 o.rmempty = true;
389
390                 o = s.taboption('adv_dns', form.Value, 'adb_lookupdomain', _('External DNS Lookup Domain'), _('External domain to check for a successful DNS backend restart. \
391                         Please note: To disable this check set this option to \'false\'.'));
392                 o.placeholder = 'example.com';
393                 o.rmempty = true;
394
395                 o = s.taboption('adv_dns', form.Flag, 'adb_dnsfilereset', _('DNS File Reset'), _('Resets the final DNS blocklist \'adb_list.overall\' after DNS backend loading. \
396                         Please note: This option starts a small ubus/adblock monitor in the background.'));
397                 o.rmempty = false;
398
399                 o = s.taboption('adv_dns', form.Flag, 'adb_dnsflush', _('Flush DNS Cache'), _('Flush the DNS Cache before adblock processing as well.'));
400                 o.rmempty = true;
401
402                 o = s.taboption('adv_dns', form.Flag, 'adb_dnsallow', _('Disable DNS Allow'), _('Disable selective DNS whitelisting (RPZ pass through).'));
403                 o.rmempty = true;
404
405                 o = s.taboption('adv_dns', form.Flag, 'adb_jail', _('Additional Jail Blocklist'), _('Builds an additional DNS blocklist to block access to all domains except those listed in the whitelist. \
406                         Please note: You can use this restrictive blocklist e.g. for guest wifi or kidsafe configurations.'));
407                 o.rmempty = true;
408
409                 o = s.taboption('adv_dns', form.Value, 'adb_jaildir', _('Jail Directory'), _('Target directory for the generated jail blocklist \'adb_list.jail\'.'));
410                 o.depends('adb_jail', '1');
411                 o.placeholder = '/tmp';
412                 o.rmempty = true;
413
414                 o = s.taboption('adv_dns', form.Flag, 'adb_dnsinotify', _('Disable DNS Restarts'), _('Disable adblock triggered restarts for dns backends with autoload/inotify functions.'));
415                 o.depends('adb_dnsflush', '0');
416                 o.rmempty = true;
417
418                 /*
419                         advanced report settings tab
420                 */
421                 o = s.taboption('adv_report', widgets.DeviceSelect, 'adb_repiface', _('Report Interface'), _('List of available network devices used by tcpdump.'));
422                 o.unspecified = true;
423                 o.nocreate = false;
424                 o.rmempty = true;
425
426                 o = s.taboption('adv_report', form.Value, 'adb_reportdir', _('Report Directory'), _('Target directory for DNS related report files. \
427                         Default is \'/tmp\', please use preferably an usb stick or another local disk.'));
428                 o.placeholder = '/tmp';
429                 o.rmempty = true;
430
431                 o = s.taboption('adv_report', form.Value, 'adb_repchunkcnt', _('Report Chunk Count'), _('Report chunk count used by tcpdump.'));
432                 o.placeholder = '5';
433                 o.datatype = 'range(1,10)';
434                 o.rmempty = true;
435
436                 o = s.taboption('adv_report', form.Value, 'adb_repchunksize', _('Report Chunk Size'), _('Report chunk size used by tcpdump in MByte.'));
437                 o.placeholder = '1';
438                 o.datatype = 'range(1,10)';
439                 o.rmempty = true;
440
441                 o = s.taboption('adv_report', form.Value, 'adb_replisten', _('Report Ports'), _('Space separated list of ports used by tcpdump.'));
442                 o.placeholder = '53';
443                 o.rmempty = true;
444
445                 /*
446                         advanced email settings tab
447                 */
448                 o = s.taboption('adv_email', form.Value, 'adb_mailsender', _('E-Mail Sender Address'), _('Sender address for adblock notification E-Mails.'));
449                 o.placeholder = 'no-reply@adblock';
450                 o.rmempty = true;
451
452                 o = s.taboption('adv_email', form.Value, 'adb_mailtopic', _('E-Mail Topic'), _('Topic for adblock notification E-Mails.'));
453                 o.placeholder = 'adblock notification';
454                 o.rmempty = true;
455
456                 o = s.taboption('adv_email', form.Value, 'adb_mailprofile', _('E-Mail Profile'), _('Profile used by \'msmtp\' for adblock notification E-Mails.'));
457                 o.placeholder = 'adb_notify';
458                 o.rmempty = true;
459
460                 o = s.taboption('adv_email', form.Value, 'adb_mailcnt', _('E-Mail Notification Count'), _('Raise the notification count, to get E-Mails if the overall blocklist count is less or equal to the given limit.'));
461                 o.placeholder = '0';
462                 o.datatype = 'min(0)';
463                 o.rmempty = true;
464
465                 /*
466                         blocklist sources tab
467                 */
468                 o = s.taboption('sources', form.MultiValue, 'adb_sources', _('Sources (Size, Focus)'));
469                 var lines, name, size, focus;
470                 lines = result[0].trim().split('\n');
471                 for (var i = 0; i < lines.length; i++) {
472                         if (lines[i].match(/^\s+\+/)) {
473                                 name  = lines[i].match(/^\s+\+\s(\w+)\s/)[1] || '-';
474                                 size  = lines[i].match(/^\s+\+\s\w+[\sx]+(\w+)/)[1] || '-';
475                                 focus = lines[i].match(/^\s+\+\s\w+[\sx]+\w+\s+([\w\+]+)/)[1] || '-';
476                                 o.value(name, name + ' (' + size + ', ' + focus + ')');
477                         }
478                 }
479                 o.rmempty = false;
480                 return m.render();
481         },
482         handleReset: null
483 });