diff options
author | Jo-Philipp Wich <jo@mein.io> | 2019-09-21 18:36:31 +0200 |
---|---|---|
committer | Jo-Philipp Wich <jo@mein.io> | 2019-09-21 18:36:31 +0200 |
commit | 71e265a7f038f172eada95576496ef240a9c83e7 (patch) | |
tree | a592edaec6bcc584ac11b014018f2a9b43ee1f00 /modules/luci-mod-system/htdocs/luci-static/resources | |
parent | 54cab2f26be6c925cab8651c254380877474a130 (diff) |
luci-mod-system: remplement fstab settings as client side view
Signed-off-by: Jo-Philipp Wich <jo@mein.io>
Diffstat (limited to 'modules/luci-mod-system/htdocs/luci-static/resources')
-rw-r--r-- | modules/luci-mod-system/htdocs/luci-static/resources/view/system/mounts.js | 428 |
1 files changed, 428 insertions, 0 deletions
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..b9028b20e7 --- /dev/null +++ b/modules/luci-mod-system/htdocs/luci-static/resources/view/system/mounts.js @@ -0,0 +1,428 @@ +'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') + ]); + }, + + 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] + }; + + 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(); + } +}); |