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