Merge ~caleb-ellis/maas:grouping-improvements into maas:master
- Git
- lp:~caleb-ellis/maas
- grouping-improvements
- Merge into master
Status: | Merged |
---|---|
Approved by: | Caleb Ellis |
Approved revision: | b48f6a6f3c309d4747d88412b4adbd24d280fab6 |
Merge reported by: | MAAS Lander |
Merged at revision: | not available |
Proposed branch: | ~caleb-ellis/maas:grouping-improvements |
Merge into: | maas:master |
Diff against target: |
459 lines (+261/-20) 5 files modified
src/maasserver/static/js/angular/directives/machines_table.js (+70/-11) src/maasserver/static/js/angular/directives/tests/test_machines_table.js (+128/-0) src/maasserver/static/partials/machines-table.html (+27/-6) src/maasserver/static/scss/_base_forms.scss (+1/-1) src/maasserver/static/scss/_tables.scss (+35/-2) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Anthony Dillon | Approve | ||
Review via email: mp+366459@code.launchpad.net |
This proposal supersedes a proposal from 2019-04-24.
Commit message
Make machine groups selectable, collapsible and display count
Description of the change
## Done
- Added checkboxes to group headings, which when clicked either selects all unselected machines in a group, or unselects all machines in a group.
- Groups are now collapsible/
- Groups now have a count of the number of machines and the number of selected machines in that group
- Note there is currently a bug when using the "update your selection" link when action can't be performed on all machines, which has been present since the first grouping MP. This will get fixed as part of https:/
## QA
- Check that selecting a group selects all machines in that group and makes the background of the group white
- Check that unselecting a single machine in a group will cause the group checkbox to become unchecked
- Check that you can expand and collapse groups
- Check that the count underneath the group label is correct
- Check that the ungrouped list still looks and works correctly
## Screenshot
https:/
MAAS Lander (maas-lander) wrote : | # |
LANDING
-b grouping-
STATUS: FAILED BUILD
LOG: http://
- b48f6a6... by Caleb Ellis
-
fix linting
Preview Diff
1 | diff --git a/src/maasserver/static/js/angular/directives/machines_table.js b/src/maasserver/static/js/angular/directives/machines_table.js | |||
2 | index 672fb66..ffc2938 100644 | |||
3 | --- a/src/maasserver/static/js/angular/directives/machines_table.js | |||
4 | +++ b/src/maasserver/static/js/angular/directives/machines_table.js | |||
5 | @@ -9,7 +9,7 @@ | |||
6 | 9 | /* @ngInject */ | 9 | /* @ngInject */ |
7 | 10 | function maasMachinesTable( | 10 | function maasMachinesTable( |
8 | 11 | MachinesManager, NotificationsManager, UsersManager, | 11 | MachinesManager, NotificationsManager, UsersManager, |
10 | 12 | GeneralManager, $document, $window, $log) { | 12 | GeneralManager, $filter, $document, $window, $log) { |
11 | 13 | return { | 13 | return { |
12 | 14 | restrict: "E", | 14 | restrict: "E", |
13 | 15 | scope: { | 15 | scope: { |
14 | @@ -90,8 +90,11 @@ function maasMachinesTable( | |||
15 | 90 | "lock", | 90 | "lock", |
16 | 91 | "unlock" | 91 | "unlock" |
17 | 92 | ]; | 92 | ]; |
18 | 93 | |||
19 | 93 | $scope.openMenu = ""; | 94 | $scope.openMenu = ""; |
20 | 94 | 95 | ||
21 | 96 | $scope.closedGroups = []; | ||
22 | 97 | |||
23 | 95 | $scope.groupBy = (list, keyGetter) => { | 98 | $scope.groupBy = (list, keyGetter) => { |
24 | 96 | const map = new Map(); | 99 | const map = new Map(); |
25 | 97 | list.forEach((item) => { | 100 | list.forEach((item) => { |
26 | @@ -200,6 +203,32 @@ function maasMachinesTable( | |||
27 | 200 | $scope.updateAllChecked(); | 203 | $scope.updateAllChecked(); |
28 | 201 | }; | 204 | }; |
29 | 202 | 205 | ||
30 | 206 | $scope.toggleCheckGroup = groupLabel => { | ||
31 | 207 | const machineGroup = $scope.groupedMachines.find(group => { | ||
32 | 208 | return group.label === groupLabel | ||
33 | 209 | }); | ||
34 | 210 | if ($scope.getGroupSelectedState(groupLabel)) { | ||
35 | 211 | machineGroup.machines.forEach(machine => { | ||
36 | 212 | MachinesManager.unselectItem(machine.system_id); | ||
37 | 213 | }); | ||
38 | 214 | } else { | ||
39 | 215 | machineGroup.machines.forEach(machine => { | ||
40 | 216 | MachinesManager.selectItem(machine.system_id); | ||
41 | 217 | }); | ||
42 | 218 | } | ||
43 | 219 | $scope.updateAllChecked(); | ||
44 | 220 | }; | ||
45 | 221 | |||
46 | 222 | $scope.toggleOpenGroup = groupLabel => { | ||
47 | 223 | if ($scope.closedGroups.includes(groupLabel)) { | ||
48 | 224 | $scope.closedGroups = $scope.closedGroups.filter(group => ( | ||
49 | 225 | group !== groupLabel | ||
50 | 226 | )); | ||
51 | 227 | } else { | ||
52 | 228 | $scope.closedGroups = [...$scope.closedGroups, groupLabel]; | ||
53 | 229 | } | ||
54 | 230 | }; | ||
55 | 231 | |||
56 | 203 | // Sorts the table by predicate. | 232 | // Sorts the table by predicate. |
57 | 204 | $scope.sortTable = function(predicate) { | 233 | $scope.sortTable = function(predicate) { |
58 | 205 | $scope.table.predicate = predicate; | 234 | $scope.table.predicate = predicate; |
59 | @@ -308,6 +337,29 @@ function maasMachinesTable( | |||
60 | 308 | } | 337 | } |
61 | 309 | }; | 338 | }; |
62 | 310 | 339 | ||
63 | 340 | $scope.getGroupSelectedState = groupLabel => { | ||
64 | 341 | const machineGroup = $scope.groupedMachines.find(group => { | ||
65 | 342 | return group.label === groupLabel | ||
66 | 343 | }); | ||
67 | 344 | return !machineGroup.machines.some(machine => !machine.$selected); | ||
68 | 345 | }; | ||
69 | 346 | |||
70 | 347 | $scope.getGroupCountString = groupLabel => { | ||
71 | 348 | const machineGroup = $scope.groupedMachines.find(group => { | ||
72 | 349 | return group.label === groupLabel | ||
73 | 350 | }); | ||
74 | 351 | const machines = machineGroup.machines.length; | ||
75 | 352 | const selected | ||
76 | 353 | = machineGroup.machines.filter(item => item.$selected).length; | ||
77 | 354 | const machinesString | ||
78 | 355 | = `${machines} ${machines === 1 ? "machine" : "machines"}`; | ||
79 | 356 | |||
80 | 357 | if (selected && selected === machines) { | ||
81 | 358 | return `${machinesString} selected`; | ||
82 | 359 | } | ||
83 | 360 | return `${machinesString}${selected ? `, ${selected} selected` : ""}`; | ||
84 | 361 | }; | ||
85 | 362 | |||
86 | 311 | $scope.updateGroupedMachines = function(field) { | 363 | $scope.updateGroupedMachines = function(field) { |
87 | 312 | if ($scope.table.filteredMachines.length === 0) { return; } | 364 | if ($scope.table.filteredMachines.length === 0) { return; } |
88 | 313 | 365 | ||
89 | @@ -393,7 +445,7 @@ function maasMachinesTable( | |||
90 | 393 | ...(machines.get('Reserved') || []) | 445 | ...(machines.get('Reserved') || []) |
91 | 394 | ] | 446 | ] |
92 | 395 | } | 447 | } |
94 | 396 | ] | 448 | ]; |
95 | 397 | return; | 449 | return; |
96 | 398 | } | 450 | } |
97 | 399 | 451 | ||
98 | @@ -424,7 +476,7 @@ function maasMachinesTable( | |||
99 | 424 | label: 'none', | 476 | label: 'none', |
100 | 425 | machines: $scope.table.filteredMachines | 477 | machines: $scope.table.filteredMachines |
101 | 426 | } | 478 | } |
103 | 427 | ] | 479 | ]; |
104 | 428 | return; | 480 | return; |
105 | 429 | } | 481 | } |
106 | 430 | 482 | ||
107 | @@ -439,19 +491,18 @@ function maasMachinesTable( | |||
108 | 439 | $scope.updateGroupedMachines($scope.groupByLabel); | 491 | $scope.updateGroupedMachines($scope.groupByLabel); |
109 | 440 | }); | 492 | }); |
110 | 441 | 493 | ||
117 | 442 | // When the list of machines changes update grouping. | 494 | $scope.$watch("search", function() { |
118 | 443 | $scope.$watch("table.machines", function() { | 495 | $scope.table.filteredMachines |
119 | 444 | if ($scope.groupByLabel !== 'none') { | 496 | = $filter('nodesFilter')($scope.table.machines, $scope.search); |
120 | 445 | $scope.updateGroupedMachines($scope.groupByLabel); | 497 | }); |
115 | 446 | } | ||
116 | 447 | }, true); | ||
121 | 448 | 498 | ||
124 | 449 | // Watch a simplified list of machines for changes to power state, | 499 | // Watch simplified list of machines for changes to power state and status, |
125 | 450 | // then set transitional state accordingly. | 500 | // then make changes accordingly. |
126 | 451 | $scope.$watch( | 501 | $scope.$watch( |
127 | 452 | scope => | 502 | scope => |
128 | 453 | scope.table.machines.map(machine => ({ | 503 | scope.table.machines.map(machine => ({ |
129 | 454 | id: machine.id, | 504 | id: machine.id, |
130 | 505 | status: machine.status, | ||
131 | 455 | state: machine.power_state | 506 | state: machine.power_state |
132 | 456 | })), | 507 | })), |
133 | 457 | (newMachines, oldMachines) => { | 508 | (newMachines, oldMachines) => { |
134 | @@ -460,11 +511,19 @@ function maasMachinesTable( | |||
135 | 460 | oldMachines.find( | 511 | oldMachines.find( |
136 | 461 | machine => machine.id === newMachine.id | 512 | machine => machine.id === newMachine.id |
137 | 462 | ) || {}; | 513 | ) || {}; |
138 | 514 | |||
139 | 515 | // Check if power state has changed, then set transitional state | ||
140 | 463 | if (newMachine.state !== oldMachine.state) { | 516 | if (newMachine.state !== oldMachine.state) { |
141 | 464 | $scope.table.machines.find( | 517 | $scope.table.machines.find( |
142 | 465 | machine => machine.id === newMachine.id | 518 | machine => machine.id === newMachine.id |
143 | 466 | ).powerTransition = undefined; | 519 | ).powerTransition = undefined; |
144 | 467 | } | 520 | } |
145 | 521 | |||
146 | 522 | // Check if status has changed, then run function to regroup machines | ||
147 | 523 | if (newMachine.status !== oldMachine.status | ||
148 | 524 | && $scope.groupByLabel !== "none") { | ||
149 | 525 | $scope.updateGroupedMachines($scope.groupByLabel); | ||
150 | 526 | } | ||
151 | 468 | }); | 527 | }); |
152 | 469 | }, | 528 | }, |
153 | 470 | true | 529 | true |
154 | diff --git a/src/maasserver/static/js/angular/directives/tests/test_machines_table.js b/src/maasserver/static/js/angular/directives/tests/test_machines_table.js | |||
155 | index c1e7f2b..72ed5e7 100644 | |||
156 | --- a/src/maasserver/static/js/angular/directives/tests/test_machines_table.js | |||
157 | +++ b/src/maasserver/static/js/angular/directives/tests/test_machines_table.js | |||
158 | @@ -170,6 +170,51 @@ describe("maasMachinesTable", function() { | |||
159 | 170 | }); | 170 | }); |
160 | 171 | }); | 171 | }); |
161 | 172 | 172 | ||
162 | 173 | describe("toggleCheckGroup", () => { | ||
163 | 174 | it("selects all unselected machines in a group", () => { | ||
164 | 175 | const directive = compileDirective(); | ||
165 | 176 | const scope = directive.isolateScope(); | ||
166 | 177 | |||
167 | 178 | const machines = [makeMachine(), makeMachine(), makeMachine()]; | ||
168 | 179 | machines[0].status = 'New'; | ||
169 | 180 | machines[1].status = 'Broken'; | ||
170 | 181 | machines[2].status = 'New'; | ||
171 | 182 | scope.table.filteredMachines = machines; | ||
172 | 183 | scope.groupedMachines = [ | ||
173 | 184 | { label: 'New', machines: [machines[0], machines[2]] }, | ||
174 | 185 | { label: 'Broken', machines: [machines[1]]} | ||
175 | 186 | ]; | ||
176 | 187 | scope.toggleCheckGroup('New'); | ||
177 | 188 | |||
178 | 189 | expect(machines[0].$selected).toBe(true); | ||
179 | 190 | expect(machines[1].$selected).toBe(false); | ||
180 | 191 | expect(machines[2].$selected).toBe(true); | ||
181 | 192 | }); | ||
182 | 193 | |||
183 | 194 | it("unselects all machines in a group if all are selected", () => { | ||
184 | 195 | const directive = compileDirective(); | ||
185 | 196 | const scope = directive.isolateScope(); | ||
186 | 197 | |||
187 | 198 | const machines = [makeMachine(), makeMachine(), makeMachine()]; | ||
188 | 199 | machines[0].status = 'New'; | ||
189 | 200 | machines[0].$selected = true; | ||
190 | 201 | machines[1].status = 'Broken'; | ||
191 | 202 | machines[1].$selected = true; | ||
192 | 203 | machines[2].status = 'New'; | ||
193 | 204 | machines[2].$selected = true; | ||
194 | 205 | scope.table.filteredMachines = machines; | ||
195 | 206 | scope.groupedMachines = [ | ||
196 | 207 | { label: 'New', machines: [machines[0], machines[2]] }, | ||
197 | 208 | { label: 'Broken', machines: [machines[1]]} | ||
198 | 209 | ]; | ||
199 | 210 | scope.toggleCheckGroup('New'); | ||
200 | 211 | |||
201 | 212 | expect(machines[0].$selected).toBe(false); | ||
202 | 213 | expect(machines[1].$selected).toBe(true); | ||
203 | 214 | expect(machines[2].$selected).toBe(false); | ||
204 | 215 | }); | ||
205 | 216 | }); | ||
206 | 217 | |||
207 | 173 | describe("toggleChecked", function() { | 218 | describe("toggleChecked", function() { |
208 | 174 | 219 | ||
209 | 175 | it("selects machine", function() { | 220 | it("selects machine", function() { |
210 | @@ -194,6 +239,26 @@ describe("maasMachinesTable", function() { | |||
211 | 194 | }); | 239 | }); |
212 | 195 | }); | 240 | }); |
213 | 196 | 241 | ||
214 | 242 | describe("toggleOpenGroup", () => { | ||
215 | 243 | it("removes group from scope.closedGroups if present", () => { | ||
216 | 244 | const directive = compileDirective(); | ||
217 | 245 | const scope = directive.isolateScope(); | ||
218 | 246 | const group = makeName("group"); | ||
219 | 247 | scope.closedGroups = [group]; | ||
220 | 248 | scope.toggleOpenGroup(group); | ||
221 | 249 | expect(scope.closedGroups).toEqual([]); | ||
222 | 250 | }); | ||
223 | 251 | |||
224 | 252 | it("adds group to scope.closedGroups if not present", () => { | ||
225 | 253 | const directive = compileDirective(); | ||
226 | 254 | const scope = directive.isolateScope(); | ||
227 | 255 | const group = makeName("group"); | ||
228 | 256 | scope.closedGroups = []; | ||
229 | 257 | scope.toggleOpenGroup(group); | ||
230 | 258 | expect(scope.closedGroups).toEqual([group]); | ||
231 | 259 | }); | ||
232 | 260 | }); | ||
233 | 261 | |||
234 | 197 | describe("sortTable", function() { | 262 | describe("sortTable", function() { |
235 | 198 | 263 | ||
236 | 199 | it("sets predicate", function() { | 264 | it("sets predicate", function() { |
237 | @@ -922,4 +987,67 @@ describe("maasMachinesTable", function() { | |||
238 | 922 | { label: 'user1', machines: [machines[1]]}]); | 987 | { label: 'user1', machines: [machines[1]]}]); |
239 | 923 | }); | 988 | }); |
240 | 924 | }); | 989 | }); |
241 | 990 | |||
242 | 991 | describe("getGroupSelectedState", () => { | ||
243 | 992 | it("returns true if all machines in a group are selected", () => { | ||
244 | 993 | const directive = compileDirective(); | ||
245 | 994 | const scope = directive.isolateScope(); | ||
246 | 995 | |||
247 | 996 | const machines = [makeMachine(), makeMachine()]; | ||
248 | 997 | machines[0].status = 'New'; | ||
249 | 998 | machines[0].$selected = true; | ||
250 | 999 | machines[1].status = 'New'; | ||
251 | 1000 | machines[1].$selected = true; | ||
252 | 1001 | scope.table.filteredMachines = machines; | ||
253 | 1002 | scope.groupedMachines = [ | ||
254 | 1003 | { label: 'New', machines: [machines[0], machines[1]] } | ||
255 | 1004 | ]; | ||
256 | 1005 | scope.getGroupSelectedState('New'); | ||
257 | 1006 | |||
258 | 1007 | expect(scope.getGroupSelectedState('New')).toBe(true); | ||
259 | 1008 | }); | ||
260 | 1009 | |||
261 | 1010 | it("returns false if not all machines in a group are selected", () => { | ||
262 | 1011 | const directive = compileDirective(); | ||
263 | 1012 | const scope = directive.isolateScope(); | ||
264 | 1013 | |||
265 | 1014 | const machines = [makeMachine(), makeMachine()]; | ||
266 | 1015 | machines[0].status = 'New'; | ||
267 | 1016 | machines[0].$selected = true; | ||
268 | 1017 | machines[1].status = 'New'; | ||
269 | 1018 | scope.table.filteredMachines = machines; | ||
270 | 1019 | scope.groupedMachines = [ | ||
271 | 1020 | { label: 'New', machines: [machines[0], machines[1]] } | ||
272 | 1021 | ]; | ||
273 | 1022 | scope.getGroupSelectedState('New'); | ||
274 | 1023 | |||
275 | 1024 | expect(scope.getGroupSelectedState('New')).toBe(false); | ||
276 | 1025 | }); | ||
277 | 1026 | }); | ||
278 | 1027 | |||
279 | 1028 | describe("getGroupCountString", () => { | ||
280 | 1029 | it(`correctly returns a string of the number of machines in a group, | ||
281 | 1030 | and the selected machines in that group`, () => { | ||
282 | 1031 | const directive = compileDirective(); | ||
283 | 1032 | const scope = directive.isolateScope(); | ||
284 | 1033 | const machines = Array.from(Array(6)).map(makeMachine); | ||
285 | 1034 | machines[0].$selected = true; | ||
286 | 1035 | machines[1].$selected = true; | ||
287 | 1036 | machines[2].$selected = true; | ||
288 | 1037 | scope.groupedMachines = [ | ||
289 | 1038 | { label: 'New', machines: [machines[0], machines[1]] }, | ||
290 | 1039 | { label: 'Ready', machines: [machines[2], machines[3]] }, | ||
291 | 1040 | { label: 'Failed', machines: [machines[4], machines[5]] } | ||
292 | 1041 | ]; | ||
293 | 1042 | |||
294 | 1043 | expect( | ||
295 | 1044 | scope.getGroupCountString('New')).toBe("2 machines selected"); | ||
296 | 1045 | expect( | ||
297 | 1046 | scope.getGroupCountString('Ready')).toBe( | ||
298 | 1047 | "2 machines, 1 selected"); | ||
299 | 1048 | expect( | ||
300 | 1049 | scope.getGroupCountString('Failed')).toBe( | ||
301 | 1050 | "2 machines"); | ||
302 | 1051 | }); | ||
303 | 1052 | }); | ||
304 | 925 | }); | 1053 | }); |
305 | diff --git a/src/maasserver/static/partials/machines-table.html b/src/maasserver/static/partials/machines-table.html | |||
306 | index abd1366..a23d462 100644 | |||
307 | --- a/src/maasserver/static/partials/machines-table.html | |||
308 | +++ b/src/maasserver/static/partials/machines-table.html | |||
309 | @@ -1,6 +1,6 @@ | |||
311 | 1 | <table data-ng-repeat="group in groupedMachines" data-ng-if="group.machines.length" class="p-table--sortable p-table--machines" role="grid"> | 1 | <table class="p-table--sortable p-table--machines" role="grid"> |
312 | 2 | <thead> | 2 | <thead> |
314 | 3 | <tr class="p-table__header p-table__row" data-ng-if="$first"> | 3 | <tr class="p-table__header p-table__row"> |
315 | 4 | <th class="p-table__col--name p-double-row u-align--left"> | 4 | <th class="p-table__col--name p-double-row u-align--left"> |
316 | 5 | <div class="p-double-row__checkbox" data-ng-if="!hideActions"> | 5 | <div class="p-double-row__checkbox" data-ng-if="!hideActions"> |
317 | 6 | <input class="checkbox" type="checkbox" data-ng-click="toggleCheckAll()" data-ng-checked="table.allViewableChecked" id="check-all" data-ng-disabled="ngDisabled()" /> | 6 | <input class="checkbox" type="checkbox" data-ng-click="toggleCheckAll()" data-ng-checked="table.allViewableChecked" id="check-all" data-ng-disabled="ngDisabled()" /> |
318 | @@ -50,11 +50,32 @@ | |||
319 | 50 | <th class="p-table__col--disks u-align--right" role="columnheader" data-ng-click="sortTable('physical_disk_count')" data-ng-class="{'is-sorted': table.predicate === 'physical_disk_count', 'sort-asc': table.reverse === false, 'sort-desc': table.reverse === true}" title="Disks">Disks</th> | 50 | <th class="p-table__col--disks u-align--right" role="columnheader" data-ng-click="sortTable('physical_disk_count')" data-ng-class="{'is-sorted': table.predicate === 'physical_disk_count', 'sort-asc': table.reverse === false, 'sort-desc': table.reverse === true}" title="Disks">Disks</th> |
320 | 51 | <th class="p-table__col--storage u-align--right" role="columnheader" data-ng-click="sortTable('storage')" data-ng-class="{'is-sorted': table.predicate === 'storage', 'sort-asc': table.reverse === false, 'sort-desc': table.reverse === true}" title="Storage">Storage</th> | 51 | <th class="p-table__col--storage u-align--right" role="columnheader" data-ng-click="sortTable('storage')" data-ng-class="{'is-sorted': table.predicate === 'storage', 'sort-asc': table.reverse === false, 'sort-desc': table.reverse === true}" title="Storage">Storage</th> |
321 | 52 | </tr> | 52 | </tr> |
323 | 53 | <tr class="p-table__group p-table__row" data-ng-if="group.label != 'none' && group.machines.length && !actionOption && search != 'in:(Selected)'"> | 53 | </thead> |
324 | 54 | </table> | ||
325 | 55 | <table data-ng-repeat="group in groupedMachines" data-ng-if="group.machines.length" class="p-table--machines" role="grid"> | ||
326 | 56 | <thead> | ||
327 | 57 | <tr | ||
328 | 58 | class="p-table__group p-table__row" | ||
329 | 59 | data-ng-class="{ | ||
330 | 60 | 'is-active': getGroupSelectedState(group.label), | ||
331 | 61 | 'p-table__placeholder': group.label === 'none' | ||
332 | 62 | }" | ||
333 | 63 | > | ||
334 | 54 | <th class="p-table__group-label p-table__col--name p-double-row u-align--left"> | 64 | <th class="p-table__group-label p-table__col--name p-double-row u-align--left"> |
336 | 55 | {$ group.label $} | 65 | <input type="checkbox" id="{$ group.label $}" data-ng-click="toggleCheckGroup(group.label)" data-ng-checked="getGroupSelectedState(group.label)" /> |
337 | 66 | <label class="p-checkbox--action u-no-margin--bottom" for="{$ group.label $}">{$ group.label $}</label> | ||
338 | 67 | <div class="p-muted-text">{$ getGroupCountString(group.label) $}</div> | ||
339 | 68 | </th> | ||
340 | 69 | <th class="p-table__col--power" role="columnheader"> | ||
341 | 70 | <button class="p-table__group-toggle p-button--base p-button--narrow" data-ng-click="toggleOpenGroup(group.label)"> | ||
342 | 71 | <i data-ng-class="{ | ||
343 | 72 | 'p-icon--minus': !closedGroups.includes(group.label), | ||
344 | 73 | 'p-icon--plus': closedGroups.includes(group.label) | ||
345 | 74 | }"> | ||
346 | 75 | Toggle | ||
347 | 76 | </i> | ||
348 | 77 | </button> | ||
349 | 56 | </th> | 78 | </th> |
350 | 57 | <th class="p-table__col--power" role="columnheader"></th> | ||
351 | 58 | <th class="p-table__col--status p-double-row" title="Status"></th> | 79 | <th class="p-table__col--status p-double-row" title="Status"></th> |
352 | 59 | <th class="p-table__col--owner p-double-row" title="Owner, Tags"></th> | 80 | <th class="p-table__col--owner p-double-row" title="Owner, Tags"></th> |
353 | 60 | <th class="p-table__col--pool" title="Pool" role="columnheader"></th> | 81 | <th class="p-table__col--pool" title="Pool" role="columnheader"></th> |
354 | @@ -66,7 +87,7 @@ | |||
355 | 66 | <th class="p-table__col--storage u-align--right" role="columnheader"></th> | 87 | <th class="p-table__col--storage u-align--right" role="columnheader"></th> |
356 | 67 | </tr> | 88 | </tr> |
357 | 68 | </thead> | 89 | </thead> |
359 | 69 | <tbody vs-repeat> | 90 | <tbody vs-repeat data-ng-if="!closedGroups.includes(group.label)"> |
360 | 70 | <tr class="p-table__row" data-ng-repeat="node in group.machines | nodesFilter:search | orderBy:table.predicate:table.reverse track by node.system_id" | 91 | <tr class="p-table__row" data-ng-repeat="node in group.machines | nodesFilter:search | orderBy:table.predicate:table.reverse track by node.system_id" |
361 | 71 | data-ng-class="{ 'table--error': machineHasError({ $machine: node }), | 92 | data-ng-class="{ 'table--error': machineHasError({ $machine: node }), |
362 | 72 | 'is-active': node.$selected, | 93 | 'is-active': node.$selected, |
363 | diff --git a/src/maasserver/static/scss/_base_forms.scss b/src/maasserver/static/scss/_base_forms.scss | |||
364 | index 79be0e7..f8c3c38 100644 | |||
365 | --- a/src/maasserver/static/scss/_base_forms.scss | |||
366 | +++ b/src/maasserver/static/scss/_base_forms.scss | |||
367 | @@ -57,7 +57,7 @@ | |||
368 | 57 | } | 57 | } |
369 | 58 | } | 58 | } |
370 | 59 | 59 | ||
372 | 60 | th & + label { | 60 | th:not(.p-table__group-label) & + label { |
373 | 61 | &::before { | 61 | &::before { |
374 | 62 | top: .65em; | 62 | top: .65em; |
375 | 63 | } | 63 | } |
376 | diff --git a/src/maasserver/static/scss/_tables.scss b/src/maasserver/static/scss/_tables.scss | |||
377 | index 007cc4d..aa5459d 100644 | |||
378 | --- a/src/maasserver/static/scss/_tables.scss | |||
379 | +++ b/src/maasserver/static/scss/_tables.scss | |||
380 | @@ -374,23 +374,38 @@ | |||
381 | 374 | 374 | ||
382 | 375 | .p-table__group { | 375 | .p-table__group { |
383 | 376 | border: 0; | 376 | border: 0; |
384 | 377 | position: relative; | ||
385 | 377 | 378 | ||
386 | 378 | .p-table__group-label { | 379 | .p-table__group-label { |
387 | 379 | color: $color-dark; | 380 | color: $color-dark; |
388 | 380 | font-size: 1rem; | 381 | font-size: 1rem; |
390 | 381 | padding: $spv-inter--regular 0 $spv-inter--regular $group-padding-left; | 382 | padding: $spv-intra 0 $spv-intra $sph-intra--condensed; |
391 | 382 | text-transform: none; | 383 | text-transform: none; |
392 | 383 | } | 384 | } |
393 | 385 | |||
394 | 386 | .p-table__group-toggle { | ||
395 | 387 | padding: 0 $sp-x-small; | ||
396 | 388 | position: absolute; | ||
397 | 389 | right: $spv-intra--expanded; | ||
398 | 390 | top: $spv-inter--regular; | ||
399 | 391 | } | ||
400 | 392 | |||
401 | 393 | &.is-open { | ||
402 | 394 | @include vf-icon-minus($color-mid-dark); | ||
403 | 395 | } | ||
404 | 384 | } | 396 | } |
405 | 385 | 397 | ||
406 | 386 | .p-table__row { | 398 | .p-table__row { |
407 | 387 | position: relative; | 399 | position: relative; |
408 | 388 | 400 | ||
409 | 401 | &::after { | ||
410 | 402 | content: ''; | ||
411 | 403 | } | ||
412 | 404 | |||
413 | 389 | &.is-grouped { | 405 | &.is-grouped { |
414 | 390 | border: 0; | 406 | border: 0; |
415 | 391 | 407 | ||
416 | 392 | &::after { | 408 | &::after { |
417 | 393 | content: ''; | ||
418 | 394 | position: absolute; | 409 | position: absolute; |
419 | 395 | left: $group-padding-left; | 410 | left: $group-padding-left; |
420 | 396 | right: 0; | 411 | right: 0; |
421 | @@ -463,6 +478,15 @@ | |||
422 | 463 | } | 478 | } |
423 | 464 | } | 479 | } |
424 | 465 | 480 | ||
425 | 481 | // Needed to keep widths of machine table correct when no grouping selected | ||
426 | 482 | .p-table__placeholder { | ||
427 | 483 | * { | ||
428 | 484 | height: 0 !important; | ||
429 | 485 | padding: 0 !important; | ||
430 | 486 | visibility: hidden; | ||
431 | 487 | } | ||
432 | 488 | } | ||
433 | 489 | |||
434 | 466 | .p-icon--placeholder { | 490 | .p-icon--placeholder { |
435 | 467 | @extend %icon; | 491 | @extend %icon; |
436 | 468 | height: map-get($icon-sizes, default); | 492 | height: map-get($icon-sizes, default); |
437 | @@ -474,6 +498,10 @@ | |||
438 | 474 | max-width: 500px; | 498 | max-width: 500px; |
439 | 475 | white-space: inherit; | 499 | white-space: inherit; |
440 | 476 | } | 500 | } |
441 | 501 | |||
442 | 502 | &:last-of-type::after { | ||
443 | 503 | display: none; | ||
444 | 504 | } | ||
445 | 477 | } | 505 | } |
446 | 478 | 506 | ||
447 | 479 | .p-table--controller-interfaces { | 507 | .p-table--controller-interfaces { |
448 | @@ -705,6 +733,11 @@ | |||
449 | 705 | color: $color-mid-dark; | 733 | color: $color-mid-dark; |
450 | 706 | margin: 0; | 734 | margin: 0; |
451 | 707 | padding: 0; | 735 | padding: 0; |
452 | 736 | |||
453 | 737 | .p-table__group-label & { | ||
454 | 738 | font-weight: 300; | ||
455 | 739 | padding-left: $sp-x-large; | ||
456 | 740 | } | ||
457 | 708 | } | 741 | } |
458 | 709 | 742 | ||
459 | 710 | .p-link--muted { | 743 | .p-link--muted { |
LGTM +1