mirror of
https://github.com/kiddin9/op-packages.git
synced 2026-11-01 03:45:13 +08:00
173 lines
6.4 KiB
JavaScript
173 lines
6.4 KiB
JavaScript
'use strict';
|
||
'require view';
|
||
'require form';
|
||
'require rpc';
|
||
'require tools.widgets as widgets';
|
||
'require librespeed.common as lscommon';
|
||
|
||
const callConfig = rpc.declare({
|
||
object: 'librespeed',
|
||
method: 'config',
|
||
expect: { '': {} }
|
||
});
|
||
|
||
return view.extend({
|
||
load() {
|
||
return callConfig().catch(() => ({}));
|
||
},
|
||
|
||
render(config) {
|
||
let m, s, o;
|
||
|
||
m = new form.Map('librespeed', _('LibreSpeed – Settings'));
|
||
|
||
s = m.section(form.NamedSection, 'main', 'librespeed', _('Measurement'));
|
||
|
||
o = s.option(widgets.NetworkSelect, 'interface', _('Interface'),
|
||
_('Logical interface the measurement is bound to.'));
|
||
o.default = 'wan';
|
||
o.nocreate = true;
|
||
|
||
o = s.option(form.Value, 'server', _('Server'),
|
||
_("Numeric server id from the LibreSpeed server list, or 'auto' to pick the closest."));
|
||
o.default = 'auto';
|
||
o.validate = function(section_id, value) {
|
||
if (value == '' || value == 'auto' || /^[0-9]+$/.test(value))
|
||
return true;
|
||
return _("Must be a server id or 'auto'");
|
||
};
|
||
|
||
o = s.option(form.Value, 'server_list', _('Server list URL'),
|
||
_('URL of a LibreSpeed server list in JSON, from a self-hosted deployment: either a static file such as https://example.org/servers.json or a generator such as https://example.org/backend/servers.php. Leave empty for the official list.'));
|
||
o.optional = true;
|
||
o.placeholder = 'https://librespeed.org/backend-servers/servers.php';
|
||
o.validate = function(section_id, value) {
|
||
if (value == '' || /^https?:\/\/.+/.test(value))
|
||
return true;
|
||
return _('Must be an http(s) URL');
|
||
};
|
||
|
||
o = s.option(form.ListValue, 'scheme', _('Protocol'),
|
||
_('On routers without AES acceleration, TLS itself can bound the result — forcing plain HTTP then measures the line rather than the cipher.'));
|
||
o.value('auto', _('server default'));
|
||
o.value('https', _('force HTTPS'));
|
||
o.value('http', _('force HTTP'));
|
||
o.default = 'auto';
|
||
|
||
s = m.section(form.NamedSection, 'schedule', 'librespeed', _('Scheduled measurements'));
|
||
|
||
o = s.option(form.Flag, 'enabled', _('Enable'),
|
||
_('Runs a measurement from cron at the chosen interval. Note that a measurement saturates the connection while it runs.'));
|
||
|
||
o = s.option(form.ListValue, 'interval', _('Interval'));
|
||
/* From the shared table, so the Test page's status label and these
|
||
* choices cannot drift apart. */
|
||
Object.keys(lscommon.INTERVALS).forEach(k =>
|
||
o.value(k, lscommon.INTERVALS[k]));
|
||
o.default = '1d';
|
||
o.depends('enabled', '1');
|
||
|
||
o = s.option(form.MultiValue, 'days', _('Days'),
|
||
_('Leave empty to run every day.'));
|
||
o.value('1', _('Monday'));
|
||
o.value('2', _('Tuesday'));
|
||
o.value('3', _('Wednesday'));
|
||
o.value('4', _('Thursday'));
|
||
o.value('5', _('Friday'));
|
||
o.value('6', _('Saturday'));
|
||
o.value('0', _('Sunday'));
|
||
o.optional = true;
|
||
o.depends('enabled', '1');
|
||
/* The init script hands this straight to cron, which wants commas. */
|
||
o.cfgvalue = function(section_id) {
|
||
const v = form.MultiValue.prototype.cfgvalue.apply(this, arguments);
|
||
return (v == null || v == '*') ? [] : String(v).split(',');
|
||
};
|
||
o.write = function(section_id, value) {
|
||
const list = Array.isArray(value) ? value.filter(v => v !== '') : [];
|
||
if (!list.length)
|
||
return this.remove(section_id);
|
||
return this.super('write', [ section_id, list.join(',') ]);
|
||
};
|
||
|
||
o = s.option(form.Value, 'hours', _('Hours'),
|
||
_('An hour window on the 24-hour clock, such as 2-5 for 02:00–05:59. A daily measurement runs at a random time inside it, drawn when the schedule is applied; shorter intervals only run within it. Leave empty for any time. A window across midnight is not supported.'));
|
||
o.placeholder = '2-5';
|
||
o.optional = true;
|
||
o.depends('enabled', '1');
|
||
o.validate = function(section_id, value) {
|
||
if (value == '')
|
||
return true;
|
||
const m = value.match(/^([01]?[0-9]|2[0-3])(-([01]?[0-9]|2[0-3]))?$/);
|
||
if (!m)
|
||
return _('Use an hour (0-23) or a range like 2-5');
|
||
if (m[3] != null && +m[3] < +m[1])
|
||
return _('The window must not cross midnight');
|
||
return true;
|
||
};
|
||
|
||
s = m.section(form.NamedSection, 'history', 'librespeed', _('History'));
|
||
|
||
o = s.option(form.Flag, 'enabled', _('Keep history'));
|
||
o.default = '1';
|
||
|
||
o = s.option(form.Value, 'path', _('Path'),
|
||
_('History stored in /tmp is lost when the router reboots.'));
|
||
o.default = '/tmp/librespeed/history.jsonl';
|
||
o.depends('enabled', '1');
|
||
|
||
o = s.option(form.ListValue, 'retention', _('Retention'));
|
||
o.value('7d', _('7 days'));
|
||
o.value('30d', _('30 days'));
|
||
o.value('90d', _('90 days'));
|
||
o.value('365d', _('1 year'));
|
||
o.default = '30d';
|
||
o.depends('enabled', '1');
|
||
|
||
o = s.option(form.Value, 'archive_path', _('Archive path'),
|
||
_('Completed days are reduced to daily minimum, average and maximum and appended here once a day, so measurements since the last flush may be lost after an unexpected power loss. Leave empty to keep no persistent history.'));
|
||
o.placeholder = '/etc/librespeed/history-daily.jsonl';
|
||
o.optional = true;
|
||
o.depends('enabled', '1');
|
||
|
||
o = s.option(form.ListValue, 'archive_retention', _('Archive retention'));
|
||
o.value('90d', _('90 days'));
|
||
o.value('365d', _('1 year'));
|
||
o.value('730d', _('2 years'));
|
||
o.default = '365d';
|
||
o.depends('enabled', '1');
|
||
|
||
return m.render().then(node => {
|
||
const cron = config && config.schedule && config.schedule.cron;
|
||
const box = E('div', { 'style': 'margin-top:1em' });
|
||
|
||
if (cron) {
|
||
/* Epochs computed by the backend in the router's timezone. */
|
||
const runs = (config && config.schedule && config.schedule.next_runs || [])
|
||
.map(e => new Date(e * 1000));
|
||
|
||
box.appendChild(E('h4', {}, [ _('Upcoming measurements') ]));
|
||
box.appendChild(E('table', { 'class': 'table', 'style': 'max-width:24em' },
|
||
runs.map(d => E('tr', { 'class': 'tr' }, [
|
||
E('td', { 'class': 'td' }, [ d.toLocaleDateString() ]),
|
||
E('td', { 'class': 'td' }, [ d.toLocaleTimeString() ])
|
||
]))));
|
||
box.appendChild(E('p', { 'style': 'color:#888' }, [
|
||
_('A daily measurement runs at a time drawn when the schedule is applied; saving draws it anew.')
|
||
]));
|
||
}
|
||
else {
|
||
box.appendChild(E('p', { 'style': 'color:#888' }, [
|
||
_('No schedule is active. Enable it above and apply to draw the measurement time.')
|
||
]));
|
||
}
|
||
|
||
/* A sibling of the form, not a child: Map.render() replaces its
|
||
* own root on every Save and Reset, and would take the schedule
|
||
* preview with it. The preview reads the applied crontab, so it
|
||
* is correct to keep it as it is until Apply. */
|
||
return E([], [ node, box ]);
|
||
});
|
||
}
|
||
});
|