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