Merge ~ya-bo-ng/maas:kvm-storage-pods-landing-view into maas:master
- Git
- lp:~ya-bo-ng/maas
- kvm-storage-pods-landing-view
- Merge into master
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) |
Related bugs: |
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.
- 5c2ed41... by Anthony Dillon
-
Fix tests
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: ba0d5fbae52d1fa
- 4f9ca99... by Anthony Dillon
-
Fix lint issues
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: 1cdc58449c2eb58
- d3d86f4... by Anthony Dillon
-
White space
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: a253d14a19de98c
- 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
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: 5af783308fd8ba6
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:/
- a647124... by Anthony Dillon
-
Fix tests
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: 0065a188774bed7
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: 91bbb1a41015ae7
MAAS Lander (maas-lander) wrote : | # |
UNIT TESTS
-b kvm-storage-
STATUS: FAILED
LOG: http://
COMMIT: 72ed76c2098d62e
- 9d0503e... by Anthony Dillon
-
Set the default pod storage
- 8499adc... by Anthony Dillon
-
Lint the pod details
Caleb Ellis (caleb-ellis) wrote : | # |
- I get an angular error: TypeError: Cannot read property 'storage_pools' of null at Scope.$
- 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
- 82e7e30... by Anthony Dillon
-
Fix the defaultPod function call order
Preview Diff
1 | diff --git a/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js b/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js |
2 | index 86eb45c..481d915 100644 |
3 | --- a/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js |
4 | +++ b/src/maasserver/static/js/angular/3rdparty/ng-tags-input.js |
5 | @@ -1119,7 +1119,7 @@ tagsInput.factory('tiUtil', ["$timeout", function($timeout) { |
6 | /* HTML templates */ |
7 | tagsInput.run(["$templateCache", function($templateCache) { |
8 | $templateCache.put('ngTagsInput/tags-input.html', |
9 | - "<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>" |
10 | + "<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>" |
11 | ); |
12 | |
13 | $templateCache.put('ngTagsInput/tag-item.html', |
14 | diff --git a/src/maasserver/static/js/angular/controllers/pod_details.js b/src/maasserver/static/js/angular/controllers/pod_details.js |
15 | index 8f015cc..192274a 100644 |
16 | --- a/src/maasserver/static/js/angular/controllers/pod_details.js |
17 | +++ b/src/maasserver/static/js/angular/controllers/pod_details.js |
18 | @@ -53,6 +53,7 @@ angular.module('MAAS').controller('PodDetailsController', [ |
19 | type: 'local', |
20 | size: 8, |
21 | tags: [], |
22 | + pool: {}, |
23 | boot: true |
24 | }] |
25 | } |
26 | @@ -211,6 +212,21 @@ angular.module('MAAS').controller('PodDetailsController', [ |
27 | }); |
28 | }; |
29 | |
30 | + $scope.openOptions = function(storage) { |
31 | + angular.forEach($scope.compose.obj.storage, function(disk) { |
32 | + disk.showOptions = false; |
33 | + }); |
34 | + storage.showOptions = true; |
35 | + } |
36 | + |
37 | + $scope.closeOptions = function(storage) { |
38 | + storage.showOptions = false; |
39 | + } |
40 | + |
41 | + $scope.selectStoragePool = function(storagePool, storage) { |
42 | + storage.pool = storagePool; |
43 | + } |
44 | + |
45 | // Return the title of the pod type. |
46 | $scope.getPodTypeTitle = function() { |
47 | var i; |
48 | @@ -274,6 +290,7 @@ angular.module('MAAS').controller('PodDetailsController', [ |
49 | type: 'local', |
50 | size: 8, |
51 | tags: [], |
52 | + pool: {}, |
53 | boot: true |
54 | }] |
55 | }; |
56 | @@ -282,16 +299,18 @@ angular.module('MAAS').controller('PodDetailsController', [ |
57 | |
58 | // Add another storage device. |
59 | $scope.composeAddStorage = function() { |
60 | - var storage = { |
61 | - type: 'local', |
62 | - size: 8, |
63 | - tags: [], |
64 | - boot: false |
65 | - }; |
66 | - if($scope.pod.capabilities.indexOf('iscsi_storage') >= 0) { |
67 | - storage.type = 'iscsi'; |
68 | - } |
69 | - $scope.compose.obj.storage.push(storage); |
70 | + var storage = { |
71 | + type: 'local', |
72 | + size: 8, |
73 | + tags: [], |
74 | + pool: $scope.getDefaultStoragePool(), |
75 | + boot: false |
76 | + }; |
77 | + |
78 | + if($scope.pod.capabilities.indexOf('iscsi_storage') >= 0) { |
79 | + storage.type = 'iscsi'; |
80 | + } |
81 | + $scope.compose.obj.storage.push(storage); |
82 | }; |
83 | |
84 | // Change which disk is the boot disk. |
85 | @@ -302,6 +321,18 @@ angular.module('MAAS').controller('PodDetailsController', [ |
86 | storage.boot = true; |
87 | }; |
88 | |
89 | + // Get the default pod pool |
90 | + $scope.getDefaultStoragePool = function () { |
91 | + var defaultPool = {}; |
92 | + console.log($scope); |
93 | + if($scope.pod.storage_pools && $scope.pod.default_storage_pool) { |
94 | + defaultPool = $scope.pod.storage_pools.filter( |
95 | + pool => pool.id == $scope.pod.default_storage_pool |
96 | + )[0]; |
97 | + } |
98 | + return defaultPool; |
99 | + }; |
100 | + |
101 | // Remove a disk from storage config. |
102 | $scope.composeRemoveDisk = function(storage) { |
103 | var idx = $scope.compose.obj.storage.indexOf(storage); |
104 | @@ -357,8 +388,10 @@ angular.module('MAAS').controller('PodDetailsController', [ |
105 | // the activeItem. |
106 | var activePod = PodsManager.getActiveItem(); |
107 | if(angular.isObject(activePod) && |
108 | - activePod.id === parseInt($routeParams.id, 10)) { |
109 | + activePod.id === parseInt($routeParams.id, 10)) { |
110 | $scope.pod = activePod; |
111 | + $scope.compose.obj.storage[0].pool = ( |
112 | + $scope.getDefaultStoragePool()); |
113 | $scope.loaded = true; |
114 | $scope.machinesSearch = 'pod-id:=' + $scope.pod.id; |
115 | $scope.startWatching(); |
116 | @@ -366,6 +399,8 @@ angular.module('MAAS').controller('PodDetailsController', [ |
117 | PodsManager.setActiveItem( |
118 | parseInt($routeParams.id, 10)).then(function(pod) { |
119 | $scope.pod = pod; |
120 | + $scope.compose.obj.storage[0].pool = ( |
121 | + $scope.getDefaultStoragePool()); |
122 | $scope.loaded = true; |
123 | $scope.machinesSearch = 'pod-id:=' + $scope.pod.id; |
124 | $scope.startWatching(); |
125 | 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 |
126 | index f1f9322..998d263 100644 |
127 | --- a/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js |
128 | +++ b/src/maasserver/static/js/angular/controllers/tests/test_pod_details.js |
129 | @@ -157,6 +157,7 @@ describe("PodDetailsController", function() { |
130 | type: 'local', |
131 | size: 8, |
132 | tags: [], |
133 | + pool: {}, |
134 | boot: true |
135 | }] |
136 | } |
137 | @@ -660,6 +661,7 @@ describe("PodDetailsController", function() { |
138 | type: 'local', |
139 | size: 8, |
140 | tags: [], |
141 | + pool: {}, |
142 | boot: true |
143 | }] |
144 | }); |
145 | @@ -678,6 +680,7 @@ describe("PodDetailsController", function() { |
146 | type: 'local', |
147 | size: 8, |
148 | tags: [], |
149 | + pool: {}, |
150 | boot: false |
151 | }); |
152 | }); |
153 | @@ -692,6 +695,7 @@ describe("PodDetailsController", function() { |
154 | type: 'iscsi', |
155 | size: 8, |
156 | tags: [], |
157 | + pool: {}, |
158 | boot: false |
159 | }); |
160 | }); |
161 | diff --git a/src/maasserver/static/js/angular/filters/format_bytes.js b/src/maasserver/static/js/angular/filters/format_bytes.js |
162 | new file mode 100644 |
163 | index 0000000..1ea393d |
164 | --- /dev/null |
165 | +++ b/src/maasserver/static/js/angular/filters/format_bytes.js |
166 | @@ -0,0 +1,75 @@ |
167 | +/* Copyright 2016-2018 Canonical Ltd. This software is licensed under the |
168 | + * GNU Affero General Public License version 3 (see the file LICENSE). |
169 | + * |
170 | + * Converts bytes into human readable string, e.g. 10 GB |
171 | + */ |
172 | + |
173 | +angular.module('MAAS').filter('formatBytes', function() { |
174 | + return function(bytes) { |
175 | + var bytesInKilobyte = 1000; |
176 | + var kilobytesInMegabyte = 1000; |
177 | + var megabytesInGigabyte = 1000; |
178 | + var gigabytesInTerabyte = 1000; |
179 | + |
180 | + var bytesInMegabyte = ( |
181 | + bytesInKilobyte * kilobytesInMegabyte |
182 | + ); |
183 | + var bytesInGigabyte = ( |
184 | + bytesInKilobyte * kilobytesInMegabyte * megabytesInGigabyte |
185 | + ); |
186 | + var bytesInTerabyte = ( |
187 | + bytesInKilobyte * |
188 | + kilobytesInMegabyte * |
189 | + megabytesInGigabyte * gigabytesInTerabyte |
190 | + ); |
191 | + |
192 | + if (bytes >= bytesInTerabyte) { |
193 | + return Math.round( |
194 | + bytes / |
195 | + bytesInKilobyte / |
196 | + kilobytesInMegabyte / |
197 | + megabytesInGigabyte / |
198 | + gigabytesInTerabyte |
199 | + ) + ' TB'; |
200 | + } else if (bytes >= bytesInGigabyte) { |
201 | + return Math.round( |
202 | + bytes / |
203 | + bytesInKilobyte / |
204 | + kilobytesInMegabyte / |
205 | + megabytesInGigabyte |
206 | + ) + ' GB'; |
207 | + } else if (bytes >= bytesInMegabyte) { |
208 | + return Math.round( |
209 | + bytes / |
210 | + bytesInKilobyte / |
211 | + kilobytesInMegabyte |
212 | + ) + ' MB'; |
213 | + } else if (bytes >= bytesInKilobyte) { |
214 | + return Math.round(bytes / bytesInKilobyte) + ' KB'; |
215 | + } else if (bytes > 0) { |
216 | + return bytes + ' B'; |
217 | + } else { |
218 | + return 0; |
219 | + } |
220 | + } |
221 | +}); |
222 | + |
223 | +angular.module('MAAS').filter('convertGigabyteToBytes', function() { |
224 | + return function(gigabytes) { |
225 | + var bytesInKilobyte = 1000; |
226 | + var kilobytesInMegabyte = 1000; |
227 | + var megabytesInGigabyte = 1000; |
228 | + |
229 | + if (gigabytes) { |
230 | + return Math.round( |
231 | + gigabytes * |
232 | + bytesInKilobyte * |
233 | + kilobytesInMegabyte * |
234 | + megabytesInGigabyte |
235 | + ); |
236 | + } else { |
237 | + return 0; |
238 | + } |
239 | + } |
240 | +}); |
241 | + |
242 | diff --git a/src/maasserver/static/js/angular/filters/format_storage_type.js b/src/maasserver/static/js/angular/filters/format_storage_type.js |
243 | new file mode 100644 |
244 | index 0000000..2d05113 |
245 | --- /dev/null |
246 | +++ b/src/maasserver/static/js/angular/filters/format_storage_type.js |
247 | @@ -0,0 +1,20 @@ |
248 | +/* Copyright 2016-2018 Canonical Ltd. This software is licensed under the |
249 | + * GNU Affero General Public License version 3 (see the file LICENSE). |
250 | + * |
251 | + * Converts storage type into human readable string, e.g. LVM |
252 | + */ |
253 | + |
254 | +angular.module('MAAS').filter('formatStorageType', function() { |
255 | + return function(storageType) { |
256 | + if (!storageType) { |
257 | + return ''; |
258 | + } |
259 | + |
260 | + switch(storageType) { |
261 | + case 'lvm': |
262 | + return 'LVM'; |
263 | + default: |
264 | + return storageType; |
265 | + } |
266 | + } |
267 | +}); |
268 | 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 |
269 | new file mode 100644 |
270 | index 0000000..4086438 |
271 | --- /dev/null |
272 | +++ b/src/maasserver/static/js/angular/filters/tests/test_format_bytes.js |
273 | @@ -0,0 +1,39 @@ |
274 | +/* Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
275 | + * GNU Affero General Public License version 3 (see the file LICENSE). |
276 | + * |
277 | + * Unit tests for formatBytes. |
278 | + */ |
279 | + |
280 | +describe("formatBytes", function() { |
281 | + |
282 | + // Load the MAAS module. |
283 | + beforeEach(module("MAAS")); |
284 | + |
285 | + // Load the formatBytes. |
286 | + var formatBytes; |
287 | + beforeEach(inject(function($filter) { |
288 | + formatBytes = $filter("formatBytes"); |
289 | + })); |
290 | + |
291 | + it("returns zero if undefined bytes", function() { |
292 | + expect(formatBytes()).toEqual(0); |
293 | + }); |
294 | + |
295 | + it("returns value in bytes if less than a kilobyte", function() { |
296 | + expect(formatBytes(456)).toEqual('456 B'); |
297 | + }); |
298 | + |
299 | + it("returns value in kilobytes if less than a megabyte", function() { |
300 | + expect(formatBytes(568000000)).toEqual('568 MB'); |
301 | + }); |
302 | + |
303 | + it("returns value in gigabytes if less than a terabyte", function() { |
304 | + expect(formatBytes(382000000000)).toEqual('382 GB'); |
305 | + }); |
306 | + |
307 | + it("returns value in terabytes if great than or equal to 1 terabyte", |
308 | + function() { |
309 | + expect(formatBytes(2000000000000)).toEqual('2 TB'); |
310 | + } |
311 | + ); |
312 | +}); |
313 | 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 |
314 | new file mode 100644 |
315 | index 0000000..5de13ca |
316 | --- /dev/null |
317 | +++ b/src/maasserver/static/js/angular/filters/tests/test_storage_type.js |
318 | @@ -0,0 +1,29 @@ |
319 | +/* Copyright 2015-2018 Canonical Ltd. This software is licensed under the |
320 | + * GNU Affero General Public License version 3 (see the file LICENSE). |
321 | + * |
322 | + * Unit tests for formatStorageType. |
323 | + */ |
324 | + |
325 | +describe("formatStorageType", function() { |
326 | + |
327 | + // Load the MAAS module. |
328 | + beforeEach(module("MAAS")); |
329 | + |
330 | + // Load the storageType. |
331 | + var storageType; |
332 | + beforeEach(inject(function($filter) { |
333 | + storageType = $filter("formatStorageType"); |
334 | + })); |
335 | + |
336 | + it("returns empty string if undefined storage type", function() { |
337 | + expect(storageType()).toEqual(''); |
338 | + }); |
339 | + |
340 | + it("returns original value if not recognised", function() { |
341 | + expect(storageType('foo')).toEqual('foo'); |
342 | + }); |
343 | + |
344 | + it("returns formatted when recognised", function() { |
345 | + expect(storageType('lvm')).toEqual('LVM'); |
346 | + }); |
347 | +}); |
348 | diff --git a/src/maasserver/static/partials/pod-details.html b/src/maasserver/static/partials/pod-details.html |
349 | index 1b41b4d..a30f03c 100644 |
350 | --- a/src/maasserver/static/partials/pod-details.html |
351 | +++ b/src/maasserver/static/partials/pod-details.html |
352 | @@ -16,7 +16,7 @@ |
353 | data-ng-model="name.value" |
354 | data-ng-disabled="!canEdit()" |
355 | data-ng-click="editName()"></span> |
356 | - </h1> |
357 | + </h1> |
358 | <button class="p-button--base u-no-margin--top ng-hide" |
359 | data-ng-show="name.editing" |
360 | data-ng-click="cancelEditName()">Cancel</button> |
361 | @@ -64,41 +64,97 @@ |
362 | <div class="row"> |
363 | <div class="col-12"> |
364 | <h3 class="p-heading--four">Storage configuration</h3> |
365 | - <table class="p-table-expanding"> |
366 | + <table class="p-table-expanding p-table--pod-storage-config"> |
367 | <thead> |
368 | - <tr> |
369 | - <th class="col-2">Location</th> |
370 | - <th class="col-3">Capacity (GB)</th> |
371 | - <th class="col-4">Tags</th> |
372 | - <th class="col-1"> |
373 | + <tr class="p-table__row"> |
374 | + <th></th> |
375 | + <th>Capacity (GB)</th> |
376 | + <th>Location</th> |
377 | + <th>Tags</th> |
378 | + <th> |
379 | <div class="u-align--center">Boot</div> |
380 | </th> |
381 | - <th class="col-2"></th> |
382 | </tr> |
383 | </thead> |
384 | <tbody> |
385 | - <tr data-ng-repeat="storage in compose.obj.storage"> |
386 | - <td class="col-2"> |
387 | + <tr class="p-table__row" data-ng-repeat="storage in compose.obj.storage"> |
388 | + <td> |
389 | + <div data-ng-if="!storage.boot"> |
390 | + <button class="p-button--base" data-ng-click="composeRemoveDisk(storage)"> |
391 | + <i class="p-icon--close"></i> |
392 | + <span class="u-off-screen">Remove</span> |
393 | + </button> |
394 | + </div> |
395 | + </td> |
396 | + <td> |
397 | <div class="form__group-input"> |
398 | - <select data-ng-model="storage.type"> |
399 | - <option value="local">Local</option> |
400 | - <option value="iscsi" |
401 | - data-ng-if="pod.capabilities.indexOf('iscsi_storage') >= 0" |
402 | - data-ng-disabled="storage.boot">iSCSI</option> |
403 | - </select> |
404 | + <input type="text" class="u-no-margin--bottom" placeholder="Enter capacity" data-ng-model="storage.size"> |
405 | </div> |
406 | </td> |
407 | - <td class="col-3"> |
408 | + <td style="overflow: visible"> |
409 | <div class="form__group-input"> |
410 | - <input type="text" placeholder="Enter capacity" data-ng-model="storage.size"> |
411 | + <div class="p-option-selector" tabindex="0"> |
412 | + <input class="p-option-selector__input u-no-margin--bottom" type="text" data-ng-model="storage.pool.name" ng-focus="openOptions(storage)"> |
413 | + <div class="p-option-selector__options" ng-if="storage.showOptions" ng-keyup="$event.keyCode == 27 ? closeOptions(storage) : null"> |
414 | + <button class="p-button--base u-float--left" ng-click="closeOptions(storage)"> |
415 | + <i class="p-icon--close">Close</i> |
416 | + </button> |
417 | + <ul class="p-inline-list u-align--right p-option-selector__options-key"> |
418 | + <li class="p-inline-list__item"> |
419 | + <span class="p-key-icon--used"></span> Used |
420 | + </li> |
421 | + <li class="p-inline-list__item"> |
422 | + <span class="p-key-icon--requests"></span> Requests |
423 | + </li> |
424 | + <li class="p-inline-list__item"> |
425 | + <span class="p-key-icon--free"></span> Free |
426 | + </li> |
427 | + </ul> |
428 | + <hr class="u-no-margin--bottom"> |
429 | + <div class="p-option-selector__option" |
430 | + tabindex="0" |
431 | + 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}"> |
432 | + <div class="p-option-selector__option-cell"> |
433 | + <div><strong>{$ storage_pool.name $}</strong></div> |
434 | + <div class="u-text--light">{$ storage_pool.path $}</div> |
435 | + </div> |
436 | + <div class="p-option-selector__option-cell u-align--right"> |
437 | + <div>{$ storage_pool.type | formatStorageType $}</div> |
438 | + <div>{$ storage_pool.total | formatBytes $}</div> |
439 | + </div> |
440 | + <div class="p-option-selector__option-cell"> |
441 | + <div class="p-chart"> |
442 | + <div class="p-chart__bar--requests" style="width: {$ (storage_pool.used / storage_pool.total * 100) + ((storage.size | convertGigabyteToBytes) / storage_pool.total * 100) $}%" |
443 | + ng-class="{'is-over': (storage_pool.used / storage_pool.total * 100) + ((storage.size | convertGigabyteToBytes) / storage_pool.total * 100) >= 100}"></div> |
444 | + <div class="p-chart__bar--used" style="width: {$ storage_pool.used / storage_pool.total * 100 $}%"></div> |
445 | + </div> |
446 | + |
447 | + <ul class="p-inline-list u-no-margin--bottom p-space-between"> |
448 | + <li class="p-inline-list__item"> |
449 | + <span class="p-key-icon--used"></span> |
450 | + {$ storage_pool.used | formatBytes $} |
451 | + </li> |
452 | + <li class="p-inline-list__item"> |
453 | + <span class="p-key-icon--requests"></span> |
454 | + {$ storage.size $} GB |
455 | + </li> |
456 | + <li class="p-inline-list__item"> |
457 | + <span class="p-key-icon--free"></span> |
458 | + {$ storage_pool.available | formatBytes $} |
459 | + </li> |
460 | + </ul> |
461 | + </div> |
462 | + </div> |
463 | + </div> |
464 | + </div> |
465 | </div> |
466 | </td> |
467 | - <td class="col-4"> |
468 | + <td> |
469 | <div class="form__group-input"> |
470 | - <tags-input data-ng-model="storage.tags" allow-tags-pattern="[\\w-]+"></tags-input> |
471 | + <tags-input data-ng-model="storage.tags" class="u-no-margin--bottom" allow-tags-pattern="[\\w-]+"></tags-input> |
472 | </div> |
473 | </td> |
474 | - <td class="col-1"> |
475 | + <td> |
476 | <div class="u-align--center"> |
477 | <input type="radio" id="{$ $index $}-boot" class="u-no-margin--right" |
478 | data-ng-click="composeSetBootDisk(storage)" |
479 | @@ -107,19 +163,21 @@ |
480 | <label for="{$ $index $}-boot"></label> |
481 | </div> |
482 | </td> |
483 | - <td class="col-2"> |
484 | - <div class="u-align--right" data-ng-if="!storage.boot"> |
485 | - <button data-ng-click="composeRemoveDisk(storage)">Remove</button> |
486 | - </div> |
487 | - </td> |
488 | </tr> |
489 | + <tr class="p-table__row"> |
490 | + <td> |
491 | + <button class="p-button--base" data-ng-click="composeAddStorage()"> |
492 | + <i class="p-icon--plus"></i> |
493 | + <span class="u-off-screen">Add another device</span> |
494 | + </button> |
495 | + </td> |
496 | </tbody> |
497 | </table> |
498 | - <button class="p-button--neutral" data-ng-click="composeAddStorage()">Add another device</button> |
499 | </div> |
500 | </div> |
501 | <div class="row"> |
502 | <div class="col-12"> |
503 | + <hr> |
504 | <p maas-obj-hide-saving><maas-obj-errors></maas-obj-errors></p> |
505 | <p maas-obj-show-saving><maas-obj-saving>Composing machine</maas-obj-saving></p> |
506 | <div class="u-align--right u-no-margin--top" maas-obj-hide-saving> |
507 | diff --git a/src/maasserver/static/scss/_patterns_charts.scss b/src/maasserver/static/scss/_patterns_charts.scss |
508 | new file mode 100644 |
509 | index 0000000..2f391ac |
510 | --- /dev/null |
511 | +++ b/src/maasserver/static/scss/_patterns_charts.scss |
512 | @@ -0,0 +1,40 @@ |
513 | +@mixin patterns_charts { |
514 | + .p-chart { |
515 | + position: relative; |
516 | + overflow: hidden; |
517 | + width: 100%; |
518 | + height: 1rem; |
519 | + border-radius: 1rem; |
520 | + background-color: $color-x-light; |
521 | + border: 1px solid $color-mid-light; |
522 | + margin-top: .25rem; |
523 | + margin-bottom: .25rem; |
524 | + } |
525 | + |
526 | + .p-chart__bar { |
527 | + position: absolute; |
528 | + top: 0; |
529 | + bottom: 0; |
530 | + left: 0; |
531 | + } |
532 | + |
533 | + .p-chart__bar--used { |
534 | + @extend .p-chart__bar; |
535 | + background-color: #c6b3d4; |
536 | + border-right: 1px solid $color-x-light; |
537 | + } |
538 | + |
539 | + .p-chart__bar--other { |
540 | + @extend .p-chart__bar; |
541 | + background-color: #adcde1; |
542 | + } |
543 | + |
544 | + .p-chart__bar--requests { |
545 | + @extend .p-chart__bar; |
546 | + background-color: #3a77af; |
547 | + |
548 | + &.is-over { |
549 | + background-color: #ff8936; |
550 | + } |
551 | + } |
552 | +} |
553 | diff --git a/src/maasserver/static/scss/_patterns_option-selector.scss b/src/maasserver/static/scss/_patterns_option-selector.scss |
554 | new file mode 100644 |
555 | index 0000000..badafbd |
556 | --- /dev/null |
557 | +++ b/src/maasserver/static/scss/_patterns_option-selector.scss |
558 | @@ -0,0 +1,130 @@ |
559 | +@mixin patterns_option-selector { |
560 | + .p-option-selector { |
561 | + position: relative; |
562 | + } |
563 | + |
564 | + .p-option-selector__input { |
565 | + @extend select; |
566 | + } |
567 | + |
568 | + .p-option-selector__options { |
569 | + position: absolute; |
570 | + top: 2.25rem; |
571 | + padding: $sph-intra 0 0; |
572 | + border: 1px solid $color-mid-light; |
573 | + border-radius: $border-radius; |
574 | + box-shadow: $box-shadow; |
575 | + background-color: $color-x-light; |
576 | + z-index: 10; |
577 | + width: 100%; |
578 | + |
579 | + @media (min-width: $breakpoint-medium) { |
580 | + width: 70vw; |
581 | + } |
582 | + |
583 | + @media (min-width: $breakpoint-large) { |
584 | + width: 750px; |
585 | + } |
586 | + } |
587 | + |
588 | + .p-option-selector__options-key { |
589 | + padding: 0 $sph-intra; |
590 | + } |
591 | + |
592 | + .p-option-selector__option { |
593 | + @include vf-focus; |
594 | + display: flex; |
595 | + align-items: center; |
596 | + flex-wrap: wrap; |
597 | + padding: $sph-intra $sph-intra; |
598 | + border-bottom: 1px solid $color-mid-light; |
599 | + cursor: pointer; |
600 | + |
601 | + &:last-child { |
602 | + border-bottom: 0; |
603 | + } |
604 | + |
605 | + &:hover { |
606 | + background-color: $color-light; |
607 | + } |
608 | + |
609 | + &.is-selected { |
610 | + background-color: $color-light; |
611 | + 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"); |
612 | + background-repeat: no-repeat; |
613 | + background-position: 10px center; |
614 | + } |
615 | + } |
616 | + |
617 | + .p-option-selector__option-cell { |
618 | + padding: $sp-small $sp-medium; |
619 | + |
620 | + &:first-child { |
621 | + width: 70%; |
622 | + |
623 | + @media (min-width: $breakpoint-medium) { |
624 | + border-right: 1px solid $color-mid-light; |
625 | + width: 35%; |
626 | + } |
627 | + |
628 | + @media (min-width: $breakpoint-large) { |
629 | + width: 40%; |
630 | + } |
631 | + } |
632 | + |
633 | + > * { |
634 | + overflow: hidden; |
635 | + text-overflow: ellipsis; |
636 | + white-space: nowrap; |
637 | + } |
638 | + |
639 | + &:nth-child(2) { |
640 | + width: 30%; |
641 | + |
642 | + @media (min-width: $breakpoint-medium) { |
643 | + width: 20%; |
644 | + border-right: 1px solid $color-mid-light; |
645 | + } |
646 | + |
647 | + @media (min-width: $breakpoint-large) { |
648 | + width: 15%; |
649 | + } |
650 | + } |
651 | + |
652 | + &:nth-child(3) { |
653 | + width: 100%; |
654 | + |
655 | + @media (min-width: $breakpoint-medium) { |
656 | + width: 45%; |
657 | + } |
658 | + } |
659 | + } |
660 | +} |
661 | + |
662 | +// Key inside option selector |
663 | +.p-key-icon { |
664 | + display: inline-block; |
665 | + width: .75rem; |
666 | + height: .75rem; |
667 | + border-width: 1px; |
668 | + border-style: solid; |
669 | + border-radius: 50%; |
670 | +} |
671 | + |
672 | +.p-key-icon--free { |
673 | + @extend .p-key-icon; |
674 | + background-color: $color-x-light; |
675 | + border-color: $color-mid-light; |
676 | +} |
677 | + |
678 | +.p-key-icon--used { |
679 | + @extend .p-key-icon; |
680 | + background-color: #c6b3d4; |
681 | + border-color: #c6b3d4; |
682 | +} |
683 | + |
684 | +.p-key-icon--requests { |
685 | + @extend .p-key-icon; |
686 | + background-color: #3a77af; |
687 | + border-color: #3a77af; |
688 | +} |
689 | diff --git a/src/maasserver/static/scss/_patterns_table-expanding.scss b/src/maasserver/static/scss/_patterns_table-expanding.scss |
690 | index d210c3f..0298c81 100644 |
691 | --- a/src/maasserver/static/scss/_patterns_table-expanding.scss |
692 | +++ b/src/maasserver/static/scss/_patterns_table-expanding.scss |
693 | @@ -1,10 +1,4 @@ |
694 | @mixin maas-table-expanding { |
695 | - .p-table-expanding__panel { |
696 | - // .p-form-validation .p-form-validation__input { |
697 | - // padding: .5rem .75rem; |
698 | - // } |
699 | - } |
700 | - |
701 | .p-table-expanding { |
702 | .p-table-expanding__panel { |
703 | @extend %vf-card; |
704 | @@ -30,7 +24,6 @@ |
705 | } |
706 | } |
707 | |
708 | - |
709 | tr { |
710 | display: table-row; //XXX (2) ove flex to only direct descendants as it is breaking nexsted tables |
711 | .is-active { |
712 | @@ -38,8 +31,14 @@ |
713 | } |
714 | } |
715 | } |
716 | + |
717 | .p-table-expanding > thead > tr, |
718 | .p-table-expanding > tbody > tr { |
719 | display: flex; //XXX (1): move flex to only direct descendants as it is breaking nexsted tables |
720 | } |
721 | + |
722 | + // Remove margins from tag inputs within tables |
723 | + .p-table-expanding .tags-input .tags .input { |
724 | + margin-bottom: 0; |
725 | + } |
726 | } |
727 | diff --git a/src/maasserver/static/scss/_tables.scss b/src/maasserver/static/scss/_tables.scss |
728 | index 509e233..0f21734 100644 |
729 | --- a/src/maasserver/static/scss/_tables.scss |
730 | +++ b/src/maasserver/static/scss/_tables.scss |
731 | @@ -452,6 +452,32 @@ |
732 | } |
733 | } |
734 | |
735 | + .p-table--pod-storage-config { |
736 | + @media (min-width: $breakpoint-small) { |
737 | + margin-bottom: 0; |
738 | + |
739 | + .p-table__row { |
740 | + th, td { |
741 | + &:nth-child(1) { |
742 | + width: 5%; |
743 | + } |
744 | + &:nth-child(2) { |
745 | + width: 20%; |
746 | + } |
747 | + &:nth-child(3) { |
748 | + width: 30%; |
749 | + } |
750 | + &:nth-child(4) { |
751 | + width: 35%; |
752 | + } |
753 | + &:nth-child(5) { |
754 | + width: 10%; |
755 | + } |
756 | + } |
757 | + } |
758 | + } |
759 | + } |
760 | + |
761 | .p-double-row { |
762 | $checkbox-space: 1rem + $sph-intra; |
763 | $icon-space: map-get($icon-sizes, default) + $sph-intra--condensed; |
764 | diff --git a/src/maasserver/static/scss/_utils.scss b/src/maasserver/static/scss/_utils.scss |
765 | index 2587691..8e95c14 100644 |
766 | --- a/src/maasserver/static/scss/_utils.scss |
767 | +++ b/src/maasserver/static/scss/_utils.scss |
768 | @@ -19,3 +19,7 @@ |
769 | .u-flex--no-wrap { |
770 | display: flex; |
771 | } |
772 | + |
773 | +.u-text--light { |
774 | + color: $color-mid-dark; |
775 | +} |
776 | diff --git a/src/maasserver/static/scss/build.scss b/src/maasserver/static/scss/build.scss |
777 | index 0846e1a..7a54ba4 100644 |
778 | --- a/src/maasserver/static/scss/build.scss |
779 | +++ b/src/maasserver/static/scss/build.scss |
780 | @@ -33,6 +33,8 @@ |
781 | @import 'tables'; |
782 | @import 'patterns_space-between'; |
783 | @import 'patterns_filter'; |
784 | +@import 'patterns_charts'; |
785 | +@import 'patterns_option-selector'; |
786 | |
787 | // Include local patterns |
788 | @include maas; |
789 | @@ -61,3 +63,5 @@ |
790 | @include maas-table-sortable; |
791 | @include maas-table-widths; |
792 | @include patterns_space-between; |
793 | +@include patterns_charts; |
794 | +@include patterns_option-selector; |
UNIT TESTS pods-landing- view lp:~ya-bo-ng/maas/+git/maas into -b master lp:~maas-committers/maas
-b kvm-storage-
STATUS: FAILED maas-ci- jenkins. internal: 8080/job/ maas/job/ branch- tester/ 3892/console a288081eb8d1fdc 8b23ed80c6
LOG: http://
COMMIT: 671f56d044dcda5