Merge ~ya-bo-ng/maas:esxi-rebuild-2 into maas:master
- Git
- lp:~ya-bo-ng/maas
- esxi-rebuild-2
- Merge into master
Status: | Merged |
---|---|
Approved by: | Anthony Dillon |
Approved revision: | 86b71e599f8ab1df011479426171e636d58ab6c9 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~ya-bo-ng/maas:esxi-rebuild-2 |
Merge into: | maas:master |
Diff against target: |
2258 lines (+1397/-303) 14 files modified
src/maasserver/static/js/angular/controllers/node_details_storage.js (+257/-8) src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js (+310/-8) src/maasserver/static/js/angular/directives/nodedetails/storage_datastores.js (+9/-0) src/maasserver/static/js/angular/entry.js (+5/-1) src/maasserver/static/js/angular/factories/machines.js (+15/-0) src/maasserver/static/js/angular/factories/tests/test_machines.js (+52/-0) src/maasserver/static/partials/node-details.html (+256/-157) src/maasserver/static/partials/nodedetails/storage/datastores.html (+112/-0) src/maasserver/static/partials/nodedetails/storage/disks-partitions.html (+319/-121) src/maasserver/static/partials/nodedetails/storage/filesystems.html (+1/-1) src/maasserver/static/scss/_base_tables.scss (+0/-1) src/maasserver/static/scss/_patterns_notification.scss (+12/-0) src/maasserver/static/scss/_tables.scss (+45/-6) src/maasserver/static/scss/_utils.scss (+4/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Lilyana Videnova (community) | Approve | ||
Steve Rydz (community) | Approve | ||
Review via email: mp+367016@code.launchpad.net |
Commit message
Update machine storage to add datastore support for ESXi
Description of the change
## Done
Updated the machine storage UI to add the ability to create a ESXi datastore from available disk.
## QA
- Open a machine is Ready state
- Open the storage tab
- Change the layout to ESXi and confirm
- See that there is a datastore table.
- Under available disks table, select a disk and create a datastore
- See that is works
- Change the layout and confirm
- Check it does not show the datastores table
## Sreenshots
Storage UI: https:/
Layout change confirmation: https:/
ESXi layout: https:/
Creating a datastore: https:/
Lilyana Videnova (lilyanavidenova) wrote : | # |
LGTM :)
Lilyana Videnova (lilyanavidenova) : | # |
Blake Rouse (blake-rouse) wrote : | # |
JS needs fixing.
Anthony Dillon (ya-bo-ng) wrote : | # |
All done Blake, thanks
Blake Rouse (blake-rouse) wrote : | # |
Approved.
By the way.... smaller branches next time!!!!!
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b esxi-rebuild-2 lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b esxi-rebuild-2 lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b esxi-rebuild-2 lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas
STATUS: FAILED BUILD
LOG: http://
Preview Diff
1 | diff --git a/src/maasserver/static/js/angular/controllers/node_details_storage.js b/src/maasserver/static/js/angular/controllers/node_details_storage.js |
2 | index 2ddf2e0..87edee0 100644 |
3 | --- a/src/maasserver/static/js/angular/controllers/node_details_storage.js |
4 | +++ b/src/maasserver/static/js/angular/controllers/node_details_storage.js |
5 | @@ -45,12 +45,23 @@ export function removeAvailableByNew() { |
6 | }; |
7 | } |
8 | |
9 | +export function datastoresOnly() { |
10 | + return function(filesystems) { |
11 | + return filesystems.filter(filesystem => { |
12 | + return filesystem.used_for === "VMFS Datastore"; |
13 | + }); |
14 | + }; |
15 | +} |
16 | + |
17 | /* @ngInject */ |
18 | export function NodeStorageController( |
19 | $scope, |
20 | MachinesManager, |
21 | ConverterService, |
22 | - UsersManager |
23 | + UsersManager, |
24 | + $log, |
25 | + $timeout, |
26 | + $filter |
27 | ) { |
28 | // From models/partitiontable.py - must be kept in sync. |
29 | var INITIAL_PARTITION_OFFSET = 4 * 1024 * 1024; |
30 | @@ -129,6 +140,8 @@ export function NodeStorageController( |
31 | } |
32 | ]; |
33 | |
34 | + var datastoreOnly = $filter("datastoresOnly"); |
35 | + |
36 | $scope.tableInfo = { column: "name" }; |
37 | $scope.has_disks = false; |
38 | $scope.filesystems = []; |
39 | @@ -148,6 +161,235 @@ export function NodeStorageController( |
40 | $scope.nodeManager = MachinesManager; |
41 | $scope.used = []; |
42 | $scope.showMembers = []; |
43 | + $scope.createNewDatastore = false; |
44 | + $scope.addToExistingDatastore = false; |
45 | + $scope.datastores = { |
46 | + new: {}, |
47 | + old: {} |
48 | + }; |
49 | + $scope.selectedAvailableDatastores = []; |
50 | + $scope.creatingDatastore = false; |
51 | + $scope.updatingDatastore = false; |
52 | + $scope.updatingOSFamily = false; |
53 | + $scope.updatingStorageLayout = false; |
54 | + $scope.confirmStorageLayout = false; |
55 | + $scope.newLayout = ""; |
56 | + $scope.addToDatastoreValid = false; |
57 | + |
58 | + // XXX: Steve Rydz 09/08/2019 |
59 | + // Hardcoded for now in current cycle as no mapping exists |
60 | + $scope.osFamilies = [ |
61 | + { |
62 | + id: "linux", |
63 | + name: "Linux", |
64 | + layouts: [ |
65 | + { |
66 | + id: "flat", |
67 | + name: "Flat" |
68 | + }, |
69 | + { |
70 | + id: "lvm", |
71 | + name: "LVM" |
72 | + }, |
73 | + { |
74 | + id: "bcache", |
75 | + name: "bcache" |
76 | + }, |
77 | + { |
78 | + id: "vmfs6", |
79 | + name: "VMFS6 (VMware ESXI)" |
80 | + }, |
81 | + { |
82 | + id: "blank", |
83 | + name: "No storage (blank) layout" |
84 | + } |
85 | + ] |
86 | + } |
87 | + ]; |
88 | + |
89 | + $scope.osFamily = $scope.osFamilies[0]; |
90 | + $scope.storageLayout = $scope.osFamily.layouts.find(layout => { |
91 | + return layout.id === $scope.node.detected_storage_layout; |
92 | + }); |
93 | + |
94 | + $scope.openStorageLayoutConfirm = function(selectedLayout) { |
95 | + $scope.osFamily.layouts.forEach(layout => { |
96 | + if (layout.id === selectedLayout) { |
97 | + $scope.newLayout = layout; |
98 | + } |
99 | + }); |
100 | + $scope.confirmStorageLayout = true; |
101 | + }; |
102 | + |
103 | + $scope.closeStorageLayoutConfirm = function() { |
104 | + $scope.confirmStorageLayout = false; |
105 | + }; |
106 | + |
107 | + $scope.updateStorageLayout = function(storageLayout) { |
108 | + storageLayout = $scope.storageLayout = $scope.newLayout; |
109 | + |
110 | + var params = { |
111 | + system_id: $scope.node.system_id, |
112 | + storage_layout: storageLayout.id |
113 | + }; |
114 | + |
115 | + $scope.updatingStorageLayout = true; |
116 | + |
117 | + MachinesManager.applyStorageLayout(params) |
118 | + .then(function() { |
119 | + $timeout(function() { |
120 | + $scope.updatingStorageLayout = false; |
121 | + }, 0); |
122 | + }) |
123 | + .catch(function(error) { |
124 | + $log.error(error); |
125 | + $timeout(function() { |
126 | + $scope.updatingStorageLayout = false; |
127 | + }, 0); |
128 | + }); |
129 | + |
130 | + $scope.closeStorageLayoutConfirm(); |
131 | + }; |
132 | + |
133 | + $scope.openNewDatastorePanel = function() { |
134 | + $scope.createNewDatastore = true; |
135 | + var selectedDisks = $scope.getSelectedAvailable(); |
136 | + $scope.datastores.new = { |
137 | + id: selectedDisks[0].id, |
138 | + name: "", |
139 | + mountpoint: selectedDisks[0].mount_point, |
140 | + filesystem: "VMFS6", |
141 | + size: selectedDisks[0].size_human |
142 | + }; |
143 | + }; |
144 | + |
145 | + $scope.closeNewDatastorePanel = function() { |
146 | + $scope.createNewDatastore = false; |
147 | + $scope.datastores.new = {}; |
148 | + }; |
149 | + |
150 | + $scope.openAddToExistingDatastorePanel = function() { |
151 | + $scope.addToExistingDatastore = true; |
152 | + $scope.selectedAvailableDatastores = $scope.getSelectedAvailable(); |
153 | + $scope.datastores.old = datastoreOnly($scope.node.disks)[0]; |
154 | + }; |
155 | + |
156 | + $scope.closeAddToExistingDatastorePanel = function() { |
157 | + $scope.addToExistingDatastore = false; |
158 | + $scope.datastores.new = {}; |
159 | + }; |
160 | + |
161 | + $scope.canPerformActionOnDatastoreSet = function() { |
162 | + var editing = $scope.addToExistingDatastore || $scope.createNewDatastore; |
163 | + var selected = $scope.selectedAvailableDatastores.length > 0; |
164 | + var vmfs6 = $scope.storageLayout.id === "vmfs6"; |
165 | + return !editing && selected && vmfs6; |
166 | + }; |
167 | + |
168 | + $scope.createDatastore = function() { |
169 | + $scope.createNewDatastore = true; |
170 | + |
171 | + var selectedAvailable = $scope.getSelectedAvailable(); |
172 | + var blockDeviceIDs = []; |
173 | + var partitionIDs = []; |
174 | + |
175 | + selectedAvailable.forEach(function(item) { |
176 | + if (item.type === "partition") { |
177 | + partitionIDs.push(item.partition_id); |
178 | + } else { |
179 | + blockDeviceIDs.push(item.block_id); |
180 | + } |
181 | + }); |
182 | + |
183 | + var params = { |
184 | + system_id: $scope.node.system_id, |
185 | + block_devices: blockDeviceIDs, |
186 | + partitions: partitionIDs, |
187 | + name: $scope.datastores.new.name |
188 | + }; |
189 | + |
190 | + $scope.creatingDatastore = true; |
191 | + |
192 | + MachinesManager.createDatastore(params) |
193 | + .then(function() { |
194 | + $timeout(function() { |
195 | + $scope.creatingDatastore = false; |
196 | + }, 0); |
197 | + $scope.closeNewDatastorePanel(); |
198 | + $scope.selectedAvailableDatastores = []; |
199 | + }) |
200 | + .catch(function(error) { |
201 | + $log.error(error); |
202 | + $timeout(function() { |
203 | + $scope.creatingDatastore = false; |
204 | + }, 0); |
205 | + }); |
206 | + }; |
207 | + |
208 | + $scope.checkAddToDatastoreValid = function() { |
209 | + var selectedAvailable = $scope.getSelectedAvailable(); |
210 | + var valid = true; |
211 | + if (selectedAvailable.length < 1) { |
212 | + valid = false; |
213 | + } |
214 | + selectedAvailable.forEach(function(item) { |
215 | + if (item.has_partitions) { |
216 | + valid = false; |
217 | + } |
218 | + }); |
219 | + $scope.addToDatastoreValid = valid; |
220 | + }; |
221 | + |
222 | + $scope.addToDatastore = function() { |
223 | + var selectedAvailable = $scope.getSelectedAvailable(); |
224 | + var blockDeviceIDs = []; |
225 | + var partitionIDs = []; |
226 | + |
227 | + selectedAvailable.forEach(function(item) { |
228 | + if (item.type === "partition") { |
229 | + partitionIDs.push(item.partition_id); |
230 | + } else { |
231 | + blockDeviceIDs.push(item.block_id); |
232 | + } |
233 | + }); |
234 | + |
235 | + var params = { |
236 | + system_id: $scope.node.system_id, |
237 | + add_block_devices: blockDeviceIDs, |
238 | + add_partitions: partitionIDs, |
239 | + name: $scope.datastores.old.name, |
240 | + vmfs_datastore_id: $scope.datastores.old.id |
241 | + }; |
242 | + |
243 | + $scope.updatingDatastore = true; |
244 | + |
245 | + MachinesManager.updateDatastore(params) |
246 | + .then(function() { |
247 | + $timeout(function() { |
248 | + $scope.updatingDatastore = false; |
249 | + }, 0); |
250 | + $scope.closeAddToExistingDatastorePanel(); |
251 | + $scope.selectedAvailableDatastores = []; |
252 | + }) |
253 | + .catch(function(error) { |
254 | + $log.error(error); |
255 | + $timeout(function() { |
256 | + $scope.updatingDatastore = false; |
257 | + }, 0); |
258 | + }); |
259 | + }; |
260 | + |
261 | + $scope.storageLayoutIsReadOnly = function(layouts) { |
262 | + return layouts.length <= 1; |
263 | + }; |
264 | + |
265 | + $scope.storageLayoutIsDisabled = function(layouts) { |
266 | + return !layouts.length; |
267 | + }; |
268 | + |
269 | + $scope.hasStorageLayout = function(storageLayout) { |
270 | + return storageLayout ? true : false; |
271 | + }; |
272 | |
273 | // Return True if the filesystem is mounted. |
274 | function isMountedFilesystem(filesystem) { |
275 | @@ -461,6 +703,7 @@ export function NodeStorageController( |
276 | type: disk.type, |
277 | model: disk.model, |
278 | serial: disk.serial, |
279 | + size_human: disk.size_human, |
280 | tags: getTags(disk), |
281 | used_for: disk.used_for, |
282 | is_boot: disk.is_boot, |
283 | @@ -480,6 +723,7 @@ export function NodeStorageController( |
284 | type: "partition", |
285 | model: "", |
286 | serial: "", |
287 | + size_human: partition.size_human, |
288 | tags: [], |
289 | used_for: partition.used_for, |
290 | is_boot: false |
291 | @@ -776,6 +1020,9 @@ export function NodeStorageController( |
292 | } else if (filesystem.original_type === "partition") { |
293 | // Delete the partition. |
294 | MachinesManager.deletePartition($scope.node, filesystem.original.id); |
295 | + } else if (filesystem.parent_type === "vmfs6") { |
296 | + // Delete the datastore. |
297 | + MachinesManager.deleteDisk($scope.node, filesystem.id); |
298 | } else { |
299 | // Delete the disk. |
300 | MachinesManager.deleteFilesystem( |
301 | @@ -878,6 +1125,8 @@ export function NodeStorageController( |
302 | $scope.toggleAvailableSelect = function(disk) { |
303 | disk.$selected = !disk.$selected; |
304 | $scope.updateAvailableSelection(true); |
305 | + $scope.selectedAvailableDatastores = $scope.getSelectedAvailable(); |
306 | + $scope.checkAddToDatastoreValid(); |
307 | }; |
308 | |
309 | // Toggle the selection of all available disks. |
310 | @@ -1592,24 +1841,24 @@ export function NodeStorageController( |
311 | }; |
312 | |
313 | // Return true when the name of the new disk is invalid. |
314 | - $scope.isNewDiskNameInvalid = function() { |
315 | + $scope.isNewDiskNameInvalid = function(newDiskName) { |
316 | if (!angular.isObject($scope.node) || !angular.isArray($scope.node.disks)) { |
317 | return true; |
318 | } |
319 | |
320 | - if ($scope.availableNew.name === "") { |
321 | + if (newDiskName === "") { |
322 | return true; |
323 | } else { |
324 | var i, j; |
325 | for (i = 0; i < $scope.node.disks.length; i++) { |
326 | var disk = $scope.node.disks[i]; |
327 | - if ($scope.availableNew.name === disk.name) { |
328 | + if (newDiskName === disk.name) { |
329 | return true; |
330 | } |
331 | if (angular.isArray(disk.partitions)) { |
332 | for (j = 0; j < disk.partitions.length; j++) { |
333 | var partition = disk.partitions[j]; |
334 | - if ($scope.availableNew.name === partition.name) { |
335 | + if (newDiskName === partition.name) { |
336 | return true; |
337 | } |
338 | } |
339 | @@ -1622,7 +1871,7 @@ export function NodeStorageController( |
340 | // Return true if bcache can be saved. |
341 | $scope.createBcacheCanSave = function() { |
342 | return ( |
343 | - !$scope.isNewDiskNameInvalid() && |
344 | + !$scope.isNewDiskNameInvalid($scope.availableNew.name) && |
345 | !$scope.isMountPointInvalid($scope.availableNew.mountPoint) |
346 | ); |
347 | }; |
348 | @@ -1831,7 +2080,7 @@ export function NodeStorageController( |
349 | // Return true if RAID can be saved. |
350 | $scope.createRAIDCanSave = function() { |
351 | return ( |
352 | - !$scope.isNewDiskNameInvalid() && |
353 | + !$scope.isNewDiskNameInvalid($scope.availableNew.name) && |
354 | !$scope.isMountPointInvalid($scope.availableNew.mountPoint) |
355 | ); |
356 | }; |
357 | @@ -1944,7 +2193,7 @@ export function NodeStorageController( |
358 | |
359 | // Return true if volume group can be saved. |
360 | $scope.createVolumeGroupCanSave = function() { |
361 | - return !$scope.isNewDiskNameInvalid(); |
362 | + return !$scope.isNewDiskNameInvalid($scope.availableNew.name); |
363 | }; |
364 | |
365 | // Confirm and create the volume group device. |
366 | diff --git a/src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js b/src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js |
367 | index f267fa8..023c970 100644 |
368 | --- a/src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js |
369 | +++ b/src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js |
370 | @@ -431,6 +431,7 @@ describe("NodeStorageController", function() { |
371 | type: disks[2].type, |
372 | model: disks[2].model, |
373 | serial: disks[2].serial, |
374 | + size_human: disks[2].size_human, |
375 | tags: disks[2].tags, |
376 | used_for: disks[2].used_for, |
377 | has_partitions: false, |
378 | @@ -443,6 +444,7 @@ describe("NodeStorageController", function() { |
379 | type: disks[3].type, |
380 | model: disks[3].model, |
381 | serial: disks[3].serial, |
382 | + size_human: disks[3].size_human, |
383 | tags: disks[3].tags, |
384 | used_for: disks[3].used_for, |
385 | has_partitions: true, |
386 | @@ -455,6 +457,7 @@ describe("NodeStorageController", function() { |
387 | type: "partition", |
388 | model: "", |
389 | serial: "", |
390 | + size_human: disks[3].partitions[1].size_human, |
391 | tags: [], |
392 | used_for: disks[3].partitions[1].used_for |
393 | } |
394 | @@ -3597,9 +3600,8 @@ describe("NodeStorageController", function() { |
395 | it("returns true if blank name", function() { |
396 | makeController(); |
397 | $scope.node.disks = []; |
398 | - $scope.availableNew.name = ""; |
399 | |
400 | - expect($scope.isNewDiskNameInvalid()).toBe(true); |
401 | + expect($scope.isNewDiskNameInvalid("")).toBe(true); |
402 | }); |
403 | |
404 | it("returns true if name used by disk", function() { |
405 | @@ -3610,9 +3612,8 @@ describe("NodeStorageController", function() { |
406 | name: name |
407 | } |
408 | ]; |
409 | - $scope.availableNew.name = name; |
410 | |
411 | - expect($scope.isNewDiskNameInvalid()).toBe(true); |
412 | + expect($scope.isNewDiskNameInvalid(name)).toBe(true); |
413 | }); |
414 | |
415 | it("returns true if name used by partition", function() { |
416 | @@ -3628,9 +3629,8 @@ describe("NodeStorageController", function() { |
417 | ] |
418 | } |
419 | ]; |
420 | - $scope.availableNew.name = name; |
421 | |
422 | - expect($scope.isNewDiskNameInvalid()).toBe(true); |
423 | + expect($scope.isNewDiskNameInvalid(name)).toBe(true); |
424 | }); |
425 | |
426 | it("returns false if the name is not already used", function() { |
427 | @@ -3646,9 +3646,8 @@ describe("NodeStorageController", function() { |
428 | ] |
429 | } |
430 | ]; |
431 | - $scope.availableNew.name = name; |
432 | |
433 | - expect($scope.isNewDiskNameInvalid()).toBe(false); |
434 | + expect($scope.isNewDiskNameInvalid(name)).toBe(false); |
435 | }); |
436 | }); |
437 | |
438 | @@ -5073,4 +5072,307 @@ describe("NodeStorageController", function() { |
439 | expect($scope.hasStorageLayoutIssues()).toBe(false); |
440 | }); |
441 | }); |
442 | + |
443 | + describe("openStorageLayoutConfirm", function() { |
444 | + it("sets 'confirmStorageLayout' to true", function() { |
445 | + makeController(); |
446 | + $scope.confirmStorageLayout = false; |
447 | + $scope.osFamilies = [ |
448 | + { |
449 | + id: "linux", |
450 | + name: "Linux", |
451 | + layouts: [ |
452 | + { |
453 | + id: "flat", |
454 | + name: "Flat" |
455 | + }, |
456 | + { |
457 | + id: "lvm", |
458 | + name: "LVM" |
459 | + }, |
460 | + { |
461 | + id: "bcache", |
462 | + name: "bcache" |
463 | + }, |
464 | + { |
465 | + id: "vmfs6", |
466 | + name: "VMFS6 (VMware ESXI)" |
467 | + }, |
468 | + { |
469 | + id: "blank", |
470 | + name: "No storage (blank) layout" |
471 | + } |
472 | + ] |
473 | + } |
474 | + ]; |
475 | + $scope.openStorageLayoutConfirm("flat"); |
476 | + expect($scope.confirmStorageLayout).toBe(true); |
477 | + }); |
478 | + |
479 | + it("sets 'newLayout' to layout argument", function() { |
480 | + makeController(); |
481 | + $scope.osFamilies = [ |
482 | + { |
483 | + id: "linux", |
484 | + name: "Linux", |
485 | + layouts: [ |
486 | + { |
487 | + id: "flat", |
488 | + name: "Flat" |
489 | + }, |
490 | + { |
491 | + id: "lvm", |
492 | + name: "LVM" |
493 | + }, |
494 | + { |
495 | + id: "bcache", |
496 | + name: "bcache" |
497 | + }, |
498 | + { |
499 | + id: "vmfs6", |
500 | + name: "VMFS6 (VMware ESXI)" |
501 | + }, |
502 | + { |
503 | + id: "blank", |
504 | + name: "No storage (blank) layout" |
505 | + } |
506 | + ] |
507 | + } |
508 | + ]; |
509 | + $scope.openStorageLayoutConfirm("flat"); |
510 | + expect($scope.newLayout).toEqual($scope.osFamilies[0].layouts[0]); |
511 | + }); |
512 | + }); |
513 | + |
514 | + describe("closeStorageLayoutConfirm", function() { |
515 | + it("sets 'confirmStorageLayout' to false", function() { |
516 | + makeController(); |
517 | + $scope.confirmStorageLayout = true; |
518 | + $scope.closeStorageLayoutConfirm(); |
519 | + expect($scope.confirmStorageLayout).toBe(false); |
520 | + }); |
521 | + }); |
522 | + |
523 | + describe("updateStorageLayout", function() { |
524 | + it("calls 'applyStorageLayout'", function() { |
525 | + makeController(); |
526 | + spyOn(MachinesManager, "applyStorageLayout").and.callFake(function() { |
527 | + var deferred = $q.defer(); |
528 | + return deferred.promise; |
529 | + }); |
530 | + $scope.newLayout = { |
531 | + id: "flat", |
532 | + name: "Flat" |
533 | + }; |
534 | + $scope.updateStorageLayout($scope.newLayout); |
535 | + expect(MachinesManager.applyStorageLayout).toHaveBeenCalled(); |
536 | + }); |
537 | + |
538 | + it("calls 'closeStorageLayoutConfirm'", function() { |
539 | + makeController(); |
540 | + spyOn(MachinesManager, "applyStorageLayout").and.callFake(function() { |
541 | + var deferred = $q.defer(); |
542 | + return deferred.promise; |
543 | + }); |
544 | + spyOn($scope, "closeStorageLayoutConfirm"); |
545 | + $scope.updateStorageLayout({ |
546 | + id: "flat", |
547 | + name: "Flat" |
548 | + }); |
549 | + expect($scope.closeStorageLayoutConfirm).toHaveBeenCalled(); |
550 | + }); |
551 | + }); |
552 | + |
553 | + describe("openNewDatastorePanel", function() { |
554 | + it("sets 'createNewDatastore' to true", function() { |
555 | + makeController(); |
556 | + $scope.createNewDatastore = false; |
557 | + $scope.available = [ |
558 | + { |
559 | + $selected: true, |
560 | + id: 1 |
561 | + } |
562 | + ]; |
563 | + $scope.openNewDatastorePanel(); |
564 | + expect($scope.createNewDatastore).toBe(true); |
565 | + }); |
566 | + |
567 | + it("sets newDatastore", function() { |
568 | + makeController(); |
569 | + $scope.available = [ |
570 | + { |
571 | + $selected: true, |
572 | + id: 1, |
573 | + mount_point: "dev/null", |
574 | + size_human: "35 GB" |
575 | + } |
576 | + ]; |
577 | + $scope.openNewDatastorePanel(); |
578 | + expect($scope.datastores.new).toEqual({ |
579 | + id: $scope.available[0].id, |
580 | + name: "", |
581 | + mountpoint: $scope.available[0].mount_point, |
582 | + filesystem: "VMFS6", |
583 | + size: $scope.available[0].size_human |
584 | + }); |
585 | + }); |
586 | + }); |
587 | + |
588 | + describe("closeNewDatastorePanel", function() { |
589 | + it("sets 'createNewDatastore' to false", function() { |
590 | + makeController(); |
591 | + $scope.createNewDatastore = true; |
592 | + $scope.closeNewDatastorePanel(); |
593 | + expect($scope.createNewDatastore).toBe(false); |
594 | + }); |
595 | + |
596 | + it("sets 'newDatastore' to '{}'", function() { |
597 | + makeController(); |
598 | + $scope.datastores.new = { id: 1, name: "" }; |
599 | + $scope.closeNewDatastorePanel(); |
600 | + expect($scope.datastores.new).toEqual({}); |
601 | + }); |
602 | + }); |
603 | + |
604 | + describe("canPerformActionOnDatastoreSet", function() { |
605 | + it("return false if not on vmsf6 storage layout", function() { |
606 | + makeController(); |
607 | + $scope.addToExistingDatastore = false; |
608 | + $scope.createNewDatastore = false; |
609 | + $scope.selectedAvailableDatastores = [1]; |
610 | + $scope.storageLayout = { id: "flat" }; |
611 | + expect($scope.canPerformActionOnDatastoreSet()).toBe(false); |
612 | + }); |
613 | + |
614 | + it("return false if already editing datastores", function() { |
615 | + makeController(); |
616 | + $scope.addToExistingDatastore = false; |
617 | + $scope.createNewDatastore = true; |
618 | + $scope.selectedAvailableDatastores = [1]; |
619 | + $scope.storageLayout = { id: "vmfs6" }; |
620 | + expect($scope.canPerformActionOnDatastoreSet()).toBe(false); |
621 | + }); |
622 | + |
623 | + it("return false if no device is selected", function() { |
624 | + makeController(); |
625 | + $scope.addToExistingDatastore = false; |
626 | + $scope.createNewDatastore = false; |
627 | + $scope.selectedAvailableDatastores = []; |
628 | + $scope.storageLayout = { id: "vmfs6" }; |
629 | + expect($scope.canPerformActionOnDatastoreSet()).toBe(false); |
630 | + }); |
631 | + |
632 | + it("return true when conditions are matched", function() { |
633 | + makeController(); |
634 | + $scope.addToExistingDatastore = false; |
635 | + $scope.createNewDatastore = false; |
636 | + $scope.selectedAvailableDatastores = [1]; |
637 | + $scope.storageLayout = { id: "vmfs6" }; |
638 | + expect($scope.canPerformActionOnDatastoreSet()).toBe(true); |
639 | + }); |
640 | + }); |
641 | + |
642 | + describe("checkAddToDatastoreValid", function() { |
643 | + it("selected disks are valid when that condition is true", function() { |
644 | + makeController(); |
645 | + var selected = { |
646 | + has_partitions: false |
647 | + }; |
648 | + spyOn($scope, "getSelectedAvailable").and.returnValue([selected]); |
649 | + expect($scope.addToDatastoreValid).toBe(false); |
650 | + $scope.checkAddToDatastoreValid(); |
651 | + expect($scope.addToDatastoreValid).toBe(true); |
652 | + }); |
653 | + |
654 | + it("selected disks are not valid disk has a partition", function() { |
655 | + makeController(); |
656 | + var selected = { |
657 | + has_partitions: true |
658 | + }; |
659 | + spyOn($scope, "getSelectedAvailable").and.returnValue([selected]); |
660 | + expect($scope.addToDatastoreValid).toBe(false); |
661 | + $scope.checkAddToDatastoreValid(); |
662 | + expect($scope.addToDatastoreValid).toBe(false); |
663 | + }); |
664 | + |
665 | + it("selected disks are not valid when no selected disks", function() { |
666 | + makeController(); |
667 | + spyOn($scope, "getSelectedAvailable").and.returnValue([]); |
668 | + expect($scope.addToDatastoreValid).toBe(false); |
669 | + $scope.checkAddToDatastoreValid(); |
670 | + expect($scope.addToDatastoreValid).toBe(false); |
671 | + }); |
672 | + }); |
673 | + |
674 | + describe("openAddToExistingDatastorePanel", function() { |
675 | + it("sets 'addToExistingDatastore' to true", function() { |
676 | + makeController(); |
677 | + $scope.addToExistingDatastore = false; |
678 | + $scope.available = [ |
679 | + { |
680 | + $selected: true, |
681 | + id: 1 |
682 | + } |
683 | + ]; |
684 | + $scope.openAddToExistingDatastorePanel(); |
685 | + expect($scope.addToExistingDatastore).toBe(true); |
686 | + }); |
687 | + |
688 | + it("sets 'selectedAvailableDatastores' to selected", function() { |
689 | + makeController(); |
690 | + $scope.datastores.old = [ |
691 | + { |
692 | + $selected: true, |
693 | + id: 1 |
694 | + } |
695 | + ]; |
696 | + $scope.openAddToExistingDatastorePanel(); |
697 | + expect($scope.selectedAvailableDatastores).toEqual($scope.available); |
698 | + }); |
699 | + |
700 | + it("sets 'datastores.old' to first disk", function() { |
701 | + makeController(); |
702 | + $scope.openAddToExistingDatastorePanel(); |
703 | + expect($scope.datastores.old).toBe($scope.node.disks[0]); |
704 | + }); |
705 | + }); |
706 | + |
707 | + describe("closeAddToExistingDatastorePanel", function() { |
708 | + it("sets 'addToExistingDatastore' to false", function() { |
709 | + makeController(); |
710 | + $scope.addToExistingDatastore = true; |
711 | + $scope.closeAddToExistingDatastorePanel(); |
712 | + expect($scope.addToExistingDatastore).toBe(false); |
713 | + }); |
714 | + |
715 | + it("sets, 'newDatasore' to '{}'", function() { |
716 | + makeController(); |
717 | + $scope.datastores.new = { id: 1, name: "" }; |
718 | + $scope.closeAddToExistingDatastorePanel(); |
719 | + expect($scope.datastores.new).toEqual({}); |
720 | + }); |
721 | + }); |
722 | + |
723 | + describe("createDatastore", function() { |
724 | + it("sets 'createNewDatastore' to true", function() { |
725 | + makeController(); |
726 | + spyOn(MachinesManager, "createDatastore").and.callFake(function() { |
727 | + var deferred = $q.defer(); |
728 | + return deferred.promise; |
729 | + }); |
730 | + $scope.createNewDatastore = false; |
731 | + $scope.createDatastore(); |
732 | + expect($scope.createNewDatastore).toBe(true); |
733 | + }); |
734 | + |
735 | + it("calls 'MachinesManager.createDatastore'", function() { |
736 | + makeController(); |
737 | + spyOn(MachinesManager, "createDatastore").and.callFake(function() { |
738 | + var deferred = $q.defer(); |
739 | + return deferred.promise; |
740 | + }); |
741 | + $scope.createDatastore(); |
742 | + expect(MachinesManager.createDatastore).toHaveBeenCalled(); |
743 | + }); |
744 | + }); |
745 | }); |
746 | diff --git a/src/maasserver/static/js/angular/directives/nodedetails/storage_datastores.js b/src/maasserver/static/js/angular/directives/nodedetails/storage_datastores.js |
747 | new file mode 100644 |
748 | index 0000000..d040063 |
749 | --- /dev/null |
750 | +++ b/src/maasserver/static/js/angular/directives/nodedetails/storage_datastores.js |
751 | @@ -0,0 +1,9 @@ |
752 | +function storageDatastores() { |
753 | + const path = "static/partials/nodedetails/storage/datastores.html"; |
754 | + return { |
755 | + restrict: "E", |
756 | + templateUrl: `${path}?v=${MAAS_config.files_version}` |
757 | + }; |
758 | +} |
759 | + |
760 | +export default storageDatastores; |
761 | diff --git a/src/maasserver/static/js/angular/entry.js b/src/maasserver/static/js/angular/entry.js |
762 | index 0cf83dc..cea8d08 100644 |
763 | --- a/src/maasserver/static/js/angular/entry.js |
764 | +++ b/src/maasserver/static/js/angular/entry.js |
765 | @@ -36,7 +36,8 @@ import { |
766 | } from "./controllers/node_details_networking"; // TODO: fix export/namespace |
767 | // prettier-ignore |
768 | import { |
769 | - removeAvailableByNew |
770 | + removeAvailableByNew, |
771 | + datastoresOnly |
772 | } from "./controllers/node_details_storage"; // TODO: fix export/namespace |
773 | // prettier-ignore |
774 | import { |
775 | @@ -152,6 +153,7 @@ import ZonesListController from "./controllers/zones_list"; |
776 | import storageDisksPartitions |
777 | from "./directives/nodedetails/storage_disks_partitions"; |
778 | import storageFilesystems from "./directives/nodedetails/storage_filesystems"; |
779 | +import storageDatastores from "./directives/nodedetails/storage_datastores"; |
780 | import maasMachinesTable from "./directives/machines_table"; |
781 | import addMachine from "./directives/nodelist/add_machine"; |
782 | import maasAccordion from "./directives/accordion"; |
783 | @@ -493,6 +495,7 @@ angular |
784 | .filter("removeDefaultVLANIfVLAN", removeDefaultVLANIfVLAN) |
785 | .filter("filterLinkModes", filterLinkModes) |
786 | .filter("removeAvailableByNew", removeAvailableByNew) |
787 | + .filter("datastoresOnly", datastoresOnly) |
788 | .filter("filterSource", filterSource) |
789 | .filter("ignoreSelf", ignoreSelf) |
790 | .filter("removeNoDHCP", removeNoDHCP) |
791 | @@ -594,6 +597,7 @@ angular |
792 | // directives |
793 | .directive("storageDisksPartitions", storageDisksPartitions) |
794 | .directive("storageFilesystems", storageFilesystems) |
795 | + .directive("storageDatastores", storageDatastores) |
796 | .directive("addMachine", addMachine) |
797 | .directive("maasAccordion", maasAccordion) |
798 | .directive("maasActionButton", maasActionButton) |
799 | diff --git a/src/maasserver/static/js/angular/factories/machines.js b/src/maasserver/static/js/angular/factories/machines.js |
800 | index c3c3a5b..e70b096 100644 |
801 | --- a/src/maasserver/static/js/angular/factories/machines.js |
802 | +++ b/src/maasserver/static/js/angular/factories/machines.js |
803 | @@ -77,6 +77,21 @@ function MachinesManager(RegionConnection, NodesManager) { |
804 | return RegionConnection.callMethod(method, params); |
805 | }; |
806 | |
807 | + MachinesManager.prototype.applyStorageLayout = function(params) { |
808 | + var method = this._handler + ".apply_storage_layout"; |
809 | + return RegionConnection.callMethod(method, params); |
810 | + }; |
811 | + |
812 | + MachinesManager.prototype.createDatastore = function(params) { |
813 | + var method = this._handler + ".create_vmfs_datastore"; |
814 | + return RegionConnection.callMethod(method, params); |
815 | + }; |
816 | + |
817 | + MachinesManager.prototype.updateDatastore = function(params) { |
818 | + var method = this._handler + ".update_vmfs_datastore"; |
819 | + return RegionConnection.callMethod(method, params); |
820 | + }; |
821 | + |
822 | return new MachinesManager(); |
823 | } |
824 | |
825 | diff --git a/src/maasserver/static/js/angular/factories/tests/test_machines.js b/src/maasserver/static/js/angular/factories/tests/test_machines.js |
826 | index 26eff00..9d4f7b6 100644 |
827 | --- a/src/maasserver/static/js/angular/factories/tests/test_machines.js |
828 | +++ b/src/maasserver/static/js/angular/factories/tests/test_machines.js |
829 | @@ -87,4 +87,56 @@ describe("MachinesManager", function() { |
830 | ); |
831 | }); |
832 | }); |
833 | + |
834 | + describe("applyStorageLayout", function() { |
835 | + it("calls apply_storage_layout", function() { |
836 | + spyOn(RegionConnection, "callMethod"); |
837 | + var params = { |
838 | + system_id: makeName("system-id"), |
839 | + mount_point: makeName("/dir") |
840 | + }; |
841 | + MachinesManager.applyStorageLayout(params); |
842 | + expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
843 | + "machine.apply_storage_layout", |
844 | + params |
845 | + ); |
846 | + }); |
847 | + }); |
848 | + |
849 | + describe("createDatastore", function() { |
850 | + it("calls create_vmfs_datastore", function() { |
851 | + spyOn(RegionConnection, "callMethod"); |
852 | + var params = { |
853 | + system_id: makeName("system-id"), |
854 | + block_devices: [1, 2, 3, 5], |
855 | + partitions: [5, 6, 7, 8], |
856 | + name: "New datastore" |
857 | + }; |
858 | + MachinesManager.createDatastore(params); |
859 | + expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
860 | + "machine.create_vmfs_datastore", |
861 | + params |
862 | + ); |
863 | + }); |
864 | + }); |
865 | + |
866 | + describe("updateDatastore", function() { |
867 | + it("calls update_vmfs_datastore", function() { |
868 | + spyOn(RegionConnection, "callMethod"); |
869 | + var params = { |
870 | + system_id: makeName("system-id"), |
871 | + add_block_devices: [1, 2, 3, 4], |
872 | + add_partitions: [5, 6, 7, 8], |
873 | + remove_partitions: [], |
874 | + remove_block_devices: [], |
875 | + name: "New datastore", |
876 | + vmfs_datastore_id: 1 |
877 | + }; |
878 | + MachinesManager.updateDatastore(params); |
879 | + expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
880 | + "machine.update_vmfs_datastore", |
881 | + params |
882 | + ); |
883 | + }); |
884 | + }); |
885 | }); |
886 | diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html |
887 | index d722404..5612ef2 100755 |
888 | --- a/src/maasserver/static/partials/node-details.html |
889 | +++ b/src/maasserver/static/partials/node-details.html |
890 | @@ -69,7 +69,7 @@ |
891 | <p class="page-header__message"><i class="p-icon--warning">Warning:</i> MAAS is not providing DHCP.</p> |
892 | </div> |
893 | </div> |
894 | - <div class="row ng-hide u-no-margin--top" data-ng-hide="isActionError() || isDeployError() || hasActionPowerError(action.option.name)"> |
895 | + <div class="row ng-hide u-no-margin--top" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name)"> |
896 | <!-- XXX blake_r 2015-02-19 - Need to add e2e test. --> |
897 | <div class="page-header__section"> |
898 | <form class="p-form"> |
899 | @@ -110,7 +110,7 @@ |
900 | </label> |
901 | <span data-ng-if="!nodesManager.isModernUbuntu(osSelection)"> |
902 | <i class="p-icon--warning"></i> |
903 | - <strong>Warning:</strong> Ubuntu 18.04 is the minimum required to create a KVM host. <a target="_blank" |
904 | + <strong>Warning:</strong> Ubuntu 18.04 is the minimum required. <a target="_blank" |
905 | class="p-link--external" |
906 | href="https://docs.maas.io/2.5/en/manage-pods-webui#add-a-kvm-host">Learn more</a> |
907 | </span> |
908 | @@ -122,11 +122,6 @@ |
909 | </div> |
910 | </div> |
911 | </div> |
912 | - <div data-ng-if="isSSHKeyWarning()" class="p-strip is-shallow u-no-padding--top"> |
913 | - <p class="u-remove-max-width"> |
914 | - <i class="p-icon--warning">Warning:</i> Login will not be possible because no SSH keys have been added to your account. To add an SSH key, visit <a href="account/prefs/">your account page</a>. |
915 | - </p> |
916 | - </div> |
917 | </div> |
918 | </div> |
919 | </div> |
920 | @@ -172,7 +167,7 @@ |
921 | </form> |
922 | </div> |
923 | </div> |
924 | - <div class="row ng-hide" data-ng-hide="isActionError() || isDeployError() || hasActionPowerError(action.option.name) || action.option.name !== 'commission'" data-ng-if="hasCustomCommissioningScripts()"> |
925 | + <div class="row ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name) || action.option.name !== 'commission'" data-ng-if="hasCustomCommissioningScripts()"> |
926 | <div class="page-header__section"> |
927 | <form class="p-form"> |
928 | <div class="col-8"> |
929 | @@ -184,7 +179,7 @@ |
930 | </form> |
931 | </div> |
932 | </div> |
933 | - <div class="row u-no-margin--top ng-hide" data-ng-hide="isActionError() || isDeployError() || hasActionPowerError(action.option.name) || (action.option.name !== 'commission' && action.option.name !== 'test')"> |
934 | + <div class="row u-no-margin--top ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name) || (action.option.name !== 'commission' && action.option.name !== 'test')"> |
935 | <hr /> |
936 | <div class="page-header__section"> |
937 | <form class="p-form"> |
938 | @@ -197,7 +192,7 @@ |
939 | </form> |
940 | </div> |
941 | </div> |
942 | - <div class="u-no-margin--top row ng-hide" data-ng-hide="isActionError() || isDeployError() || hasActionPowerError(action.option.name)" data-ng-if="action.option.name === 'commission' || action.option.name === 'test' && !action.showing_confirmation"> |
943 | + <div class="u-no-margin--top row ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name)" data-ng-if="action.option.name === 'commission' || action.option.name === 'test' && !action.showing_confirmation"> |
944 | <hr /> |
945 | <div class="page-header__section col-12"> |
946 | <form class="p-form"> |
947 | @@ -219,18 +214,18 @@ |
948 | <div data-ng-repeat="confirmation_detail in action.confirmation_details"> |
949 | <span>{$ confirmation_detail $}</span> |
950 | </div> |
951 | - <div class="u-equal-height"> |
952 | - <div class="col-9 u-vertically-center"> |
953 | - <p class="u-remove-max-width"> |
954 | - Are you sure you want to {$ action.actionOption.name $} this {$ type_name $}? |
955 | - </p> |
956 | - </div> |
957 | - <div class="col-3"> |
958 | - <div class="u-align--right"> |
959 | - <button class="p-button--base" data-ng-click="actionCancel()">No</button> |
960 | - <button class="p-button--negative" data-ng-click="actionGo()">Yes</button> |
961 | - </div> |
962 | - </div> |
963 | + <div class="u-equal-height"> |
964 | + <div class="col-9 u-vertically-center"> |
965 | + <p class="u-remove-max-width"> |
966 | + Are you sure you want to {$ action.actionOption.name $} this {$ type_name $}? |
967 | + </p> |
968 | + </div> |
969 | + <div class="col-3"> |
970 | + <div class="u-align--right"> |
971 | + <button class="p-button--base" data-ng-click="actionCancel()">No</button> |
972 | + <button class="p-button--negative" data-ng-click="actionGo()">Yes</button> |
973 | + </div> |
974 | + </div> |
975 | </div> |
976 | </div> |
977 | </div> |
978 | @@ -273,6 +268,18 @@ |
979 | </div> |
980 | </div> |
981 | </div> |
982 | + <div class="row u-equal-height ng-hide" data-ng-show="!isDeployError() && isSSHKeyError()"> |
983 | + <div class="col-9 u-vertically-center"> |
984 | + <p class="u-remove-max-width"> |
985 | + <i class="p-icon--error">Error:</i> Node cannot be {$ action.option.sentence $}, because an SSH key has not been added to your account. To add an SSH key, visit <a href="account/prefs/">your account page</a>. |
986 | + </p> |
987 | + </div> |
988 | + <div class="col-3"> |
989 | + <div class="u-align--right"> |
990 | + <button class="p-button--base" data-ng-click="actionCancel()">Cancel</button> |
991 | + </div> |
992 | + </div> |
993 | + </div> |
994 | </div> |
995 | |
996 | <nav class="p-tabs u-hr--fixed-width"> |
997 | @@ -394,97 +401,97 @@ |
998 | </div> |
999 | <div class="p-strip is-shallow"> |
1000 | <div class="row u-equal-height" data-ng-if="isDevice"> |
1001 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1002 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1003 | <div maas-card-loader="device"></div> |
1004 | - </div> |
1005 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1006 | + </div> |
1007 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1008 | <div maas-card-loader="tags"></div> |
1009 | - </div> |
1010 | + </div> |
1011 | </div> |
1012 | <div class="row u-equal-height" data-ng-if="node.node_type == 3"> |
1013 | - <div class="col-4 p-card--highlighted action-card u-border--solid"> |
1014 | + <div class="col-4 p-card--highlighted action-card u-border--solid"> |
1015 | <div maas-card-loader="services"></div> |
1016 | - </div> |
1017 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1018 | + </div> |
1019 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1020 | <div maas-card-loader="controller"></div> |
1021 | - </div> |
1022 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1023 | + </div> |
1024 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1025 | <div maas-card-loader="hardware_info"></div> |
1026 | - </div> |
1027 | + </div> |
1028 | </div> |
1029 | <div class="row u-equal-height" data-ng-if="node.node_type == 3"> |
1030 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.cpu_test_status === 3 }"> |
1031 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.cpu_test_status === 3 }"> |
1032 | <div maas-card-loader="cpu"></div> |
1033 | - </div> |
1034 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.memory_test_status === 3 }"> |
1035 | + </div> |
1036 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.memory_test_status === 3 }"> |
1037 | <div maas-card-loader="memory"></div> |
1038 | - </div> |
1039 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.storage_test_status === 3 }"> |
1040 | + </div> |
1041 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.storage_test_status === 3 }"> |
1042 | <div maas-card-loader="storage"></div> |
1043 | - </div> |
1044 | + </div> |
1045 | </div> |
1046 | <div class="row u-equal-height" data-ng-if="node.node_type == 3"> |
1047 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1048 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1049 | <div maas-card-loader="tags"></div> |
1050 | - </div> |
1051 | + </div> |
1052 | </div> |
1053 | <div class="row u-equal-height" data-ng-if="node.node_type == 2 || node.node_type == 4"> |
1054 | - <div class="col-4 p-card--highlighted action-card u-border--solid"> |
1055 | + <div class="col-4 p-card--highlighted action-card u-border--solid"> |
1056 | <div maas-card-loader="services"></div> |
1057 | - </div> |
1058 | - <div class="col-8"> |
1059 | + </div> |
1060 | + <div class="col-8"> |
1061 | <div class="row u-equal-height"> |
1062 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1063 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1064 | <div maas-card-loader="images"></div> |
1065 | - </div> |
1066 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1067 | + </div> |
1068 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1069 | <div maas-card-loader="controller"></div> |
1070 | - </div> |
1071 | + </div> |
1072 | </div> |
1073 | <div class="row u-equal-height"> |
1074 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1075 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1076 | <div maas-card-loader="hardware_info"></div> |
1077 | - </div> |
1078 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1079 | + </div> |
1080 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid"> |
1081 | <div maas-card-loader="tags"></div> |
1082 | - </div> |
1083 | + </div> |
1084 | + </div> |
1085 | </div> |
1086 | - </div> |
1087 | </div> |
1088 | <div class="row u-equal-height" data-ng-if="node.node_type == 2 || node.node_type == 4"> |
1089 | - <div class="col-4 p-card--highlighted action-card u-border--solid" data-ng-class="{ 'is-error': node.cpu_test_status === 3 }"> |
1090 | + <div class="col-4 p-card--highlighted action-card u-border--solid" data-ng-class="{ 'is-error': node.cpu_test_status === 3 }"> |
1091 | <div maas-card-loader="cpu"></div> |
1092 | - </div> |
1093 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.memory_test_status === 3 }"> |
1094 | + </div> |
1095 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.memory_test_status === 3 }"> |
1096 | <div maas-card-loader="memory"></div> |
1097 | - </div> |
1098 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.storage_test_status === 3 }"> |
1099 | + </div> |
1100 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-class="{ 'is-error': node.storage_test_status === 3 }"> |
1101 | <div maas-card-loader="storage"></div> |
1102 | - </div> |
1103 | + </div> |
1104 | </div> |
1105 | <div class="row u-equal-height" data-ng-if="!isDevice && !isController"> |
1106 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController" data-ng-class="{ 'is-error': node.cpu_test_status === 3 }"> |
1107 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController" data-ng-class="{ 'is-error': node.cpu_test_status === 3 }"> |
1108 | <div maas-card-loader="cpu"></div> |
1109 | - </div> |
1110 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController" data-ng-class="{ 'is-error': node.memory_test_status === 3 }"> |
1111 | + </div> |
1112 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController" data-ng-class="{ 'is-error': node.memory_test_status === 3 }"> |
1113 | <div maas-card-loader="memory"></div> |
1114 | - </div> |
1115 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="node.node_type != 3" data-ng-class="{ 'is-error': node.storage_test_status === 3 }"> |
1116 | + </div> |
1117 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="node.node_type != 3" data-ng-class="{ 'is-error': node.storage_test_status === 3 }"> |
1118 | <div maas-card-loader="storage"></div> |
1119 | - </div> |
1120 | + </div> |
1121 | </div> |
1122 | <div class="row u-equal-height" data-ng-if="!isDevice && !isController"> |
1123 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController"> |
1124 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController"> |
1125 | <div maas-card-loader="machine"></div> |
1126 | - </div> |
1127 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController"> |
1128 | + </div> |
1129 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController"> |
1130 | <div maas-card-loader="hardware_info"></div> |
1131 | - </div> |
1132 | - <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController"> |
1133 | + </div> |
1134 | + <div class="col-4 p-card--highlighted action-card action-card--positionable u-border--solid" data-ng-if="!isController"> |
1135 | <div maas-card-loader="tags"></div> |
1136 | - </div> |
1137 | + </div> |
1138 | + </div> |
1139 | </div> |
1140 | - </div> |
1141 | </section> |
1142 | <section class="p-strip" data-ng-if="section.area === 'containers'"> |
1143 | <div class="row"> |
1144 | @@ -839,7 +846,7 @@ |
1145 | </td> |
1146 | <td> |
1147 | <div class="u-no-margin--top" data-ng-repeat="subnet in vlanRow['subnets']"> |
1148 | - <a href="#/subnet/{$ subnet.id $}" title="{$ getSubnetText(subnet) $}">{$ getSubnetText(subnet) $}</a> |
1149 | + <a href="#/subnet/{$ subnet.id $}" title="{$ getSubnetText(subnet) $}">{$ getSubnetText(subnet) $}</a> |
1150 | </div> |
1151 | </td> |
1152 | <td> |
1153 | @@ -2890,105 +2897,197 @@ |
1154 | </form> |
1155 | </div> |
1156 | </section> |
1157 | - <section class="p-strip" data-ng-if="section.area === 'storage'"> |
1158 | - <form data-ng-controller="NodeStorageController"> |
1159 | - <div class="row"> |
1160 | - <div class="col-12"> |
1161 | - <div class="p-notification--negative ng-hide" data-ng-hide="has_disks"> |
1162 | - <p class="p-notification__response"> |
1163 | - <span class="p-notification__status">Error:</span> No storage information. Commissioning this node will gather the storage information. |
1164 | - </p> |
1165 | - </div> |
1166 | - <div class="p-notification ng-hide" data-ng-show="isAllStorageDisabled() && canEdit()"> |
1167 | - <p class="p-notification__response">Storage configuration cannot be modified unless the machine is Ready or Allocated.</p> |
1168 | - </div> |
1169 | - <div class="p-notification ng-hide" data-ng-show="!isUbuntuOS() && !isCentOS()"> |
1170 | - <p class="p-notification__response">Custom storage configuration is only supported on Ubuntu, CentOS, and RHEL.</p> |
1171 | - </div> |
1172 | - <div class="p-notification ng-hide" data-ng-show="!isUbuntuOS()"> |
1173 | - <p class="p-notification__response">Bcache and ZFS are only supported on Ubuntu.</p> |
1174 | + |
1175 | + <section class="p-strip is-shallow" data-ng-if="section.area === 'storage'" data-ng-controller="NodeStorageController"> |
1176 | + <div class="row"> |
1177 | + <div class="col-12"> |
1178 | + <div class="p-notification--negative ng-hide" data-ng-hide="has_disks"> |
1179 | + <p class="p-notification__response"> |
1180 | + <span class="p-notification__status">Error:</span> No storage information. Commissioning this node will gather the storage information. |
1181 | + </p> |
1182 | + </div> |
1183 | + <div class="p-notification ng-hide" data-ng-show="isAllStorageDisabled() && canEdit()"> |
1184 | + <p class="p-notification__response">Storage configuration cannot be modified unless the machine is Ready or Allocated.</p> |
1185 | + </div> |
1186 | + <div class="p-notification ng-hide" data-ng-show="!isUbuntuOS() && !isCentOS()"> |
1187 | + <p class="p-notification__response">Custom storage configuration is only supported on Ubuntu, CentOS, and RHEL.</p> |
1188 | + </div> |
1189 | + <div class="p-notification ng-hide" data-ng-show="!isUbuntuOS()"> |
1190 | + <p class="p-notification__response">Bcache and ZFS are only supported on Ubuntu.</p> |
1191 | + </div> |
1192 | + <div data-ng-repeat="issue in node.storage_layout_issues" class="p-notification--negative ng-hide" data-ng-show="hasStorageLayoutIssues()"> |
1193 | + <p class="p-notification__response"> |
1194 | + <span class="p-notification__status">Error:</span> {$ issue $} |
1195 | + </p> |
1196 | + </div> |
1197 | + </div> |
1198 | + </div> |
1199 | + </section> |
1200 | + |
1201 | + <section class="p-strip u-no-padding--bottom" data-ng-if="section.area === 'storage'" data-ng-controller="NodeStorageController"> |
1202 | + <form> |
1203 | + <div data-ng-if="node.status_code === 4 || node.status_code === 10"> |
1204 | + <div class="row" data-ng-if="!confirmStorageLayout"> |
1205 | + <div class="col-12 prefix-9 u-sv3"> |
1206 | + <div class="p-form__group u-align--right"> |
1207 | + <div data-ng-if="storageLayoutIsDisabled(osFamily.layouts) && storageLayoutIsDisabled(osFamily.layouts)"> |
1208 | + <button class="p-button--neutral" disabled>Change storage layout</button> |
1209 | + </div> |
1210 | + <div data-ng-if="!(storageLayoutIsDisabled(osFamily.layouts) && storageLayoutIsDisabled(osFamily.layouts))"> |
1211 | + <div class="p-contextual-menu" toggle-ctrl> |
1212 | + <button class="p-button--neutral p-contextual-menu__toggle u-no-margin--bottom" data-ng-click="toggleMenu()"> |
1213 | + <span data-ng-if="!updatingStorageLayout"> |
1214 | + Change storage layout |
1215 | + <i class="p-icon--chevron" data-ng-class="{'u-rotate':isToggled}"></i> |
1216 | + </span> |
1217 | + |
1218 | + <span data-ng-if="updatingStorageLayout"> |
1219 | + <i class="p-icon--spinner u-animation--spin"></i> |
1220 | + |
1221 | + Updating storage layout… |
1222 | + </span> |
1223 | + </button> |
1224 | + <div class="p-contextual-menu__dropdown" role="menu" data-ng-show="isToggled"> |
1225 | + <button class="p-contextual-menu__link" data-ng-click="toggleMenu(); openStorageLayoutConfirm('flat')">Flat</button> |
1226 | + <button class="p-contextual-menu__link" data-ng-click="toggleMenu(); openStorageLayoutConfirm('lvm')">LVM</button> |
1227 | + <button class="p-contextual-menu__link" data-ng-click="toggleMenu(); openStorageLayoutConfirm('bcache')">bcache</button> |
1228 | + <hr class="u-no-margin--bottom"/> |
1229 | + <button class="p-contextual-menu__link" data-ng-click="toggleMenu(); openStorageLayoutConfirm('vmfs6')">VMFS6 (VMware ESXi)</button> |
1230 | + <hr class="u-no-margin--bottom"/> |
1231 | + <button class="p-contextual-menu__link" data-ng-click="toggleMenu(); openStorageLayoutConfirm('blank')">No storage (blank) layout</button> |
1232 | + </div> |
1233 | + </div> |
1234 | + </div> |
1235 | + </div> |
1236 | </div> |
1237 | - <div data-ng-repeat="issue in node.storage_layout_issues" class="p-notification--negative ng-hide" data-ng-show="hasStorageLayoutIssues()"> |
1238 | - <p class="p-notification__response"> |
1239 | - <span class="p-notification__status">Error:</span> {$ issue $} |
1240 | - </p> |
1241 | + </div> |
1242 | + <div class="row" data-ng-if="confirmStorageLayout"> |
1243 | + <div class="p-card--highlighted"> |
1244 | + <div class="col-6 u-no-margin--left"> |
1245 | + <div class="p-notification--caution is-subtle" data-ng-if="newLayout.id === 'vmfs6'"> |
1246 | + <p class="p-notification__response"> |
1247 | + <strong>Are you sure you want to change the storage layout to VMFS6?</strong><br /> |
1248 | + Any changes done already will be discarded.<br /> |
1249 | + This layout allows only for the deployment of <strong>VMware ESXi</strong> images.<br /> |
1250 | + The storage layout will be applied to a node when it is deployed. |
1251 | + </p> |
1252 | + </div> |
1253 | + <div class="p-notification--caution is-subtle" data-ng-if="newLayout.id === 'lvm'"> |
1254 | + <p class="p-notification__response"> |
1255 | + <strong>Are you sure you want to change the storage layout to LVM?</strong><br /> |
1256 | + Any changes done already will be lost.<br /> |
1257 | + The storage layout will be applied to a node when it is deployed. |
1258 | + </p> |
1259 | + </div> |
1260 | + <div class="p-notification--caution is-subtle" data-ng-if="newLayout.id === 'blank'"> |
1261 | + <p class="p-notification__response"> |
1262 | + <strong>Are you sure you want to change this storage layout to blank?</strong><br /> |
1263 | + Used disks will be returned to available, and any volume groups, raid sets, |
1264 | caches, and filesystems removed.<br /> |
1265 | + The storage layout will be applied to a node when it is deployed. |
1266 | + </p> |
1267 | + </div> |
1268 | + <div class="p-notification--caution is-subtle" data-ng-if="newLayout.id !== 'lvm' && newLayout.id !== 'vmfs6' && newLayout.id !== 'blank'"> |
1269 | + <p class="p-notification__response"> |
1270 | + <strong>Are you sure you want to change the storage layout to {$ newLayout.id $}?</strong><br /> |
1271 | + Any changes done already will be lost.<br /> |
1272 | + The storage layout will be applied to a node when it is deployed. |
1273 | + </p> |
1274 | + </div> |
1275 | + </div> |
1276 | + <div class="col-6 u-align--right"> |
1277 | + <button class="p-button--base" |
1278 | + data-ng-click="closeStorageLayoutConfirm()">Cancel</button> |
1279 | + <button class="p-button--negative" |
1280 | + data-ng-click="updateStorageLayout(storageLayout)"> |
1281 | + Change storage layout |
1282 | + </button> |
1283 | + </div> |
1284 | </div> |
1285 | </div> |
1286 | </div> |
1287 | - <div class="row"> |
1288 | - <div data-ng-controller="NodeFilesystemsController"> |
1289 | - <storage-filesystems></storage-filesystems> |
1290 | + |
1291 | + <div class="p-strip"> |
1292 | + <div class="row" data-ng-if="storageLayout.id === 'vmfs6'"> |
1293 | + <div data-ng-controller="NodeFilesystemsController"> |
1294 | + <storage-datastores></storage-datastores> |
1295 | + </div> |
1296 | </div> |
1297 | - <div data-ng-show="cachesets.length"> |
1298 | - <div class="row"> |
1299 | - <h3 class="p-heading--four">Available cache sets</h3> |
1300 | + |
1301 | + <div class="row"> |
1302 | + <div data-ng-controller="NodeFilesystemsController" data-ng-if="storageLayout.id !== 'vmfs6'"> |
1303 | + <storage-filesystems></storage-filesystems> |
1304 | </div> |
1305 | - <div class="row"> |
1306 | - <table class="p-table-expanding"> |
1307 | - <thead> |
1308 | - <tr> |
1309 | - <th class="col-2">Name</th> |
1310 | - <th class="col-3">Size</th> |
1311 | - <th class="col-4">Used by</th> |
1312 | - <th class="col-2"> |
1313 | - <div class="u-align--right">Actions</div> |
1314 | - </th> |
1315 | - </tr> |
1316 | - </thead> |
1317 | - <tbody> |
1318 | - <tr data-ng-repeat="cacheset in cachesets" data-ng-class="{ 'is-active': cacheset.$selected }"> |
1319 | - <td class="col-2" aria-label="Name" title="{$ cacheset.name $}">{$ cacheset.name $}</td> |
1320 | - <td class="col-3" aria-label="Size" title="{$ cacheset.size_human $}">{$ cacheset.size_human $}</td> |
1321 | - <td class="col-4" aria-label="Used by" title="{$ cacheset.used_by $}">{$ cacheset.used_by $}</td> |
1322 | - <td class="col-2 p-table--action-cell"> |
1323 | - <div class="u-align--right"> |
1324 | - <div class="p-contextual-menu" toggle-ctrl data-ng-if="canDeleteCacheSet(cacheset) && !cacheset.$selected"> |
1325 | - <button class="p-button--base is-small p-contextual-menu__toggle" data-ng-click="toggleMenu()"> |
1326 | - <i class="p-icon--contextual-menu u-no-margin--right">Actions</i> |
1327 | - </button> |
1328 | - <div class="p-contextual-menu__dropdown" role="menu" data-ng-show="isToggled"> |
1329 | - <button class="p-contextual-menu__link" |
1330 | - aria-label="Remove" |
1331 | - data-ng-show="canDeleteCacheSet(cacheset)" |
1332 | - data-ng-click="toggleMenu(); quickCacheSetDelete(cacheset)">Remove…</button> |
1333 | - </div> |
1334 | - </div> |
1335 | - </div> |
1336 | - </td> |
1337 | - <td class="p-table-expanding__panel col-12" data-ng-if="cacheset.$selected && cachesetsMode === 'delete'"> |
1338 | - <div class="row" data-ng-if="windowWidth <= 768"> |
1339 | - <div class="col-8"> |
1340 | - <h2 data-ng-click="filesystemCancel()" class="p-heading--four"> |
1341 | - <span data-ng-if="cachesetsMode === 'delete'">Removing {$ cacheset.name $}</span> |
1342 | - </h2> |
1343 | + <div data-ng-show="cachesets.length"> |
1344 | + <div class="row"> |
1345 | + <h3 class="p-heading--four">Available cache sets</h3> |
1346 | + </div> |
1347 | + <div class="row"> |
1348 | + <table class="p-table-expanding"> |
1349 | + <thead> |
1350 | + <tr> |
1351 | + <th class="col-2">Name</th> |
1352 | + <th class="col-3">Size</th> |
1353 | + <th class="col-4">Used by</th> |
1354 | + <th class="col-2"> |
1355 | + <div class="u-align--right">Actions</div> |
1356 | + </th> |
1357 | + </tr> |
1358 | + </thead> |
1359 | + <tbody> |
1360 | + <tr data-ng-repeat="cacheset in cachesets" data-ng-class="{ 'is-active': cacheset.$selected }"> |
1361 | + <td class="col-2" aria-label="Name" title="{$ cacheset.name $}">{$ cacheset.name $}</td> |
1362 | + <td class="col-3" aria-label="Size" title="{$ cacheset.size_human $}">{$ cacheset.size_human $}</td> |
1363 | + <td class="col-4" aria-label="Used by" title="{$ cacheset.used_by $}">{$ cacheset.used_by $}</td> |
1364 | + <td class="col-2 p-table--action-cell"> |
1365 | + <div class="u-align--right"> |
1366 | + <div class="p-contextual-menu" toggle-ctrl data-ng-if="canDeleteCacheSet(cacheset) && !cacheset.$selected"> |
1367 | + <button class="p-button--base is-small p-contextual-menu__toggle" data-ng-click="toggleMenu()"> |
1368 | + <i class="p-icon--contextual-menu u-no-margin--right">Actions</i> |
1369 | + </button> |
1370 | + <div class="p-contextual-menu__dropdown" role="menu" data-ng-show="isToggled"> |
1371 | + <button class="p-contextual-menu__link" |
1372 | + aria-label="Remove" |
1373 | + data-ng-show="canDeleteCacheSet(cacheset)" |
1374 | + data-ng-click="toggleMenu(); quickCacheSetDelete(cacheset)">Remove…</button> |
1375 | + </div> |
1376 | </div> |
1377 | - </div> |
1378 | - <div class="row" data-ng-class="{ 'is-active': cachesetsMode !== null && cachesetsMode !== 'multi' }"> |
1379 | - <div data-ng-show="cachesetsMode === 'single' && canDeleteCacheSet(cacheset)"> |
1380 | - <button class="p-button--base" |
1381 | - data-ng-show="canDeleteCacheSet(cacheset)" |
1382 | - data-ng-click="cacheSetDelete()">Remove</button> |
1383 | </div> |
1384 | - <div class="row ng-hide" data-ng-show="cachesetsMode === 'delete'"> |
1385 | + </td> |
1386 | + <td class="p-table-expanding__panel col-12" data-ng-if="cacheset.$selected && cachesetsMode === 'delete'"> |
1387 | + <div class="row" data-ng-if="windowWidth <= 768"> |
1388 | <div class="col-8"> |
1389 | - <p><span class="p-icon--warning">Warning:</span> Are you sure you want to delete this cache set?</p> |
1390 | + <h2 data-ng-click="filesystemCancel()" class="p-heading--four"> |
1391 | + <span data-ng-if="cachesetsMode === 'delete'">Removing {$ cacheset.name $}</span> |
1392 | + </h2> |
1393 | + </div> |
1394 | + </div> |
1395 | + <div class="row" data-ng-class="{ 'is-active': cachesetsMode !== null && cachesetsMode !== 'multi' }"> |
1396 | + <div data-ng-show="cachesetsMode === 'single' && canDeleteCacheSet(cacheset)"> |
1397 | + <button class="p-button--base" |
1398 | + data-ng-show="canDeleteCacheSet(cacheset)" |
1399 | + data-ng-click="cacheSetDelete()">Remove</button> |
1400 | </div> |
1401 | - <div class="col-4"> |
1402 | - <div class="u-align--right"> |
1403 | - <button class="p-button--base" type="button" data-ng-click="cacheSetCancel()">Cancel</button> |
1404 | - <button class="p-button--negative u-no-margin--top" data-ng-click="cacheSetConfirmDelete(cacheset)">Remove cache set</button> |
1405 | + <div class="row ng-hide" data-ng-show="cachesetsMode === 'delete'"> |
1406 | + <div class="col-8"> |
1407 | + <p><span class="p-icon--warning">Warning:</span> Are you sure you want to delete this cache set?</p> |
1408 | + </div> |
1409 | + <div class="col-4"> |
1410 | + <div class="u-align--right"> |
1411 | + <button class="p-button--base" type="button" data-ng-click="cacheSetCancel()">Cancel</button> |
1412 | + <button class="p-button--negative u-no-margin--top" data-ng-click="cacheSetConfirmDelete(cacheset)">Remove cache set</button> |
1413 | + </div> |
1414 | </div> |
1415 | </div> |
1416 | </div> |
1417 | - </div> |
1418 | - </td> |
1419 | - </tr> |
1420 | - </tbody> |
1421 | - </table> |
1422 | + </td> |
1423 | + </tr> |
1424 | + </tbody> |
1425 | + </table> |
1426 | + </div> |
1427 | + </div> |
1428 | + <div> |
1429 | + <storage-disks-partitions></storage-disks-partitions> |
1430 | </div> |
1431 | - </div> |
1432 | - <div> |
1433 | - <storage-disks-partitions></storage-disks-partitions> |
1434 | </div> |
1435 | </div> |
1436 | </form> |
1437 | diff --git a/src/maasserver/static/partials/nodedetails/storage/datastores.html b/src/maasserver/static/partials/nodedetails/storage/datastores.html |
1438 | new file mode 100644 |
1439 | index 0000000..28984c8 |
1440 | --- /dev/null |
1441 | +++ b/src/maasserver/static/partials/nodedetails/storage/datastores.html |
1442 | @@ -0,0 +1,112 @@ |
1443 | +<div class="p-strip is-shallow"> |
1444 | + <h3 class="p-heading--four">Datastores</h3> |
1445 | + |
1446 | + <table class="p-table-expanding p-table--datastores col-12" role="grid"> |
1447 | + <thead> |
1448 | + <tr class="p-table__row"> |
1449 | + <th scope="col" aria-sort="none" class="p-table__cell">Name</th> |
1450 | + <th scope="col" aria-sort="none" class="p-table__cell">Filesystem</th> |
1451 | + <th scope="col" aria-sort="none" class="p-table__cell">Size</th> |
1452 | + <th scope="col" aria-sort="none" class="p-table__cell">Mount point</th> |
1453 | + <th scope="col" aria-sort="none" class="p-table__cell u-align--right">Actions</th> |
1454 | + <th class="u-hide"> |
1455 | + <!-- empty cell for validation --> |
1456 | + </th> |
1457 | + </tr> |
1458 | + </thead> |
1459 | + <tbody> |
1460 | + <tr data-ng-hide="node.disks.length" class="col-12"> |
1461 | + <td> |
1462 | + No datastores defined. |
1463 | + </td> |
1464 | + </tr> |
1465 | + <tr class="p-table__row" data-ng-repeat="filesystem in node.disks" data-ng-class="{ 'is-active': filesystem.$selected }" data-ng-if="filesystem.used_for == 'VMFS Datastore'"> |
1466 | + <td role="gridcell" class="p-table__cell" aria-label="Name" title="{$ filesystem.name $}">{$ filesystem.name $}</td> |
1467 | + <td role="gridcell" class="p-table__cell" aria-label="Filesystem" title="VMFS6">VMFS6</td> |
1468 | + <td role="gridcell" class="p-table__cell" aria-label="Size" title="{$ filesystem.size_human $}">{$ filesystem.size_human $}</td> |
1469 | + <td role="gridcell" class="p-table__cell" aria-label="Mount point" title="{$ filesystem.path $}">{$ filesystem.path $}</td> |
1470 | + <td role="gridcell" class="p-table__cell p-table--action-cell u-align--right"> |
1471 | + <div class="p-contextual-menu" toggle-ctrl data-ng-if="!isAllStorageDisabled()"> |
1472 | + <button class="p-button--base p-contextual-menu__toggle" aria-controls="#{$ item.name $}-menu" |
1473 | + data-ng-click="toggleMenu()" aria-haspopup="true"> |
1474 | + <i class="p-icon--contextual-menu">Actions</i> |
1475 | + </button> |
1476 | + <div class="p-contextual-menu__dropdown" role="menu" data-ng-show="isToggled" id="{$ item.name $}-menu"> |
1477 | + <button class="p-contextual-menu__link" aria-label="Remove" |
1478 | + data-ng-click="toggleMenu(); quickFilesystemDelete(filesystem)" |
1479 | + data-ng-show="!isAllStorageDisabled() && filesystemMode !== 'delete'">Remove…</button> |
1480 | + </div> |
1481 | + </div> |
1482 | + </td> |
1483 | + <td class="p-table-expanding__panel--bordered" |
1484 | + data-ng-if="filesystem.$selected && filesystemMode === 'delete'" |
1485 | + aria-hidden="!filesystem.$selected && filesystemMode !== 'delete'"> |
1486 | + <div class="row u-flex--no-wrap" data-ng-if="windowWidth <= 768"> |
1487 | + <h2 data-ng-click="filesystemCancel()" class="p-heading--four"> |
1488 | + <span data-ng-if="filesystemMode === 'delete'">Removing {$ filesystem.name $}</span> |
1489 | + </h2> |
1490 | + <button class="p-button--close" data-ng-click="filesystemCancel()"><span |
1491 | + class="p-icon--close">Cancel</span></button> |
1492 | + </div> |
1493 | + <div data-ng-if="filesystemMode !== null && filesystemMode !== 'multi'" |
1494 | + data-ng-class="{ 'is-active': filesystemMode !== null && filesystemMode !== 'multi' }"> |
1495 | + <div data-ng-if="filesystemMode === 'delete'" class="p-space-between"> |
1496 | + <p><span class="p-icon--warning">Warning:</span> Are you sure you want to remove this {$ |
1497 | + getRemoveTypeText(filesystem) $}?</p> |
1498 | + <div class="p-space-between__align-right"> |
1499 | + <button class="p-button--base u-width--auto" type="button" |
1500 | + data-ng-click="filesystemCancel(filesystem)">Cancel</button> |
1501 | + <button class="p-button--negative u-width--auto" |
1502 | + data-ng-click="filesystemConfirmDelete(filesystem)">Remove</button> |
1503 | + </div> |
1504 | + </div> |
1505 | + </div> |
1506 | + </td> |
1507 | + </tr> |
1508 | + |
1509 | + <tr class="is-active p-table__row" data-ng-if="dropdown" data-ng-switch="dropdown"> |
1510 | + <!-- Adding a new TMPFS or RAMFPS filesystem --> |
1511 | + <td class="p-table-expanding__panel" data-ng-controller="NodeAddSpecialFilesystemController" |
1512 | + data-ng-switch-when="special"> |
1513 | + <maas-obj-form obj="newFilesystem" manager="machineManager" manager-method="mountSpecialFilesystem" |
1514 | + inline="false" save-on-blur="false" after-save="cancel"> |
1515 | + <div class="row" data-ng-if="windowWidth <= 768"> |
1516 | + <div class="u-flex--no-wrap"> |
1517 | + <h2 data-ng-click="cancel()" class="u-align--left p-heading--four">Adding filesystem</h2> |
1518 | + <button class="p-button--close" data-ng-click="cancel()" type="button"> |
1519 | + <i class="p-icon--close">Cancel</i></button> |
1520 | + </div> |
1521 | + </div> |
1522 | + <div class="row p-form p-form--stacked"> |
1523 | + <div class="col-6"> |
1524 | + <div class="p-form__group"> |
1525 | + <label class="p-form__label mobile-col-2">Description</label> |
1526 | + <div class="p-form__control p-form__control--placeholder mobile-col-2"> |
1527 | + <span data-ng-bind="description"></span> |
1528 | + </div> |
1529 | + </div> |
1530 | + <maas-obj-field type="options" key="fstype" label="Filesystem" subtle="false" |
1531 | + options="type for type in specialFilesystemTypes"></maas-obj-field> |
1532 | + </div> |
1533 | + <div class="col-6"> |
1534 | + <maas-obj-field type="text" key="mount_point" label="Mount point" subtle="false" |
1535 | + placeholder="Absolute path"></maas-obj-field> |
1536 | + <maas-obj-field type="text" key="mount_options" label="Mount options" subtle="false" |
1537 | + placeholder="Separated by commas, no spaces"></maas-obj-field> |
1538 | + </div> |
1539 | + </div> |
1540 | + <hr> |
1541 | + <div class="p-space-between"> |
1542 | + <maas-obj-errors></maas-obj-errors> |
1543 | + <div class="p-space-between__align-right"> |
1544 | + <button class="p-button--base u-width--auto" type="button" data-ng-click="cancel()">Cancel</button> |
1545 | + <button class="p-button--neutral u-width--auto ng-binding" data-ng-disabled="!canMount()" |
1546 | + maas-obj-save>Mount</button> |
1547 | + </div> |
1548 | + </div> |
1549 | + </maas-obj-form> |
1550 | + </td> |
1551 | + </tr> |
1552 | + </tbody> |
1553 | + </table> |
1554 | +</div> |
1555 | \ No newline at end of file |
1556 | diff --git a/src/maasserver/static/partials/nodedetails/storage/disks-partitions.html b/src/maasserver/static/partials/nodedetails/storage/disks-partitions.html |
1557 | index 8f85fc7..041f4ae 100644 |
1558 | --- a/src/maasserver/static/partials/nodedetails/storage/disks-partitions.html |
1559 | +++ b/src/maasserver/static/partials/nodedetails/storage/disks-partitions.html |
1560 | @@ -4,27 +4,25 @@ |
1561 | <table class="p-table-expanding p-table--disks-partitions col-12"> |
1562 | <thead> |
1563 | <tr class="p-table__row"> |
1564 | - <th class="col-3"> |
1565 | - <div class="u-float--left"> |
1566 | - <input type="checkbox" class="checkbox u-float--left" id="available-check-all" data-ng-hide="isAvailableDisabled()" data-ng-checked="availableAllSelected" |
1567 | - data-ng-click="toggleAvailableAllSelect()" data-ng-disabled="isAvailableDisabled()" /> |
1568 | - <label for="available-check-all"></label> |
1569 | - </div> |
1570 | - <a data-ng-click="tableInfo.column = 'name'" data-ng-class="{'p-link--soft': tableInfo.column === 'name'}">Name</a> |
1571 | - <span class="divide"> | </span> |
1572 | - <a data-ng-click="tableInfo.column = 'model'" data-ng-class="{'p-link--soft': tableInfo.column === 'model'}">Model</a> |
1573 | - <span class="divide"> | </span> |
1574 | - <a data-ng-click="tableInfo.column = 'serial'" data-ng-class="{'p-link--soft': tableInfo.column === 'serial'}">Serial</a> |
1575 | - <span class="divide"> | </span> |
1576 | - <a data-ng-click="tableInfo.column = 'firmware_version'" data-ng-class="{'p-link--soft': tableInfo.column === 'firmware_version'}">Firmware</a> |
1577 | + <th class="p-double-row p-table__cell"> |
1578 | + <div class="p-double-row__checkbox"> </div> |
1579 | + <div class="p-double-row__rows-container--checkbox"> |
1580 | + <div>Name</div> |
1581 | + <div>Serial</div> |
1582 | + </div> |
1583 | + </th> |
1584 | + <th class="p-double-row p-table__cell"> |
1585 | + <div>Model</div> |
1586 | + <div>Firmware</div> |
1587 | </th> |
1588 | - <th class="col-1"><div class="u-align--center">Boot</div></th> |
1589 | - <th class="col-1">Size</th> |
1590 | - <th class="col-1">Type</th> |
1591 | - <th class="col-2">Filesystem</th> |
1592 | - <th class="col-2">Tags</th> |
1593 | - <th class="col-1">Health</th> |
1594 | - <th class="col-1"><div class="u-align--right">Actions</div></th> |
1595 | + <th class="p-table__cell"><div class="u-align--center">Boot</div></th> |
1596 | + <th class="p-table__cell">Size</th> |
1597 | + <th class="col-3 p-double-row p-table__cell"> |
1598 | + <div>Type</div> |
1599 | + <div>Tags</div> |
1600 | + </th> |
1601 | + <th class="p-table__cell">Health</th> |
1602 | + <th class="p-table__cell"><div class="u-align--right">Actions</div></th> |
1603 | </tr> |
1604 | </thead> |
1605 | <tbody> |
1606 | @@ -34,32 +32,34 @@ |
1607 | </td> |
1608 | </tr> |
1609 | <tr class="p-table__row" data-ng-repeat="item in available | removeAvailableByNew:availableNew" |
1610 | - data-ng-class="{ 'is-active': item.$selected }"> |
1611 | - <td class="col-3 p-form-validation" aria-label="Name" |
1612 | + data-ng-class="{ 'is-active': item.$selected }" data-ng-if="item.parent_type !== 'vmfs6'"> |
1613 | + <td class="p-form-validation p-double-row p-table__cell" aria-label="Name" |
1614 | data-ng-class="{ 'is-error': isNameInvalid(item) }"> |
1615 | - <div class="u-float--left"> |
1616 | - <input type="checkbox" class="checkbox u-float--left" id="{$ item.name $}" |
1617 | - data-ng-hide="isAvailableDisabled()" |
1618 | - data-ng-checked="item.$selected" |
1619 | - data-ng-click="toggleAvailableSelect(item)" |
1620 | - data-ng-disabled="isAvailableDisabled()" /> |
1621 | - <label for="{$ item.name $}"> |
1622 | - <span data-ng-show="tableInfo.column === 'name'" |
1623 | - data-ng-hide="availableMode === 'edit' && item.$selected" |
1624 | - title="{$ item.name $}">{$ item.name $}</span> |
1625 | - </label> |
1626 | - </div> |
1627 | - <span data-ng-show="tableInfo.column === 'model'" title="{$ item.model $}">{$ item.model $}</span> |
1628 | - <span data-ng-show="tableInfo.column === 'serial'" title="{$ item.serial $}">{$ item.serial $}</span> |
1629 | - <span data-ng-show="tableInfo.column === 'firmware_version'" title="{$ item.firmware_version $}">{$ item.firmware_version $}</span> |
1630 | - <input type="text" class="p-form-validation__input" |
1631 | - data-ng-model="item.name" |
1632 | - data-ng-show="availableMode === 'edit' && item.$selected" |
1633 | - data-ng-disabled="item.type === 'partition' || isAllStorageDisabled() || !canEdit()" |
1634 | - data-ng-change="nameHasChanged(item)"> |
1635 | + <div class="p-double-row__checkbox"> |
1636 | + <input type="checkbox" class="checkbox u-float--left" id="{$ item.name $}" data-ng-hide="isAvailableDisabled()" data-ng-checked="item.$selected" data-ng-click="toggleAvailableSelect(item)" data-ng-disabled="isAvailableDisabled()" /> |
1637 | + <label for="{$ item.name $}"></label> |
1638 | + </div> |
1639 | + <div class="p-double-row__rows-container--checkbox"> |
1640 | + <div class="p-double-row__main-row"> |
1641 | + {$ item.name $} |
1642 | + </div> |
1643 | + <div class="p-double-row__muted-row"> |
1644 | + {$ item.serial $} |
1645 | + </div> |
1646 | + </div> |
1647 | </td> |
1648 | - <td class="col-1" aria-label="Boot"> |
1649 | - <div class="u-align--center"> |
1650 | + <td class="p-double-row p-table__cell"> |
1651 | + <div class="p-double-row__container"> |
1652 | + <div class="p-double-row__main-row"> |
1653 | + {$ item.model $} |
1654 | + </div> |
1655 | + <div class="p-double-row__muted-row"> |
1656 | + {$ item.firmware_version $} |
1657 | + </div> |
1658 | + </div> |
1659 | + </td> |
1660 | + <td class="p-table__cell" aria-label="Boot"> |
1661 | + <div class="u-align--center" data-ng-if="storageLayout.id !== 'vmfs6'"> |
1662 | <input type="radio" name="boot-disk" id="{$ item.name $}-boot" class="u-no-margin--right" |
1663 | data-ng-click="setAsBootDisk(item)" |
1664 | data-ng-checked="item.is_boot" |
1665 | @@ -69,30 +69,31 @@ |
1666 | <label for="{$ item.name $}-boot" data-ng-hide="availableMode === 'edit' && item.$selected"></label> |
1667 | </div> |
1668 | </td> |
1669 | - <td class="col-1" aria-label="Size"> |
1670 | + <td class="p-table__cell" aria-label="Size"> |
1671 | <span data-ng-hide="availableMode === 'edit' && item.$selected" title="{$ item.size_human $}"> |
1672 | - {$ item.size_human $} <span class="table__label ng-hide" data-ng-show="showFreeSpace(item)">Free: {$ item.available_size_human $}</span> |
1673 | + {$ item.size_human $} <span class="table__labeldatastore-name ng-hide" data-ng-show="showFreeSpace(item)">Free: {$ item.available_size_human $}</span> |
1674 | </span> |
1675 | </td> |
1676 | - <td class="col-1" aria-label="type"> |
1677 | - <span data-ng-hide="availableMode === 'edit' && item.$selected" title="{$ getDeviceType(item) $}">{$ getDeviceType(item) $}</span> |
1678 | - </td> |
1679 | - <td class="col-2" aria-label="Filesystem"> |
1680 | - <span data-ng-hide="availableMode === 'edit' && item.$selected" title="{$ item.fstype $}">{$ item.fstype $}</span> |
1681 | - </td> |
1682 | - <td class="col-2" aria-label="Tags"> |
1683 | - <span class="table__tag" data-ng-repeat="tag in item.tags" data-ng-hide="item.$options.editingTags"> |
1684 | - <a href="#/machines/?query=storage_tags:({$ tag.text $})" title="{$ tag.text $}">{$ tag.text $}</a> |
1685 | - </span> |
1686 | + <td class="p-double-row p-table__cell" aria-label="type"> |
1687 | + <div class="p-double-rows__container"> |
1688 | + <div class="p-double-row__main-row"> |
1689 | + {$ getDeviceType(item) $} |
1690 | + </div> |
1691 | + <div class="p-double-row__muted-row"> |
1692 | + <span class="table__tag" data-ng-repeat="tag in item.tags" data-ng-hide="item.$options.editingTags"> |
1693 | + <a href="#/machines/?query=storage_tags:({$ tag.text $})" title="{$ tag.text $}">{$ tag.text $}</a> |
1694 | + </span> |
1695 | + </div> |
1696 | + </div> |
1697 | </td> |
1698 | - <td class="col-1" aria-label="Health"> |
1699 | + <td class="p-table__cell" aria-label="Health"> |
1700 | <span data-maas-script-status="script-status" data-script-status="item.test_status" data-ng-if="item.type === 'physical'"></span> |
1701 | <span data-ng-if="item.test_status === 0 || item.test_status === 1 || item.test_status === 2 || item.test_status === 5 || item.test_status === 7" title="Ok">Ok</span> |
1702 | <span data-ng-if="item.test_status === 3 || item.test_status === 4 || item.test_status === 8" title="Error">Error</span> |
1703 | <span data-ng-if="item.test_status === 6" title="Degraded">Degraded</span> |
1704 | <span data-ng-if="item.test_status === -1" title="Unknown">Unknown</span> |
1705 | </td> |
1706 | - <td class="col-1 p-table--action-cell"> |
1707 | + <td class="p-table--action-cell p-table__cell"> |
1708 | <div class="u-align--right"> |
1709 | <div class="p-contextual-menu" toggle-ctrl |
1710 | data-ng-if="canAddLogicalVolume(item) || canAddPartition(item) || canEdit(item) || canDelete(item)"> |
1711 | @@ -463,7 +464,7 @@ |
1712 | <label class="checkbox-label" for="bcache-create"></label> |
1713 | </div> |
1714 | <div class="u-float--left p-form-validation" |
1715 | - data-ng-class="{ 'is-error': isNewDiskNameInvalid() }"> |
1716 | + data-ng-class="{ 'is-error': isNewDiskNameInvalid(availableNew.name) }"> |
1717 | <input type="text" class="p-form-validation__input" |
1718 | data-ng-model="availableNew.name"> |
1719 | </div> |
1720 | @@ -481,7 +482,7 @@ |
1721 | <div class="row"> |
1722 | <div class="col-6"> |
1723 | <div class="p-form__group u-hide--large p-form-validation" |
1724 | - data-ng-class="{ 'is-error': isNewDiskNameInvalid() }"> |
1725 | + data-ng-class="{ 'is-error': isNewDiskNameInvalid(availableNew.name) }"> |
1726 | <label for="bcache-name" class="p-form__label">Name</label> |
1727 | <div class="p-form__control"> |
1728 | <input type="text" id="bcache-name" data-ng-model="availableNew.name" |
1729 | @@ -600,7 +601,7 @@ |
1730 | <label for="raid-create"></label> |
1731 | </div> |
1732 | <div class="u-float--left p-form-validation" |
1733 | - data-ng-class="{ 'is-error': isNewDiskNameInvalid() }"> |
1734 | + data-ng-class="{ 'is-error': isNewDiskNameInvalid(availableNew.name) }"> |
1735 | <input type="text" class="p-form-validation__input" |
1736 | data-ng-model="availableNew.name"> |
1737 | </div> |
1738 | @@ -619,7 +620,7 @@ |
1739 | <div class="col-6"> |
1740 | <div class="p-form__group u-hide--medium u-hide--large"> |
1741 | <label for="new-raid-name" class="p-form__label">Name</label> |
1742 | - <div class="p-form__control" data-ng-class="{ 'is-error': isNewDiskNameInvalid() }"> |
1743 | + <div class="p-form__control" data-ng-class="{ 'is-error': isNewDiskNameInvalid(availableNew.name) }"> |
1744 | <input type="text" id="new-raid-name" data-ng-model="availableNew.name"> |
1745 | </div> |
1746 | </div> |
1747 | @@ -734,7 +735,7 @@ |
1748 | <label for="vg-create"></label> |
1749 | </div> |
1750 | <div class="u-float--left p-form-validation" |
1751 | - data-ng-class="{ 'is-error': isNewDiskNameInvalid() }"> |
1752 | + data-ng-class="{ 'is-error': isNewDiskNameInvalid(availableNew.name) }"> |
1753 | <input type="text" class="p-form-validation__input" |
1754 | data-ng-model="availableNew.name"> |
1755 | </div> |
1756 | @@ -753,7 +754,7 @@ |
1757 | <div class="col-6"> |
1758 | <div class="p-form__group p-form-validation"> |
1759 | <label for="new-vg-name" class="p-form__label">Name</label> |
1760 | - <div class="p-form__control" data-ng-class="{ 'is-error': isNewDiskNameInvalid() }"> |
1761 | + <div class="p-form__control" data-ng-class="{ 'is-error': isNewDiskNameInvalid(availableNew.name) }"> |
1762 | <input type="text" class="p-form-validation__input" id="new-vg-name" data-ng-model="availableNew.name"> |
1763 | </div> |
1764 | </div> |
1765 | @@ -806,57 +807,212 @@ |
1766 | </tr> |
1767 | </tbody> |
1768 | </table> |
1769 | - <button class="p-button--neutral p-tooltip p-tooltip--top-center" |
1770 | - data-ng-disabled="!canCreateRAID()" |
1771 | - data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1772 | - data-ng-click="createRAID()"> |
1773 | - Create RAID |
1774 | - <span class="p-tooltip__message" role="tooltip">Select two or more physical devices to create a RAID</span> |
1775 | - </button> |
1776 | - <button class="p-button--neutral p-tooltip p-tooltip--top-center" |
1777 | - data-ng-disabled="!canCreateVolumeGroup()" |
1778 | - data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1779 | - data-ng-click="createVolumeGroup()"> |
1780 | - Create volume group |
1781 | - <span class="p-tooltip__message" role="tooltip">Select one or more devices to create a volume group</span> |
1782 | - </button> |
1783 | - <button class="p-button--neutral p-tooltip p-tooltip--top-center" |
1784 | - data-ng-disabled="!canCreateCacheSet()" |
1785 | - data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1786 | - data-ng-click="createCacheSet()"> |
1787 | - Create cache Set |
1788 | - <span class="p-tooltip__message" role="tooltip">Select one device to create a cache set</span> |
1789 | - </button> |
1790 | - <button class="p-button--neutral p-tooltip--top-center" |
1791 | - data-ng-class="{ 'p-tooltip': !canCreateBcache() }" |
1792 | - data-ng-disabled="!canCreateBcache()" |
1793 | - data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1794 | - data-ng-click="createBcache()"> |
1795 | - Create bcache |
1796 | - <span class="p-tooltip__message" role="tooltip">{$ getCannotCreateBcacheMsg() $}</span> |
1797 | - </button> |
1798 | + |
1799 | + <div class="p-card" data-ng-if="createNewDatastore"> |
1800 | + <div class="row"> |
1801 | + <div class="row p-form--stacked"> |
1802 | + <div class="col-6"> |
1803 | + <div class="p-form__group p-form-validation" |
1804 | + data-ng-class="{ 'is-error': isNewDiskNameInvalid(datastores.new.name) }"> |
1805 | + <label for="datastore-name" class="p-form__label u-sv3">Name</label> |
1806 | + <div class="p-form__control"> |
1807 | + <input type="text" |
1808 | + name="datastore-name" |
1809 | + id="datastore-name" |
1810 | + class="p-form-validation__input" |
1811 | + data-ng-model="datastores.new.name" |
1812 | + data-ng-keydown="$event.keyCode == 13 && !isNewDiskNameInvalid(datastores.new.name) && $event.preventDefault()" |
1813 | + data-ng-keyup="$event.keyCode == 13 && !isNewDiskNameInvalid(datastores.new.name) && createDatastore()"> |
1814 | + |
1815 | + <p class="p-form-validation__message" |
1816 | + data-ng-if="isNewDiskNameInvalid(datastores.new.name) && datastores.new.name != ''"> |
1817 | + <strong>Error:</strong> Disk name is already in use |
1818 | + </p> |
1819 | + <p class="p-form-validation__message" |
1820 | + data-ng-if="isNewDiskNameInvalid(datastores.new.name) && datastores.new.name == ''"> |
1821 | + <strong>Error:</strong> Please enter a name for your new datastore |
1822 | + </p> |
1823 | + </div> |
1824 | + </div> |
1825 | + </div> |
1826 | + <div class="col-6"> |
1827 | + <div class="p-form__group"> |
1828 | + <label for="datastore-filesystem" class="p-form__label u-sv3">Filesystem</label> |
1829 | + <div class="p-form__control"> |
1830 | + <p>{$ datastores.new.filesystem $}</p> |
1831 | + </div> |
1832 | + </div> |
1833 | + <div class="p-form__group" data-ng-if="datastores.new.path"> |
1834 | + <label for="datastore-mountpoint" class="p-form__label">Mount point</label> |
1835 | + <div class="p-form__control"> |
1836 | + <p>{$ datastores.new.path $}</p> |
1837 | + </div> |
1838 | + </div> |
1839 | + </div> |
1840 | + </div> |
1841 | + <div class="u-sv2"> |
1842 | + <hr /> |
1843 | + </div> |
1844 | + <div class="u-align--right"> |
1845 | + <button class="p-button--neutral u-no-margin--bottom" data-ng-click="closeNewDatastorePanel()" data-ng-disabled="creatingDatastore">Cancel</button> |
1846 | + <button class="p-button--positive u-no-margin--bottom" data-ng-click="createDatastore()" data-ng-if="!creatingDatastore" data-ng-disabled="isNewDiskNameInvalid(datastores.new.name)"> |
1847 | + Create datastore |
1848 | + </button> |
1849 | + <button class="p-button--positive u-no-margin--bottom" data-ng-if="creatingDatastore"> |
1850 | + <i class="p-icon--spinner is-light u-animation--spin"></i> |
1851 | + |
1852 | + Creating datastore |
1853 | + </button> |
1854 | + </div> |
1855 | + </div> |
1856 | + </div> |
1857 | + <div class="p-card" data-ng-if="addToExistingDatastore"> |
1858 | + <div class="row p-form--stacked"> |
1859 | + <div class="col-6"> |
1860 | + <div class="p-form__group"> |
1861 | + <label for="datastore-name" class="p-form__label u-sv3">Datastore</label> |
1862 | + <div class="p-form__control"> |
1863 | + <select name="datastore-name" id="datastore-name" |
1864 | + data-ng-model="datastores.old" |
1865 | + data-ng-options="disk as disk.name for disk in node.disks | datastoresOnly"> |
1866 | + </select> |
1867 | + </div> |
1868 | + </div> |
1869 | + </div> |
1870 | + <div class="col-6"> |
1871 | + <div class="p-form__group"> |
1872 | + <label for="datastore-mountpoint" class="p-form__label">Mount point</label> |
1873 | + <div class="p-form__control"> |
1874 | + <p>{$ datastores.old.path $}</p> |
1875 | + </div> |
1876 | + </div> |
1877 | + </div> |
1878 | + </div> |
1879 | + <div class="u-sv2"> |
1880 | + <hr /> |
1881 | + </div> |
1882 | + <div class="u-align--right"> |
1883 | + <button class="p-button--neutral u-no-margin--bottom" data-ng-click="closeAddToExistingDatastorePanel()" data-ng-disabled="updatingDatastore">Cancel</button> |
1884 | + <span class="p-tooltip p-tooltip--top-right"> |
1885 | + <button class="p-button--positive u-no-margin--bottom" |
1886 | + data-ng-click="addToDatastore()" |
1887 | + data-ng-if="!updatingDatastore" |
1888 | + data-ng-disabled="!addToDatastoreValid"> |
1889 | + Add to datastore |
1890 | + </button> |
1891 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="!addToDatastoreValid">Disks with partitions cannot be added to a datastore</span> |
1892 | + </span> |
1893 | + <button class="p-button--positive u-no-margin--bottom" data-ng-if="updatingDatastore"> |
1894 | + <i class="p-icon--spinner is-light u-animation--spin"></i> |
1895 | + |
1896 | + Adding to datastore |
1897 | + </button> |
1898 | + </div> |
1899 | + </div> |
1900 | + |
1901 | + <div> |
1902 | + <span class="p-tooltip p-tooltip--top-left"> |
1903 | + <button class="p-button--neutral" |
1904 | + data-ng-disabled="!canCreateRAID() || storageLayout.id === 'vmfs6'" |
1905 | + data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1906 | + data-ng-click="createRAID()"> |
1907 | + Create RAID |
1908 | + </button> |
1909 | + <span data-ng-if="!canCreateRAID()"> |
1910 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id !== 'vmfs6'">Select two or more physical devices to create a RAID</span> |
1911 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id === 'vmfs6'">Not supported in the VMFS6 layout</span> |
1912 | + </span> |
1913 | + </span> |
1914 | + <span class="p-tooltip p-tooltip--top-center"> |
1915 | + <button class="p-button--neutral" |
1916 | + data-ng-disabled="!canCreateVolumeGroup() || storageLayout.id === 'vmfs6'" |
1917 | + data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1918 | + data-ng-click="createVolumeGroup()"> |
1919 | + Create volume group |
1920 | + </button> |
1921 | + <span data-ng-if="!canCreateVolumeGroup()"> |
1922 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id !== 'vmfs6'">Select one or more devices to create a volume group</span> |
1923 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id === 'vmfs6'">Not supported in the VMFS6 layout</span> |
1924 | + </span> |
1925 | + </span> |
1926 | + <span class="p-tooltip p-tooltip--top-center"> |
1927 | + <button class="p-button--neutral" |
1928 | + data-ng-disabled="!canCreateCacheSet() || storageLayout.id === 'vmfs6'" |
1929 | + data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1930 | + data-ng-click="createCacheSet()"> |
1931 | + Create cache Set |
1932 | + </button> |
1933 | + <span data-ng-if="!canCreateCacheSet()"> |
1934 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id !== 'vmfs6'">Select one device to create a cache set</span> |
1935 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id === 'vmfs6'">Not supported in the VMFS6 layout</span> |
1936 | + </span> |
1937 | + </span> |
1938 | + <span class="p-tooltip p-tooltip--top-center"> |
1939 | + <button class="p-button--neutral" |
1940 | + data-ng-class="{ 'p-tooltip': !canCreateBcache() }" |
1941 | + data-ng-disabled="!canCreateBcache() || storageLayout.id === 'vmfs6'" |
1942 | + data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1943 | + data-ng-click="createBcache()"> |
1944 | + Create bcache |
1945 | + </button> |
1946 | + <span data-ng-if="!canCreateBcache()"> |
1947 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id !== 'vmfs6'">{$ getCannotCreateBcacheMsg() $}</span> |
1948 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id === 'vmfs6'">Not supported in the VMFS6 layout</span> |
1949 | + </span> |
1950 | + </span> |
1951 | + <span class="p-tooltip p-tooltip--top-center"> |
1952 | + <button class="p-button--neutral" |
1953 | + data-ng-click="openNewDatastorePanel()" |
1954 | + data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1955 | + data-ng-disabled="!canPerformActionOnDatastoreSet()"> |
1956 | + Create new datastore |
1957 | + </button> |
1958 | + <span data-ng-if="!canPerformActionOnDatastoreSet()"> |
1959 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id === 'vmfs6'">Select one or more devices to create a datastore</span> |
1960 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id !== 'vmfs6'">Only available in the VMFS6 layout</span> |
1961 | + </span> |
1962 | + </span> |
1963 | + <span class="p-tooltip p-tooltip--top-center" data-ng-if="storageLayout.id === 'vmfs6'"> |
1964 | + <button class="p-button--neutral" |
1965 | + data-ng-click="openAddToExistingDatastorePanel()" |
1966 | + data-ng-hide="isAllStorageDisabled() || !canEdit()" |
1967 | + data-ng-disabled="!canPerformActionOnDatastoreSet()"> |
1968 | + Add to existing datastore |
1969 | + </button> |
1970 | + <span data-ng-if="!canPerformActionOnDatastoreSet()"> |
1971 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id === 'vmfs6'">Select one or more devices to add an existing datastore</span> |
1972 | + <span class="p-tooltip__message" role="tooltip" data-ng-if="storageLayout.id !== 'vmfs6'">Only available in the VMFS6 layout</span> |
1973 | + </span> |
1974 | + </span> |
1975 | + </div> |
1976 | </div> |
1977 | </div> |
1978 | + |
1979 | <div class="p-strip is-shallow"> |
1980 | <div class="row"> |
1981 | - <hr> |
1982 | <h3 class="p-heading--four">Used disks and partitions</h3> |
1983 | - <table class="p-table-expanding"> |
1984 | + <table class="p-table-expanding p-table--used-disks"> |
1985 | <thead> |
1986 | - <tr> |
1987 | - <th class="col-3"> |
1988 | - <a data-ng-click="tableInfo.column = 'name'" data-ng-class="{'p-link--soft': tableInfo.column === 'name'}">Name</a> |
1989 | - <span class="divide"> | </span> |
1990 | - <a data-ng-click="tableInfo.column = 'model'" data-ng-class="{'p-link--soft': tableInfo.column === 'model'}">Model</a> |
1991 | - <span class="divide"> | </span> |
1992 | - <a data-ng-click="tableInfo.column = 'serial'" data-ng-class="{'p-link--soft': tableInfo.column === 'serial'}">Serial</a> |
1993 | - <span class="divide"> | </span> |
1994 | - <a data-ng-click="tableInfo.column = 'firmware_version'" data-ng-class="{'p-link--soft': tableInfo.column === 'firmware_version'}">Firmware</a> |
1995 | + <tr class="p-table__row"> |
1996 | + <th class="p-double-row p-table__cell"> |
1997 | + <div>Name</div> |
1998 | + <div>Serial</div> |
1999 | + </th> |
2000 | + <th class="p-double-row p-table__cell"> |
2001 | + <div>Model</div> |
2002 | + <div>Firmware</div> |
2003 | + </th> |
2004 | + <th class="p-table__cell"><div class="u-align--center">Boot</div></th> |
2005 | + <th class="p-table__cell">Size</th> |
2006 | + <th class="p-double-row p-table__cell"> |
2007 | + <div>Type</div> |
2008 | + <div>Tags</div> |
2009 | + </th> |
2010 | + <th class="p-table__cell">Health</th> |
2011 | + <th class="p-double-row p-table__cell"> |
2012 | + <div>Used for</div> |
2013 | + <div>Mount point</div> |
2014 | </th> |
2015 | - <th class="col-1"><div class="u-align--center">Boot</div></th> |
2016 | - <th class="col-2">Device type</th> |
2017 | - <th class="col-3">Used for</th> |
2018 | - <th class="col-2">Health</th> |
2019 | </tr> |
2020 | </thead> |
2021 | <tbody> |
2022 | @@ -865,16 +1021,31 @@ |
2023 | No disk or partition has been fully utilized. |
2024 | </td> |
2025 | </tr> |
2026 | - <tr data-ng-repeat="item in used" class="table__row details__used"> |
2027 | - <td class="col-3" aria-label="Name"> |
2028 | - <span data-ng-show="tableInfo.column === 'name'" title="{$ item.name $}">{$ item.name $}</span> |
2029 | - <span data-ng-show="tableInfo.column === 'model'" title="{$ item.model $}">{$ item.model $}</span> |
2030 | - <span data-ng-show="tableInfo.column === 'serial'" title="{$ item.serial $}">{$ item.serial $}</span> |
2031 | - <span data-ng-show="tableInfo.column === 'firmware_version'" title="{$ item.firmware_version $}">{$ item.firmware_version $}</span> |
2032 | + <tr data-ng-repeat="item in used" class="table__row details__used p-table__row"> |
2033 | + <td class="p-double-row p-table__cell" aria-label="Name"> |
2034 | + <div class="p-double-rows__container"> |
2035 | + <div class="p-double-row__main-row"> |
2036 | + {$ item.name $} |
2037 | + </div> |
2038 | + <div class="p-double-row__muted-row"> |
2039 | + {$ item.serial $} |
2040 | + </div> |
2041 | + </div> |
2042 | + </td> |
2043 | + <td class="p-double-row p-table__cell"> |
2044 | + <div class="p-double-rows__container"> |
2045 | + <div class="p-double-row__main-row"> |
2046 | + {$ item.model $} |
2047 | + </div> |
2048 | + <div class="p-double-row__muted-row"> |
2049 | + {$ item.firmware_version $} |
2050 | + </div> |
2051 | + </div> |
2052 | </td> |
2053 | - <td class="col-1" aria-label="Boot disk"> |
2054 | - <div class="u-align--center"> |
2055 | + <td aria-label="Boot disk" class="p-table__cell"> |
2056 | + <div class="u-align--center" data-ng-hide="item.parent_type !== 'vmfs6' && !item.is_boot"> |
2057 | <input type="radio" id="{$ item.name $}-boot" name="boot-disk" |
2058 | + class="u-no-margin--right" |
2059 | data-ng-click="setAsBootDisk(item)" |
2060 | data-ng-checked="item.is_boot" |
2061 | data-ng-if="item.type === 'physical'" |
2062 | @@ -882,19 +1053,46 @@ |
2063 | <label for="{$ item.name $}-boot"></label> |
2064 | </div> |
2065 | </td> |
2066 | - <td class="col-2" aria-label="Device type" title="{$ getDeviceType(item) $}">{$ getDeviceType(item) $}</td> |
2067 | - <td class="col-3" aria-label="Used for" title="{$ item.used_for $}">{$ item.used_for $}</td> |
2068 | - <td class="col-2" aria-label="Health"> |
2069 | + <td class="p-table__cell">{$ item.size_human $}</td> |
2070 | + <td class="p-double-row p-table__cell"> |
2071 | + <div class="p-double-rows__container"> |
2072 | + <div class="p-double-row__main-row"> |
2073 | + {$ getDeviceType(item) $} |
2074 | + </div> |
2075 | + <div class="p-double-row__muted-row"> |
2076 | + <span class="table__tag" data-ng-repeat="tag in item.tags" data-ng-hide="item.$options.editingTags"> |
2077 | + <a href="#/machines/?query=storage_tags:({$ tag.text $})" title="{$ tag.text $}">{$ tag.text $}</a> |
2078 | + </span> |
2079 | + </div> |
2080 | + </div> |
2081 | + </td> |
2082 | + <td aria-label="Health" class="p-table__cell"> |
2083 | <span data-ng-if="item.type === 'physical'"> |
2084 | <span data-maas-script-status="script-status" data-script-status="item.test_status"></span> |
2085 | - <span data-ng-if="item.test_status === 0 || item.test_status === 1 || item.test_status === 2 || item.test_status === 5 || item.test_status === 7" title="Ok">Ok</span> |
2086 | - <span data-ng-if="item.test_status === 3 || item.test_status === 4 || item.test_status === 8" title="Error">Error</span> |
2087 | + <span |
2088 | + data-ng-if="item.test_status === 0 || item.test_status === 1 || item.test_status === 2 || item.test_status === 5 || item.test_status === 7" |
2089 | + title="Ok">Ok</span> |
2090 | + <span data-ng-if="item.test_status === 3 || item.test_status === 4 || item.test_status === 8" |
2091 | + title="Error">Error</span> |
2092 | <span data-ng-if="item.test_status === 6" title="Degraded">Degraded</span> |
2093 | <span data-ng-if="item.test_status === -1" title="Unknown">Unknown</span> |
2094 | </span> |
2095 | </td> |
2096 | + <td class="p-double-row p-table__cell" aria-label="Used for" title="{$ item.used_for $}"> |
2097 | + <div class="p-double-rows__container"> |
2098 | + <div class="p-double-row__main-row"> |
2099 | + {$ item.used_for $} |
2100 | + </div> |
2101 | + <div class="p-double-row__muted-row"> |
2102 | + {$ item.mount_point $} |
2103 | + </div> |
2104 | + </div> |
2105 | + </td> |
2106 | </tr> |
2107 | </tbody> |
2108 | </table> |
2109 | </div> |
2110 | </div> |
2111 | + |
2112 | +<p>Learn more about deploying <a href="https://docs.maas.io/en/installconfig-images" class="p-link--external" target="_blank">Windows</a></p> |
2113 | +<p>Change the default layout in <a href="/MAAS/settings/storage/">Settings › Storage</a></p> |
2114 | diff --git a/src/maasserver/static/partials/nodedetails/storage/filesystems.html b/src/maasserver/static/partials/nodedetails/storage/filesystems.html |
2115 | index 340739e..908e7aa 100644 |
2116 | --- a/src/maasserver/static/partials/nodedetails/storage/filesystems.html |
2117 | +++ b/src/maasserver/static/partials/nodedetails/storage/filesystems.html |
2118 | @@ -110,7 +110,7 @@ |
2119 | </tr> |
2120 | </tbody> |
2121 | </table> |
2122 | - <button class="p-button--neutral p-tooltip--top-center" data-ng-disabled="dropdown !== null" data-ng-class="{ 'p-tooltip': dropdown === null}" |
2123 | + <button class="p-button--neutral p-tooltip--top-left" data-ng-disabled="dropdown !== null" data-ng-class="{ 'p-tooltip': dropdown === null}" |
2124 | data-ng-if="!isAllStorageDisabled()" data-ng-click="addSpecialFilesystem()"> |
2125 | Add special filesystem |
2126 | <span class="p-tooltip__message" role="tooltip">Create a tmpfs or ramfs filesystem</span> |
2127 | diff --git a/src/maasserver/static/scss/_base_tables.scss b/src/maasserver/static/scss/_base_tables.scss |
2128 | index 710e416..bfdd68e 100644 |
2129 | --- a/src/maasserver/static/scss/_base_tables.scss |
2130 | +++ b/src/maasserver/static/scss/_base_tables.scss |
2131 | @@ -40,7 +40,6 @@ |
2132 | flex-basis: auto !important; |
2133 | flex-grow: 0; |
2134 | vertical-align: top; |
2135 | - padding-bottom: 0.05rem; |
2136 | |
2137 | &:first-of-type { |
2138 | padding-left: $sph-intra--condensed; |
2139 | diff --git a/src/maasserver/static/scss/_patterns_notification.scss b/src/maasserver/static/scss/_patterns_notification.scss |
2140 | index ab3dab2..65620d4 100644 |
2141 | --- a/src/maasserver/static/scss/_patterns_notification.scss |
2142 | +++ b/src/maasserver/static/scss/_patterns_notification.scss |
2143 | @@ -1,6 +1,7 @@ |
2144 | @mixin maas-p-notifications { |
2145 | @include maas-notification; |
2146 | @include maas-notification-group; |
2147 | + @include maas-notification-subtle; |
2148 | } |
2149 | |
2150 | @mixin maas-notification-group { |
2151 | @@ -23,3 +24,14 @@ |
2152 | } |
2153 | } |
2154 | } |
2155 | + |
2156 | +@mixin maas-notification-subtle { |
2157 | + [class*="p-notification"].is-subtle { |
2158 | + background: transparent; |
2159 | + box-shadow: none; |
2160 | + |
2161 | + &::before { |
2162 | + height: 0; |
2163 | + } |
2164 | + } |
2165 | +} |
2166 | diff --git a/src/maasserver/static/scss/_tables.scss b/src/maasserver/static/scss/_tables.scss |
2167 | index 47738fd..6194abb 100644 |
2168 | --- a/src/maasserver/static/scss/_tables.scss |
2169 | +++ b/src/maasserver/static/scss/_tables.scss |
2170 | @@ -326,9 +326,12 @@ |
2171 | } |
2172 | } |
2173 | |
2174 | - .p-table--disks-partitions { |
2175 | + .p-table--disks-partitions, |
2176 | + .p-table--used-disks { |
2177 | .p-table__row { |
2178 | .p-table__cell { |
2179 | + flex: 0 0 auto !important; |
2180 | + |
2181 | &:nth-child(1) { |
2182 | width: 15%; |
2183 | } |
2184 | @@ -346,22 +349,58 @@ |
2185 | } |
2186 | |
2187 | &:nth-child(5) { |
2188 | - width: 12%; |
2189 | + width: 22%; |
2190 | } |
2191 | |
2192 | &:nth-child(6) { |
2193 | + width: 22%; |
2194 | + } |
2195 | + |
2196 | + &:nth-child(7) { |
2197 | width: 10%; |
2198 | } |
2199 | + } |
2200 | + } |
2201 | + } |
2202 | + |
2203 | + .p-table--used-disks { |
2204 | + .p-table__row { |
2205 | + .p-table__cell { |
2206 | + &:nth-child(6) { |
2207 | + width: 7%; |
2208 | + } |
2209 | |
2210 | &:nth-child(7) { |
2211 | - width: 12%; |
2212 | + width: 25%; |
2213 | } |
2214 | + } |
2215 | + } |
2216 | + } |
2217 | |
2218 | - &:nth-child(8) { |
2219 | - width: 10%; |
2220 | + .p-table--datastores { |
2221 | + flex: 0 0 auto !important; |
2222 | + |
2223 | + .p-table__row { |
2224 | + .p-table__cell { |
2225 | + flex: 0 0 auto !important; |
2226 | + |
2227 | + &:nth-child(1) { |
2228 | + width: 15%; |
2229 | + } |
2230 | + |
2231 | + &:nth-child(2) { |
2232 | + width: 22%; |
2233 | } |
2234 | |
2235 | - &:nth-child(9) { |
2236 | + &:nth-child(3) { |
2237 | + width: 9%; |
2238 | + } |
2239 | + |
2240 | + &:nth-child(4) { |
2241 | + width: 44%; |
2242 | + } |
2243 | + |
2244 | + &:nth-child(5) { |
2245 | width: 10%; |
2246 | } |
2247 | } |
2248 | diff --git a/src/maasserver/static/scss/_utils.scss b/src/maasserver/static/scss/_utils.scss |
2249 | index f6528d2..1724cf8 100644 |
2250 | --- a/src/maasserver/static/scss/_utils.scss |
2251 | +++ b/src/maasserver/static/scss/_utils.scss |
2252 | @@ -67,3 +67,7 @@ $table-h-indent: $sph-intra--condensed; |
2253 | .u-mirror--y { |
2254 | transform: rotate(180deg); |
2255 | } |
2256 | + |
2257 | +.u-rotate { |
2258 | + transform: rotate(180deg); |
2259 | +} |
LGTM +1