diff --git a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
index 32cb10596b..af06d840d0 100644
--- a/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
+++ b/modules/luci-base/root/usr/share/rpcd/acl.d/luci-base.json
@@ -25,13 +25,15 @@
"/*": [ "list" ],
"/etc/crontabs/root": [ "read" ],
"/etc/dropbear/authorized_keys": [ "read" ],
+ "/etc/filesystems": [ "read" ],
"/etc/rc.local": [ "read" ],
+ "/proc/filesystems": [ "read" ],
"/proc/sys/kernel/hostname": [ "read" ]
},
"ubus": {
"file": [ "list", "read", "stat" ],
"iwinfo": [ "assoclist", "freqlist", "txpowerlist", "countrylist" ],
- "luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getWirelessDevices", "getSwconfigFeatures", "getSwconfigPortState" ],
+ "luci": [ "getBoardJSON", "getDUIDHints", "getHostHints", "getIfaddrs", "getInitList", "getLocaltime", "getTimezones", "getDHCPLeases", "getLEDs", "getNetworkDevices", "getUSBDevices", "getWirelessDevices", "getSwconfigFeatures", "getSwconfigPortState", "getBlockDevices", "getMountPoints" ],
"network.device": [ "status" ],
"network.interface": [ "dump" ],
"network": [ "get_proto_handlers" ],
@@ -45,12 +47,13 @@
"/etc/crontabs/root": [ "write" ],
"/etc/dropbear/authorized_keys": [ "write" ],
"/etc/luci-uploads/*": [ "write" ],
- "/etc/rc.local": [ "write" ]
+ "/etc/rc.local": [ "write" ],
+ "/sbin/block": [ "exec" ]
},
"ubus": {
- "file": [ "write", "remove" ],
+ "file": [ "write", "remove", "exec" ],
"iwinfo": [ "scan" ],
- "luci": [ "setInitAction", "setLocaltime", "setPassword" ],
+ "luci": [ "setInitAction", "setLocaltime", "setPassword", "setBlockDetect", "setUmount" ],
"uci": [ "add", "apply", "confirm", "delete", "order", "set", "rename" ]
},
"uci": [ "*" ]
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 (%s)'.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 (%s)'.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 (%s)'.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 block mount 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 (e.g. /dev/sda1
)'));
+ 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 = '' +
+ '
%s
'.format(_('Make sure to clone the root filesystem using something like the commands below:')) + + '' + + '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' + + '' + ; + + 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 RAM. Be aware that swapping data is a very slow process as the swap-device cannot be accessed with the high datarates of the RAM.')); + 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 (e.g.
/dev/sda1
)'));
+ 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/luasrc/controller/admin/system.lua b/modules/luci-mod-system/luasrc/controller/admin/system.lua
index 0ce08d10bd..eaf1105dd6 100644
--- a/modules/luci-mod-system/luasrc/controller/admin/system.lua
+++ b/modules/luci-mod-system/luasrc/controller/admin/system.lua
@@ -23,9 +23,7 @@ function index()
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/*")
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 /dev/sda1
)"))
-
-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 = [[
-%s
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-]] %{ - 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 (e.g. ext3)")) - -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
/dev/sda1
)"))
-
-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