'use strict'; 'require view'; 'require fs'; 'require poll'; 'require ui'; 'require uci'; 'require rpc'; 'require form'; 'require tools.widgets as widgets'; const callRcList = rpc.declare({ object: 'rc', method: 'list', params: [ 'name' ], expect: { '': {} }, filter(res) { for (var k in res) return +res[k].enabled; return null; } }); const callRcInit = rpc.declare({ object: 'rc', method: 'init', params: [ 'name', 'action' ], expect: { result: false } }); const callGetUnixtime = rpc.declare({ object: 'luci', method: 'getUnixtime', expect: { result: 0 } }); const callSetLocaltime = rpc.declare({ object: 'luci', method: 'setLocaltime', params: [ 'localtime' ], expect: { result: 0 } }); const callTimezone = rpc.declare({ object: 'luci', method: 'getTimezones', expect: { '': {} } }); function formatTime(epoch) { const date = new Date(epoch * 1000); const zn = uci.get('system', '@system[0]', 'zonename')?.replaceAll(' ', '_') || 'UTC'; const ts = uci.get('system', '@system[0]', 'clock_timestyle') || 0; const hc = uci.get('system', '@system[0]', 'clock_hourcycle') || 0; return new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: (ts == 0) ? 'long' : 'full', hourCycle: (hc == 0) ? undefined : hc, timeZone: zn }).format(date); } const CBILocalTime = form.DummyValue.extend({ renderWidget(section_id, option_id, cfgvalue) { return E([], [ E('input', { 'id': 'localtime', 'type': 'text', 'readonly': true, 'value': formatTime(cfgvalue) }), E('br'), E('span', { 'class': 'control-group' }, [ E('button', { 'class': 'cbi-button cbi-button-apply', 'click': ui.createHandlerFn(this, function() { return callSetLocaltime(Math.floor(Date.now() / 1000)); }), 'disabled': (this.readonly != null) ? this.readonly : this.map.readonly }, _('Sync with browser')), ' ', this.ntpd_support ? E('button', { 'class': 'cbi-button cbi-button-apply', 'click': ui.createHandlerFn(this, function() { return callRcInit('sysntpd', 'restart'); }), 'disabled': (this.readonly != null) ? this.readonly : this.map.readonly }, _('Sync with NTP-Server')) : '' ]) ]); }, }); return view.extend({ load() { return Promise.all([ callRcList('sysntpd'), callTimezone(), callGetUnixtime(), uci.load('luci'), uci.load('system'), L.hasSystemFeature('zram') ? L.resolveDefault(fs.read('/sys/block/zram0/comp_algorithm'), '') : '' ]); }, render([ntpd_enabled, timezones, unixtime, _luci, _system, zram_algorithms]) { let m, s, o; const system_section = uci.sections('system', 'system')[0]; m = new form.Map('system', _('System'), _('Here you can configure the basic aspects of your device like its hostname or the timezone.')); this.map = m; this.systemSid = system_section?.['.name']; m.chain('luci'); s = m.section(form.TypedSection, 'system', _('System Properties')); s.anonymous = true; s.addremove = false; s.tab('general', _('General Settings')); s.tab('logging', _('Logging')); s.tab('timesync', _('Time Synchronization')); s.tab('language', _('Language and Style')); /* * System Properties */ o = s.taboption('general', CBILocalTime, '_systime', _('Local Time')); o.cfgvalue = function() { return unixtime }; o.ntpd_support = ntpd_enabled; o = s.taboption('general', form.Value, 'hostname', _('Hostname')); o.datatype = 'hostname'; /* could be used also as a default for LLDP, SNMP "system description" in the future */ o = s.taboption('general', form.Value, 'description', _('Description'), _('An optional, short description for this device')); o.optional = true; o = s.taboption('general', form.TextValue, 'notes', _('Notes'), _('Optional, free-form notes about this device')); o.optional = true; o = s.taboption('general', form.ListValue, 'zonename', _('Timezone')); o.value('UTC'); const zones = Object.keys(timezones || {}).sort(); for (let zone of zones) o.value(zone); o.write = function(section_id, formvalue) { const tz = timezones[formvalue] ? timezones[formvalue].tzstring : null; uci.set('system', section_id, 'zonename', formvalue); uci.set('system', section_id, 'timezone', tz); }; o = s.taboption('general', form.Flag, 'clock_timestyle', _('Full TimeZone Name'), _('Unchecked means the timezone offset (E.g. GMT+1) is displayed')); o = s.taboption('general', form.ListValue, 'clock_hourcycle', _('Time Format')); o.value('', _('Default')); o.value('h12', _('12-Hour Clock')); o.value('h23', _('24-Hour Clock')); /* * Logging */ o = s.taboption('logging', form.Value, 'log_size', _('System log buffer size'), "kiB"); o.optional = true; o.placeholder = 128; o.datatype = 'uinteger'; o = s.taboption('logging', form.Value, 'log_ip', _('External system log server')); o.optional = true; o.placeholder = '0.0.0.0'; o.datatype = 'host'; o = s.taboption('logging', form.Value, 'log_port', _('External system log server port')); o.optional = true; o.placeholder = 514; o.datatype = 'port'; o = s.taboption('logging', form.ListValue, 'log_proto', _('External system log server protocol')); o.value('udp', 'UDP'); o.value('tcp', 'TCP'); o = s.taboption('logging', form.Value, 'log_file', _('Write system log to file')); o.optional = true; o.placeholder = '/tmp/system.log'; o = s.taboption('logging', form.ListValue, 'conloglevel', _('Log output level'), _('Only affects dmesg kernel log')); o.value(8, _('Debug')); o.value(7, _('Info')); o.value(6, _('Notice')); o.value(5, _('Warning')); o.value(4, _('Error')); o.value(3, _('Critical')); o.value(2, _('Alert')); o.value(1, _('Emergency')); o = s.taboption('logging', form.ListValue, 'cronloglevel', _('Cron Log Level')); o.default = 7; o.value(7, _('Normal')); o.value(9, _('Disabled')); o.value(5, _('Debug')); /* * Zram Properties */ if (L.hasSystemFeature('zram')) { s.tab('zram', _('ZRam Settings')); o = s.taboption('zram', form.Flag, 'zram_enabled', _('Enable')); o.enabled = '1'; o.disabled = '0'; o.default = '1'; o.rmempty = false; o.cfgvalue = function(section_id) { return String(uci.get('system', section_id, 'zram_enabled') ?? '1'); }; o = s.taboption('zram', form.Value, 'zram_size_mb', _('ZRam Size'), _('Size of the ZRam device in megabytes')); o.optional = true; o.placeholder = 16; o.datatype = 'uinteger'; o.depends('zram_enabled', '1'); const supported_algorithms = String(zram_algorithms || '') .trim() .split(/\s+/) .map(algo => algo.replace(/[\[\]]/g, '')) .filter((algo, index, list) => algo && list.indexOf(algo) === index); const current_algorithm = uci.get('system', '@system[0]', 'zram_comp_algo'); o = s.taboption('zram', form.ListValue, 'zram_comp_algo', _('ZRam Compression Algorithm')); o.optional = true; o.default = current_algorithm || supported_algorithms[0] || 'lzo'; o.depends('zram_enabled', '1'); if (!supported_algorithms.length) { [ 'lzo', 'lz4', 'zstd' ].forEach(algo => o.value(algo, algo)); } else { supported_algorithms.forEach(algo => o.value(algo, algo)); if (current_algorithm && supported_algorithms.indexOf(current_algorithm) < 0) o.value(current_algorithm, '%s (%s)'.format(current_algorithm, _('unsupported'))); } } /* * Language & Style */ o = s.taboption('language', form.ListValue, '_lang', _('Language')) o.uciconfig = 'luci'; o.ucisection = 'main'; o.ucioption = 'lang'; o.value('auto', _('auto')); const l = Object.assign({ en: 'English' }, uci.get('luci', 'languages')); const keys = Object.keys(l).sort(); for (let k of keys) if (k.charAt(0) != '.') o.value(k, l[k]); o = s.taboption('language', form.ListValue, '_mediaurlbase', _('Design')) o.uciconfig = 'luci'; o.ucisection = 'main'; o.ucioption = 'mediaurlbase'; const th = Object.keys(uci.get('luci', 'themes') || {}).sort(); for (let t of th) if (t.charAt(0) != '.') o.value(uci.get('luci', 'themes', t), t); o = s.taboption('language', form.Flag, '_tablefilters', _('Table Filters')); o.default = o.disabled; o.uciconfig = 'luci'; o.ucisection = 'main'; o.ucioption = 'tablefilters'; /* * NTP */ if (L.hasSystemFeature('sysntpd')) { var default_servers = [ '0.openwrt.pool.ntp.org', '1.openwrt.pool.ntp.org', '2.openwrt.pool.ntp.org', '3.openwrt.pool.ntp.org' ]; o = s.taboption('timesync', form.Flag, 'enabled', _('Enable NTP client')); o.rmempty = false; o.ucisection = 'ntp'; o.default = o.disabled; o.write = function(section_id, value) { ntpd_enabled = +value; if (ntpd_enabled && !uci.get('system', 'ntp')) { uci.add('system', 'timeserver', 'ntp'); uci.set('system', 'ntp', 'server', default_servers); } if (!ntpd_enabled) uci.set('system', 'ntp', 'enabled', 0); else uci.unset('system', 'ntp', 'enabled'); return callRcInit('sysntpd', 'enable'); }; o.load = function(section_id) { return (ntpd_enabled == 1 && uci.get('system', 'ntp') != null && uci.get('system', 'ntp', 'enabled') != 0) ? '1' : '0'; }; o = s.taboption('timesync', form.Flag, 'enable_server', _('Provide NTP server')); o.ucisection = 'ntp'; o.depends('enabled', '1'); o = s.taboption('timesync', widgets.NetworkSelect, 'interface', _('Bind NTP server'), _('Provide the NTP server to the selected interface or, if unspecified, to all interfaces')); o.ucisection = 'ntp'; o.depends('enable_server', '1'); o.multiple = false; o.nocreate = true; o.optional = true; o = s.taboption('timesync', form.Flag, 'use_dhcp', _('Use DHCP advertised servers')); o.ucisection = 'ntp'; o.default = o.enabled; o.depends('enabled', '1'); o = s.taboption('timesync', form.DynamicList, 'server', _('NTP server candidates'), _('List of upstream NTP server candidates with which to synchronize.')); o.datatype = 'host(0)'; o.ucisection = 'ntp'; o.depends('enabled', '1'); o.load = function(section_id) { return uci.get('system', 'ntp', 'server'); }; } return m.render().then(function(mapEl) { poll.add(function() { return callGetUnixtime().then(function(t) { mapEl.querySelector('#localtime').value = formatTime(t); }); }); return mapEl; }); }, isZramChanged() { if (!L.hasSystemFeature('zram') || !this.map || !this.systemSid) return false; const enabled = this.map.lookupOption('zram_enabled', this.systemSid); const size = this.map.lookupOption('zram_size_mb', this.systemSid); const algo = this.map.lookupOption('zram_comp_algo', this.systemSid); const current_enabled = String(uci.get('system', '@system[0]', 'zram_enabled') ?? '1'); const current_size = String(uci.get('system', '@system[0]', 'zram_size_mb') ?? ''); const current_algo = String(uci.get('system', '@system[0]', 'zram_comp_algo') ?? ''); const form_enabled = String(enabled ? (enabled[0].formvalue(enabled[1]) ?? '') : current_enabled); const form_size = String(size ? (size[0].formvalue(size[1]) ?? '') : current_size); const form_algo = String(algo ? (algo[0].formvalue(algo[1]) ?? '') : current_algo); return (form_enabled !== current_enabled || form_size !== current_size || form_algo !== current_algo); }, handleSaveApply(ev, mode) { if (this.isZramChanged()) { const fn = L.bind(() => { callRcInit('zram', 'restart'); document.removeEventListener('uci-applied', fn); }); document.addEventListener('uci-applied', fn); } return this.super('handleSaveApply', [ev, mode]); } });