Merge lp:~blake-rouse/maas/fix-cache-clusters-images into lp:~maas-committers/maas/trunk
- fix-cache-clusters-images
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Blake Rouse |
Approved revision: | no longer in the source branch. |
Merged at revision: | 5045 |
Proposed branch: | lp:~blake-rouse/maas/fix-cache-clusters-images |
Merge into: | lp:~maas-committers/maas/trunk |
Diff against target: |
786 lines (+469/-83) 14 files modified
src/maasserver/static/js/angular/controllers/node_details.js (+6/-27) src/maasserver/static/js/angular/controllers/node_details_storage.js (+2/-1) src/maasserver/static/js/angular/controllers/nodes_list.js (+1/-23) src/maasserver/static/js/angular/directives/controller_image_status.js (+156/-0) src/maasserver/static/js/angular/directives/tests/test_controller_image_status.js (+240/-0) src/maasserver/static/js/angular/directives/tests/test_version_reloader.js (+5/-0) src/maasserver/static/js/angular/directives/version_reloader.js (+3/-0) src/maasserver/static/js/angular/maas.js (+32/-13) src/maasserver/static/partials/node-details.html (+2/-2) src/maasserver/static/partials/nodes-list.html (+3/-1) src/maasserver/templates/maasserver/js-conf.html (+15/-13) src/maasserver/views/combo.py (+1/-0) src/maasserver/websockets/handlers/controller.py (+1/-1) src/maasserver/websockets/handlers/tests/test_controller.py (+2/-2) |
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-cache-clusters-images |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Gavin Panella (community) | Approve | ||
Jeffrey C Jones (community) | Approve | ||
Review via email: mp+295585@code.launchpad.net |
Commit message
Convert the controllers image status to a directive to allow it to be used across MAAS. Add ?v={version} to templateUrl in routes to prevent caching on newer versions.
Description of the change
Gavin Panella (allenap) wrote : | # |
So much code for such a little thing! Still, at least it's bottled now.
There are a few hundred lines of diff for something that, at first glance, appears unrelated, so Needs Information for that alone.
Blake Rouse (blake-rouse) wrote : | # |
> So much code for such a little thing! Still, at least it's bottled now.
Thats javascript! ;-)
> There are a few hundred lines of diff for something that, at first glance, appears unrelated, so
> Needs Information for that alone.
That addresses the bug linked to this branch. That bug shows that he was viewing a 1.9 UI with 2.0 javascript. The issue is that the partial views are being cached and the cache is not being broken with the ?v={version} on the file path.
In the process of fixing that bug I ran into an issue where the controllers listing was causing the regiond to crash. I fixed that, but by the time I was done trapnine landed image statuses on the details page as well. So I had to fix both, what was a very simple fix became large to do it correctly.
Gavin Panella (allenap) wrote : | # |
Cool, thanks for the reply. Looks like Jeff went through it fairly closely, so +1.
Blake Rouse (blake-rouse) : | # |
MAAS Lander (maas-lander) wrote : | # |
The attempt to merge lp:~blake-rouse/maas/fix-cache-clusters-images into lp:maas failed. Below is the output from the failed tests.
Hit:1 http://
Get:2 http://
Get:3 http://
Hit:4 http://
Fetched 189 kB in 0s (425 kB/s)
Reading package lists...
sudo DEBIAN_
--no-
Reading package lists...
Building dependency tree...
Reading state information...
apache2 is already the newest version (2.4.18-2ubuntu3).
archdetect-deb is already the newest version (1.117ubuntu2).
authbind is already the newest version (2.1.1+nmu1).
bash is already the newest version (4.3-14ubuntu1).
build-essential is already the newest version (12.1ubuntu2).
bzr is already the newest version (2.7.0-2ubuntu1).
curl is already the newest version (7.47.0-1ubuntu2).
debhelper is already the newest version (9.20160115ubun
distro-info is already the newest version (0.14build1).
freeipmi-tools is already the newest version (1.4.11-1ubuntu1).
git is already the newest version (1:2.7.4-0ubuntu1).
isc-dhcp-common is already the newest version (4.3.3-5ubuntu12).
libjs-angularjs is already the newest version (1.2.28-1ubuntu2).
libjs-jquery is already the newest version (1.11.3+dfsg-4).
libjs-yui3-full is already the newest version (3.5.1-1ubuntu3).
libjs-yui3-min is already the newest version (3.5.1-1ubuntu3).
make is already the newest version (4.1-6).
postgresql is already the newest version (9.5+173).
pxelinux is already the...
Preview Diff
1 | === modified file 'src/maasserver/static/js/angular/controllers/node_details.js' |
2 | --- src/maasserver/static/js/angular/controllers/node_details.js 2016-05-21 03:24:08 +0000 |
3 | +++ src/maasserver/static/js/angular/controllers/node_details.js 2016-05-25 20:28:15 +0000 |
4 | @@ -185,8 +185,10 @@ |
5 | return; |
6 | } |
7 | |
8 | - $scope.summary.zone.selected = ZonesManager.getItemFromList( |
9 | - $scope.node.zone.id); |
10 | + if(angular.isObject($scope.node.zone)) { |
11 | + $scope.summary.zone.selected = ZonesManager.getItemFromList( |
12 | + $scope.node.zone.id); |
13 | + } |
14 | $scope.summary.architecture.selected = $scope.node.architecture; |
15 | $scope.summary.min_hwe_kernel.selected = $scope.node.min_hwe_kernel; |
16 | $scope.summary.tags = angular.copy($scope.node.tags); |
17 | @@ -335,23 +337,6 @@ |
18 | }); |
19 | } |
20 | |
21 | - function getControllersImageSyncStatus() { |
22 | - if(angular.isObject($scope.node)) { |
23 | - $scope.nodesManager.checkImageStates( |
24 | - [{system_id: $scope.node.system_id}]).then( |
25 | - function(results) { |
26 | - // Results is a map of system_id to displayable status. |
27 | - if(results[$scope.node.system_id]) { |
28 | - $scope.node.image_sync_status = |
29 | - results[$scope.node.system_id]; |
30 | - } else { |
31 | - $scope.node.image_sync_status = "Unknown"; |
32 | - } |
33 | - } |
34 | - ); |
35 | - } |
36 | - } |
37 | - |
38 | // Starts the watchers on the scope. |
39 | function startWatching() { |
40 | // Update the title and name when the node fqdn changes. |
41 | @@ -398,13 +383,6 @@ |
42 | $scope.$watch("node.summary_yaml", updateMachineOutput); |
43 | $scope.$watch("node.commissioning_results", updateMachineOutput); |
44 | $scope.$watch("node.installation_results", updateMachineOutput); |
45 | - |
46 | - if($scope.isController) { |
47 | - getControllersImageSyncStatus(); |
48 | - $scope.imageStatusPoll = $interval(function() { |
49 | - getControllersImageSyncStatus(); |
50 | - }, 10000); |
51 | - } |
52 | } |
53 | |
54 | // Called when the node has been loaded. |
55 | @@ -867,7 +845,8 @@ |
56 | }; |
57 | |
58 | $scope.getPowerEventError = function() { |
59 | - if(!angular.isObject($scope.node)) { |
60 | + if(!angular.isObject($scope.node) || |
61 | + !angular.isArray($scope.node.events)) { |
62 | return; |
63 | } |
64 | |
65 | |
66 | === modified file 'src/maasserver/static/js/angular/controllers/node_details_storage.js' |
67 | --- src/maasserver/static/js/angular/controllers/node_details_storage.js 2016-03-28 13:54:47 +0000 |
68 | +++ src/maasserver/static/js/angular/controllers/node_details_storage.js 2016-05-25 20:28:15 +0000 |
69 | @@ -2000,7 +2000,8 @@ |
70 | |
71 | // Returns true if there are storage layout errors |
72 | $scope.hasStorageLayoutIssues = function() { |
73 | - if(angular.isObject($scope.node)) { |
74 | + if(angular.isObject($scope.node) && |
75 | + angular.isArray($scope.node.storage_layout_issues)) { |
76 | return $scope.node.storage_layout_issues.length > 0; |
77 | } |
78 | return false; |
79 | |
80 | === modified file 'src/maasserver/static/js/angular/controllers/nodes_list.js' |
81 | --- src/maasserver/static/js/angular/controllers/nodes_list.js 2016-05-21 03:24:08 +0000 |
82 | +++ src/maasserver/static/js/angular/controllers/nodes_list.js 2016-05-25 20:28:15 +0000 |
83 | @@ -124,6 +124,7 @@ |
84 | errors: {} |
85 | }; |
86 | $scope.tabs.controllers.zoneSelection = null; |
87 | + $scope.tabs.controllers.syncStatuses = {}; |
88 | |
89 | // Options for add hardware dropdown. |
90 | $scope.addHardwareOption = null; |
91 | @@ -570,21 +571,6 @@ |
92 | return DEVICE_IP_ASSIGNMENT[ipAssignment]; |
93 | }; |
94 | |
95 | - $scope.getControllersImageSyncStatus = function() { |
96 | - ControllersManager.checkImageStates($scope.controllers).then( |
97 | - function(results) { |
98 | - angular.forEach($scope.controllers, function(controller) { |
99 | - // Results is a map of system_id to displayable status. |
100 | - if(results[controller.system_id]) { |
101 | - controller.image_sync_status = |
102 | - results[controller.system_id]; |
103 | - } else { |
104 | - controller.image_sync_status = "Unknown"; |
105 | - } |
106 | - }); |
107 | - }); |
108 | - }; |
109 | - |
110 | // Return true if the authenticated user is super user. |
111 | $scope.isSuperUser = function() { |
112 | return UsersManager.isSuperUser(); |
113 | @@ -597,14 +583,6 @@ |
114 | [MachinesManager, DevicesManager, ControllersManager, |
115 | GeneralManager, ZonesManager, UsersManager, ServicesManager]).then( |
116 | function() { |
117 | - |
118 | - if($routeParams.tab === "controllers") { |
119 | - $scope.getControllersImageSyncStatus(); |
120 | - $scope.statusPoll = $interval(function() { |
121 | - $scope.getControllersImageSyncStatus(); |
122 | - }, 10000); |
123 | - } |
124 | - |
125 | $scope.loading = false; |
126 | }); |
127 | |
128 | |
129 | === added file 'src/maasserver/static/js/angular/directives/controller_image_status.js' |
130 | --- src/maasserver/static/js/angular/directives/controller_image_status.js 1970-01-01 00:00:00 +0000 |
131 | +++ src/maasserver/static/js/angular/directives/controller_image_status.js 2016-05-25 20:28:15 +0000 |
132 | @@ -0,0 +1,156 @@ |
133 | +/* Copyright 2016 Canonical Ltd. This software is licensed under the |
134 | + * GNU Affero General Public License version 3 (see the file LICENSE). |
135 | + * |
136 | + * Controller image status directive. |
137 | + * |
138 | + * Shows the image status for a controller. |
139 | + */ |
140 | + |
141 | + angular.module('MAAS').service('ControllerImageStatusService', |
142 | + ['$timeout', '$interval', 'ControllersManager', function( |
143 | + $timeout, $interval, ControllersManager) { |
144 | + var self = this; |
145 | + |
146 | + // List of controllers that need to have the image status updated. |
147 | + this.controllers = []; |
148 | + |
149 | + // List of current controller statues. |
150 | + this.statuses = {}; |
151 | + |
152 | + // Interval function that is called to update the statuses. |
153 | + this.updateStatuses = function() { |
154 | + var controllerIds = []; |
155 | + angular.forEach(self.controllers, function(system_id) { |
156 | + controllerIds.push({system_id: system_id}); |
157 | + }); |
158 | + |
159 | + // Check the image states. |
160 | + ControllersManager.checkImageStates(controllerIds).then( |
161 | + function(results) { |
162 | + angular.forEach(controllerIds, function(controller) { |
163 | + var status = results[controller.system_id]; |
164 | + if(status) { |
165 | + self.statuses[controller.system_id] = status; |
166 | + } else { |
167 | + self.statuses[controller.system_id] = "Unknown"; |
168 | + } |
169 | + }); |
170 | + }); |
171 | + }; |
172 | + |
173 | + // Register this controller system_id. |
174 | + this.register = function(system_id) { |
175 | + var known = self.controllers.indexOf(system_id) >= 0; |
176 | + if(!known) { |
177 | + self.controllers.push(system_id); |
178 | + } |
179 | + |
180 | + // When the interval is already running and its a new controller then |
181 | + // the interval needs to be reset. When it already exists it doesn't |
182 | + // need to be reset. |
183 | + if(angular.isDefined(self.runningInterval)) { |
184 | + if(known) { |
185 | + return; |
186 | + } else { |
187 | + $interval.cancel(self.runningInterval); |
188 | + self.runningInterval = undefined; |
189 | + } |
190 | + } |
191 | + |
192 | + // If its not running and the timeout has been started we re-create |
193 | + // the timeout. This delays the start of the interval until the |
194 | + // all directives on the page have been fully loaded. |
195 | + if(angular.isDefined(self.startTimeout)) { |
196 | + $timeout.cancel(self.startTimeout); |
197 | + } |
198 | + self.startTimeout = $timeout(function() { |
199 | + self.startTimeout = undefined; |
200 | + self.runningInterval = $interval(function() { |
201 | + self.updateStatuses(); |
202 | + }, 10 * 1000); |
203 | + self.updateStatuses(); |
204 | + }, 100); |
205 | + }; |
206 | + |
207 | + // Unregister the controller. |
208 | + this.unregister = function(system_id) { |
209 | + var idx = self.controllers.indexOf(system_id); |
210 | + if(idx > -1) { |
211 | + self.controllers.splice(idx, 1); |
212 | + } |
213 | + |
214 | + // If no controllers are left stop all intervals and timeouts. |
215 | + if(self.controllers.length === 0) { |
216 | + if(angular.isDefined(self.startTimeout)) { |
217 | + $timeout.cancel(self.startTimeout); |
218 | + self.startTimeout = undefined; |
219 | + } |
220 | + if(angular.isDefined(self.runningInterval)) { |
221 | + $interval.cancel(self.runningInterval); |
222 | + self.runningInterval = undefined; |
223 | + } |
224 | + } |
225 | + }; |
226 | + |
227 | + // Return true if the spinner should be shown. |
228 | + this.showSpinner = function(system_id) { |
229 | + var status = self.statuses[system_id]; |
230 | + if(angular.isString(status) && status !== "Syncing") { |
231 | + return false; |
232 | + } else { |
233 | + return true; |
234 | + } |
235 | + }; |
236 | + |
237 | + // Get the image status. |
238 | + this.getImageStatus = function(system_id) { |
239 | + var status = self.statuses[system_id]; |
240 | + if(angular.isString(status)) { |
241 | + return status; |
242 | + } else { |
243 | + return "Asking for status..."; |
244 | + } |
245 | + }; |
246 | +}]); |
247 | + |
248 | +angular.module('MAAS').directive('maasControllerImageStatus', |
249 | + ['ControllerImageStatusService', function( |
250 | + ControllerImageStatusService) { |
251 | + |
252 | + return { |
253 | + restrict: "E", |
254 | + scope: { |
255 | + systemId: "=" |
256 | + }, |
257 | + template: [ |
258 | + '<i class="icon loading" data-ng-if="showSpinner()"></i> ', |
259 | + '{$ getImageStatus() $}'].join(''), |
260 | + link: function(scope, element, attrs) { |
261 | + // Don't register until the systemId is set. |
262 | + var unwatch, registered = false; |
263 | + unwatch = scope.$watch("systemId", function() { |
264 | + if(angular.isDefined(scope.systemId) && !registered) { |
265 | + ControllerImageStatusService.register(scope.systemId); |
266 | + registered = true; |
267 | + unwatch(); |
268 | + } |
269 | + }); |
270 | + |
271 | + scope.showSpinner = function() { |
272 | + return ControllerImageStatusService.showSpinner( |
273 | + scope.systemId); |
274 | + }; |
275 | + scope.getImageStatus = function() { |
276 | + return ControllerImageStatusService.getImageStatus( |
277 | + scope.systemId); |
278 | + }; |
279 | + |
280 | + // Unregister when destroyed. |
281 | + scope.$on("$destroy", function() { |
282 | + if(registered) { |
283 | + ControllerImageStatusService.unregister(scope.systemId); |
284 | + } |
285 | + }); |
286 | + } |
287 | + }; |
288 | +}]); |
289 | |
290 | === added file 'src/maasserver/static/js/angular/directives/tests/test_controller_image_status.js' |
291 | --- src/maasserver/static/js/angular/directives/tests/test_controller_image_status.js 1970-01-01 00:00:00 +0000 |
292 | +++ src/maasserver/static/js/angular/directives/tests/test_controller_image_status.js 2016-05-25 20:28:15 +0000 |
293 | @@ -0,0 +1,240 @@ |
294 | +/* Copyright 2016 Canonical Ltd. This software is licensed under the |
295 | + * GNU Affero General Public License version 3 (see the file LICENSE). |
296 | + * |
297 | + * Unit tests for controller image status directive. |
298 | + */ |
299 | + |
300 | +describe("maasControllerImageStatus", function() { |
301 | + |
302 | + // Load the MAAS module. |
303 | + beforeEach(module("MAAS")); |
304 | + |
305 | + // Get the manager and the directive service. |
306 | + var $timeout, $interval, $q; |
307 | + var ControllersManager, ControllerImageStatusService; |
308 | + beforeEach(inject(function($injector) { |
309 | + $timeout = $injector.get("$timeout"); |
310 | + $interval = $injector.get("$interval"); |
311 | + $q = $injector.get("$q"); |
312 | + ControllersManager = $injector.get("ControllersManager"); |
313 | + ControllerImageStatusService = $injector.get( |
314 | + "ControllerImageStatusService"); |
315 | + })); |
316 | + |
317 | + // Create a new scope before each test. |
318 | + var $scope; |
319 | + beforeEach(inject(function($rootScope) { |
320 | + $scope = $rootScope.$new(); |
321 | + })); |
322 | + |
323 | + // Return the compiled directive with the osinfo from the scope. |
324 | + function compileDirective() { |
325 | + var directive; |
326 | + var html = [ |
327 | + '<div>', |
328 | + '<maas-controller-image-status system-id="system_id">', |
329 | + '</maas-controller-image-status>', |
330 | + '</div>'].join(''); |
331 | + |
332 | + // Compile the directive. |
333 | + inject(function($compile) { |
334 | + directive = $compile(html)($scope); |
335 | + }); |
336 | + |
337 | + // Perform the digest cycle to finish the compile. |
338 | + $scope.$digest(); |
339 | + return directive.find("maas-controller-image-status"); |
340 | + } |
341 | + |
342 | + describe("service", function() { |
343 | + |
344 | + var service; |
345 | + beforeEach(function() { |
346 | + service = ControllerImageStatusService; |
347 | + }); |
348 | + |
349 | + describe("updateStatuses", function() { |
350 | + it("calls checkImageStates with controllers", function() { |
351 | + service.controllers = [ |
352 | + makeName("systemId"), makeName("systemId")]; |
353 | + spyOn(ControllersManager, "checkImageStates").and.returnValue( |
354 | + $q.defer().promise); |
355 | + service.updateStatuses(); |
356 | + |
357 | + expect( |
358 | + ControllersManager.checkImageStates).toHaveBeenCalledWith([ |
359 | + { system_id: service.controllers[0] }, |
360 | + { system_id: service.controllers[1] } |
361 | + ]); |
362 | + }); |
363 | + |
364 | + it("sets statuses with result", function() { |
365 | + var defer = $q.defer(); |
366 | + service.controllers = [ |
367 | + makeName("systemId"), |
368 | + makeName("systemId"), |
369 | + makeName("systemId")]; |
370 | + spyOn(ControllersManager, "checkImageStates").and.returnValue( |
371 | + defer.promise); |
372 | + service.updateStatuses(); |
373 | + |
374 | + var results = {}; |
375 | + results[service.controllers[0]] = makeName("status"); |
376 | + results[service.controllers[1]] = makeName("status"); |
377 | + defer.resolve(results); |
378 | + $scope.$digest(); |
379 | + |
380 | + var statues = {}; |
381 | + statues[service.controllers[0]] = ( |
382 | + results[service.controllers[0]]); |
383 | + statues[service.controllers[1]] = ( |
384 | + results[service.controllers[1]]); |
385 | + statues[service.controllers[2]] = "Unknown"; |
386 | + expect(service.statuses).toEqual(statues); |
387 | + }); |
388 | + }); |
389 | + |
390 | + describe("register", function() { |
391 | + it("added to controllers and starts timer", function() { |
392 | + var systemId = makeName("systemId"); |
393 | + service.register(systemId); |
394 | + |
395 | + expect(service.controllers).toEqual([systemId]); |
396 | + expect(service.startTimeout).toBeDefined(); |
397 | + $timeout.cancel(service.startTimeout); |
398 | + }); |
399 | + |
400 | + it("added multiple and restarts timer", function() { |
401 | + var systemId1 = makeName("systemId"); |
402 | + var systemId2 = makeName("systemId"); |
403 | + |
404 | + service.register(systemId1); |
405 | + var firstTimeout = service.startTimeout; |
406 | + |
407 | + service.register(systemId2); |
408 | + var secondTimeout = service.startTimeout; |
409 | + |
410 | + expect(firstTimeout).not.toBe(secondTimeout); |
411 | + expect(secondTimeout).toBeDefined(); |
412 | + $timeout.cancel(secondTimeout); |
413 | + }); |
414 | + |
415 | + it("starts interval after 100ms", function() { |
416 | + var systemId = makeName("systemId"); |
417 | + spyOn(service, "updateStatuses"); |
418 | + |
419 | + service.register(systemId); |
420 | + $timeout.flush(100); |
421 | + |
422 | + expect(service.runningInterval).toBeDefined(); |
423 | + }); |
424 | + |
425 | + it("cancels running interval on new controller", function() { |
426 | + var systemId = makeName("systemId"); |
427 | + |
428 | + var interval = service.runningInterval = {}; |
429 | + spyOn($interval, "cancel"); |
430 | + |
431 | + service.register(systemId); |
432 | + expect($interval.cancel).toHaveBeenCalledWith(interval); |
433 | + expect(service.runningInterval).not.toBeDefined(); |
434 | + expect(service.controllers).toEqual([systemId]); |
435 | + expect(service.startTimeout).toBeDefined(); |
436 | + $timeout.cancel(service.startTimeout); |
437 | + }); |
438 | + }); |
439 | + |
440 | + describe("unregister", function() { |
441 | + it("removes controller", function() { |
442 | + var systemId = makeName("systemId"); |
443 | + service.controllers.push(systemId); |
444 | + service.unregister(systemId); |
445 | + |
446 | + expect(service.controllers.length).toBe(0); |
447 | + }); |
448 | + |
449 | + it("stops timeout and interval", function() { |
450 | + var systemId = makeName("systemId"); |
451 | + service.controllers.push(systemId); |
452 | + |
453 | + var startTimeout = service.startTimeout = {}; |
454 | + var runningInterval = service.runningInterval = {}; |
455 | + spyOn($timeout, "cancel"); |
456 | + spyOn($interval, "cancel"); |
457 | + |
458 | + service.unregister(systemId); |
459 | + expect($timeout.cancel).toHaveBeenCalledWith(startTimeout); |
460 | + expect(service.startTimeout).not.toBeDefined(); |
461 | + expect($interval.cancel).toHaveBeenCalledWith(runningInterval); |
462 | + expect(service.runningInterval).not.toBeDefined(); |
463 | + }); |
464 | + }); |
465 | + |
466 | + describe("showSpinner", function() { |
467 | + it("returns false if set and not syncing", function() { |
468 | + var systemId = makeName("systemId"); |
469 | + var status = "out-of-sync"; |
470 | + service.statuses[systemId] = status; |
471 | + expect(service.showSpinner(systemId)).toBe(false); |
472 | + }); |
473 | + |
474 | + it("returns true if not set", function() { |
475 | + var systemId = makeName("systemId"); |
476 | + expect(service.showSpinner(systemId)).toBe(true); |
477 | + }); |
478 | + |
479 | + it("returns true if set and syncing", function() { |
480 | + var systemId = makeName("systemId"); |
481 | + var status = "Syncing"; |
482 | + service.statuses[systemId] = status; |
483 | + expect(service.showSpinner(systemId)).toBe(true); |
484 | + }); |
485 | + }); |
486 | + |
487 | + describe("getImageStatus", function() { |
488 | + it("returns status if set", function() { |
489 | + var systemId = makeName("systemId"); |
490 | + var status = "out-of-sync"; |
491 | + service.statuses[systemId] = status; |
492 | + expect(service.getImageStatus(systemId)).toBe(status); |
493 | + }); |
494 | + |
495 | + it("returns asking", function() { |
496 | + var systemId = makeName("systemId"); |
497 | + expect(service.getImageStatus(systemId)).toBe( |
498 | + "Asking for status..."); |
499 | + }); |
500 | + }); |
501 | + }); |
502 | + |
503 | + describe("directive", function() { |
504 | + it("registers when systemId is set", function() { |
505 | + spyOn(ControllerImageStatusService, "register"); |
506 | + var directive = compileDirective(); |
507 | + |
508 | + // Should only be called once system_id is set. |
509 | + expect( |
510 | + ControllerImageStatusService.register).not.toHaveBeenCalled(); |
511 | + $scope.system_id = makeName("systemId"); |
512 | + $scope.$digest(); |
513 | + expect(ControllerImageStatusService.register).toHaveBeenCalledWith( |
514 | + $scope.system_id); |
515 | + }); |
516 | + |
517 | + it("unregisters when destroyed", function() { |
518 | + spyOn(ControllerImageStatusService, "register"); |
519 | + spyOn(ControllerImageStatusService, "unregister"); |
520 | + var directive = compileDirective(); |
521 | + |
522 | + $scope.system_id = makeName("systemId"); |
523 | + $scope.$digest(); |
524 | + expect(ControllerImageStatusService.register).toHaveBeenCalledWith( |
525 | + $scope.system_id); |
526 | + |
527 | + directive.scope().$destroy(); |
528 | + expect( |
529 | + ControllerImageStatusService.unregister).toHaveBeenCalledWith( |
530 | + $scope.system_id); |
531 | + }); |
532 | + }); |
533 | +}); |
534 | |
535 | === modified file 'src/maasserver/static/js/angular/directives/tests/test_version_reloader.js' |
536 | --- src/maasserver/static/js/angular/directives/tests/test_version_reloader.js 2015-10-21 19:48:04 +0000 |
537 | +++ src/maasserver/static/js/angular/directives/tests/test_version_reloader.js 2016-05-25 20:28:15 +0000 |
538 | @@ -35,6 +35,11 @@ |
539 | $scope = $rootScope.$new(); |
540 | })); |
541 | |
542 | + // Prevent console.log messages in tests. |
543 | + beforeEach(function() { |
544 | + spyOn(console, "log"); |
545 | + }); |
546 | + |
547 | // Return the compiled directive with the items from the scope. |
548 | function compileDirective() { |
549 | var directive; |
550 | |
551 | === modified file 'src/maasserver/static/js/angular/directives/version_reloader.js' |
552 | --- src/maasserver/static/js/angular/directives/version_reloader.js 2015-10-21 19:48:04 +0000 |
553 | +++ src/maasserver/static/js/angular/directives/version_reloader.js 2016-05-25 20:28:15 +0000 |
554 | @@ -27,6 +27,9 @@ |
555 | GeneralManager.enableAutoReload(true); |
556 | $scope.$watch("version.text", |
557 | function(newValue, oldValue) { |
558 | + console.log( |
559 | + "Detected new MAAS version; " + |
560 | + "forcing reload."); |
561 | if(newValue !== oldValue) { |
562 | $scope.reloadPage(); |
563 | } |
564 | |
565 | === modified file 'src/maasserver/static/js/angular/maas.js' |
566 | --- src/maasserver/static/js/angular/maas.js 2016-04-11 16:23:26 +0000 |
567 | +++ src/maasserver/static/js/angular/maas.js 2016-05-25 20:28:15 +0000 |
568 | @@ -13,6 +13,12 @@ |
569 | $interpolateProvider.startSymbol('{$'); |
570 | $interpolateProvider.endSymbol('$}'); |
571 | |
572 | + // Helper that wrappers the templateUrl to append the files version |
573 | + // to the path. Used to override client cache. |
574 | + function versionedPath(path) { |
575 | + return path + "?v=" + MAAS_config.files_version; |
576 | + } |
577 | + |
578 | // Setup routes only for the index page, all remaining pages should |
579 | // not use routes. Once all pages are converted to using Angular this |
580 | // will go away. Causing the page to never have to reload. |
581 | @@ -24,60 +30,73 @@ |
582 | if(path === href) { |
583 | $routeProvider. |
584 | when('/nodes', { |
585 | - templateUrl: 'static/partials/nodes-list.html', |
586 | + templateUrl: versionedPath( |
587 | + 'static/partials/nodes-list.html'), |
588 | controller: 'NodesListController' |
589 | }). |
590 | when('/node/:system_id/result/:filename', { |
591 | - templateUrl: 'static/partials/node-result.html', |
592 | + templateUrl: versionedPath( |
593 | + 'static/partials/node-result.html'), |
594 | controller: 'NodeResultController' |
595 | }). |
596 | when('/node/:system_id/events', { |
597 | - templateUrl: 'static/partials/node-events.html', |
598 | + templateUrl: versionedPath( |
599 | + 'static/partials/node-events.html'), |
600 | controller: 'NodeEventsController' |
601 | }). |
602 | when('/node/:type/:system_id', { |
603 | - templateUrl: 'static/partials/node-details.html', |
604 | + templateUrl: versionedPath( |
605 | + 'static/partials/node-details.html'), |
606 | controller: 'NodeDetailsController' |
607 | }). |
608 | when('/node/:system_id', { |
609 | - templateUrl: 'static/partials/node-details.html', |
610 | + templateUrl: versionedPath( |
611 | + 'static/partials/node-details.html'), |
612 | controller: 'NodeDetailsController' |
613 | }). |
614 | when('/domains', { |
615 | - templateUrl: 'static/partials/domains-list.html', |
616 | + templateUrl: versionedPath( |
617 | + 'static/partials/domains-list.html'), |
618 | controller: 'DomainsListController' |
619 | }). |
620 | when('/domain/:domain_id', { |
621 | - templateUrl: 'static/partials/domain-details.html', |
622 | + templateUrl: versionedPath( |
623 | + 'static/partials/domain-details.html'), |
624 | controller: 'DomainDetailsController' |
625 | }). |
626 | when('/space/:space_id', { |
627 | - templateUrl: 'static/partials/space-details.html', |
628 | + templateUrl: versionedPath( |
629 | + 'static/partials/space-details.html'), |
630 | controller: 'SpaceDetailsController' |
631 | }). |
632 | when('/fabric/:fabric_id', { |
633 | - templateUrl: 'static/partials/fabric-details.html', |
634 | + templateUrl: versionedPath( |
635 | + 'static/partials/fabric-details.html'), |
636 | controller: 'FabricDetailsController' |
637 | }). |
638 | when('/subnets', { |
639 | redirectTo: '/networks?by=fabric' |
640 | }). |
641 | when('/networks', { |
642 | - templateUrl: 'static/partials/networks-list.html', |
643 | + templateUrl: versionedPath( |
644 | + 'static/partials/networks-list.html'), |
645 | controller: 'NetworksListController', |
646 | reloadOnSearch: false |
647 | }). |
648 | when('/subnet/:subnet_id', { |
649 | - templateUrl: 'static/partials/subnet-details.html', |
650 | + templateUrl: versionedPath( |
651 | + 'static/partials/subnet-details.html'), |
652 | controller: 'SubnetDetailsController' |
653 | }). |
654 | when('/vlan/:vlan_id', { |
655 | - templateUrl: 'static/partials/vlan-details.html', |
656 | + templateUrl: versionedPath( |
657 | + 'static/partials/vlan-details.html'), |
658 | controller: 'VLANDetailsController', |
659 | controllerAs: 'vlanDetails' |
660 | }). |
661 | when('/settings/:section', { |
662 | - templateUrl: 'static/partials/settings.html', |
663 | + templateUrl: versionedPath( |
664 | + 'static/partials/settings.html'), |
665 | controller: 'SettingsController' |
666 | }). |
667 | otherwise({ |
668 | |
669 | === modified file 'src/maasserver/static/partials/node-details.html' |
670 | --- src/maasserver/static/partials/node-details.html 2016-05-23 14:38:36 +0000 |
671 | +++ src/maasserver/static/partials/node-details.html 2016-05-25 20:28:15 +0000 |
672 | @@ -224,7 +224,7 @@ |
673 | </dd> |
674 | </dl> |
675 | </div> |
676 | - <div data-ng-show="isController"> |
677 | + <div data-ng-if="isController"> |
678 | <dl> |
679 | <dt class="two-col">Type</dt> |
680 | <dd class="four-col last-col"> |
681 | @@ -236,7 +236,7 @@ |
682 | </dd> |
683 | <dt class="two-col">Images status</dt> |
684 | <dd class="four-col last-col"> |
685 | - {$ node.image_sync_status || 'Checking...' $} |
686 | + <maas-controller-image-status system-id="node.system_id"></maas-controller-image-status> |
687 | </dd> |
688 | <dt class="two-col">Zone</dt> |
689 | <dd class="four-col last-col"> |
690 | |
691 | === modified file 'src/maasserver/static/partials/nodes-list.html' |
692 | --- src/maasserver/static/partials/nodes-list.html 2016-05-20 18:49:43 +0000 |
693 | +++ src/maasserver/static/partials/nodes-list.html 2016-05-25 20:28:15 +0000 |
694 | @@ -810,7 +810,9 @@ |
695 | <td class="table-col--4 table-listing__cell align-center" data-maas-controller-status="controller" data-maas-services="services"></td> |
696 | <td class="table-col--25 table-listing__cell">{$ controller.node_type_display $}</td> |
697 | <td class="table-col--15 table-listing__cell">{$ controller.last_image_sync || 'Never' $}</td> |
698 | - <td class="table-col--15 table-listing__cell">{$ controller.image_sync_status || 'Checking...' $}</td> |
699 | + <td class="table-col--15 table-listing__cell"> |
700 | + <maas-controller-image-status system-id="controller.system_id"></maas-controller-image-status> |
701 | + </td> |
702 | </tr> |
703 | </tbody> |
704 | </table> |
705 | |
706 | === modified file 'src/maasserver/templates/maasserver/js-conf.html' |
707 | --- src/maasserver/templates/maasserver/js-conf.html 2015-11-12 16:13:05 +0000 |
708 | +++ src/maasserver/templates/maasserver/js-conf.html 2016-05-25 20:28:15 +0000 |
709 | @@ -1,16 +1,3 @@ |
710 | -<script type="text/javascript" |
711 | - src="{% url "merge" filename="jquery.js" %}?v={{files_version}}"> |
712 | -</script> |
713 | -<script type="text/javascript" |
714 | - src="{% url "merge" filename="angular.js" %}?v={{files_version}}"> |
715 | -</script> |
716 | -<script type="text/javascript" |
717 | - src="{% url "merge" filename="ng-tags-input.js" %}?v={{files_version}}"> |
718 | -</script> |
719 | -<script type="text/javascript" |
720 | - src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}"> |
721 | -</script> |
722 | - |
723 | <script type="text/javascript"> |
724 | <!-- |
725 | var YUI_config = { |
726 | @@ -30,10 +17,25 @@ |
727 | account_handler: '{% url "account_handler" %}', |
728 | images_handler: '{% url "images" %}' |
729 | }, |
730 | + files_version: '{{files_version}}', |
731 | debug: {% if YUI_DEBUG %}true{% else %}false{% endif %} |
732 | }; |
733 | // --> |
734 | </script> |
735 | + |
736 | +<script type="text/javascript" |
737 | + src="{% url "merge" filename="jquery.js" %}?v={{files_version}}"> |
738 | +</script> |
739 | +<script type="text/javascript" |
740 | + src="{% url "merge" filename="angular.js" %}?v={{files_version}}"> |
741 | +</script> |
742 | +<script type="text/javascript" |
743 | + src="{% url "merge" filename="ng-tags-input.js" %}?v={{files_version}}"> |
744 | +</script> |
745 | +<script type="text/javascript" |
746 | + src="{% url "merge" filename="maas-angular.js" %}?v={{files_version}}"> |
747 | +</script> |
748 | + |
749 | <script type="text/javascript" |
750 | src="{% url "merge" filename="yui.js" %}?v={{files_version}}"> |
751 | </script> |
752 | |
753 | === modified file 'src/maasserver/views/combo.py' |
754 | --- src/maasserver/views/combo.py 2016-05-11 19:01:48 +0000 |
755 | +++ src/maasserver/views/combo.py 2016-05-25 20:28:15 +0000 |
756 | @@ -80,6 +80,7 @@ |
757 | "js/angular/directives/accordion.js", |
758 | "js/angular/directives/call_to_action.js", |
759 | "js/angular/directives/code_lines.js", |
760 | + "js/angular/directives/controller_image_status.js", |
761 | "js/angular/directives/controller_status.js", |
762 | "js/angular/directives/dbl_click_overlay.js", |
763 | "js/angular/directives/enter_blur.js", |
764 | |
765 | === modified file 'src/maasserver/websockets/handlers/controller.py' |
766 | --- src/maasserver/websockets/handlers/controller.py 2016-04-22 21:25:06 +0000 |
767 | +++ src/maasserver/websockets/handlers/controller.py 2016-05-25 20:28:15 +0000 |
768 | @@ -98,5 +98,5 @@ |
769 | # We use a RackController method; without the cast, it's a Node. |
770 | node = typecast_to_node_type(node) |
771 | if isinstance(node, RackController): |
772 | - result[node.system_id] = node.get_image_sync_status() |
773 | + result[node.system_id] = node.get_image_sync_status().title() |
774 | return result |
775 | |
776 | === modified file 'src/maasserver/websockets/handlers/tests/test_controller.py' |
777 | --- src/maasserver/websockets/handlers/tests/test_controller.py 2016-05-16 08:36:33 +0000 |
778 | +++ src/maasserver/websockets/handlers/tests/test_controller.py 2016-05-25 20:28:15 +0000 |
779 | @@ -82,5 +82,5 @@ |
780 | {"system_id": node1.system_id}, |
781 | {"system_id": node2.system_id}]) |
782 | self.assertEqual({ |
783 | - node1.system_id: "unknown", |
784 | - node2.system_id: "unknown"}, data) |
785 | + node1.system_id: "Unknown", |
786 | + node2.system_id: "Unknown"}, data) |
Nice, now the status can be plopped anywhere. A few minor comments below.