diff options
Diffstat (limited to 'modules/luci-mod-system')
17 files changed, 908 insertions, 987 deletions
diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/crontab.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/crontab.js new file mode 100644 index 0000000000..286155790a --- /dev/null +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/crontab.js @@ -0,0 +1,49 @@ +'use strict'; +'require rpc'; + +return L.view.extend({ + callFileRead: rpc.declare({ + object: 'file', + method: 'read', + params: [ 'path' ], + expect: { data: '' } + }), + + callFileWrite: rpc.declare({ + object: 'file', + method: 'write', + params: [ 'path', 'data' ] + }), + + load: function() { + return this.callFileRead('/etc/crontabs/root'); + }, + + handleSave: function(ev) { + var value = (document.querySelector('textarea').value || '').trim().replace(/\r\n/g, '\n') + '\n'; + + return this.callFileWrite('/etc/crontabs/root', value).then(function(rc) { + if (rc != 0) + throw rpc.getStatusText(rc); + + document.querySelector('textarea').value = value; + L.ui.addNotification(null, E('p', _('Contents have been saved.')), 'info'); + + }).catch(function(e) { + L.ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e))); + }); + }, + + render: function(crontab) { + return E([ + E('h2', _('Scheduled Tasks')), + E('p', {}, + _('This is the system crontab in which scheduled tasks can be defined.') + + _('<br/>Note: you need to manually restart the cron service if the crontab file was empty before editing.')), + E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 10 }, crontab != null ? crontab : '')) + ]); + }, + + handleSaveApply: null, + handleReset: null +}); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/dropbear.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/dropbear.js new file mode 100644 index 0000000000..7a8b1428d5 --- /dev/null +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/dropbear.js @@ -0,0 +1,42 @@ +'use strict'; +'require form'; +'require tools.widgets as widgets'; + +return L.view.extend({ + render: function() { + var m, s, o; + + m = new form.Map('dropbear', _('SSH Access'), _('Dropbear offers <abbr title="Secure Shell">SSH</abbr> network shell access and an integrated <abbr title="Secure Copy">SCP</abbr> server')); + + s = m.section(form.TypedSection, 'dropbear', _('Dropbear Instance')); + s.anonymous = true; + s.addremove = true; + s.addbtntitle = _('Add instance'); + + o = s.option(widgets.NetworkSelect, 'Interface', _('Interface'), _('Listen only on the given interface or, if unspecified, on all')); + o.nocreate = true; + o.unspecified = true; + + o = s.option(form.Value, 'Port', _('Port')); + o.datatype = 'port'; + o.placeholder = 22; + + o = s.option(form.Flag, 'PasswordAuth', _('Password authentication'), _('Allow <abbr title="Secure Shell">SSH</abbr> password authentication')); + o.enabled = 'on'; + o.disabled = 'off'; + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.Flag, 'RootPasswordAuth', _('Allow root logins with password'), _('Allow the <em>root</em> user to login with password')); + o.enabled = 'on'; + o.disabled = 'off'; + o.default = o.enabled; + + o = s.option(form.Flag, 'GatewayPorts', _('Gateway Ports'), _('Allow remote hosts to connect to local SSH forwarded ports')); + o.enabled = 'on'; + o.disabled = 'off'; + o.default = o.disabled; + + return m.render(); + } +}); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js index a5bda05761..8a93aeb8c8 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/leds.js @@ -2,8 +2,9 @@ 'require uci'; 'require rpc'; 'require form'; +'require tools.widgets as widgets'; -var callLeds, callUSB, callNetdevs; +var callLeds, callUSB; callLeds = rpc.declare({ object: 'luci', @@ -17,31 +18,17 @@ callUSB = rpc.declare({ expect: { '': {} } }); -callNetdevs = rpc.declare({ - object: 'luci', - method: 'getIfaddrs', - expect: { result: [] }, - filter: function(res) { - var devs = {}; - for (var i = 0; i < res.length; i++) - devs[res[i].name] = true; - return Object.keys(devs).sort(); - } -}); - return L.view.extend({ load: function() { return Promise.all([ callLeds(), - callUSB(), - callNetdevs() + callUSB() ]); }, render: function(results) { var leds = results[0], usb = results[1], - netdevs = results[2], triggers = {}, trigger, m, s, o; @@ -53,9 +40,11 @@ return L.view.extend({ _('<abbr title="Light Emitting Diode">LED</abbr> Configuration'), _('Customizes the behaviour of the device <abbr title="Light Emitting Diode">LED</abbr>s if possible.')); - s = m.section(form.TypedSection, 'led', ''); + s = m.section(form.GridSection, 'led', ''); s.anonymous = true; s.addremove = true; + s.sortable = true; + s.addbtntitle = _('Add LED action'); s.option(form.Value, 'name', _('Name')); @@ -66,32 +55,35 @@ return L.view.extend({ o.rmempty = false; trigger = s.option(form.ListValue, 'trigger', _('Trigger')); - Object.keys(triggers).sort().forEach(function(t) { trigger.value(t, t.replace(/-/g, '')) }); if (usb.devices && usb.devices.length) - trigger.value('usbdev'); + triggers['usbdev'] = true; if (usb.ports && usb.ports.length) - trigger.value('usbport'); + triggers['usbport'] = true; + Object.keys(triggers).sort().forEach(function(t) { trigger.value(t, t.replace(/-/g, '')) }); o = s.option(form.Value, 'delayon', _('On-State Delay')); + o.modalonly = true; o.depends('trigger', 'timer'); o = s.option(form.Value, 'delayoff', _('Off-State Delay')); + o.modalonly = true; o.depends('trigger', 'timer'); - o = s.option(form.ListValue, '_net_dev', _('Device')); + o = s.option(widgets.DeviceSelect, '_net_dev', _('Device')); o.rmempty = true; o.ucioption = 'dev'; + o.modalonly = true; + o.noaliases = true; o.depends('trigger', 'netdev'); o.remove = function(section_id) { var t = trigger.formvalue(section_id); if (t != 'netdev' && t != 'usbdev') uci.unset('system', section_id, 'dev'); }; - o.value(''); - netdevs.sort().forEach(function(dev) { o.value(dev) }); o = s.option(form.MultiValue, 'mode', _('Trigger Mode')); o.rmempty = true; + o.modalonly = true; o.depends('trigger', 'netdev'); o.value('link', _('Link On')); o.value('tx', _('Transmit')); @@ -102,6 +94,7 @@ return L.view.extend({ o.depends('trigger', 'usbdev'); o.rmempty = true; o.ucioption = 'dev'; + o.modalonly = true; o.remove = function(section_id) { var t = trigger.formvalue(section_id); if (t != 'netdev' && t != 'usbdev') @@ -117,6 +110,7 @@ return L.view.extend({ o = s.option(form.MultiValue, 'port', _('USB Ports')); o.depends('trigger', 'usbport'); o.rmempty = true; + o.modalonly = true; o.cfgvalue = function(section_id) { var ports = [], value = uci.get('system', section_id, 'port'); @@ -146,6 +140,7 @@ return L.view.extend({ } o = s.option(form.Value, 'port_mask', _('Switch Port Mask')); + o.modalonly = true; o.depends('trigger', 'switch0'); o.depends('trigger', 'switch1'); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/mounts.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/mounts.js new file mode 100644 index 0000000000..301ebab331 --- /dev/null +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/mounts.js @@ -0,0 +1,432 @@ +'use strict'; +'require uci'; +'require rpc'; +'require form'; + +var callBlockDevices, callMountPoints, callBlockDetect, callUmount, + callFileRead, callFileStat, callFileExec; + +callBlockDevices = rpc.declare({ + object: 'luci', + method: 'getBlockDevices', + expect: { '': {} } +}); + +callMountPoints = rpc.declare({ + object: 'luci', + method: 'getMountPoints', + expect: { result: [] } +}); + +callBlockDetect = rpc.declare({ + object: 'luci', + method: 'setBlockDetect', + expect: { result: false } +}); + +callUmount = rpc.declare({ + object: 'luci', + method: 'setUmount', + params: [ 'path' ], + expect: { result: false } +}); + +callFileRead = rpc.declare({ + object: 'file', + method: 'read', + params: [ 'path' ], + expect: { data: '' }, + filter: function(s) { + return (s || '').split(/\n/).filter(function(ln) { + return ln.match(/\S/) && !ln.match(/^nodev\t/); + }).map(function(ln) { + return ln.trim(); + }); + } +}); + +callFileStat = rpc.declare({ + object: 'file', + method: 'stat', + params: [ 'path' ], + expect: { '': {} }, + filter: function(st) { + return (L.isObject(st) && st.path != null); + } +}); + +callFileExec = rpc.declare({ + object: 'file', + method: 'exec', + params: [ 'command', 'params' ], + expect: { code: 255 } +}); + +function device_textvalue(devices, section_id) { + var v = (uci.get('fstab', section_id, 'uuid') || '').toLowerCase(), + e = Object.keys(devices).filter(function(dev) { return (devices[dev].uuid || '-').toLowerCase() == v })[0]; + + if (v) { + this.section.devices[section_id] = devices[e]; + + if (e && devices[e].size) + return E('span', 'UUID: %h (%s, %1024.2mB)'.format(v, devices[e].dev, devices[e].size)); + else if (e) + return E('span', 'UUID: %h (%s)'.format(v, devices[e].dev)); + else + return E('span', 'UUID: %h (<em>%s</em>)'.format(v, _('not present'))); + } + + v = uci.get('fstab', section_id, 'label'); + e = Object.keys(devices).filter(function(dev) { return devices[dev].label == v })[0]; + + if (v) { + this.section.devices[section_id] = this.section.devices[section_id] || devices[e]; + + if (e && devices[e].size) + return E('span', 'Label: %h (%s, %1024.2mB)'.format(v, devices[e].dev, devices[e].size)); + else if (e) + return E('span', 'Label: %h (%s)'.format(v, devices[e].dev)); + else + return E('span', 'Label: %h (<em>%s</em>)'.format(v, _('not present'))); + } + + v = uci.get('fstab', section_id, 'device'); + e = Object.keys(devices).filter(function(dev) { return devices[dev].dev == v })[0]; + + if (v) { + this.section.devices[section_id] = this.section.devices[section_id] || devices[e]; + + if (e && devices[e].size) + return E('span', '%h (%1024.2mB)'.format(v, devices[e].size)); + else if (e) + return E('span', '%h'.format(v)); + else + return E('span', '%h (<em>%s</em>)'.format(v, _('not present'))); + } +} + +return L.view.extend({ + handleDetect: function(m, ev) { + return callBlockDetect() + .then(L.bind(uci.unload, uci, 'fstab')) + .then(L.bind(m.render, m)); + }, + + handleMountAll: function(m, ev) { + return callFileExec('/sbin/block', ['mount']) + .then(function(rc) { + if (rc != 0) + L.ui.addNotification(null, E('p', _('The <em>block mount</em> command failed with code %d').format(rc))); + }) + .then(L.bind(uci.unload, uci, 'fstab')) + .then(L.bind(m.render, m)); + }, + + handleUmount: function(m, path, ev) { + return callUmount(path) + .then(L.bind(uci.unload, uci, 'fstab')) + .then(L.bind(m.render, m)); + }, + + load: function() { + return Promise.all([ + callBlockDevices(), + callFileRead('/proc/filesystems'), + callFileRead('/etc/filesystems'), + callFileStat('/usr/sbin/e2fsck'), + callFileStat('/usr/sbin/fsck.f2fs'), + callFileStat('/usr/sbin/dosfsck'), + callFileStat('/usr/bin/btrfsck'), + uci.load('fstab') + ]); + }, + + render: function(results) { + var devices = results[0], + procfs = results[1], + etcfs = results[2], + triggers = {}, + trigger, m, s, o; + + var filesystems = procfs.concat(etcfs.filter(function(fs) { + return procfs.indexOf(fs) < 0; + })).sort(); + + var fsck = { + ext2: results[3], + ext3: results[3], + ext4: results[3], + f2fs: results[4], + vfat: results[5], + btrfs: results[6] + }; + + if (!uci.sections('fstab', 'global').length) + uci.add('fstab', 'global'); + + m = new form.Map('fstab', _('Mount Points')); + + s = m.section(form.TypedSection, 'global', _('Global Settings')); + s.addremove = false; + s.anonymous = true; + + o = s.option(form.Button, '_detect', _('Generate Config'), _('Find all currently attached filesystems and swap and replace configuration with defaults based on what was detected')); + o.onclick = this.handleDetect.bind(this, m); + o.inputstyle = 'reload'; + + o = s.option(form.Button, '_mountall', _('Mount attached devices'), _('Attempt to enable configured mount points for attached devices')); + o.onclick = this.handleMountAll.bind(this, m); + o.inputstyle = 'reload'; + + o = s.option(form.Flag, 'anon_swap', _('Anonymous Swap'), _('Mount swap not specifically configured')); + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.Flag, 'anon_mount', _('Anonymous Mount'), _('Mount filesystems not specifically configured')); + o.default = o.disabled; + o.rmempty = false; + + o = s.option(form.Flag, 'auto_swap', _('Automount Swap'), _('Automatically mount swap on hotplug')); + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.Flag, 'auto_mount', _('Automount Filesystem'), _('Automatically mount filesystems on hotplug')); + o.default = o.enabled; + o.rmempty = false; + + o = s.option(form.Flag, 'check_fs', _('Check filesystems before mount'), _('Automatically check filesystem for errors before mounting')); + o.default = o.disabled; + o.rmempty = false; + + + // Mount status table + o = s.option(form.DummyValue, '_mtab'); + + o.load = function(section_id) { + return callMountPoints().then(L.bind(function(mounts) { + this.mounts = mounts; + }, this)); + }; + + o.render = L.bind(function(view, section_id) { + var table = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th' }, _('Filesystem')), + E('div', { 'class': 'th' }, _('Mount Point')), + E('div', { 'class': 'th center' }, _('Available')), + E('div', { 'class': 'th center' }, _('Used')), + E('div', { 'class': 'th' }, _('Unmount')) + ]) + ]); + + var rows = []; + + for (var i = 0; i < this.mounts.length; i++) { + var used = this.mounts[i].size - this.mounts[i].free, + umount = true; + + if (/^\/(overlay|rom|tmp(?:\/.+)?|dev(?:\/.+)?|)$/.test(this.mounts[i].mount)) + umount = false; + + rows.push([ + this.mounts[i].device, + this.mounts[i].mount, + '%1024.2mB / %1024.2mB'.format(this.mounts[i].avail, this.mounts[i].size), + '%.2f%% (%1024.2mB)'.format(100 / this.mounts[i].size * used, used), + umount ? E('button', { + 'class': 'btn cbi-button-remove', + 'click': L.ui.createHandlerFn(view, 'handleUmount', m, this.mounts[i].mount) + }, [ _('Unmount') ]) : '-' + ]); + } + + cbi_update_table(table, rows, E('em', _('Unable to obtain mount information'))); + + return E([], [ E('h3', _('Mounted file systems')), table ]); + }, o, this); + + + // Mounts + s = m.section(form.GridSection, 'mount', _('Mount Points'), _('Mount Points define at which point a memory device will be attached to the filesystem')); + s.modaltitle = _('Mount Points - Mount Entry'); + s.anonymous = true; + s.addremove = true; + s.sortable = true; + s.devices = {}; + + s.renderHeaderRows = function(/* ... */) { + var trEls = form.GridSection.prototype.renderHeaderRows.apply(this, arguments); + return trEls.childNodes[0]; + } + + s.tab('general', _('General Settings')); + s.tab('advanced', _('Advanced Settings')); + + o = s.taboption('general', form.Flag, 'enabled', _('Enabled')); + o.rmempty = false; + o.editable = true; + + o = s.taboption('general', form.DummyValue, '_device', _('Device')); + o.rawhtml = true; + o.modalonly = false; + o.write = function() {}; + o.remove = function() {}; + o.textvalue = device_textvalue.bind(o, devices); + + o = s.taboption('general', form.Value, 'uuid', _('UUID'), _('If specified, mount the device by its UUID instead of a fixed device node')); + o.modalonly = true; + o.value('', _('-- match by uuid --')); + + var devs = Object.keys(devices).sort(); + for (var i = 0; i < devs.length; i++) { + var dev = devices[devs[i]]; + if (dev.uuid && dev.size) + o.value(dev.uuid, '%s (%s, %1024.2mB)'.format(dev.uuid, dev.dev, dev.size)); + else if (dev.uuid) + o.value(dev.uuid, '%s (%s)'.format(dev.uuid, dev.dev)); + } + + o = s.taboption('general', form.Value, 'label', _('Label'), _('If specified, mount the device by the partition label instead of a fixed device node')); + o.modalonly = true; + o.depends('uuid', ''); + o.value('', _('-- match by label --')); + + for (var i = 0; i < devs.length; i++) { + var dev = devices[devs[i]]; + if (dev.label && dev.size) + o.value(dev.label, '%s (%s, %1024.2mB)'.format(dev.label, dev.dev, dev.size)); + else if (dev.label) + o.value(dev.label, '%s (%s)'.format(dev.label, dev.dev)); + } + + o = s.taboption('general', form.Value, 'device', _('Device'), _('The device file of the memory or partition (<abbr title="for example">e.g.</abbr> <code>/dev/sda1</code>)')); + o.modalonly = true; + o.depends({ uuid: '', label: '' }); + + for (var i = 0; i < devs.length; i++) { + var dev = devices[devs[i]]; + if (dev.size) + o.value(dev.dev, '%s (%1024.2mB)'.format(dev.dev, dev.size)); + else + o.value(dev.dev); + } + + o = s.taboption('general', form.Value, 'target', _('Mount point'), _('Specifies the directory the device is attached to')); + o.value('/', _('Use as root filesystem (/)')); + o.value('/overlay', _('Use as external overlay (/overlay)')); + o.rmempty = false; + + o = s.taboption('general', form.DummyValue, '__notice', _('Root preparation')); + o.depends('target', '/'); + o.modalonly = true; + o.rawhtml = true; + o.default = '' + + '<p>%s</p>'.format(_('Make sure to clone the root filesystem using something like the commands below:')) + + '<pre>' + + 'mkdir -p /tmp/introot\n' + + 'mkdir -p /tmp/extroot\n' + + 'mount --bind / /tmp/introot\n' + + 'mount /dev/sda1 /tmp/extroot\n' + + 'tar -C /tmp/introot -cvf - . | tar -C /tmp/extroot -xf -\n' + + 'umount /tmp/introot\n' + + 'umount /tmp/extroot\n' + + '</pre>' + ; + + o = s.taboption('advanced', form.ListValue, 'fstype', _('Filesystem')); + + o.textvalue = function(section_id) { + var dev = this.section.devices[section_id], + text = this.cfgvalue(section_id) || 'auto'; + + if (dev && dev.type && dev.type != text) + text += ' (%s)'.format(dev.type); + + return text; + }; + + o.value('', 'auto'); + + for (var i = 0; i < filesystems.length; i++) + o.value(filesystems[i]); + + o = s.taboption('advanced', form.Value, 'options', _('Mount options'), _('See "mount" manpage for details')); + o.textvalue = function(section_id) { return this.cfgvalue(section_id) || 'defaults' }; + o.placeholder = 'defaults'; + + s.taboption('advanced', form.Flag, 'enabled_fsck', _('Run filesystem check'), _('Run a filesystem check before mounting the device')); + + + // Swaps + s = m.section(form.GridSection, 'swap', _('SWAP'), _('If your physical memory is insufficient unused data can be temporarily swapped to a swap-device resulting in a higher amount of usable <abbr title="Random Access Memory">RAM</abbr>. Be aware that swapping data is a very slow process as the swap-device cannot be accessed with the high datarates of the <abbr title="Random Access Memory">RAM</abbr>.')); + s.modaltitle = _('Mount Points - Swap Entry'); + s.anonymous = true; + s.addremove = true; + s.sortable = true; + s.devices = {}; + + s.renderHeaderRows = function(/* ... */) { + var trEls = form.GridSection.prototype.renderHeaderRows.apply(this, arguments); + trEls.childNodes[0].childNodes[1].style.width = '90%'; + return trEls.childNodes[0]; + } + + o = s.option(form.Flag, 'enabled', _('Enabled')); + o.rmempty = false; + o.editable = true; + + o = s.option(form.DummyValue, '_device', _('Device')); + o.modalonly = false; + o.textvalue = device_textvalue.bind(o, devices); + + o = s.option(form.Value, 'uuid', _('UUID'), _('If specified, mount the device by its UUID instead of a fixed device node')); + o.modalonly = true; + o.value('', _('-- match by uuid --')); + + var devs = Object.keys(devices).sort(); + for (var i = 0; i < devs.length; i++) { + var dev = devices[devs[i]]; + if (dev.dev.match(/^\/dev\/(mtdblock|ubi|ubiblock)\d/)) + continue; + + if (dev.uuid && dev.size) + o.value(dev.uuid, '%s (%s, %1024.2mB)'.format(dev.uuid, dev.dev, dev.size)); + else if (dev.uuid) + o.value(dev.uuid, '%s (%s)'.format(dev.uuid, dev.dev)); + } + + o = s.option(form.Value, 'label', _('Label'), _('If specified, mount the device by the partition label instead of a fixed device node')); + o.modalonly = true; + o.depends('uuid', ''); + o.value('', _('-- match by label --')); + + for (var i = 0; i < devs.length; i++) { + var dev = devices[devs[i]]; + if (dev.dev.match(/^\/dev\/(mtdblock|ubi|ubiblock)\d/)) + continue; + + if (dev.label && dev.size) + o.value(dev.label, '%s (%s, %1024.2mB)'.format(dev.label, dev.dev, dev.size)); + else if (dev.label) + o.value(dev.label, '%s (%s)'.format(dev.label, dev.dev)); + } + + o = s.option(form.Value, 'device', _('Device'), _('The device file of the memory or partition (<abbr title="for example">e.g.</abbr> <code>/dev/sda1</code>)')); + o.modalonly = true; + o.depends({ uuid: '', label: '' }); + + for (var i = 0; i < devs.length; i++) { + var dev = devices[devs[i]]; + if (dev.dev.match(/^\/dev\/(mtdblock|ubi|ubiblock)\d/)) + continue; + + if (dev.size) + o.value(dev.dev, '%s (%1024.2mB)'.format(dev.dev, dev.size)); + else + o.value(dev.dev); + } + + return m.render(); + } +}); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/password.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/password.js index 7a79d7e2da..6c5ffa1b26 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/password.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/password.js @@ -1,31 +1,94 @@ -function submitPassword(ev) { - var pw1 = document.body.querySelector('[name="pw1"]'), - pw2 = document.body.querySelector('[name="pw2"]'); - - if (!pw1.value.length || !pw2.value.length) - return; - - if (pw1.value === pw2.value) { - L.showModal(_('Change login password'), - E('p', { class: 'spinning' }, _('Changing password…'))); - - L.post('admin/system/admin/password/json', { password: pw1.value }, - function() { - showModal(_('Change login password'), [ - E('div', _('The system password has been successfully changed.')), - E('div', { 'class': 'right' }, - E('div', { class: 'btn', click: L.hideModal }, _('Dismiss'))) - ]); - - pw1.value = pw2.value = ''; - }); - } - else { - L.showModal(_('Change login password'), [ - E('div', { class: 'alert-message warning' }, - _('Given password confirmation did not match, password not changed!')), - E('div', { 'class': 'right' }, - E('div', { class: 'btn', click: L.hideModal }, _('Dismiss'))) - ]); +'use strict'; +'require form'; +'require rpc'; + +var formData = { + password: { + pw1: null, + pw2: null } -} +}; + +var callSetPassword = rpc.declare({ + object: 'luci', + method: 'setPassword', + params: [ 'username', 'password' ], + expect: { result: false } +}); + +return L.view.extend({ + checkPassword: function(section_id, value) { + var strength = document.querySelector('.cbi-value-description'), + strongRegex = new RegExp("^(?=.{8,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\\W).*$", "g"), + mediumRegex = new RegExp("^(?=.{7,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$", "g"), + enoughRegex = new RegExp("(?=.{6,}).*", "g"); + + if (strength && value.length) { + if (false == enoughRegex.test(value)) + strength.innerHTML = '%s: <span style="color:red">%s</span>'.format(_('Password strength'), _('More Characters')); + else if (strongRegex.test(value)) + strength.innerHTML = '%s: <span style="color:green">%s</span>'.format(_('Password strength'), _('Strong')); + else if (mediumRegex.test(value)) + strength.innerHTML = '%s: <span style="color:orange">%s</span>'.format(_('Password strength'), _('Medium')); + else + strength.innerHTML = '%s: <span style="color:red">%s</span>'.format(_('Password strength'), _('Weak')); + } + + return true; + }, + + render: function() { + var m, s, o; + + m = new form.JSONMap(formData, _('Router Password'), _('Changes the administrator password for accessing the device')); + s = m.section(form.NamedSection, 'password', 'password'); + + o = s.option(form.Value, 'pw1', _('Password')); + o.password = true; + o.validate = this.checkPassword; + + o = s.option(form.Value, 'pw2', _('Confirmation'), ' '); + o.password = true; + o.renderWidget = function(/* ... */) { + var node = form.Value.prototype.renderWidget.apply(this, arguments); + + node.childNodes[1].addEventListener('keydown', function(ev) { + if (ev.keyCode == 13 && !ev.currentTarget.classList.contains('cbi-input-invalid')) + document.querySelector('.cbi-button-save').click(); + }); + + return node; + }; + + return m.render(); + }, + + handleSave: function() { + var map = document.querySelector('.cbi-map'); + + return L.dom.callClassMethod(map, 'save').then(function() { + if (formData.password.pw1 == null || formData.password.pw1.length == 0) + return; + + if (formData.password.pw1 != formData.password.pw2) { + L.ui.addNotification(null, E('p', _('Given password confirmation did not match, password not changed!')), 'danger'); + return; + } + + return callSetPassword('root', formData.password.pw1).then(function(success) { + if (success) + L.ui.addNotification(null, E('p', _('The system password has been successfully changed.')), 'info'); + else + L.ui.addNotification(null, E('p', _('Failed to change the system password.')), 'danger'); + + formData.password.pw1 = null; + formData.password.pw2 = null; + + L.dom.callClassMethod(map, 'render'); + }); + }); + }, + + handleSaveApply: null, + handleReset: null +}); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js index d298b3be98..a68cb6b0bf 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/sshkeys.js @@ -1,4 +1,7 @@ -SSHPubkeyDecoder.prototype = { +'use strict'; +'require rpc'; + +var SSHPubkeyDecoder = L.Class.singleton({ lengthDecode: function(s, off) { var l = (s.charCodeAt(off++) << 24) | @@ -85,19 +88,29 @@ SSHPubkeyDecoder.prototype = { return null; } } -}; +}); -function SSHPubkeyDecoder() {} +var callFileRead = rpc.declare({ + object: 'file', + method: 'read', + params: [ 'path' ], + expect: { data: '' } +}); + +var callFileWrite = rpc.declare({ + object: 'file', + method: 'write', + params: [ 'path', 'data' ] +}); function renderKeys(keys) { - var list = document.querySelector('.cbi-dynlist[name="sshkeys"]'), - decoder = new SSHPubkeyDecoder(); + var list = document.querySelector('.cbi-dynlist[name="sshkeys"]'); while (!matchesElem(list.firstElementChild, '.add-item')) list.removeChild(list.firstElementChild); keys.forEach(function(key) { - var pubkey = decoder.decode(key); + var pubkey = SSHPubkeyDecoder.decode(key); if (pubkey) list.insertBefore(E('div', { class: 'item', @@ -117,19 +130,16 @@ function renderKeys(keys) { } function saveKeys(keys) { - L.showModal(_('Add key'), E('div', { class: 'spinning' }, _('Saving keys…'))); - L.post('admin/system/admin/sshkeys/json', { keys: JSON.stringify(keys) }, function(xhr, keys) { - renderKeys(keys); - L.hideModal(); - }); + return callFileWrite('/etc/dropbear/authorized_keys', keys.join('\n') + '\n') + .then(renderKeys.bind(this, keys)) + .then(L.ui.hideModal); } function addKey(ev) { - var decoder = new SSHPubkeyDecoder(), - list = findParent(ev.target, '.cbi-dynlist'), + var list = findParent(ev.target, '.cbi-dynlist'), input = list.querySelector('input[type="text"]'), key = input.value.trim(), - pubkey = decoder.decode(key), + pubkey = SSHPubkeyDecoder.decode(key), keys = []; if (!key.length) @@ -140,21 +150,26 @@ function addKey(ev) { }); if (keys.indexOf(key) !== -1) { - L.showModal(_('Add key'), [ + L.ui.showModal(_('Add key'), [ E('div', { class: 'alert-message warning' }, _('The given SSH public key has already been added.')), E('div', { class: 'right' }, E('div', { class: 'btn', click: L.hideModal }, _('Close'))) ]); } else if (!pubkey) { - L.showModal(_('Add key'), [ + L.ui.showModal(_('Add key'), [ E('div', { class: 'alert-message warning' }, _('The given SSH public key is invalid. Please supply proper public RSA or ECDSA keys.')), E('div', { class: 'right' }, E('div', { class: 'btn', click: L.hideModal }, _('Close'))) ]); } else { keys.push(key); - saveKeys(keys); input.value = ''; + + return saveKeys(keys).then(function() { + var added = list.querySelector('[data-key="%s"]'.format(key)); + if (added) + added.classList.add('flash'); + }); } } @@ -175,7 +190,7 @@ function removeKey(ev) { E('div', { class: 'right' }, [ E('div', { class: 'btn', click: L.hideModal }, _('Cancel')), ' ', - E('div', { class: 'btn danger', click: function(ev) { saveKeys(keys) } }, _('Delete key')), + E('div', { class: 'btn danger', click: L.ui.createHandlerFn(this, saveKeys, keys) }, _('Delete key')), ]) ]); } @@ -205,11 +220,67 @@ function dropKey(ev) { ev.preventDefault(); } -window.addEventListener('dragover', function(ev) { ev.preventDefault() }); -window.addEventListener('drop', function(ev) { ev.preventDefault() }); +function handleWindowDragDropIgnore(ev) { + ev.preventDefault() +} -requestAnimationFrame(function() { - L.get('admin/system/admin/sshkeys/json', null, function(xhr, keys) { - renderKeys(keys); - }); +return L.view.extend({ + load: function() { + return callFileRead('/etc/dropbear/authorized_keys').then(function(data) { + return (data || '').split(/\n/).map(function(line) { + return line.trim(); + }).filter(function(line) { + return line.match(/^ssh-/) != null; + }); + }); + }, + + render: function(keys) { + var list = E('div', { 'class': 'cbi-dynlist', 'dragover': dragKey, 'drop': dropKey }, [ + E('div', { 'class': 'add-item' }, [ + E('input', { + 'class': 'cbi-input-text', + 'type': 'text', + 'placeholder': _('Paste or drag SSH key file…') , + 'keydown': function(ev) { if (ev.keyCode === 13) addKey(ev) } + }), + E('button', { + 'class': 'cbi-button', + 'click': L.ui.createHandlerFn(this, addKey) + }, _('Add key')) + ]) + ]); + + keys.forEach(L.bind(function(key) { + var pubkey = SSHPubkeyDecoder.decode(key); + if (pubkey) + list.insertBefore(E('div', { + class: 'item', + click: L.ui.createHandlerFn(this, removeKey), + 'data-key': key + }, [ + E('strong', pubkey.comment || _('Unnamed key')), E('br'), + E('small', [ + '%s, %s'.format(pubkey.type, pubkey.curve || _('%d Bit').format(pubkey.bits)), + E('br'), E('code', pubkey.fprint) + ]) + ]), list.lastElementChild); + }, this)); + + if (list.firstElementChild === list.lastElementChild) + list.insertBefore(E('p', _('No public keys present yet.')), list.lastElementChild); + + window.addEventListener('dragover', handleWindowDragDropIgnore); + window.addEventListener('drop', handleWindowDragDropIgnore); + + return E('div', {}, [ + E('h2', _('SSH-Keys')), + E('div', { 'class': 'cbi-section-descr' }, _('Public keys allow for the passwordless SSH logins with a higher security compared to the use of plain passwords. In order to upload a new key to the device, paste an OpenSSH compatible public key line or drag a <code>.pub</code> file into the input field.')), + E('div', { 'class': 'cbi-section-node' }, list) + ]); + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null }); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/startup.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/startup.js new file mode 100644 index 0000000000..365e6c8ed8 --- /dev/null +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/startup.js @@ -0,0 +1,147 @@ +'use strict'; +'require rpc'; + +return L.view.extend({ + callInitList: rpc.declare({ + object: 'luci', + method: 'getInitList', + expect: { '': {} } + }), + + callInitAction: rpc.declare({ + object: 'luci', + method: 'setInitAction', + params: [ 'name', 'action' ], + expect: { result: false } + }), + + callFileRead: rpc.declare({ + object: 'file', + method: 'read', + params: [ 'path' ], + expect: { data: '' } + }), + + callFileWrite: rpc.declare({ + object: 'file', + method: 'write', + params: [ 'path', 'data' ] + }), + + load: function() { + return Promise.all([ + this.callFileRead('/etc/rc.local'), + this.callInitList() + ]); + }, + + handleAction: function(name, action, ev) { + return this.callInitAction(name, action).then(function(success) { + if (success != true) + throw _('Command failed'); + + return true; + }).catch(function(e) { + L.ui.addNotification(null, E('p', _('Failed to execute "/etc/init.d/%s %s" action: %s').format(name, action, e))); + }); + }, + + handleEnableDisable: function(name, isEnabled, ev) { + return this.handleAction(name, isEnabled ? 'disable' : 'enable', ev).then(L.bind(function(name, isEnabled, cell) { + L.dom.content(cell, this.renderEnableDisable({ + name: name, + enabled: isEnabled + })); + }, this, name, !isEnabled, ev.currentTarget.parentNode)); + }, + + handleRcLocalSave: function(ev) { + var value = (document.querySelector('textarea').value || '').trim().replace(/\r\n/g, '\n') + '\n'; + + return this.callFileWrite('/etc/rc.local', value).then(function(rc) { + if (rc != 0) + throw rpc.getStatusText(rc); + + document.querySelector('textarea').value = value; + L.ui.addNotification(null, E('p', _('Contents have been saved.')), 'info'); + }).catch(function(e) { + L.ui.addNotification(null, E('p', _('Unable to save contents: %s').format(e))); + }); + }, + + renderEnableDisable: function(init) { + return E('button', { + class: 'btn cbi-button-%s'.format(init.enabled ? 'positive' : 'negative'), + click: L.ui.createHandlerFn(this, 'handleEnableDisable', init.name, init.enabled) + }, init.enabled ? _('Enabled') : _('Disabled')); + }, + + render: function(data) { + var rcLocal = data[0], + initList = data[1], + rows = [], list = []; + + var table = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr table-titles' }, [ + E('div', { 'class': 'th' }, _('Start priority')), + E('div', { 'class': 'th' }, _('Initscript')), + E('div', { 'class': 'th' }, _('Enable/Disable')), + E('div', { 'class': 'th' }, _('Start')), + E('div', { 'class': 'th' }, _('Restart')), + E('div', { 'class': 'th' }, _('Stop')) + ]) + ]); + + for (var init in initList) + if (initList[init].index < 100) + list.push(Object.assign({ name: init }, initList[init])); + + list.sort(function(a, b) { + if (a.index != b.index) + return a.index - b.index + + return a.name > b.name; + }); + + for (var i = 0; i < list.length; i++) { + rows.push([ + '%02d'.format(list[i].index), + list[i].name, + this.renderEnableDisable(list[i]), + E('button', { 'class': 'btn cbi-button-action', 'click': L.ui.createHandlerFn(this, 'handleAction', list[i].name, 'start') }, _('Start')), + E('button', { 'class': 'btn cbi-button-action', 'click': L.ui.createHandlerFn(this, 'handleAction', list[i].name, 'restart') }, _('Restart')), + E('button', { 'class': 'btn cbi-button-action', 'click': L.ui.createHandlerFn(this, 'handleAction', list[i].name, 'stop') }, _('Stop')) + ]); + } + + cbi_update_table(table, rows); + + var view = E('div', {}, [ + E('h2', _('Startup')), + E('div', {}, [ + E('div', { 'data-tab': 'init', 'data-tab-title': _('Initscripts') }, [ + E('p', {}, _('You can enable or disable installed init scripts here. Changes will applied after a device reboot.<br /><strong>Warning: If you disable essential init scripts like "network", your device might become inaccessible!</strong>')), + table + ]), + E('div', { 'data-tab': 'rc', 'data-tab-title': _('Local Startup') }, [ + E('p', {}, _('This is the content of /etc/rc.local. Insert your own commands here (in front of \'exit 0\') to execute them at the end of the boot process.')), + E('p', {}, E('textarea', { 'style': 'width:100%', 'rows': 20 }, rcLocal != null ? rcLocal : '')), + E('div', { 'class': 'cbi-page-actions' }, [ + E('button', { + 'class': 'btn cbi-button-save', + 'click': L.ui.createHandlerFn(this, 'handleRcLocalSave') + }, _('Save')) + ]) + ]) + ]) + ]); + + L.ui.tabs.initTabGroup(view.lastElementChild.childNodes); + + return view; + }, + + handleSaveApply: null, + handleSave: null, + handleReset: null +}); diff --git a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js index 1ed8f64d8f..ac6586af46 100644 --- a/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/system.js @@ -47,33 +47,27 @@ callTimezone = rpc.declare({ CBILocalTime = form.DummyValue.extend({ renderWidget: function(section_id, option_id, cfgvalue) { return E([], [ - E('span', { 'id': 'localtime' }, - new Date(cfgvalue * 1000).toLocaleString()), + E('span', {}, [ + E('input', { + 'id': 'localtime', + 'type': 'text', + 'readonly': true, + 'value': new Date(cfgvalue * 1000).toLocaleString() + }) + ]), ' ', E('button', { 'class': 'cbi-button cbi-button-apply', - 'click': function() { - this.blur(); - this.classList.add('spinning'); - this.disabled = true; - callSetLocaltime(Math.floor(Date.now() / 1000)).then(L.bind(function() { - this.classList.remove('spinning'); - this.disabled = false; - }, this)); - } + 'click': L.ui.createHandlerFn(this, function() { + return callSetLocaltime(Math.floor(Date.now() / 1000)); + }) }, _('Sync with browser')), ' ', this.ntpd_support ? E('button', { 'class': 'cbi-button cbi-button-apply', - 'click': function() { - this.blur(); - this.classList.add('spinning'); - this.disabled = true; - callInitAction('sysntpd', 'restart').then(L.bind(function() { - this.classList.remove('spinning'); - this.disabled = false; - }, this)); - } + 'click': L.ui.createHandlerFn(this, function() { + return callInitAction('sysntpd', 'restart'); + }) }, _('Sync with NTP-Server')) : '' ]); }, @@ -83,7 +77,6 @@ return L.view.extend({ load: function() { return Promise.all([ callInitList('sysntpd'), - callInitList('zram'), callTimezone(), callGetLocaltime(), uci.load('luci'), @@ -92,11 +85,10 @@ return L.view.extend({ }, render: function(rpc_replies) { - var ntpd_support = rpc_replies[0], - zram_support = rpc_replies[1], - timezones = rpc_replies[2], - localtime = rpc_replies[3], - ntp_setup, ntp_enabled, m, s, o; + var ntpd_enabled = rpc_replies[0], + timezones = rpc_replies[1], + localtime = rpc_replies[2], + m, s, o; m = new form.Map('system', _('System'), @@ -119,7 +111,7 @@ return L.view.extend({ o = s.taboption('general', CBILocalTime, '_systime', _('Local Time')); o.cfgvalue = function() { return localtime }; - o.ntpd_support = ntpd_support; + o.ntpd_support = ntpd_enabled; o = s.taboption('general', form.Value, 'hostname', _('Hostname')); o.datatype = 'hostname'; @@ -184,7 +176,7 @@ return L.view.extend({ * Zram Properties */ - if (zram_support != null) { + if (L.hasSystemFeature('zram')) { s.tab('zram', _('ZRam Settings')); o = s.taboption('zram', form.Value, 'zram_size_mb', _('ZRam Size'), _('Size of the ZRam device in megabytes')); @@ -234,7 +226,7 @@ return L.view.extend({ * NTP */ - if (ntpd_support != null) { + 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' @@ -245,14 +237,14 @@ return L.view.extend({ o.ucisection = 'ntp'; o.default = o.disabled; o.write = function(section_id, value) { - ntpd_support = +value; + ntpd_enabled = +value; - if (ntpd_support && !uci.get('system', 'ntp')) { + if (ntpd_enabled && !uci.get('system', 'ntp')) { uci.add('system', 'timeserver', 'ntp'); uci.set('system', 'ntp', 'server', default_servers); } - if (!ntpd_support) + if (!ntpd_enabled) uci.set('system', 'ntp', 'enabled', 0); else uci.unset('system', 'ntp', 'enabled'); @@ -260,7 +252,7 @@ return L.view.extend({ return callInitAction('sysntpd', 'enable'); }; o.load = function(section_id) { - return (ntpd_support == 1 && + return (ntpd_enabled == 1 && uci.get('system', 'ntp') != null && uci.get('system', 'ntp', 'enabled') != 0) ? '1' : '0'; }; @@ -284,7 +276,7 @@ return L.view.extend({ return m.render().then(function(mapEl) { L.Poll.add(function() { return callGetLocaltime().then(function(t) { - mapEl.querySelector('#localtime').innerHTML = new Date(t * 1000).toLocaleString(); + mapEl.querySelector('#localtime').value = new Date(t * 1000).toLocaleString(); }); }); diff --git a/modules/luci-mod-system/luasrc/controller/admin/system.lua b/modules/luci-mod-system/luasrc/controller/admin/system.lua index d73a1cbdb4..d6e1dc7815 100644 --- a/modules/luci-mod-system/luasrc/controller/admin/system.lua +++ b/modules/luci-mod-system/luasrc/controller/admin/system.lua @@ -8,26 +8,20 @@ function index() local fs = require "nixio.fs" entry({"admin", "system", "system"}, view("system/system"), _("System"), 1) - entry({"admin", "system", "clock_status"}, post_on({ set = true }, "action_clock_status")) - entry({"admin", "system", "ntp_restart"}, call("action_ntp_restart"), nil).leaf = true entry({"admin", "system", "admin"}, firstchild(), _("Administration"), 2) - entry({"admin", "system", "admin", "password"}, template("admin_system/password"), _("Router Password"), 1) - entry({"admin", "system", "admin", "password", "json"}, post("action_password")) + entry({"admin", "system", "admin", "password"}, view("system/password"), _("Router Password"), 1) if fs.access("/etc/config/dropbear") then - entry({"admin", "system", "admin", "dropbear"}, cbi("admin_system/dropbear"), _("SSH Access"), 2) - entry({"admin", "system", "admin", "sshkeys"}, template("admin_system/sshkeys"), _("SSH-Keys"), 3) - entry({"admin", "system", "admin", "sshkeys", "json"}, post_on({ keys = true }, "action_sshkeys")) + entry({"admin", "system", "admin", "dropbear"}, view("system/dropbear"), _("SSH Access"), 2) + entry({"admin", "system", "admin", "sshkeys"}, view("system/sshkeys"), _("SSH-Keys"), 3) end - entry({"admin", "system", "startup"}, form("admin_system/startup"), _("Startup"), 45) - entry({"admin", "system", "crontab"}, form("admin_system/crontab"), _("Scheduled Tasks"), 46) + entry({"admin", "system", "startup"}, view("system/startup"), _("Startup"), 45) + entry({"admin", "system", "crontab"}, view("system/crontab"), _("Scheduled Tasks"), 46) if fs.access("/sbin/block") and fs.access("/etc/config/fstab") then - entry({"admin", "system", "fstab"}, cbi("admin_system/fstab"), _("Mount Points"), 50) - entry({"admin", "system", "fstab", "mount"}, cbi("admin_system/fstab/mount"), nil).leaf = true - entry({"admin", "system", "fstab", "swap"}, cbi("admin_system/fstab/swap"), nil).leaf = true + entry({"admin", "system", "mounts"}, view("system/mounts"), _("Mount Points"), 50) end local nodes, number = fs.glob("/sys/class/leds/*") @@ -49,30 +43,6 @@ function index() entry({"admin", "system", "reboot", "call"}, post("action_reboot")) end -function action_clock_status() - local set = tonumber(luci.http.formvalue("set")) - if set ~= nil and set > 0 then - local date = os.date("*t", set) - if date then - luci.sys.call("date -s '%04d-%02d-%02d %02d:%02d:%02d'" %{ - date.year, date.month, date.day, date.hour, date.min, date.sec - }) - luci.sys.call("/etc/init.d/sysfixtime restart") - end - end - - luci.http.prepare_content("application/json") - luci.http.write_json({ timestring = os.date("%c") }) -end - -function action_ntp_restart() - if nixio.fs.access("/etc/init.d/sysntpd") then - os.execute("/etc/init.d/sysntpd restart") - end - luci.http.prepare_content("text/plain") - luci.http.write("0") -end - local function image_supported(image) return (os.execute("sysupgrade -T %q >/dev/null" % image) == 0) end @@ -282,67 +252,6 @@ function action_reset() http.redirect(luci.dispatcher.build_url('admin/system/flashops')) end -function action_password() - local password = luci.http.formvalue("password") - if not password then - luci.http.status(400, "Bad Request") - return - end - - luci.http.prepare_content("application/json") - luci.http.write_json({ code = luci.sys.user.setpasswd("root", password) }) -end - -function action_sshkeys() - local keys = luci.http.formvalue("keys") - if keys then - keys = luci.jsonc.parse(keys) - if not keys or type(keys) ~= "table" then - luci.http.status(400, "Bad Request") - return - end - - local fd, err = io.open("/etc/dropbear/authorized_keys", "w") - if not fd then - luci.http.status(503, err) - return - end - - local _, k - for _, k in ipairs(keys) do - if type(k) == "string" and k:match("^%w+%-") then - fd:write(k) - fd:write("\n") - end - end - - fd:close() - end - - local fd, err = io.open("/etc/dropbear/authorized_keys", "r") - if not fd then - luci.http.status(503, err) - return - end - - local rv = {} - while true do - local ln = fd:read("*l") - if not ln then - break - elseif ln:match("^[%w%-]+%s+[A-Za-z0-9+/=]+$") or - ln:match("^[%w%-]+%s+[A-Za-z0-9+/=]+%s") - then - rv[#rv+1] = ln - end - end - - fd:close() - - luci.http.prepare_content("application/json") - luci.http.write_json(rv) -end - function action_reboot() luci.sys.reboot() end diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua deleted file mode 100644 index beb24e7cae..0000000000 --- a/modules/luci-mod-system/luasrc/model/cbi/admin_system/crontab.lua +++ /dev/null @@ -1,33 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Copyright 2008-2013 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local cronfile = "/etc/crontabs/root" - -f = SimpleForm("crontab", translate("Scheduled Tasks"), - translate("This is the system crontab in which scheduled tasks can be defined.") .. - translate("<br/>Note: you need to manually restart the cron service if the " .. - "crontab file was empty before editing.")) - -t = f:field(TextValue, "crons") -f.forcewrite = true -t.rmempty = true -t.rows = 10 -function t.cfgvalue() - return fs.readfile(cronfile) or "" -end - -function f.handle(self, state, data) - if state == FORM_VALID then - if data.crons then - fs.writefile(cronfile, data.crons:gsub("\r\n", "\n")) - luci.sys.call("/usr/bin/crontab %q" % cronfile) - else - fs.writefile(cronfile, "") - end - end - return true -end - -return f diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/dropbear.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/dropbear.lua deleted file mode 100644 index 1a1695d2be..0000000000 --- a/modules/luci-mod-system/luasrc/model/cbi/admin_system/dropbear.lua +++ /dev/null @@ -1,53 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Copyright 2011-2018 Jo-Philipp Wich <jo@mein.io> --- Licensed to the public under the Apache License 2.0. - -m = Map("dropbear", translate("SSH Access"), - translate("Dropbear offers <abbr title=\"Secure Shell\">SSH</abbr> network shell access and an integrated <abbr title=\"Secure Copy\">SCP</abbr> server")) -m.apply_on_parse = true - -s = m:section(TypedSection, "dropbear", translate("Dropbear Instance")) -s.anonymous = true -s.addremove = true - - -ni = s:option(Value, "Interface", translate("Interface"), - translate("Listen only on the given interface or, if unspecified, on all")) - -ni.template = "cbi/network_netlist" -ni.nocreate = true -ni.unspecified = true - - -pt = s:option(Value, "Port", translate("Port"), - translate("Specifies the listening port of this <em>Dropbear</em> instance")) - -pt.datatype = "port" -pt.default = 22 - - -pa = s:option(Flag, "PasswordAuth", translate("Password authentication"), - translate("Allow <abbr title=\"Secure Shell\">SSH</abbr> password authentication")) - -pa.enabled = "on" -pa.disabled = "off" -pa.default = pa.enabled -pa.rmempty = false - - -ra = s:option(Flag, "RootPasswordAuth", translate("Allow root logins with password"), - translate("Allow the <em>root</em> user to login with password")) - -ra.enabled = "on" -ra.disabled = "off" -ra.default = ra.enabled - - -gp = s:option(Flag, "GatewayPorts", translate("Gateway ports"), - translate("Allow remote hosts to connect to local SSH forwarded ports")) - -gp.enabled = "on" -gp.disabled = "off" -gp.default = gp.disabled - -return m diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua deleted file mode 100644 index 4a31146a03..0000000000 --- a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab.lua +++ /dev/null @@ -1,272 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Licensed to the public under the Apache License 2.0. - -require("luci.tools.webadmin") - -local fs = require "nixio.fs" -local util = require "nixio.util" -local tp = require "luci.template.parser" - -local block = io.popen("block info", "r") -local ln, dev, devices = nil, nil, {} - -repeat - ln = block:read("*l") - dev = ln and ln:match("^/dev/(.-):") - - if dev then - local e, s, key, val = { } - - for key, val in ln:gmatch([[(%w+)="(.-)"]]) do - e[key:lower()] = val - devices[val] = e - end - - s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev))) - - e.dev = "/dev/%s" % dev - e.size = s and math.floor(s / 2048) - - devices[e.dev] = e - end -until not ln - -block:close() - -m = Map("fstab", translate("Mount Points")) -s = m:section(TypedSection, "global", translate("Global Settings")) -s.addremove = false -s.anonymous = true - -detect = s:option(Button, "block_detect", translate("Generate Config"), translate("Find all currently attached filesystems and swap and replace configuration with defaults based on what was detected")) -detect.inputstyle = "reload" - -detect.write = function(self, section) - luci.sys.call("block detect >/etc/config/fstab") - luci.http.redirect(luci.dispatcher.build_url("admin/system", "fstab")) -end - -o = s:option(Flag, "anon_swap", translate("Anonymous Swap"), translate("Mount swap not specifically configured")) -o.default = o.disabled -o.rmempty = false - -o = s:option(Flag, "anon_mount", translate("Anonymous Mount"), translate("Mount filesystems not specifically configured")) -o.default = o.disabled -o.rmempty = false - -o = s:option(Flag, "auto_swap", translate("Automount Swap"), translate("Automatically mount swap on hotplug")) -o.default = o.enabled -o.rmempty = false - -o = s:option(Flag, "auto_mount", translate("Automount Filesystem"), translate("Automatically mount filesystems on hotplug")) -o.default = o.enabled -o.rmempty = false - -o = s:option(Flag, "check_fs", translate("Check filesystems before mount"), translate("Automatically check filesystem for errors before mounting")) -o.default = o.disabled -o.rmempty = false - -local mounts = luci.sys.mounts() -local non_system_mounts = {} -for rawmount, val in pairs(mounts) do - if (string.find(val.mountpoint, "/tmp/.jail") == nil) then - repeat - val.umount = false - if (val.mountpoint == "/") then - break - elseif (val.mountpoint == "/overlay") then - break - elseif (val.mountpoint == "/rom") then - break - elseif (val.mountpoint == "/tmp") then - break - elseif (val.mountpoint == "/tmp/shm") then - break - elseif (val.mountpoint == "/tmp/upgrade") then - break - elseif (val.mountpoint == "/dev") then - break - end - val.umount = true - until true - non_system_mounts[rawmount] = val - end -end - -v = m:section(Table, non_system_mounts, translate("Mounted file systems")) - -fs = v:option(DummyValue, "fs", translate("Filesystem")) - -mp = v:option(DummyValue, "mountpoint", translate("Mount Point")) - -avail = v:option(DummyValue, "avail", translate("Available")) -function avail.cfgvalue(self, section) - return luci.tools.webadmin.byte_format( - ( tonumber(mounts[section].available) or 0 ) * 1024 - ) .. " / " .. luci.tools.webadmin.byte_format( - ( tonumber(mounts[section].blocks) or 0 ) * 1024 - ) -end - -used = v:option(DummyValue, "used", translate("Used")) -function used.cfgvalue(self, section) - return ( mounts[section].percent or "0%" ) .. " (" .. - luci.tools.webadmin.byte_format( - ( tonumber(mounts[section].used) or 0 ) * 1024 - ) .. ")" -end - -unmount = v:option(Button, "unmount", translate("Unmount")) -function unmount.cfgvalue(self, section) - return non_system_mounts[section].umount -end - -unmount.render = function(self, section, scope) - self.title = translate("Unmount") - self.inputstyle = "remove" - Button.render(self, section, scope) -end - -unmount.write = function(self, section) - if non_system_mounts[section].umount then - luci.sys.call("/bin/umount '%s'" % luci.util.shellstartsqescape(non_system_mounts[section].mountpoint)) - return luci.http.redirect(luci.dispatcher.build_url("admin/system", "fstab")) - end -end - -mount = m:section(TypedSection, "mount", translate("Mount Points"), translate("Mount Points define at which point a memory device will be attached to the filesystem")) -mount.anonymous = true -mount.addremove = true -mount.template = "cbi/tblsection" -mount.extedit = luci.dispatcher.build_url("admin/system/fstab/mount/%s") - -mount.create = function(...) - local sid = TypedSection.create(...) - if sid then - luci.http.redirect(mount.extedit % sid) - return - end -end - - -mount:option(Flag, "enabled", translate("Enabled")).rmempty = false - -dev = mount:option(DummyValue, "device", translate("Device")) -dev.rawhtml = true -dev.cfgvalue = function(self, section) - local v, e - - v = m.uci:get("fstab", section, "uuid") - e = v and devices[v:lower()] - if v and e and e.size then - return "UUID: %s (%s, %d MB)" %{ tp.pcdata(v), e.dev, e.size } - elseif v and e then - return "UUID: %s (%s)" %{ tp.pcdata(v), e.dev } - elseif v then - return "UUID: %s (<em>%s</em>)" %{ tp.pcdata(v), translate("not present") } - end - - v = m.uci:get("fstab", section, "label") - e = v and devices[v] - if v and e and e.size then - return "Label: %s (%s, %d MB)" %{ tp.pcdata(v), e.dev, e.size } - elseif v and e then - return "Label: %s (%s)" %{ tp.pcdata(v), e.dev } - elseif v then - return "Label: %s (<em>%s</em>)" %{ tp.pcdata(v), translate("not present") } - end - - v = Value.cfgvalue(self, section) or "?" - e = v and devices[v] - if v and e and e.size then - return "%s (%d MB)" %{ tp.pcdata(v), e.size } - elseif v and e then - return tp.pcdata(v) - elseif v then - return "%s (<em>%s</em>)" %{ tp.pcdata(v), translate("not present") } - end -end - -mp = mount:option(DummyValue, "target", translate("Mount Point")) -mp.cfgvalue = function(self, section) - if m.uci:get("fstab", section, "is_rootfs") == "1" then - return "/overlay" - else - return Value.cfgvalue(self, section) or "?" - end -end - -fs = mount:option(DummyValue, "fstype", translate("Filesystem")) -fs.cfgvalue = function(self, section) - local v, e - - v = m.uci:get("fstab", section, "uuid") - v = v and v:lower() or m.uci:get("fstab", section, "label") - v = v or m.uci:get("fstab", section, "device") - - e = v and devices[v] - - return e and e.type or m.uci:get("fstab", section, "fstype") or "?" -end - -op = mount:option(DummyValue, "options", translate("Options")) -op.cfgvalue = function(self, section) - return Value.cfgvalue(self, section) or "defaults" -end - -rf = mount:option(DummyValue, "is_rootfs", translate("Root")) -rf.cfgvalue = function(self, section) - local target = m.uci:get("fstab", section, "target") - if target == "/" then - return translate("yes") - elseif target == "/overlay" then - return translate("overlay") - else - return translate("no") - end -end - -ck = mount:option(DummyValue, "enabled_fsck", translate("Check")) -ck.cfgvalue = function(self, section) - return Value.cfgvalue(self, section) == "1" - and translate("yes") or translate("no") -end - - -swap = m:section(TypedSection, "swap", translate("SWAP"), translate("If your physical memory is insufficient unused data can be temporarily swapped to a swap-device resulting in a higher amount of usable <abbr title=\"Random Access Memory\">RAM</abbr>. Be aware that swapping data is a very slow process as the swap-device cannot be accessed with the high datarates of the <abbr title=\"Random Access Memory\">RAM</abbr>.")) -swap.anonymous = true -swap.addremove = true -swap.template = "cbi/tblsection" -swap.extedit = luci.dispatcher.build_url("admin/system/fstab/swap/%s") - -swap.create = function(...) - local sid = TypedSection.create(...) - if sid then - luci.http.redirect(swap.extedit % sid) - return - end -end - - -swap:option(Flag, "enabled", translate("Enabled")).rmempty = false - -dev = swap:option(DummyValue, "device", translate("Device")) -dev.cfgvalue = function(self, section) - local v - - v = m.uci:get("fstab", section, "uuid") - if v then return "UUID: %s" % v end - - v = m.uci:get("fstab", section, "label") - if v then return "Label: %s" % v end - - v = Value.cfgvalue(self, section) or "?" - e = v and devices[v] - if v and e and e.size then - return "%s (%s MB)" % {v, e.size} - else - return v - end -end - -return m diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua deleted file mode 100644 index f21a2775e1..0000000000 --- a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/mount.lua +++ /dev/null @@ -1,158 +0,0 @@ --- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local util = require "nixio.util" - -local has_fscheck = fs.access("/usr/sbin/e2fsck") - -local block = io.popen("block info", "r") -local ln, dev, devices = nil, nil, {} - -repeat - ln = block:read("*l") - dev = ln and ln:match("^/dev/(.-):") - - if dev then - local e, s, key, val = { } - - for key, val in ln:gmatch([[(%w+)="(.-)"]]) do - e[key:lower()] = val - end - - s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev))) - - e.dev = "/dev/%s" % dev - e.size = s and math.floor(s / 2048) - - devices[#devices+1] = e - end -until not ln - -block:close() - - -m = Map("fstab", translate("Mount Points - Mount Entry")) -m.redirect = luci.dispatcher.build_url("admin/system/fstab") - -if not arg[1] or m.uci:get("fstab", arg[1]) ~= "mount" then - luci.http.redirect(m.redirect) - return -end - - - -mount = m:section(NamedSection, arg[1], "mount", translate("Mount Entry")) -mount.anonymous = true -mount.addremove = false - -mount:tab("general", translate("General Settings")) -mount:tab("advanced", translate("Advanced Settings")) - - -mount:taboption("general", Flag, "enabled", translate("Enable this mount")).rmempty = false - - -o = mount:taboption("general", Value, "uuid", translate("UUID"), - translate("If specified, mount the device by its UUID instead of a fixed device node")) - -o:value("", translate("-- match by uuid --")) - -for i, d in ipairs(devices) do - if d.uuid and d.size then - o:value(d.uuid, "%s (%s, %d MB)" %{ d.uuid, d.dev, d.size }) - elseif d.uuid then - o:value(d.uuid, "%s (%s)" %{ d.uuid, d.dev }) - end -end - - -o = mount:taboption("general", Value, "label", translate("Label"), - translate("If specified, mount the device by the partition label instead of a fixed device node")) - -o:value("", translate("-- match by label --")) - -o:depends("uuid", "") - -for i, d in ipairs(devices) do - if d.label and d.size then - o:value(d.label, "%s (%s, %d MB)" %{ d.label, d.dev, d.size }) - elseif d.label then - o:value(d.label, "%s (%s)" %{ d.label, d.dev }) - end -end - - -o = mount:taboption("general", Value, "device", translate("Device"), - translate("The device file of the memory or partition (<abbr title=\"for example\">e.g.</abbr> <code>/dev/sda1</code>)")) - -o:value("", translate("-- match by device --")) - -o:depends({ uuid = "", label = "" }) - -for i, d in ipairs(devices) do - if d.size then - o:value(d.dev, "%s (%d MB)" %{ d.dev, d.size }) - else - o:value(d.dev) - end -end - - -o = mount:taboption("general", Value, "target", translate("Mount point"), - translate("Specifies the directory the device is attached to")) - -o:value("/", translate("Use as root filesystem (/)")) -o:value("/overlay", translate("Use as external overlay (/overlay)")) - - -o = mount:taboption("general", DummyValue, "__notice", translate("Root preparation")) -o:depends("target", "/") -o.rawhtml = true -o.default = [[ -<p>%s</p><pre>mkdir -p /tmp/introot -mkdir -p /tmp/extroot -mount --bind / /tmp/introot -mount /dev/sda1 /tmp/extroot -tar -C /tmp/introot -cvf - . | tar -C /tmp/extroot -xf - -umount /tmp/introot -umount /tmp/extroot</pre> -]] %{ - translate("Make sure to clone the root filesystem using something like the commands below:"), - -} - - -o = mount:taboption("advanced", Value, "fstype", translate("Filesystem"), - translate("The filesystem that was used to format the memory (<abbr title=\"for example\">e.g.</abbr> <samp><abbr title=\"Third Extended Filesystem\">ext3</abbr></samp>)")) - -o:value("", "auto") - -local fs -for fs in io.lines("/proc/filesystems") do - fs = fs:match("%S+") - if fs ~= "nodev" then - o:value(fs) - end -end - -local ok, lines = pcall(io.lines, "/etc/filesystem") -if ok then - local fs - for fs in lines do - o:value(fs) - end -end - -o = mount:taboption("advanced", Value, "options", translate("Mount options"), - translate("See \"mount\" manpage for details")) - -o.placeholder = "defaults" - - -if has_fscheck then - o = mount:taboption("advanced", Flag, "enabled_fsck", translate("Run filesystem check"), - translate("Run a filesystem check before mounting the device")) -end - -return m diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua deleted file mode 100644 index 82468d5fcc..0000000000 --- a/modules/luci-mod-system/luasrc/model/cbi/admin_system/fstab/swap.lua +++ /dev/null @@ -1,54 +0,0 @@ --- Copyright 2010 Jo-Philipp Wich <jow@openwrt.org> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local util = require "nixio.util" - -local devices = {} -util.consume((fs.glob("/dev/sd*")), devices) -util.consume((fs.glob("/dev/hd*")), devices) -util.consume((fs.glob("/dev/scd*")), devices) -util.consume((fs.glob("/dev/mmc*")), devices) - -local size = {} -for i, dev in ipairs(devices) do - local s = tonumber((fs.readfile("/sys/class/block/%s/size" % dev:sub(6)))) - size[dev] = s and math.floor(s / 2048) -end - - -m = Map("fstab", translate("Mount Points - Swap Entry")) -m.redirect = luci.dispatcher.build_url("admin/system/fstab") - -if not arg[1] or m.uci:get("fstab", arg[1]) ~= "swap" then - luci.http.redirect(m.redirect) - return -end - - -mount = m:section(NamedSection, arg[1], "swap", translate("Swap Entry")) -mount.anonymous = true -mount.addremove = false - -mount:tab("general", translate("General Settings")) -mount:tab("advanced", translate("Advanced Settings")) - - -mount:taboption("general", Flag, "enabled", translate("Enable this swap")).rmempty = false - - -o = mount:taboption("general", Value, "device", translate("Device"), - translate("The device file of the memory or partition (<abbr title=\"for example\">e.g.</abbr> <code>/dev/sda1</code>)")) - -for i, d in ipairs(devices) do - o:value(d, size[d] and "%s (%s MB)" % {d, size[d]}) -end - -o = mount:taboption("advanced", Value, "uuid", translate("UUID"), - translate("If specified, mount the device by its UUID instead of a fixed device node")) - -o = mount:taboption("advanced", Value, "label", translate("Label"), - translate("If specified, mount the device by the partition label instead of a fixed device node")) - - -return m diff --git a/modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua b/modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua deleted file mode 100644 index 99ddb09701..0000000000 --- a/modules/luci-mod-system/luasrc/model/cbi/admin_system/startup.lua +++ /dev/null @@ -1,104 +0,0 @@ --- Copyright 2008 Steven Barth <steven@midlink.org> --- Copyright 2010-2012 Jo-Philipp Wich <jow@openwrt.org> --- Copyright 2010 Manuel Munz <freifunk at somakoma dot de> --- Licensed to the public under the Apache License 2.0. - -local fs = require "nixio.fs" -local sys = require "luci.sys" - -local inits = { } -local handled = false - -for _, name in ipairs(sys.init.names()) do - local index = sys.init.index(name) - local enabled = sys.init.enabled(name) - - if index < 255 then - inits["%02i.%s" % { index, name }] = { - name = name, - index = tostring(index), - enabled = enabled - } - end -end - - -m = SimpleForm("initmgr", translate("Initscripts"), translate("You can enable or disable installed init scripts here. Changes will applied after a device reboot.<br /><strong>Warning: If you disable essential init scripts like \"network\", your device might become inaccessible!</strong>")) -m.reset = false -m.submit = false - - -s = m:section(Table, inits) - -i = s:option(DummyValue, "index", translate("Start priority")) -n = s:option(DummyValue, "name", translate("Initscript")) - - -e = s:option(Button, "endisable", translate("Enable/Disable")) - -e.render = function(self, section, scope) - if inits[section].enabled then - self.title = translate("Enabled") - self.inputstyle = "save" - else - self.title = translate("Disabled") - self.inputstyle = "reset" - end - - Button.render(self, section, scope) -end - -e.write = function(self, section) - if inits[section].enabled then - handled = true - inits[section].enabled = false - return sys.init.disable(inits[section].name) - else - handled = true - inits[section].enabled = true - return sys.init.enable(inits[section].name) - end -end - - -start = s:option(Button, "start", translate("Start")) -start.inputstyle = "apply" -start.write = function(self, section) - handled = true - sys.call("/etc/init.d/%s %s >/dev/null" %{ inits[section].name, self.option }) -end - -restart = s:option(Button, "restart", translate("Restart")) -restart.inputstyle = "reload" -restart.write = start.write - -stop = s:option(Button, "stop", translate("Stop")) -stop.inputstyle = "remove" -stop.write = start.write - - - -f = SimpleForm("rc", translate("Local Startup"), - translate("This is the content of /etc/rc.local. Insert your own commands here (in front of 'exit 0') to execute them at the end of the boot process.")) - -t = f:field(TextValue, "rcs") -t.forcewrite = true -t.rmempty = true -t.rows = 20 - -function t.cfgvalue() - return fs.readfile("/etc/rc.local") or "" -end - -function f.handle(self, state, data) - if not handled and state == FORM_VALID then - if data.rcs then - fs.writefile("/etc/rc.local", data.rcs:gsub("\r\n", "\n")) - else - fs.writefile("/etc/rc.local", "") - end - end - return true -end - -return m, f diff --git a/modules/luci-mod-system/luasrc/view/admin_system/password.htm b/modules/luci-mod-system/luasrc/view/admin_system/password.htm deleted file mode 100644 index 6ca02a83c1..0000000000 --- a/modules/luci-mod-system/luasrc/view/admin_system/password.htm +++ /dev/null @@ -1,59 +0,0 @@ -<%+header%> - -<input type="password" aria-hidden="true" style="position:absolute; left:-10000px" /> - -<script type="text/javascript"> -function checkPassword() { - var pw1 = document.body.querySelector('[name="pw1"]'); - var view = document.getElementById("passstrength"); - - var strongRegex = new RegExp("^(?=.{8,})(?=.*[A-Z])(?=.*[a-z])(?=.*[0-9])(?=.*\\W).*$", "g"); - var mediumRegex = new RegExp("^(?=.{7,})(((?=.*[A-Z])(?=.*[a-z]))|((?=.*[A-Z])(?=.*[0-9]))|((?=.*[a-z])(?=.*[0-9]))).*$", "g"); - var enoughRegex = new RegExp("(?=.{6,}).*", "g"); - if (false == enoughRegex.test(pw1.value)) { - view.innerHTML = '<%:Password strength%>: <span style="color:red"><%:More Characters%></span>'; - } else if (strongRegex.test(pw1.value)) { - view.innerHTML = '<%:Password strength%>: <span style="color:green"><%:Strong%></span>'; - } else if (mediumRegex.test(pw1.value)) { - view.innerHTML = '<%:Password strength%>: <span style="color:orange"><%:Medium%></span>'; - } else { - view.innerHTML = '<%:Password strength%>: <span style="color:red"><%:Weak%></span>'; - } - return true; -} -</script> - -<div class="cbi-map"> - <h2><%:Router Password%></h2> - - <div class="cbi-section-descr"> - <%:Changes the administrator password for accessing the device%> - </div> - - <div class="cbi-section-node"> - <div class="cbi-value"> - <label class="cbi-value-title" for="image"><%:Password%></label> - <div class="cbi-value-field"> - <input type="password" name="pw1" onkeyup="checkPassword()"/><!-- - --><button class="cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" aria-label="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'">∗</button> - </div> - </div> - - <div class="cbi-value"> - <label class="cbi-value-title" for="image"><%:Confirmation%></label> - <div class="cbi-value-field"> - <input type="password" name="pw2" onkeydown="if (event.keyCode === 13) submitPassword(event)" /><!-- - --><button class="cbi-button cbi-button-neutral" title="<%:Reveal/hide password%>" aria-label="<%:Reveal/hide password%>" onclick="var e = this.previousElementSibling; e.type = (e.type === 'password') ? 'text' : 'password'">∗</button> - <div id="passstrength" class="cbi-value-description"></div> - </div> - </div> - </div> -</div> - -<div class="cbi-page-actions"> - <button class="btn cbi-button-apply" onclick="submitPassword(event)"><%:Save%></button> -</div> - -<script type="application/javascript" src="<%=resource%>/view/system/password.js"></script> - -<%+footer%> diff --git a/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm b/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm deleted file mode 100644 index ac453f3f6c..0000000000 --- a/modules/luci-mod-system/luasrc/view/admin_system/sshkeys.htm +++ /dev/null @@ -1,46 +0,0 @@ -<%+header%> - -<style type="text/css"> - .cbi-dynlist { - max-width: 100%; - } - - .cbi-dynlist .item > small { - display: block; - direction: rtl; - overflow: hidden; - text-align: left; - } - - .cbi-dynlist .item > small > code { - direction: ltr; - white-space: nowrap; - unicode-bidi: bidi-override; - } - - @media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { - .cbi-dynlist .item > small { direction: ltr } - } -</style> - -<div class="cbi-map"> - <h2><%:SSH-Keys%></h2> - - <div class="cbi-section-descr"> - <%_Public keys allow for the passwordless SSH logins with a higher security compared to the use of plain passwords. In order to upload a new key to the device, paste an OpenSSH compatible public key line or drag a <code>.pub</code> file into the input field.%> - </div> - - <div class="cbi-section-node"> - <div class="cbi-dynlist" name="sshkeys"> - <p class="spinning"><%:Loading SSH keys…%></p> - <div class="add-item" ondragover="dragKey(event)" ondrop="dropKey(event)"> - <input class="cbi-input-text" type="text" placeholder="<%:Paste or drag SSH key file…%>" onkeydown="if (event.keyCode === 13) addKey(event)" /> - <button class="cbi-button" onclick="addKey(event)"><%:Add key%></button> - </div> - </div> - </div> -</div> - -<script type="application/javascript" src="<%=resource%>/view/system/sshkeys.js"></script> - -<%+footer%> |