Merge ~ya-bo-ng/maas:kvm-storage-pods-landing-view into maas:master

Proposed by Anthony Dillon
Status: Merged
Merge reported by: Blake Rouse
Merged at revision: 82e7e306cd6861e6c99e3d0839257244f4d56143
Proposed branch: ~ya-bo-ng/maas:kvm-storage-pods-landing-view
Merge into: maas:master
Diff against target: 794 lines (+509/-46)
14 files modified
src/maasserver/static/js/angular/3rdparty/ng-tags-input.js (+1/-1)
src/maasserver/static/js/angular/controllers/pod_details.js (+46/-11)
src/maasserver/static/js/angular/controllers/tests/test_pod_details.js (+4/-0)
src/maasserver/static/js/angular/filters/format_bytes.js (+75/-0)
src/maasserver/static/js/angular/filters/format_storage_type.js (+20/-0)
src/maasserver/static/js/angular/filters/tests/test_format_bytes.js (+39/-0)
src/maasserver/static/js/angular/filters/tests/test_storage_type.js (+29/-0)
src/maasserver/static/partials/pod-details.html (+85/-27)
src/maasserver/static/scss/_patterns_charts.scss (+40/-0)
src/maasserver/static/scss/_patterns_option-selector.scss (+130/-0)
src/maasserver/static/scss/_patterns_table-expanding.scss (+6/-7)
src/maasserver/static/scss/_tables.scss (+26/-0)
src/maasserver/static/scss/_utils.scss (+4/-0)
src/maasserver/static/scss/build.scss (+4/-0)
Reviewer Review Type Date Requested Status
Caleb Ellis (community) Needs Fixing
MAAS Lander Needs Fixing
MAAS Maintainers Pending
Review via email: mp+354690@code.launchpad.net

Commit message

KVM storage pod configuration UI

Description of the change

Added a storage drop down widget to the compose pod table.

To post a comment you must log in.
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3892/console
COMMIT: 671f56d044dcda5a288081eb8d1fdc8b23ed80c6

review: Needs Fixing
5c2ed41... by Anthony Dillon

Fix tests

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3893/console
COMMIT: ba0d5fbae52d1fab586e84b5ee85f5001ea101c8

review: Needs Fixing
4f9ca99... by Anthony Dillon

Fix lint issues

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3894/console
COMMIT: 1cdc58449c2eb58989a187085be7a530e2c164bc

review: Needs Fixing
d3d86f4... by Anthony Dillon

White space

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3896/console
COMMIT: a253d14a19de98c8d4e8929825894b628a2bda2a

review: Needs Fixing
f52cea9... by Anthony Dillon

Make the dropdown keyboard accessible

e905752... by Anthony Dillon

Make the storage location unique to the storage object

f212454... by Anthony Dillon

Set default storage pool

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3939/console
COMMIT: 5af783308fd8ba63cef2a4c4f248e3347c98dd68

review: Needs Fixing
Revision history for this message
Martin Storey (cassiocassio) wrote :

Following a call... notes:

8 GB mininimum check?
compose machine button grey and warning around capacity field?

default should be selected
“name (default)” shuold be be shown

tick align with name of pool?
spacing of green tick - not wide enough
tick should be black chosen tick not green success tick

love the “impossible to remove the boot partition” function… check with team nobody has a good reason to make unbootable machines, you never know

it’s big pulldown, but still a pulldown -
- single click to open - stays open
- click and drag - stays open, unike a system pulldown
- click to select an item, selects that pool, AND closes
- moving focus, e.g. tab to tags field, also closes…
- HOWEVER clicking on capacity

Capacity field is a number so should be input type="number”>

escape key to close dropdown
mouseover on cross rectangular - square would make more visual sense

focus to tags should close

consider: alighing used key to left and putting close X top right?

what happens when there are 10 pools to pick from, or 20?
max depth and clipping and autoscroll

clicking + to add another row needs to close any open dropdown?

handle multiple requests to the same pool in the same compose dialog with “other” colour and o+o in the key…
https://zpl.io/2j4v7GQ

a647124... by Anthony Dillon

Fix tests

Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3943/console
COMMIT: 0065a188774bed75a55f5d4eaacc55116b251bd6

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3944/console
COMMIT: 91bbb1a41015ae7193dcbee00d533004c3ea1925

review: Needs Fixing
Revision history for this message
MAAS Lander (maas-lander) wrote :

UNIT TESTS
-b kvm-storage-pods-landing-view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas

STATUS: FAILED
LOG: http://maas-ci-jenkins.internal:8080/job/maas/job/branch-tester/3945/console
COMMIT: 72ed76c2098d62e7aa2b324f2b3be739d3075e70

review: Needs Fixing
9d0503e... by Anthony Dillon

Set the default pod storage

8499adc... by Anthony Dillon

Lint the pod details

Revision history for this message
Caleb Ellis (caleb-ellis) wrote :

- I get an angular error: TypeError: Cannot read property 'storage_pools' of null at Scope.$scope.getDefaultStoragePool. If I go back a couple of commits it works.

- How many different locations or storage configurations can we expect? It's already pretty close to the bottom of my viewport, and with it attached to a sticky header it's possible for content to become unreachable. Maybe we can remove sticky when the header is expanded?

- Other comments inline

review: Needs Fixing
82e7e30... by Anthony Dillon

Fix the defaultPod function call order

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js b/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js
index 86eb45c..481d915 100644
--- a/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js
+++ b/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js
@@ -1119,7 +1119,7 @@ tagsInput.factory('tiUtil', ["$timeout", function($timeout) {
1119/* HTML templates */1119/* HTML templates */
1120tagsInput.run(["$templateCache", function($templateCache) {1120tagsInput.run(["$templateCache", function($templateCache) {
1121 $templateCache.put('ngTagsInput/tags-input.html',1121 $templateCache.put('ngTagsInput/tags-input.html',
1122 "<div class=\"host\" tabindex=\"-1\" data-ng-click=\"eventHandlers.host.click()\" ti-transclude-append=\"\"><div class=\"tags\" data-ng-class=\"{focused: hasFocus}\"><ul class=\"tag-list\"><li class=\"tag-item\" data-ng-repeat=\"tag in tagList.items track by track(tag)\" data-ng-class=\"{ selected: tag == tagList.selected }\"><ti-tag-item data=\"tag\"></ti-tag-item></li></ul><input class=\"input u-no-margin--top\" autocomplete=\"off\" data-ng-model=\"newTag.text\" data-ng-change=\"eventHandlers.input.change(newTag.text)\" data-ng-keydown=\"eventHandlers.input.keydown($event)\" data-ng-focus=\"eventHandlers.input.focus($event)\" data-ng-blur=\"eventHandlers.input.blur($event)\" data-ng-paste=\"eventHandlers.input.paste($event)\" data-ng-trim=\"false\" data-ng-class=\"{'invalid-tag': newTag.invalid}\" data-ng-disabled=\"disabled\" ti-bind-attrs=\"{type: options.type, placeholder: options.placeholder, tabindex: options.tabindex, spellcheck: options.spellcheck}\" ti-autosize=\"\"></div></div>"1122 "<div class=\"host\" tabindex=\"-1\" data-ng-click=\"eventHandlers.host.click()\" ti-transclude-append=\"\"><div class=\"tags\" data-ng-class=\"{focused: hasFocus}\"><ul class=\"tag-list\"><li class=\"tag-item\" data-ng-repeat=\"tag in tagList.items track by track(tag)\" data-ng-class=\"{ selected: tag == tagList.selected }\"><ti-tag-item data=\"tag\"></ti-tag-item></li></ul><input class=\"input u-no-margin--top u-no-margin--bottom\" autocomplete=\"off\" data-ng-model=\"newTag.text\" data-ng-change=\"eventHandlers.input.change(newTag.text)\" data-ng-keydown=\"eventHandlers.input.keydown($event)\" data-ng-focus=\"eventHandlers.input.focus($event)\" data-ng-blur=\"eventHandlers.input.blur($event)\" data-ng-paste=\"eventHandlers.input.paste($event)\" data-ng-trim=\"false\" data-ng-class=\"{'invalid-tag': newTag.invalid}\" data-ng-disabled=\"disabled\" ti-bind-attrs=\"{type: options.type, placeholder: options.placeholder, tabindex: options.tabindex, spellcheck: options.spellcheck}\" ti-autosize=\"\"></div></div>"
1123 );1123 );
11241124
1125 $templateCache.put('ngTagsInput/tag-item.html',1125 $templateCache.put('ngTagsInput/tag-item.html',
diff --git a/src/maasserver/static/js/angular/controllers/pod_details.js b/src/maasserver/static/js/angular/controllers/pod_details.js
index 8f015cc..192274a 100644
--- a/src/maasserver/static/js/angular/controllers/pod_details.js
+++ b/src/maasserver/static/js/angular/controllers/pod_details.js
@@ -53,6 +53,7 @@ angular.module('MAAS').controller('PodDetailsController', [
53 type: 'local',53 type: 'local',
54 size: 8,54 size: 8,
55 tags: [],55 tags: [],
56 pool: {},
56 boot: true57 boot: true
57 }]58 }]
58 }59 }
@@ -211,6 +212,21 @@ angular.module('MAAS').controller('PodDetailsController', [
211 });212 });
212 };213 };
213214
215 $scope.openOptions = function(storage) {
216 angular.forEach($scope.compose.obj.storage, function(disk) {
217 disk.showOptions = false;
218 });
219 storage.showOptions = true;
220 }
221
222 $scope.closeOptions = function(storage) {
223 storage.showOptions = false;
224 }
225
226 $scope.selectStoragePool = function(storagePool, storage) {
227 storage.pool = storagePool;
228 }
229
214 // Return the title of the pod type.230 // Return the title of the pod type.
215 $scope.getPodTypeTitle = function() {231 $scope.getPodTypeTitle = function() {
216 var i;232 var i;
@@ -274,6 +290,7 @@ angular.module('MAAS').controller('PodDetailsController', [
274 type: 'local',290 type: 'local',
275 size: 8,291 size: 8,
276 tags: [],292 tags: [],
293 pool: {},
277 boot: true294 boot: true
278 }]295 }]
279 };296 };
@@ -282,16 +299,18 @@ angular.module('MAAS').controller('PodDetailsController', [
282299
283 // Add another storage device.300 // Add another storage device.
284 $scope.composeAddStorage = function() {301 $scope.composeAddStorage = function() {
285 var storage = {302 var storage = {
286 type: 'local',303 type: 'local',
287 size: 8,304 size: 8,
288 tags: [],305 tags: [],
289 boot: false306 pool: $scope.getDefaultStoragePool(),
290 };307 boot: false
291 if($scope.pod.capabilities.indexOf('iscsi_storage') >= 0) {308 };
292 storage.type = 'iscsi';309
293 }310 if($scope.pod.capabilities.indexOf('iscsi_storage') >= 0) {
294 $scope.compose.obj.storage.push(storage);311 storage.type = 'iscsi';
312 }
313 $scope.compose.obj.storage.push(storage);
295 };314 };
296315
297 // Change which disk is the boot disk.316 // Change which disk is the boot disk.
@@ -302,6 +321,18 @@ angular.module('MAAS').controller('PodDetailsController', [
302 storage.boot = true;321 storage.boot = true;
303 };322 };
304323
324 // Get the default pod pool
325 $scope.getDefaultStoragePool = function () {
326 var defaultPool = {};
327 console.log($scope);
328 if($scope.pod.storage_pools && $scope.pod.default_storage_pool) {
329 defaultPool = $scope.pod.storage_pools.filter(
330 pool => pool.id == $scope.pod.default_storage_pool
331 )[0];
332 }
333 return defaultPool;
334 };
335
305 // Remove a disk from storage config.336 // Remove a disk from storage config.
306 $scope.composeRemoveDisk = function(storage) {337 $scope.composeRemoveDisk = function(storage) {
307 var idx = $scope.compose.obj.storage.indexOf(storage);338 var idx = $scope.compose.obj.storage.indexOf(storage);
@@ -357,8 +388,10 @@ angular.module('MAAS').controller('PodDetailsController', [
357 // the activeItem.388 // the activeItem.
358 var activePod = PodsManager.getActiveItem();389 var activePod = PodsManager.getActiveItem();
359 if(angular.isObject(activePod) &&390 if(angular.isObject(activePod) &&
360 activePod.id === parseInt($routeParams.id, 10)) {391 activePod.id === parseInt($routeParams.id, 10)) {
361 $scope.pod = activePod;392 $scope.pod = activePod;
393 $scope.compose.obj.storage[0].pool = (
394 $scope.getDefaultStoragePool());
362 $scope.loaded = true;395 $scope.loaded = true;
363 $scope.machinesSearch = 'pod-id:=' + $scope.pod.id;396 $scope.machinesSearch = 'pod-id:=' + $scope.pod.id;
364 $scope.startWatching();397 $scope.startWatching();
@@ -366,6 +399,8 @@ angular.module('MAAS').controller('PodDetailsController', [
366 PodsManager.setActiveItem(399 PodsManager.setActiveItem(
367 parseInt($routeParams.id, 10)).then(function(pod) {400 parseInt($routeParams.id, 10)).then(function(pod) {
368 $scope.pod = pod;401 $scope.pod = pod;
402 $scope.compose.obj.storage[0].pool = (
403 $scope.getDefaultStoragePool());
369 $scope.loaded = true;404 $scope.loaded = true;
370 $scope.machinesSearch = 'pod-id:=' + $scope.pod.id;405 $scope.machinesSearch = 'pod-id:=' + $scope.pod.id;
371 $scope.startWatching();406 $scope.startWatching();
diff --git a/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js b/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js
index f1f9322..998d263 100644
--- a/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js
+++ b/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js
@@ -157,6 +157,7 @@ describe("PodDetailsController", function() {
157 type: 'local',157 type: 'local',
158 size: 8,158 size: 8,
159 tags: [],159 tags: [],
160 pool: {},
160 boot: true161 boot: true
161 }]162 }]
162 }163 }
@@ -660,6 +661,7 @@ describe("PodDetailsController", function() {
660 type: 'local',661 type: 'local',
661 size: 8,662 size: 8,
662 tags: [],663 tags: [],
664 pool: {},
663 boot: true665 boot: true
664 }]666 }]
665 });667 });
@@ -678,6 +680,7 @@ describe("PodDetailsController", function() {
678 type: 'local',680 type: 'local',
679 size: 8,681 size: 8,
680 tags: [],682 tags: [],
683 pool: {},
681 boot: false684 boot: false
682 });685 });
683 });686 });
@@ -692,6 +695,7 @@ describe("PodDetailsController", function() {
692 type: 'iscsi',695 type: 'iscsi',
693 size: 8,696 size: 8,
694 tags: [],697 tags: [],
698 pool: {},
695 boot: false699 boot: false
696 });700 });
697 });701 });
diff --git a/src/maasserver/static/js/angular/filters/format_bytes.js b/src/maasserver/static/js/angular/filters/format_bytes.js
698new file mode 100644702new file mode 100644
index 0000000..1ea393d
--- /dev/null
+++ b/src/maasserver/static/js/angular/filters/format_bytes.js
@@ -0,0 +1,75 @@
1/* Copyright 2016-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Converts bytes into human readable string, e.g. 10 GB
5 */
6
7angular.module('MAAS').filter('formatBytes', function() {
8 return function(bytes) {
9 var bytesInKilobyte = 1000;
10 var kilobytesInMegabyte = 1000;
11 var megabytesInGigabyte = 1000;
12 var gigabytesInTerabyte = 1000;
13
14 var bytesInMegabyte = (
15 bytesInKilobyte * kilobytesInMegabyte
16 );
17 var bytesInGigabyte = (
18 bytesInKilobyte * kilobytesInMegabyte * megabytesInGigabyte
19 );
20 var bytesInTerabyte = (
21 bytesInKilobyte *
22 kilobytesInMegabyte *
23 megabytesInGigabyte * gigabytesInTerabyte
24 );
25
26 if (bytes >= bytesInTerabyte) {
27 return Math.round(
28 bytes /
29 bytesInKilobyte /
30 kilobytesInMegabyte /
31 megabytesInGigabyte /
32 gigabytesInTerabyte
33 ) + ' TB';
34 } else if (bytes >= bytesInGigabyte) {
35 return Math.round(
36 bytes /
37 bytesInKilobyte /
38 kilobytesInMegabyte /
39 megabytesInGigabyte
40 ) + ' GB';
41 } else if (bytes >= bytesInMegabyte) {
42 return Math.round(
43 bytes /
44 bytesInKilobyte /
45 kilobytesInMegabyte
46 ) + ' MB';
47 } else if (bytes >= bytesInKilobyte) {
48 return Math.round(bytes / bytesInKilobyte) + ' KB';
49 } else if (bytes > 0) {
50 return bytes + ' B';
51 } else {
52 return 0;
53 }
54 }
55});
56
57angular.module('MAAS').filter('convertGigabyteToBytes', function() {
58 return function(gigabytes) {
59 var bytesInKilobyte = 1000;
60 var kilobytesInMegabyte = 1000;
61 var megabytesInGigabyte = 1000;
62
63 if (gigabytes) {
64 return Math.round(
65 gigabytes *
66 bytesInKilobyte *
67 kilobytesInMegabyte *
68 megabytesInGigabyte
69 );
70 } else {
71 return 0;
72 }
73 }
74});
75
diff --git a/src/maasserver/static/js/angular/filters/format_storage_type.js b/src/maasserver/static/js/angular/filters/format_storage_type.js
0new file mode 10064476new file mode 100644
index 0000000..2d05113
--- /dev/null
+++ b/src/maasserver/static/js/angular/filters/format_storage_type.js
@@ -0,0 +1,20 @@
1/* Copyright 2016-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Converts storage type into human readable string, e.g. LVM
5 */
6
7angular.module('MAAS').filter('formatStorageType', function() {
8 return function(storageType) {
9 if (!storageType) {
10 return '';
11 }
12
13 switch(storageType) {
14 case 'lvm':
15 return 'LVM';
16 default:
17 return storageType;
18 }
19 }
20});
diff --git a/src/maasserver/static/js/angular/filters/tests/test_format_bytes.js b/src/maasserver/static/js/angular/filters/tests/test_format_bytes.js
0new file mode 10064421new file mode 100644
index 0000000..4086438
--- /dev/null
+++ b/src/maasserver/static/js/angular/filters/tests/test_format_bytes.js
@@ -0,0 +1,39 @@
1/* Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Unit tests for formatBytes.
5 */
6
7describe("formatBytes", function() {
8
9 // Load the MAAS module.
10 beforeEach(module("MAAS"));
11
12 // Load the formatBytes.
13 var formatBytes;
14 beforeEach(inject(function($filter) {
15 formatBytes = $filter("formatBytes");
16 }));
17
18 it("returns zero if undefined bytes", function() {
19 expect(formatBytes()).toEqual(0);
20 });
21
22 it("returns value in bytes if less than a kilobyte", function() {
23 expect(formatBytes(456)).toEqual('456 B');
24 });
25
26 it("returns value in kilobytes if less than a megabyte", function() {
27 expect(formatBytes(568000000)).toEqual('568 MB');
28 });
29
30 it("returns value in gigabytes if less than a terabyte", function() {
31 expect(formatBytes(382000000000)).toEqual('382 GB');
32 });
33
34 it("returns value in terabytes if great than or equal to 1 terabyte",
35 function() {
36 expect(formatBytes(2000000000000)).toEqual('2 TB');
37 }
38 );
39});
diff --git a/src/maasserver/static/js/angular/filters/tests/test_storage_type.js b/src/maasserver/static/js/angular/filters/tests/test_storage_type.js
0new file mode 10064440new file mode 100644
index 0000000..5de13ca
--- /dev/null
+++ b/src/maasserver/static/js/angular/filters/tests/test_storage_type.js
@@ -0,0 +1,29 @@
1/* Copyright 2015-2018 Canonical Ltd. This software is licensed under the
2 * GNU Affero General Public License version 3 (see the file LICENSE).
3 *
4 * Unit tests for formatStorageType.
5 */
6
7describe("formatStorageType", function() {
8
9 // Load the MAAS module.
10 beforeEach(module("MAAS"));
11
12 // Load the storageType.
13 var storageType;
14 beforeEach(inject(function($filter) {
15 storageType = $filter("formatStorageType");
16 }));
17
18 it("returns empty string if undefined storage type", function() {
19 expect(storageType()).toEqual('');
20 });
21
22 it("returns original value if not recognised", function() {
23 expect(storageType('foo')).toEqual('foo');
24 });
25
26 it("returns formatted when recognised", function() {
27 expect(storageType('lvm')).toEqual('LVM');
28 });
29});
diff --git a/src/maasserver/static/partials/pod-details.html b/src/maasserver/static/partials/pod-details.html
index 1b41b4d..a30f03c 100644
--- a/src/maasserver/static/partials/pod-details.html
+++ b/src/maasserver/static/partials/pod-details.html
@@ -16,7 +16,7 @@
16 data-ng-model="name.value"16 data-ng-model="name.value"
17 data-ng-disabled="!canEdit()"17 data-ng-disabled="!canEdit()"
18 data-ng-click="editName()"></span>18 data-ng-click="editName()"></span>
19 </h1> 19 </h1>
20 <button class="p-button--base u-no-margin--top ng-hide"20 <button class="p-button--base u-no-margin--top ng-hide"
21 data-ng-show="name.editing"21 data-ng-show="name.editing"
22 data-ng-click="cancelEditName()">Cancel</button>22 data-ng-click="cancelEditName()">Cancel</button>
@@ -64,41 +64,97 @@
64 <div class="row">64 <div class="row">
65 <div class="col-12">65 <div class="col-12">
66 <h3 class="p-heading--four">Storage configuration</h3>66 <h3 class="p-heading--four">Storage configuration</h3>
67 <table class="p-table-expanding">67 <table class="p-table-expanding p-table--pod-storage-config">
68 <thead>68 <thead>
69 <tr>69 <tr class="p-table__row">
70 <th class="col-2">Location</th>70 <th></th>
71 <th class="col-3">Capacity (GB)</th>71 <th>Capacity (GB)</th>
72 <th class="col-4">Tags</th>72 <th>Location</th>
73 <th class="col-1">73 <th>Tags</th>
74 <th>
74 <div class="u-align--center">Boot</div>75 <div class="u-align--center">Boot</div>
75 </th>76 </th>
76 <th class="col-2"></th>
77 </tr>77 </tr>
78 </thead>78 </thead>
79 <tbody>79 <tbody>
80 <tr data-ng-repeat="storage in compose.obj.storage">80 <tr class="p-table__row" data-ng-repeat="storage in compose.obj.storage">
81 <td class="col-2">81 <td>
82 <div data-ng-if="!storage.boot">
83 <button class="p-button--base" data-ng-click="composeRemoveDisk(storage)">
84 <i class="p-icon--close"></i>
85 <span class="u-off-screen">Remove</span>
86 </button>
87 </div>
88 </td>
89 <td>
82 <div class="form__group-input">90 <div class="form__group-input">
83 <select data-ng-model="storage.type">91 <input type="text" class="u-no-margin--bottom" placeholder="Enter capacity" data-ng-model="storage.size">
84 <option value="local">Local</option>
85 <option value="iscsi"
86 data-ng-if="pod.capabilities.indexOf('iscsi_storage') >= 0"
87 data-ng-disabled="storage.boot">iSCSI</option>
88 </select>
89 </div>92 </div>
90 </td>93 </td>
91 <td class="col-3">94 <td style="overflow: visible">
92 <div class="form__group-input">95 <div class="form__group-input">
93 <input type="text" placeholder="Enter capacity" data-ng-model="storage.size">96 <div class="p-option-selector" tabindex="0">
97 <input class="p-option-selector__input u-no-margin--bottom" type="text" data-ng-model="storage.pool.name" ng-focus="openOptions(storage)">
98 <div class="p-option-selector__options" ng-if="storage.showOptions" ng-keyup="$event.keyCode == 27 ? closeOptions(storage) : null">
99 <button class="p-button--base u-float--left" ng-click="closeOptions(storage)">
100 <i class="p-icon--close">Close</i>
101 </button>
102 <ul class="p-inline-list u-align--right p-option-selector__options-key">
103 <li class="p-inline-list__item">
104 <span class="p-key-icon--used"></span> Used
105 </li>
106 <li class="p-inline-list__item">
107 <span class="p-key-icon--requests"></span> Requests
108 </li>
109 <li class="p-inline-list__item">
110 <span class="p-key-icon--free"></span> Free
111 </li>
112 </ul>
113 <hr class="u-no-margin--bottom">
114 <div class="p-option-selector__option"
115 tabindex="0"
116 ng-repeat="storage_pool in pod.storage_pools" ng-click="selectStoragePool(storage_pool, storage)" ng-keyup="$event.keyCode == 13 ? selectStoragePool(storage_pool, storage) : null" ng-class="{'is-selected': storage.pool === storage_pool}">
117 <div class="p-option-selector__option-cell">
118 <div><strong>{$ storage_pool.name $}</strong></div>
119 <div class="u-text--light">{$ storage_pool.path $}</div>
120 </div>
121 <div class="p-option-selector__option-cell u-align--right">
122 <div>{$ storage_pool.type | formatStorageType $}</div>
123 <div>{$ storage_pool.total | formatBytes $}</div>
124 </div>
125 <div class="p-option-selector__option-cell">
126 <div class="p-chart">
127 <div class="p-chart__bar--requests" style="width: {$ (storage_pool.used / storage_pool.total * 100) + ((storage.size | convertGigabyteToBytes) / storage_pool.total * 100) $}%"
128 ng-class="{'is-over': (storage_pool.used / storage_pool.total * 100) + ((storage.size | convertGigabyteToBytes) / storage_pool.total * 100) >= 100}"></div>
129 <div class="p-chart__bar--used" style="width: {$ storage_pool.used / storage_pool.total * 100 $}%"></div>
130 </div>
131
132 <ul class="p-inline-list u-no-margin--bottom p-space-between">
133 <li class="p-inline-list__item">
134 <span class="p-key-icon--used"></span>
135 {$ storage_pool.used | formatBytes $}
136 </li>
137 <li class="p-inline-list__item">
138 <span class="p-key-icon--requests"></span>
139 {$ storage.size $} GB
140 </li>
141 <li class="p-inline-list__item">
142 <span class="p-key-icon--free"></span>
143 {$ storage_pool.available | formatBytes $}
144 </li>
145 </ul>
146 </div>
147 </div>
148 </div>
149 </div>
94 </div>150 </div>
95 </td>151 </td>
96 <td class="col-4">152 <td>
97 <div class="form__group-input">153 <div class="form__group-input">
98 <tags-input data-ng-model="storage.tags" allow-tags-pattern="[\\w-]+"></tags-input>154 <tags-input data-ng-model="storage.tags" class="u-no-margin--bottom" allow-tags-pattern="[\\w-]+"></tags-input>
99 </div>155 </div>
100 </td>156 </td>
101 <td class="col-1">157 <td>
102 <div class="u-align--center">158 <div class="u-align--center">
103 <input type="radio" id="{$ $index $}-boot" class="u-no-margin--right"159 <input type="radio" id="{$ $index $}-boot" class="u-no-margin--right"
104 data-ng-click="composeSetBootDisk(storage)"160 data-ng-click="composeSetBootDisk(storage)"
@@ -107,19 +163,21 @@
107 <label for="{$ $index $}-boot"></label>163 <label for="{$ $index $}-boot"></label>
108 </div>164 </div>
109 </td>165 </td>
110 <td class="col-2">
111 <div class="u-align--right" data-ng-if="!storage.boot">
112 <button data-ng-click="composeRemoveDisk(storage)">Remove</button>
113 </div>
114 </td>
115 </tr>166 </tr>
167 <tr class="p-table__row">
168 <td>
169 <button class="p-button--base" data-ng-click="composeAddStorage()">
170 <i class="p-icon--plus"></i>
171 <span class="u-off-screen">Add another device</span>
172 </button>
173 </td>
116 </tbody>174 </tbody>
117 </table>175 </table>
118 <button class="p-button--neutral" data-ng-click="composeAddStorage()">Add another device</button>
119 </div>176 </div>
120 </div>177 </div>
121 <div class="row">178 <div class="row">
122 <div class="col-12">179 <div class="col-12">
180 <hr>
123 <p maas-obj-hide-saving><maas-obj-errors></maas-obj-errors></p>181 <p maas-obj-hide-saving><maas-obj-errors></maas-obj-errors></p>
124 <p maas-obj-show-saving><maas-obj-saving>Composing machine</maas-obj-saving></p>182 <p maas-obj-show-saving><maas-obj-saving>Composing machine</maas-obj-saving></p>
125 <div class="u-align--right u-no-margin--top" maas-obj-hide-saving>183 <div class="u-align--right u-no-margin--top" maas-obj-hide-saving>
diff --git a/src/maasserver/static/scss/_patterns_charts.scss b/src/maasserver/static/scss/_patterns_charts.scss
126new file mode 100644184new file mode 100644
index 0000000..2f391ac
--- /dev/null
+++ b/src/maasserver/static/scss/_patterns_charts.scss
@@ -0,0 +1,40 @@
1@mixin patterns_charts {
2 .p-chart {
3 position: relative;
4 overflow: hidden;
5 width: 100%;
6 height: 1rem;
7 border-radius: 1rem;
8 background-color: $color-x-light;
9 border: 1px solid $color-mid-light;
10 margin-top: .25rem;
11 margin-bottom: .25rem;
12 }
13
14 .p-chart__bar {
15 position: absolute;
16 top: 0;
17 bottom: 0;
18 left: 0;
19 }
20
21 .p-chart__bar--used {
22 @extend .p-chart__bar;
23 background-color: #c6b3d4;
24 border-right: 1px solid $color-x-light;
25 }
26
27 .p-chart__bar--other {
28 @extend .p-chart__bar;
29 background-color: #adcde1;
30 }
31
32 .p-chart__bar--requests {
33 @extend .p-chart__bar;
34 background-color: #3a77af;
35
36 &.is-over {
37 background-color: #ff8936;
38 }
39 }
40}
diff --git a/src/maasserver/static/scss/_patterns_option-selector.scss b/src/maasserver/static/scss/_patterns_option-selector.scss
0new file mode 10064441new file mode 100644
index 0000000..badafbd
--- /dev/null
+++ b/src/maasserver/static/scss/_patterns_option-selector.scss
@@ -0,0 +1,130 @@
1@mixin patterns_option-selector {
2 .p-option-selector {
3 position: relative;
4 }
5
6 .p-option-selector__input {
7 @extend select;
8 }
9
10 .p-option-selector__options {
11 position: absolute;
12 top: 2.25rem;
13 padding: $sph-intra 0 0;
14 border: 1px solid $color-mid-light;
15 border-radius: $border-radius;
16 box-shadow: $box-shadow;
17 background-color: $color-x-light;
18 z-index: 10;
19 width: 100%;
20
21 @media (min-width: $breakpoint-medium) {
22 width: 70vw;
23 }
24
25 @media (min-width: $breakpoint-large) {
26 width: 750px;
27 }
28 }
29
30 .p-option-selector__options-key {
31 padding: 0 $sph-intra;
32 }
33
34 .p-option-selector__option {
35 @include vf-focus;
36 display: flex;
37 align-items: center;
38 flex-wrap: wrap;
39 padding: $sph-intra $sph-intra;
40 border-bottom: 1px solid $color-mid-light;
41 cursor: pointer;
42
43 &:last-child {
44 border-bottom: 0;
45 }
46
47 &:hover {
48 background-color: $color-light;
49 }
50
51 &.is-selected {
52 background-color: $color-light;
53 background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg width='17' height='17' xmlns='http://www.w3.org/2000/svg'%3E%3Cg transform='translate(1 1)' fill='none' fill-rule='evenodd'%3E%3Ccircle stroke='%230e8420' stroke-width='1.5' fill='%230e8420' cx='7.25' cy='7.25' r='7.25'/%3E%3Cpath fill='%23fff' d='M11.05 4.173l-.066.058L6.25 8.378l-2.776-2.38-.839.948L6.25 10.75l5.5-5.787-.7-.79z'/%3E%3C/g%3E%3C/svg%3E");
54 background-repeat: no-repeat;
55 background-position: 10px center;
56 }
57 }
58
59 .p-option-selector__option-cell {
60 padding: $sp-small $sp-medium;
61
62 &:first-child {
63 width: 70%;
64
65 @media (min-width: $breakpoint-medium) {
66 border-right: 1px solid $color-mid-light;
67 width: 35%;
68 }
69
70 @media (min-width: $breakpoint-large) {
71 width: 40%;
72 }
73 }
74
75 > * {
76 overflow: hidden;
77 text-overflow: ellipsis;
78 white-space: nowrap;
79 }
80
81 &:nth-child(2) {
82 width: 30%;
83
84 @media (min-width: $breakpoint-medium) {
85 width: 20%;
86 border-right: 1px solid $color-mid-light;
87 }
88
89 @media (min-width: $breakpoint-large) {
90 width: 15%;
91 }
92 }
93
94 &:nth-child(3) {
95 width: 100%;
96
97 @media (min-width: $breakpoint-medium) {
98 width: 45%;
99 }
100 }
101 }
102}
103
104// Key inside option selector
105.p-key-icon {
106 display: inline-block;
107 width: .75rem;
108 height: .75rem;
109 border-width: 1px;
110 border-style: solid;
111 border-radius: 50%;
112}
113
114.p-key-icon--free {
115 @extend .p-key-icon;
116 background-color: $color-x-light;
117 border-color: $color-mid-light;
118}
119
120.p-key-icon--used {
121 @extend .p-key-icon;
122 background-color: #c6b3d4;
123 border-color: #c6b3d4;
124}
125
126.p-key-icon--requests {
127 @extend .p-key-icon;
128 background-color: #3a77af;
129 border-color: #3a77af;
130}
diff --git a/src/maasserver/static/scss/_patterns_table-expanding.scss b/src/maasserver/static/scss/_patterns_table-expanding.scss
index d210c3f..0298c81 100644
--- a/src/maasserver/static/scss/_patterns_table-expanding.scss
+++ b/src/maasserver/static/scss/_patterns_table-expanding.scss
@@ -1,10 +1,4 @@
1@mixin maas-table-expanding {1@mixin maas-table-expanding {
2 .p-table-expanding__panel {
3 // .p-form-validation .p-form-validation__input {
4 // padding: .5rem .75rem;
5 // }
6 }
7
8 .p-table-expanding {2 .p-table-expanding {
9 .p-table-expanding__panel {3 .p-table-expanding__panel {
10 @extend %vf-card;4 @extend %vf-card;
@@ -30,7 +24,6 @@
30 }24 }
31 }25 }
3226
33
34 tr {27 tr {
35 display: table-row; //XXX (2) ove flex to only direct descendants as it is breaking nexsted tables28 display: table-row; //XXX (2) ove flex to only direct descendants as it is breaking nexsted tables
36 .is-active {29 .is-active {
@@ -38,8 +31,14 @@
38 }31 }
39 }32 }
40 }33 }
34
41 .p-table-expanding > thead > tr,35 .p-table-expanding > thead > tr,
42 .p-table-expanding > tbody > tr {36 .p-table-expanding > tbody > tr {
43 display: flex; //XXX (1): move flex to only direct descendants as it is breaking nexsted tables37 display: flex; //XXX (1): move flex to only direct descendants as it is breaking nexsted tables
44 }38 }
39
40 // Remove margins from tag inputs within tables
41 .p-table-expanding .tags-input .tags .input {
42 margin-bottom: 0;
43 }
45}44}
diff --git a/src/maasserver/static/scss/_tables.scss b/src/maasserver/static/scss/_tables.scss
index 509e233..0f21734 100644
--- a/src/maasserver/static/scss/_tables.scss
+++ b/src/maasserver/static/scss/_tables.scss
@@ -452,6 +452,32 @@
452 }452 }
453 }453 }
454454
455 .p-table--pod-storage-config {
456 @media (min-width: $breakpoint-small) {
457 margin-bottom: 0;
458
459 .p-table__row {
460 th, td {
461 &:nth-child(1) {
462 width: 5%;
463 }
464 &:nth-child(2) {
465 width: 20%;
466 }
467 &:nth-child(3) {
468 width: 30%;
469 }
470 &:nth-child(4) {
471 width: 35%;
472 }
473 &:nth-child(5) {
474 width: 10%;
475 }
476 }
477 }
478 }
479 }
480
455 .p-double-row {481 .p-double-row {
456 $checkbox-space: 1rem + $sph-intra;482 $checkbox-space: 1rem + $sph-intra;
457 $icon-space: map-get($icon-sizes, default) + $sph-intra--condensed;483 $icon-space: map-get($icon-sizes, default) + $sph-intra--condensed;
diff --git a/src/maasserver/static/scss/_utils.scss b/src/maasserver/static/scss/_utils.scss
index 2587691..8e95c14 100644
--- a/src/maasserver/static/scss/_utils.scss
+++ b/src/maasserver/static/scss/_utils.scss
@@ -19,3 +19,7 @@
19.u-flex--no-wrap {19.u-flex--no-wrap {
20 display: flex;20 display: flex;
21}21}
22
23.u-text--light {
24 color: $color-mid-dark;
25}
diff --git a/src/maasserver/static/scss/build.scss b/src/maasserver/static/scss/build.scss
index 0846e1a..7a54ba4 100644
--- a/src/maasserver/static/scss/build.scss
+++ b/src/maasserver/static/scss/build.scss
@@ -33,6 +33,8 @@
33@import 'tables';33@import 'tables';
34@import 'patterns_space-between';34@import 'patterns_space-between';
35@import 'patterns_filter';35@import 'patterns_filter';
36@import 'patterns_charts';
37@import 'patterns_option-selector';
3638
37// Include local patterns39// Include local patterns
38@include maas;40@include maas;
@@ -61,3 +63,5 @@
61@include maas-table-sortable;63@include maas-table-sortable;
62@include maas-table-widths;64@include maas-table-widths;
63@include patterns_space-between;65@include patterns_space-between;
66@include patterns_charts;
67@include patterns_option-selector;

Subscribers

People subscribed via source and target branches