Merge lp:~blake-rouse/maas/fix-1566920 into lp:~maas-committers/maas/trunk

Proposed by Blake Rouse
Status: Merged
Approved by: Blake Rouse
Approved revision: no longer in the source branch.
Merged at revision: 4899
Proposed branch: lp:~blake-rouse/maas/fix-1566920
Merge into: lp:~maas-committers/maas/trunk
Diff against target: 710 lines (+240/-181)
6 files modified
src/maasserver/static/js/angular/controllers/node_details.js (+54/-81)
src/maasserver/static/js/angular/controllers/tests/test_node_details.js (+129/-42)
src/maasserver/static/js/angular/factories/general.js (+1/-2)
src/maasserver/static/js/angular/factories/tests/test_general.js (+3/-2)
src/maasserver/static/partials/node-details.html (+50/-51)
src/maasserver/static/partials/node-events.html (+3/-3)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1566920
Reviewer Review Type Date Requested Status
Andres Rodriguez (community) Needs Fixing
Mike Pontillo (community) Approve
Review via email: mp+291416@code.launchpad.net

Commit message

Fix handling no connected rack controllers on the machine details page. Cleanup error messages on the machine details page. Fix power_type options from being reset mid type.

To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote :

Looks good to me. One comment/nit below.

review: Approve
Revision history for this message
Andres Rodriguez (andreserl) wrote :

missing changelog entry

review: Needs Fixing

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/static/js/angular/controllers/node_details.js'
2--- src/maasserver/static/js/angular/controllers/node_details.js 2016-03-28 13:54:47 +0000
3+++ src/maasserver/static/js/angular/controllers/node_details.js 2016-04-08 20:38:44 +0000
4@@ -40,21 +40,6 @@
5 $scope.checkingPower = false;
6 $scope.devices = [];
7
8- // Holds errors that are displayed on the details page.
9- $scope.errors = {
10- invalid_arch: {
11- viewable: false,
12- message: "This node has an invalid architecture. Update the " +
13- "architecture for this node in the summary section below."
14- },
15- missing_power: {
16- viewable: false,
17- message: "This node does not have a power type set and " +
18- "MAAS will be unable to control it. Update the power " +
19- "information in the power section below."
20- }
21- };
22-
23 // Node header section.
24 $scope.header = {
25 editing: false,
26@@ -110,51 +95,6 @@
27 summaryType: 'yaml'
28 };
29
30- // Show given error.
31- function showError(name) {
32- $scope.errors[name].viewable = true;
33- }
34-
35- // Hide given error.
36- function hideError(name) {
37- $scope.errors[name].viewable = false;
38- }
39-
40- // Return true if the error is viewable.
41- function isErrorViewable(name) {
42- return $scope.errors[name].viewable;
43- }
44-
45- // Return true if the architecture for the given node is invalid.
46- function hasInvalidArchitecture(node) {
47- return (
48- node.architecture === "" ||
49- $scope.summary.architecture.options.indexOf(
50- node.architecture) === -1);
51- }
52-
53- // Update the shown errors based on the status of the node.
54- function updateErrors() {
55- if('controller' !== $routeParams.type) {
56- // Check if the nodes power type is null, if so then show the
57- // missing_power error.
58- if($scope.node.power_type === "") {
59- showError("missing_power");
60- } else {
61- hideError("missing_power");
62- }
63-
64- // Show architecture error if the node has no architecture
65- // or if the current architecture is not in the available
66- // architectures.
67- if(hasInvalidArchitecture($scope.node)) {
68- showError("invalid_arch");
69- } else {
70- hideError("invalid_arch");
71- }
72- }
73- }
74-
75 // Updates the page title.
76 function updateTitle() {
77 if($scope.node && $scope.node.fqdn) {
78@@ -206,9 +146,6 @@
79
80 // Updates the currently selected items in the power section.
81 function updatePower() {
82- // Update the viewable errors.
83- updateErrors();
84-
85 // Do not update the selected items, when editing this would
86 // cause the users selection to change.
87 if($scope.power.editing) {
88@@ -242,9 +179,6 @@
89
90 // Updates the currently selected items in the summary section.
91 function updateSummary() {
92- // Update the viewable errors.
93- updateErrors();
94-
95 // Do not update the selected items, when editing this would
96 // cause the users selection to change.
97 if($scope.summary.editing) {
98@@ -260,7 +194,9 @@
99 // Force editing mode on, if the architecture is invalid. This is
100 // placed at the bottom because we wanted the selected items to
101 // be filled in at least once.
102- if($scope.canEdit() && hasInvalidArchitecture($scope.node)) {
103+ if($scope.canEdit() &&
104+ $scope.hasUsableArchitectures() &&
105+ $scope.hasInvalidArchitecture()) {
106 $scope.summary.editing = true;
107 }
108 }
109@@ -434,6 +370,7 @@
110 // are updated.
111 $scope.$watch("node.power_type", updatePower);
112 $scope.$watch("node.power_parameters", updatePower);
113+ $scope.$watchCollection("power_types", updatePower);
114
115 // Update the services when the services list is updated.
116 $scope.$watch("node.service_ids", updateServices);
117@@ -446,18 +383,6 @@
118 $scope.$watch("node.installation_results", updateMachineOutput);
119 }
120
121- // Update the node with new data on the region.
122- $scope.updateNode = function(node) {
123- return $scope.nodesManager.updateItem(node).then(function(node) {
124- updateHeader();
125- updateSummary();
126- }, function(error) {
127- console.log(error);
128- updateHeader();
129- updateSummary();
130- });
131- };
132-
133 // Called when the node has been loaded.
134 function nodeLoaded(node) {
135 $scope.node = node;
136@@ -479,6 +404,18 @@
137 }
138 }
139
140+ // Update the node with new data on the region.
141+ $scope.updateNode = function(node) {
142+ return $scope.nodesManager.updateItem(node).then(function(node) {
143+ updateHeader();
144+ updateSummary();
145+ }, function(error) {
146+ console.log(error);
147+ updateHeader();
148+ updateSummary();
149+ });
150+ };
151+
152 // Called for autocomplete when the user is typing a tag name.
153 $scope.tagsAutocomplete = function(query) {
154 return TagsManager.autocomplete(query);
155@@ -681,6 +618,32 @@
156 return UsersManager.isSuperUser();
157 };
158
159+ // Return true if their are usable architectures.
160+ $scope.hasUsableArchitectures = function() {
161+ return $scope.summary.architecture.options.length > 0;
162+ };
163+
164+ // Return the placeholder text for the architecture dropdown.
165+ $scope.getArchitecturePlaceholder = function() {
166+ if($scope.hasUsableArchitectures()) {
167+ return "Choose an architecture";
168+ } else {
169+ return "-- No usable architectures --";
170+ }
171+ };
172+
173+ // Return true if the saved architecture is invalid.
174+ $scope.hasInvalidArchitecture = function() {
175+ if(angular.isObject($scope.node)) {
176+ return (
177+ $scope.node.architecture === "" ||
178+ $scope.summary.architecture.options.indexOf(
179+ $scope.node.architecture) === -1);
180+ } else {
181+ return false;
182+ }
183+ };
184+
185 // Return true if the current architecture selection is invalid.
186 $scope.invalidArchitecture = function() {
187 return (
188@@ -689,9 +652,19 @@
189 $scope.summary.architecture.selected) === -1);
190 };
191
192+ // Return true if at least a rack controller is connected to the
193+ // region controller.
194+ $scope.isRackControllerConnected = function() {
195+ // If power_types exist then a rack controller is connected.
196+ return $scope.power_types.length > 0;
197+ };
198+
199 // Return true when the edit buttons can be clicked.
200 $scope.canEdit = function() {
201- return $scope.isSuperUser() && !$scope.isController;
202+ return (
203+ $scope.isRackControllerConnected() &&
204+ $scope.isSuperUser() &&
205+ !$scope.isController);
206 };
207
208 // Called to edit the node name.
209@@ -761,7 +734,7 @@
210 // Called to cancel editing in the summary section.
211 $scope.cancelEditSummary = function() {
212 // Leave edit mode only if node has valid architecture.
213- if(!hasInvalidArchitecture($scope.node)) {
214+ if(!$scope.hasInvalidArchitecture()) {
215 $scope.summary.editing = false;
216 }
217
218
219=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details.js'
220--- src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2016-03-28 13:54:47 +0000
221+++ src/maasserver/static/js/angular/controllers/tests/test_node_details.js 2016-04-08 20:38:44 +0000
222@@ -328,37 +328,25 @@
223 expect($rootScope.title).toBe(node.fqdn);
224 });
225
226- it("invalid_arch error visible if node architecture empty", function() {
227- node.architecture = "";
228-
229- var controller = makeControllerResolveSetActiveItem();
230- expect($scope.errors.invalid_arch.viewable).toBe(true);
231- });
232-
233- it("invalid_arch error visible if node architecture not present",
234- function() {
235- GeneralManager._data.architectures.data = [makeName("arch")];
236-
237- var controller = makeControllerResolveSetActiveItem();
238- expect($scope.errors.invalid_arch.viewable).toBe(true);
239- });
240-
241- it("invalid_arch error not visible if node architecture present",
242- function() {
243- GeneralManager._data.architectures.data = [node.architecture];
244-
245- var controller = makeControllerResolveSetActiveItem();
246- expect($scope.errors.invalid_arch.viewable).toBe(false);
247- });
248-
249 it("summary section placed in edit mode if architecture blank",
250 function() {
251 node.architecture = "";
252+ GeneralManager._data.power_types.data = [{}];
253+ GeneralManager._data.architectures.data = ["amd64/generic"];
254
255 var controller = makeControllerResolveSetActiveItem();
256 expect($scope.summary.editing).toBe(true);
257 });
258
259+ it("summary section not placed in edit mode if no usable architectures",
260+ function() {
261+ node.architecture = "";
262+ GeneralManager._data.power_types.data = [{}];
263+
264+ var controller = makeControllerResolveSetActiveItem();
265+ expect($scope.summary.editing).toBe(false);
266+ });
267+
268 it("summary section not placed in edit mode if architecture present",
269 function() {
270 GeneralManager._data.architectures.data = [node.architecture];
271@@ -375,25 +363,21 @@
272 expect($scope.summary.tags).toEqual(node.tags);
273 });
274
275- it("missing_power error visible if node power_type empty", function() {
276- var controller = makeControllerResolveSetActiveItem();
277- expect($scope.errors.missing_power.viewable).toBe(true);
278- });
279-
280- it("missing_power error not visible if node power_type empty", function() {
281- node.power_type = makeName("power");
282-
283- var controller = makeControllerResolveSetActiveItem();
284- expect($scope.errors.missing_power.viewable).toBe(false);
285- });
286-
287 it("power section placed in edit mode if power_type blank", function() {
288+ GeneralManager._data.power_types.data = [{}];
289+
290 var controller = makeControllerResolveSetActiveItem();
291 expect($scope.power.editing).toBe(true);
292 });
293
294+ it("power section not placed in edit mode if no power_types", function() {
295+ var controller = makeControllerResolveSetActiveItem();
296+ expect($scope.power.editing).toBe(false);
297+ });
298+
299 it("power section not placed in edit mode if power_type", function() {
300 node.power_type = makeName("power");
301+ GeneralManager._data.power_types.data = [{}];
302
303 var controller = makeControllerResolveSetActiveItem();
304 expect($scope.power.editing).toBe(false);
305@@ -564,7 +548,8 @@
306 expect(watchCollections).toEqual([
307 $scope.summary.architecture.options,
308 $scope.summary.min_hwe_kernel.options,
309- $scope.summary.zone.options
310+ $scope.summary.zone.options,
311+ "power_types"
312 ]);
313 });
314
315@@ -1190,6 +1175,75 @@
316 });
317 });
318
319+ describe("hasUsableArchitectures", function() {
320+
321+ it("returns true if architecture available", function() {
322+ var controller = makeController();
323+ $scope.summary.architecture.options = ["amd64/generic"];
324+ expect($scope.hasUsableArchitectures()).toBe(true);
325+ });
326+
327+ it("returns false if no architecture available", function() {
328+ var controller = makeController();
329+ $scope.summary.architecture.options = [];
330+ expect($scope.hasUsableArchitectures()).toBe(false);
331+ });
332+ });
333+
334+ describe("getArchitecturePlaceholder", function() {
335+
336+ it("returns choose if architecture available", function() {
337+ var controller = makeController();
338+ $scope.summary.architecture.options = ["amd64/generic"];
339+ expect($scope.getArchitecturePlaceholder()).toBe(
340+ "Choose an architecture");
341+ });
342+
343+ it("returns error if no architecture available", function() {
344+ var controller = makeController();
345+ $scope.summary.architecture.options = [];
346+ expect($scope.getArchitecturePlaceholder()).toBe(
347+ "-- No usable architectures --");
348+ });
349+ });
350+
351+ describe("hasInvalidArchitecture", function() {
352+
353+ it("returns false if node is null", function() {
354+ var controller = makeController();
355+ $scope.node = null;
356+ $scope.summary.architecture.options = ["amd64/generic"];
357+ expect($scope.hasInvalidArchitecture()).toBe(false);
358+ });
359+
360+ it("returns true if node.architecture is blank", function() {
361+ var controller = makeController();
362+ $scope.node = {
363+ architecture: ""
364+ };
365+ $scope.summary.architecture.options = ["amd64/generic"];
366+ expect($scope.hasInvalidArchitecture()).toBe(true);
367+ });
368+
369+ it("returns true if node.architecture not in options", function() {
370+ var controller = makeController();
371+ $scope.node = {
372+ architecture: "i386/generic"
373+ };
374+ $scope.summary.architecture.options = ["amd64/generic"];
375+ expect($scope.hasInvalidArchitecture()).toBe(true);
376+ });
377+
378+ it("returns false if node.architecture in options", function() {
379+ var controller = makeController();
380+ $scope.node = {
381+ architecture: "amd64/generic"
382+ };
383+ $scope.summary.architecture.options = ["amd64/generic"];
384+ expect($scope.hasInvalidArchitecture()).toBe(false);
385+ });
386+ });
387+
388 describe("invalidArchitecture", function() {
389
390 it("returns true if selected architecture empty", function() {
391@@ -1214,25 +1268,58 @@
392 });
393 });
394
395+ describe("isRackControllerConnected", function() {
396+
397+ it("returns false no power_types", function() {
398+ var controller = makeController();
399+ $scope.power_types = [];
400+ expect($scope.isRackControllerConnected()).toBe(false);
401+ });
402+
403+ it("returns true if power_types", function() {
404+ var controller = makeController();
405+ $scope.power_types = [{}];
406+ expect($scope.isRackControllerConnected()).toBe(true);
407+ });
408+ });
409+
410 describe("canEdit", function() {
411
412- it("returns false if not super user and not controller", function() {
413+ it("returns false if not super user", function() {
414 var controller = makeController();
415 $scope.isController = false;
416 spyOn($scope, "isSuperUser").and.returnValue(false);
417+ spyOn(
418+ $scope,
419+ "isRackControllerConnected").and.returnValue(true);
420 expect($scope.canEdit()).toBe(false);
421 });
422
423- it("returns false if super user and controller", function() {
424+ it("returns false if controller", function() {
425 var controller = makeController();
426 $scope.isController = true;
427- expect($scope.canEdit()).toBe(false);
428- });
429-
430- it("returns true if super user and not controller",
431+ spyOn(
432+ $scope,
433+ "isRackControllerConnected").and.returnValue(true);
434+ expect($scope.canEdit()).toBe(false);
435+ });
436+
437+ it("returns false if rack disconnected", function() {
438+ var controller = makeController();
439+ $scope.isController = false;
440+ spyOn(
441+ $scope,
442+ "isRackControllerConnected").and.returnValue(false);
443+ expect($scope.canEdit()).toBe(false);
444+ });
445+
446+ it("returns true if super user, rack connected, not controller",
447 function() {
448 var controller = makeController();
449 $scope.isController = false;
450+ spyOn(
451+ $scope,
452+ "isRackControllerConnected").and.returnValue(true);
453 expect($scope.canEdit()).toBe(true);
454 });
455 });
456
457=== modified file 'src/maasserver/static/js/angular/factories/general.js'
458--- src/maasserver/static/js/angular/factories/general.js 2016-03-28 13:54:47 +0000
459+++ src/maasserver/static/js/angular/factories/general.js 2016-04-08 20:38:44 +0000
460@@ -108,7 +108,7 @@
461 polling: false,
462 nextPromise: null,
463 replaceData: function(oldData, newData) {
464- // Update or add new power types.
465+ // Add new power types.
466 var i, j, newPowerType, oldPowerType;
467 for(i = 0; i < newData.length; i++) {
468 newPowerType = newData[i];
469@@ -116,7 +116,6 @@
470 for(j = 0; j < oldData.length; j++) {
471 oldPowerType = oldData[j];
472 if(newPowerType.name === oldPowerType.name) {
473- angular.copy(newPowerType, oldPowerType);
474 newItem = false;
475 break;
476 }
477
478=== modified file 'src/maasserver/static/js/angular/factories/tests/test_general.js'
479--- src/maasserver/static/js/angular/factories/tests/test_general.js 2016-03-28 13:54:47 +0000
480+++ src/maasserver/static/js/angular/factories/tests/test_general.js 2016-04-08 20:38:44 +0000
481@@ -169,7 +169,7 @@
482 expect(data).toEqual([newPowerType]);
483 });
484
485- it("updates power_type in array to be same object", function() {
486+ it("doesnt update power_type in array to be same object", function() {
487 var oldPowerType = {
488 name: makeName("power"),
489 fields: [
490@@ -188,7 +188,8 @@
491 ]
492 };
493 replaceData(data, [newPowerType]);
494- expect(data).toEqual([newPowerType]);
495+ expect(data).not.toEqual([newPowerType]);
496+ expect(data[0]).toEqual(oldPowerType);
497 expect(data[0]).toBe(oldPowerType);
498 });
499
500
501=== modified file 'src/maasserver/static/partials/node-details.html'
502--- src/maasserver/static/partials/node-details.html 2016-03-28 13:54:47 +0000
503+++ src/maasserver/static/partials/node-details.html 2016-04-08 20:38:44 +0000
504@@ -137,19 +137,24 @@
505 </div>
506 </div>
507 </header>
508- <div class="inner-wrapper">
509- <ul class="flash-messages">
510- <li class="flash-messages__item error ng-hide"
511- data-ng-repeat="(error_type, error) in errors"
512- data-ng-show="error.viewable">{$ error.message $}</li>
513- </ul>
514- </div>
515 <div class="row">
516 <div class="inner-wrapper">
517 <form>
518 <div class="twelve-col">
519 <h2 class="title" data-ng-hide="isController">Machine summary</h2>
520 <h2 class="title" data-ng-show="isController">Controller summary</h2>
521+ <ul class="flash-messages" data-ng-if="isSuperUser()">
522+ <li class="flash-messages__item error"
523+ data-ng-if="!isController && !isRackControllerConnected()">Editing is currently disabled because no rack controller is currently connected to the region.</li>
524+ <li class="flash-messages__item error"
525+ data-ng-if="!isController && hasInvalidArchitecture() && isRackControllerConnected() && hasUsableArchitectures()">
526+ This machine currently has an invalid architecture. Update the architecture of this machine to make it deployable.
527+ </li>
528+ <li class="flash-messages__item error"
529+ data-ng-if="!isController && hasInvalidArchitecture() && isRackControllerConnected() && !hasUsableArchitectures()">
530+ No boot images have been imported for a valid architecture to be selected. Visit the <a href="images">images page</a> to start the import process.
531+ </li>
532+ </ul>
533 </div>
534 <fieldset class="six-col">
535 <div class="ng-hide" data-ng-show="isSuperUser() && !isController">
536@@ -167,12 +172,12 @@
537 <div class="inline">
538 <label for="architecture" class="two-col">Architecture</label>
539 <div class="three-col">
540- <select name="architecture" id="architecture" placeholder="Choose an architecture"
541+ <select name="architecture" id="architecture"
542 data-ng-disabled="!summary.editing"
543 data-ng-model="summary.architecture.selected"
544 data-ng-options="arch for arch in summary.architecture.options"
545 data-ng-class="{ invalid: invalidArchitecture() }">
546- <option value="" disabled="disabled">Choose an architecture</option>
547+ <option value="" disabled="disabled">{$ getArchitecturePlaceholder() $}</option>
548 </select>
549 </div>
550 </div>
551@@ -332,32 +337,41 @@
552 <div class="inner-wrapper">
553 <form class="disabled">
554 <div class="twelve-col">
555- <h2 class="title">Power</h2>
556- </div>
557- <div class="twelve-col ng-hide info" data-ng-show="power.type.name == 'manual'">
558- <ul class="flash-messages">
559- <li class="flash-messages__item info">
560- Power control for this power type will need to be handled manually.
561- </li>
562- </ul>
563- </div>
564- <div class="twelve-col ng-hide error" data-ng-show="hasPowerError()">
565- <ul class="flash-messages">
566- <li class="flash-messages__item error">
567+ <h2 class="title left">Power</h2>
568+ </div>
569+ <div class="twelve-col">
570+ <ul class="flash-messages">
571+ <li class="flash-messages__item error" data-ng-if="!isRackControllerConnected()">
572+ Editing is currently disabled because no rack controller is current connected to the region.
573+ </li>
574+ <li class="flash-messages__item error" data-ng-if="isRackControllerConnected() && node.power_type == ''">
575+ This node does not have a power type set and MAAS will be unable to control it. Update the power
576+ information below.
577+ </li>
578+ <li class="flash-messages__item error" data-ng-if="hasPowerError()">
579 Power control software for this power type is missing from the
580 rack controller. To proceed, install the {$ getPowerErrors() $}
581 on the rack controller.
582 </li>
583+ <li class="flash-messages__item info" data-ng-if="power.type.name == 'manual'">
584+ Power control for this power type will need to be handled manually.
585+ </li>
586+ <li class="flash-messages__item info" data-ng-if="power.editing && node.power_bmc_node_count > 1">
587+ This power controller manages {$ node.power_bmc_node_count - 1 $} other
588+ {$ node.power_bmc_node_count > 3 ? "nodes" : "node" $}.
589+ Changing power parameters will affect these nodes.
590+ </li>
591 </ul>
592 </div>
593- <fieldset class="six-col"
594+ <fieldset class="six-col ng-hide"
595+ data-ng-show="power_types.length"
596 data-ng-disabled="!power.editing"
597 data-maas-power-parameters="power_types"
598 data-ng-model="power">
599 </fieldset>
600 <div class="controls" data-ng-hide="power.editing">
601 <a href="" class="link-cta-ubuntu secondary"
602- data-ng-show="canEdit()"
603+ data-ng-show="canEdit() && power_types.length"
604 data-ng-click="editPower()">Edit</a>
605 </div>
606 <div class="controls ng-hide" data-ng-show="power.editing">
607@@ -367,16 +381,7 @@
608 data-ng-class="{ secondary: invalidPowerType() }"
609 data-ng-click="saveEditPower()">Save changes</button>
610 </div>
611- <div class="twelve-col ng-hide info" data-ng-show="power.editing && node.power_bmc_node_count > 1">
612- <ul class="flash-messages">
613- <li class="flash-messages__item info">
614- This power controller manages {$ node.power_bmc_node_count - 1 $} other
615- {$ node.power_bmc_node_count > 3 ? "nodes" : "node" $}.
616- Changing power parameters will affect these nodes. If this is not what you want,
617- create a new power controller unique to this node by changing the power type.
618- </li>
619- </ul>
620- </div>
621+
622 </form>
623 </div>
624 </div>
625@@ -495,7 +500,7 @@
626 </div>
627 <div class="table__data table-col--30">
628 <span data-ng-repeat="subnet in vlanRow['subnets']">
629- <div class="margin-bottom--ten">
630+ <div class="margin-bottom--ten">
631 <a href="#/subnet/{$ subnet.id $}">{$ getSubnetText(subnet) $}</a>
632 </div>
633 </span>
634@@ -515,25 +520,23 @@
635 <div class="row">
636 <div class="inner-wrapper">
637 <div class="twelve-col" data-ng-hide="loaded">
638- <h2 class="title">Network</h2>
639+ <h2 class="title">Interfaces</h2>
640 <div class="twelve-col">Loading...</div>
641 </div>
642 <form class="ng-hide" data-ng-show="loaded">
643 <div class="twelve-col">
644- <h2 class="title">Network</h2>
645+ <h2 class="title">Interfaces</h2>
646 <div class="twelve-col error">
647- <ul class="flash-messages ng-hide" data-ng-show="!isController && isAllNetworkingDisabled() && isSuperUser()">
648- <li class="flash-messages__item info">
649- Network configuration cannot be modified unless the node is Ready or Broken.
650- </li>
651- </ul>
652- <ul class="flash-messages ng-hide" data-ng-show="!node.on_network">
653- <li class="flash-messages__item error">
654+ <ul class="flash-messages" data-ng-if="
655+ (!isController && isAllNetworkingDisabled() && isSuperUser()) ||
656+ !node.on_network || (!isController && !isUbuntuOS())">
657+ <li class="flash-messages__item error" data-ng-if="!node.on_network">
658 Node must be connected to a network.
659 </li>
660- </ul>
661- <ul class="flash-messages ng-hide" data-ng-show="!isController && !isUbuntuOS()">
662- <li class="flash-messages__item info">
663+ <li class="flash-messages__item info" data-ng-if="!isController && isAllNetworkingDisabled() && isSuperUser()">
664+ Interface configuration cannot be modified unless the node is Ready or Broken.
665+ </li>
666+ <li class="flash-messages__item info" data-ng-if="!isController && !isUbuntuOS()">
667 Custom network configuration only supported on Ubuntu. Using OS default configuration.
668 </li>
669 </ul>
670@@ -1105,12 +1108,10 @@
671
672 </main>
673 </section>
674-
675 <a class="link-cta-ubuntu secondary tooltip"
676 data-tooltip="Create a tmpfs or ramfs filesystem"
677 ng-disabled="dropdown !== null"
678 ng-click="addSpecialFilesystem()">Add special filesystem</a>
679-
680 </div>
681 <div class="twelve-col padding-bottom" data-ng-show="cachesets.length">
682 <h3>Available cache sets</h3>
683@@ -1869,9 +1870,7 @@
684 <div class="inner-wrapper">
685 <div class="twelve-col">
686 <h2 class="title">Latest machine events</h2>
687- <h2 data-ng-hide="node.events.length">
688- No events.
689- </h2>
690+ <p data-ng-hide="node.events.length">No events.</p>
691 <table class="table-listing no-hover" data-ng-show="node.events.length">
692 <thead>
693 <tr class="table-listing__row">
694
695=== modified file 'src/maasserver/static/partials/node-events.html'
696--- src/maasserver/static/partials/node-events.html 2016-03-31 22:30:51 +0000
697+++ src/maasserver/static/partials/node-events.html 2016-04-08 20:38:44 +0000
698@@ -31,9 +31,9 @@
699 <table class="table-listing " id="events-listing">
700 <thead>
701 <tr class="table-listing__row">
702- <th class="table-listing__header t1"></th>
703- <th class="table-listing__header t79">Event</th>
704- <th class="table-listing__header t20">Time</th>
705+ <th class="table-listing__header table-col--1"></th>
706+ <th class="table-listing__header table-col--79">Event</th>
707+ <th class="table-listing__header table-col--20">Time</th>
708 </tr>
709 </thead>
710 <tbody>