Merge pull request #1735 from sumpfralle/olsr-jsoninfo-parser-handle-empty-result
[oweals/luci.git] / modules / luci-mod-system / htdocs / luci-static / resources / view / system / mounts.js
1 'use strict';
2 'require uci';
3 'require rpc';
4 'require form';
5
6 var callBlockDevices, callMountPoints, callBlockDetect, callUmount,
7     callFileRead, callFileStat, callFileExec;
8
9 callBlockDevices = rpc.declare({
10         object: 'luci',
11         method: 'getBlockDevices',
12         expect: { '': {} }
13 });
14
15 callMountPoints = rpc.declare({
16         object: 'luci',
17         method: 'getMountPoints',
18         expect: { result: [] }
19 });
20
21 callBlockDetect = rpc.declare({
22         object: 'luci',
23         method: 'setBlockDetect',
24         expect: { result: false }
25 });
26
27 callUmount = rpc.declare({
28         object: 'luci',
29         method: 'setUmount',
30         params: [ 'path' ],
31         expect: { result: false }
32 });
33
34 callFileRead = rpc.declare({
35         object: 'file',
36         method: 'read',
37         params: [ 'path' ],
38         expect: { data: '' },
39         filter: function(s) {
40                 return (s || '').split(/\n/).filter(function(ln) {
41                         return ln.match(/\S/) && !ln.match(/^nodev\t/);
42                 }).map(function(ln) {
43                         return ln.trim();
44                 });
45         }
46 });
47
48 callFileStat = rpc.declare({
49         object: 'file',
50         method: 'stat',
51         params: [ 'path' ],
52         expect: { '': {} },
53         filter: function(st) {
54                 return (L.isObject(st) && st.path != null);
55         }
56 });
57
58 callFileExec = rpc.declare({
59         object: 'file',
60         method: 'exec',
61         params: [ 'command', 'params' ],
62         expect: { code: 255 }
63 });
64
65 function device_textvalue(devices, section_id) {
66         var v = (uci.get('fstab', section_id, 'uuid') || '').toLowerCase(),
67             e = Object.keys(devices).filter(function(dev) { return (devices[dev].uuid || '-').toLowerCase() == v })[0];
68
69         if (v) {
70                 this.section.devices[section_id] = devices[e];
71
72                 if (e && devices[e].size)
73                         return E('span', 'UUID: %h (%s, %1024.2mB)'.format(v, devices[e].dev, devices[e].size));
74                 else if (e)
75                         return E('span', 'UUID: %h (%s)'.format(v, devices[e].dev));
76                 else
77                         return E('span', 'UUID: %h (<em>%s</em>)'.format(v, _('not present')));
78         }
79
80         v = uci.get('fstab', section_id, 'label');
81         e = Object.keys(devices).filter(function(dev) { return devices[dev].label == v })[0];
82
83         if (v) {
84                 this.section.devices[section_id] = this.section.devices[section_id] || devices[e];
85
86                 if (e && devices[e].size)
87                         return E('span', 'Label: %h (%s, %1024.2mB)'.format(v, devices[e].dev, devices[e].size));
88                 else if (e)
89                         return E('span', 'Label: %h (%s)'.format(v, devices[e].dev));
90                 else
91                         return E('span', 'Label: %h (<em>%s</em>)'.format(v, _('not present')));
92         }
93
94         v = uci.get('fstab', section_id, 'device');
95         e = Object.keys(devices).filter(function(dev) { return devices[dev].dev == v })[0];
96
97         if (v) {
98                 this.section.devices[section_id] = this.section.devices[section_id] || devices[e];
99
100                 if (e && devices[e].size)
101                         return E('span', '%h (%1024.2mB)'.format(v, devices[e].size));
102                 else if (e)
103                         return E('span', '%h'.format(v));
104                 else
105                         return E('span', '%h (<em>%s</em>)'.format(v, _('not present')));
106         }
107 }
108
109 return L.view.extend({
110         handleDetect: function(m, ev) {
111                 return callBlockDetect()
112                         .then(L.bind(uci.unload, uci, 'fstab'))
113                         .then(L.bind(m.render, m));
114         },
115
116         handleMountAll: function(m, ev) {
117                 return callFileExec('/sbin/block', ['mount'])
118                         .then(function(rc) {
119                                 if (rc != 0)
120                                         L.ui.addNotification(null, E('p', _('The <em>block mount</em> command failed with code %d').format(rc)));
121                         })
122                         .then(L.bind(uci.unload, uci, 'fstab'))
123                         .then(L.bind(m.render, m));
124         },
125
126         handleUmount: function(m, path, ev) {
127                 return callUmount(path)
128                         .then(L.bind(uci.unload, uci, 'fstab'))
129                         .then(L.bind(m.render, m));
130         },
131
132         load: function() {
133                 return Promise.all([
134                         callBlockDevices(),
135                         callFileRead('/proc/filesystems'),
136                         callFileRead('/etc/filesystems'),
137                         callFileStat('/usr/sbin/e2fsck'),
138                         callFileStat('/usr/sbin/fsck.f2fs'),
139                         callFileStat('/usr/sbin/dosfsck'),
140                         callFileStat('/usr/bin/btrfsck'),
141                         uci.load('fstab')
142                 ]);
143         },
144
145         render: function(results) {
146                 var devices = results[0],
147                     procfs = results[1],
148                     etcfs = results[2],
149                     triggers = {},
150                     trigger, m, s, o;
151
152                 var filesystems = procfs.concat(etcfs.filter(function(fs) {
153                         return procfs.indexOf(fs) < 0;
154                 })).sort();
155
156                 var fsck = {
157                         ext2: results[3],
158                         ext3: results[3],
159                         ext4: results[3],
160                         f2fs: results[4],
161                         vfat: results[5],
162                         btrfs: results[6]
163                 };
164
165                 if (!uci.sections('fstab', 'global').length)
166                         uci.add('fstab', 'global');
167
168                 m = new form.Map('fstab', _('Mount Points'));
169
170                 s = m.section(form.TypedSection, 'global', _('Global Settings'));
171                 s.addremove = false;
172                 s.anonymous = true;
173
174                 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'));
175                 o.onclick = this.handleDetect.bind(this, m);
176                 o.inputstyle = 'reload';
177
178                 o = s.option(form.Button, '_mountall', _('Mount attached devices'), _('Attempt to enable configured mount points for attached devices'));
179                 o.onclick = this.handleMountAll.bind(this, m);
180                 o.inputstyle = 'reload';
181
182                 o = s.option(form.Flag, 'anon_swap', _('Anonymous Swap'), _('Mount swap not specifically configured'));
183                 o.default = o.disabled;
184                 o.rmempty = false;
185
186                 o = s.option(form.Flag, 'anon_mount', _('Anonymous Mount'), _('Mount filesystems not specifically configured'));
187                 o.default = o.disabled;
188                 o.rmempty = false;
189
190                 o = s.option(form.Flag, 'auto_swap', _('Automount Swap'), _('Automatically mount swap on hotplug'));
191                 o.default = o.enabled;
192                 o.rmempty = false;
193
194                 o = s.option(form.Flag, 'auto_mount', _('Automount Filesystem'), _('Automatically mount filesystems on hotplug'));
195                 o.default = o.enabled;
196                 o.rmempty = false;
197
198                 o = s.option(form.Flag, 'check_fs', _('Check filesystems before mount'), _('Automatically check filesystem for errors before mounting'));
199                 o.default = o.disabled;
200                 o.rmempty = false;
201
202
203                 // Mount status table
204                 o = s.option(form.DummyValue, '_mtab');
205
206                 o.load = function(section_id) {
207                         return callMountPoints().then(L.bind(function(mounts) {
208                                 this.mounts = mounts;
209                         }, this));
210                 };
211
212                 o.render = L.bind(function(view, section_id) {
213                         var table = E('div', { 'class': 'table' }, [
214                                 E('div', { 'class': 'tr table-titles' }, [
215                                         E('div', { 'class': 'th' }, _('Filesystem')),
216                                         E('div', { 'class': 'th' }, _('Mount Point')),
217                                         E('div', { 'class': 'th center' }, _('Available')),
218                                         E('div', { 'class': 'th center' }, _('Used')),
219                                         E('div', { 'class': 'th' }, _('Unmount'))
220                                 ])
221                         ]);
222
223                         var rows = [];
224
225                         for (var i = 0; i < this.mounts.length; i++) {
226                                 var used = this.mounts[i].size - this.mounts[i].free,
227                                     umount = true;
228
229                                 if (/^\/(overlay|rom|tmp(?:\/.+)?|dev(?:\/.+)?|)$/.test(this.mounts[i].mount))
230                                         umount = false;
231
232                                 rows.push([
233                                         this.mounts[i].device,
234                                         this.mounts[i].mount,
235                                         '%1024.2mB / %1024.2mB'.format(this.mounts[i].avail, this.mounts[i].size),
236                                         '%.2f%% (%1024.2mB)'.format(100 / this.mounts[i].size * used, used),
237                                         umount ? E('button', {
238                                                 'class': 'btn cbi-button-remove',
239                                                 'click': L.ui.createHandlerFn(view, 'handleUmount', m, this.mounts[i].mount)
240                                         }, [ _('Unmount') ]) : '-'
241                                 ]);
242                         }
243
244                         cbi_update_table(table, rows, E('em', _('Unable to obtain mount information')));
245
246                         return E([], [ E('h3', _('Mounted file systems')), table ]);
247                 }, o, this);
248
249
250                 // Mounts
251                 s = m.section(form.GridSection, 'mount', _('Mount Points'), _('Mount Points define at which point a memory device will be attached to the filesystem'));
252                 s.modaltitle = _('Mount Points - Mount Entry');
253                 s.anonymous = true;
254                 s.addremove = true;
255                 s.sortable  = true;
256                 s.devices   = {};
257
258                 s.renderHeaderRows = function(/* ... */) {
259                         var trEls = form.GridSection.prototype.renderHeaderRows.apply(this, arguments);
260                         return trEls.childNodes[0];
261                 }
262
263                 s.tab('general', _('General Settings'));
264                 s.tab('advanced', _('Advanced Settings'));
265
266                 o = s.taboption('general', form.Flag, 'enabled', _('Enabled'));
267                 o.rmempty  = false;
268                 o.editable = true;
269
270                 o = s.taboption('general', form.DummyValue, '_device', _('Device'));
271                 o.rawhtml   = true;
272                 o.modalonly = false;
273                 o.write = function() {};
274                 o.remove = function() {};
275                 o.textvalue = device_textvalue.bind(o, devices);
276
277                 o = s.taboption('general', form.Value, 'uuid', _('UUID'), _('If specified, mount the device by its UUID instead of a fixed device node'));
278                 o.modalonly = true;
279                 o.value('', _('-- match by uuid --'));
280
281                 var devs = Object.keys(devices).sort();
282                 for (var i = 0; i < devs.length; i++) {
283                         var dev = devices[devs[i]];
284                         if (dev.uuid && dev.size)
285                                 o.value(dev.uuid, '%s (%s, %1024.2mB)'.format(dev.uuid, dev.dev, dev.size));
286                         else if (dev.uuid)
287                                 o.value(dev.uuid, '%s (%s)'.format(dev.uuid, dev.dev));
288                 }
289
290                 o = s.taboption('general', form.Value, 'label', _('Label'), _('If specified, mount the device by the partition label instead of a fixed device node'));
291                 o.modalonly = true;
292                 o.depends('uuid', '');
293                 o.value('', _('-- match by label --'));
294
295                 for (var i = 0; i < devs.length; i++) {
296                         var dev = devices[devs[i]];
297                         if (dev.label && dev.size)
298                                 o.value(dev.label, '%s (%s, %1024.2mB)'.format(dev.label, dev.dev, dev.size));
299                         else if (dev.label)
300                                 o.value(dev.label, '%s (%s)'.format(dev.label, dev.dev));
301                 }
302
303                 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>)'));
304                 o.modalonly = true;
305                 o.depends({ uuid: '', label: '' });
306
307                 for (var i = 0; i < devs.length; i++) {
308                         var dev = devices[devs[i]];
309                         if (dev.size)
310                                 o.value(dev.dev, '%s (%1024.2mB)'.format(dev.dev, dev.size));
311                         else
312                                 o.value(dev.dev);
313                 }
314
315                 o = s.taboption('general', form.Value, 'target', _('Mount point'), _('Specifies the directory the device is attached to'));
316                 o.value('/', _('Use as root filesystem (/)'));
317                 o.value('/overlay', _('Use as external overlay (/overlay)'));
318                 o.rmempty = false;
319
320                 o = s.taboption('general', form.DummyValue, '__notice', _('Root preparation'));
321                 o.depends('target', '/');
322                 o.modalonly = true;
323                 o.rawhtml = true;
324                 o.default = '' +
325                         '<p>%s</p>'.format(_('Make sure to clone the root filesystem using something like the commands below:')) +
326                         '<pre>' +
327                                 'mkdir -p /tmp/introot\n' +
328                                 'mkdir -p /tmp/extroot\n' +
329                                 'mount --bind / /tmp/introot\n' +
330                                 'mount /dev/sda1 /tmp/extroot\n' +
331                                 'tar -C /tmp/introot -cvf - . | tar -C /tmp/extroot -xf -\n' +
332                                 'umount /tmp/introot\n' +
333                                 'umount /tmp/extroot\n' +
334                         '</pre>'
335                 ;
336
337                 o = s.taboption('advanced', form.ListValue, 'fstype', _('Filesystem'));
338
339                 o.textvalue = function(section_id) {
340                         var dev = this.section.devices[section_id],
341                             text = this.cfgvalue(section_id) || 'auto';
342
343                         if (dev && dev.type && dev.type != text)
344                                 text += ' (%s)'.format(dev.type);
345
346                         return text;
347                 };
348
349                 o.value('', 'auto');
350
351                 for (var i = 0; i < filesystems.length; i++)
352                         o.value(filesystems[i]);
353
354                 o = s.taboption('advanced', form.Value, 'options', _('Mount options'), _('See "mount" manpage for details'));
355                 o.textvalue = function(section_id) { return this.cfgvalue(section_id) || 'defaults' };
356                 o.placeholder = 'defaults';
357
358                 s.taboption('advanced', form.Flag, 'enabled_fsck', _('Run filesystem check'), _('Run a filesystem check before mounting the device'));
359
360
361                 // Swaps
362                 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>.'));
363                 s.modaltitle = _('Mount Points - Swap Entry');
364                 s.anonymous = true;
365                 s.addremove = true;
366                 s.sortable  = true;
367                 s.devices   = {};
368
369                 s.renderHeaderRows = function(/* ... */) {
370                         var trEls = form.GridSection.prototype.renderHeaderRows.apply(this, arguments);
371                         trEls.childNodes[0].childNodes[1].style.width = '90%';
372                         return trEls.childNodes[0];
373                 }
374
375                 o = s.option(form.Flag, 'enabled', _('Enabled'));
376                 o.rmempty  = false;
377                 o.editable = true;
378
379                 o = s.option(form.DummyValue, '_device', _('Device'));
380                 o.modalonly = false;
381                 o.textvalue = device_textvalue.bind(o, devices);
382
383                 o = s.option(form.Value, 'uuid', _('UUID'), _('If specified, mount the device by its UUID instead of a fixed device node'));
384                 o.modalonly = true;
385                 o.value('', _('-- match by uuid --'));
386
387                 var devs = Object.keys(devices).sort();
388                 for (var i = 0; i < devs.length; i++) {
389                         var dev = devices[devs[i]];
390                         if (dev.dev.match(/^\/dev\/(mtdblock|ubi|ubiblock)\d/))
391                                 continue;
392
393                         if (dev.uuid && dev.size)
394                                 o.value(dev.uuid, '%s (%s, %1024.2mB)'.format(dev.uuid, dev.dev, dev.size));
395                         else if (dev.uuid)
396                                 o.value(dev.uuid, '%s (%s)'.format(dev.uuid, dev.dev));
397                 }
398
399                 o = s.option(form.Value, 'label', _('Label'), _('If specified, mount the device by the partition label instead of a fixed device node'));
400                 o.modalonly = true;
401                 o.depends('uuid', '');
402                 o.value('', _('-- match by label --'));
403
404                 for (var i = 0; i < devs.length; i++) {
405                         var dev = devices[devs[i]];
406                         if (dev.dev.match(/^\/dev\/(mtdblock|ubi|ubiblock)\d/))
407                                 continue;
408
409                         if (dev.label && dev.size)
410                                 o.value(dev.label, '%s (%s, %1024.2mB)'.format(dev.label, dev.dev, dev.size));
411                         else if (dev.label)
412                                 o.value(dev.label, '%s (%s)'.format(dev.label, dev.dev));
413                 }
414
415                 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>)'));
416                 o.modalonly = true;
417                 o.depends({ uuid: '', label: '' });
418
419                 for (var i = 0; i < devs.length; i++) {
420                         var dev = devices[devs[i]];
421                         if (dev.dev.match(/^\/dev\/(mtdblock|ubi|ubiblock)\d/))
422                                 continue;
423
424                         if (dev.size)
425                                 o.value(dev.dev, '%s (%1024.2mB)'.format(dev.dev, dev.size));
426                         else
427                                 o.value(dev.dev);
428                 }
429
430                 return m.render();
431         }
432 });