Merge lp:~trapnine/maas/fix-1570152 into lp:~maas-committers/maas/trunk
- fix-1570152
- Merge into trunk
Proposed by
Jeffrey C Jones
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Jeffrey C Jones | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 4939 | ||||
Proposed branch: | lp:~trapnine/maas/fix-1570152 | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
690 lines (+255/-198) 16 files modified
src/maasserver/static/js/angular/controllers/add_device.js (+2/-2) src/maasserver/static/js/angular/controllers/domain_details.js (+4/-23) src/maasserver/static/js/angular/controllers/fabric_details.js (+4/-23) src/maasserver/static/js/angular/controllers/space_details.js (+4/-23) src/maasserver/static/js/angular/controllers/subnet_details.js (+36/-4) src/maasserver/static/js/angular/controllers/tests/test_add_device.js (+4/-4) src/maasserver/static/js/angular/controllers/tests/test_add_domain.js (+0/-35) src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js (+39/-0) src/maasserver/static/js/angular/factories/subnets.js (+5/-0) src/maasserver/static/js/angular/factories/tests/test_spaces.js (+13/-0) src/maasserver/static/js/angular/factories/tests/test_subnets.js (+13/-0) src/maasserver/static/js/angular/factories/tests/test_vlans.js (+13/-0) src/maasserver/static/partials/subnet-details.html (+38/-84) src/maasserver/websockets/handlers/subnet.py (+9/-0) src/maasserver/websockets/handlers/tests/test_space.py (+36/-0) src/maasserver/websockets/handlers/tests/test_subnet.py (+35/-0) |
||||
To merge this branch: | bzr merge lp:~trapnine/maas/fix-1570152 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Blake Rouse (community) | Approve | ||
Review via email: mp+292108@code.launchpad.net |
Commit message
Add delete button to subnet details page.
Description of the change
Add delete button to subnet details page.
Also added missing 'delete' testing in space and vlan factories/handlers (drive-by).
To post a comment you must log in.
Revision history for this message
Jeffrey C Jones (trapnine) : | # |
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/add_device.js' |
2 | --- src/maasserver/static/js/angular/controllers/add_device.js 2016-03-28 13:54:47 +0000 |
3 | +++ src/maasserver/static/js/angular/controllers/add_device.js 2016-04-20 05:12:39 +0000 |
4 | @@ -238,7 +238,7 @@ |
5 | }; |
6 | |
7 | // Convert the Python dict error message to displayed message. |
8 | - $scope.convertPythonDictToErrorMsg = function(pythonError) { |
9 | + $scope.devicePythonDictToErrorMsg = function(pythonError) { |
10 | var elements = pythonError.match(/'([A-Za-z0-9 \.:_\-]+)'/g); |
11 | var result = '', msg = ''; |
12 | for (k=0; k < elements.length; ++k) { |
13 | @@ -282,7 +282,7 @@ |
14 | $scope.hide(); |
15 | } |
16 | }, function(error) { |
17 | - $scope.error = $scope.convertPythonDictToErrorMsg(error); |
18 | + $scope.error = $scope.devicePythonDictToErrorMsg(error); |
19 | }); |
20 | }; |
21 | |
22 | |
23 | === modified file 'src/maasserver/static/js/angular/controllers/domain_details.js' |
24 | --- src/maasserver/static/js/angular/controllers/domain_details.js 2016-03-28 13:54:47 +0000 |
25 | +++ src/maasserver/static/js/angular/controllers/domain_details.js 2016-04-20 05:12:39 +0000 |
26 | @@ -66,29 +66,10 @@ |
27 | $scope.confirmingDelete = false; |
28 | }; |
29 | |
30 | - // Convert the Python dict error message to displayed message. |
31 | - // We know it's probably a form ValidationError dictionary, so just use |
32 | - // it as such, and recover if that doesn't parse as JSON. |
33 | - $scope.convertPythonDictToErrorMsg = function(pythonError) { |
34 | - var dictionary; |
35 | - try { |
36 | - dictionary = JSON.parse(pythonError); |
37 | - } catch(e) { |
38 | - if (e instanceof SyntaxError) { |
39 | - return pythonError; |
40 | - } else { |
41 | - throw e; |
42 | - } |
43 | - } |
44 | - var result = '', msg = ''; |
45 | - var key; |
46 | - angular.forEach(dictionary, function(value, key) { |
47 | - result += key + ": "; |
48 | - angular.forEach(dictionary[key], function(value) { |
49 | - result += value + " "; |
50 | - }); |
51 | - }); |
52 | - return result; |
53 | + // Called when an error message from the Python world needs to be |
54 | + // rendered in the UI. |
55 | + $scope.convertPythonDictToErrorMsg = function(error) { |
56 | + return ManagerHelperService.parseLikelyValidationError(error); |
57 | }; |
58 | |
59 | // Called when the confirm delete domain button is pressed. |
60 | |
61 | === modified file 'src/maasserver/static/js/angular/controllers/fabric_details.js' |
62 | --- src/maasserver/static/js/angular/controllers/fabric_details.js 2016-03-25 13:52:27 +0000 |
63 | +++ src/maasserver/static/js/angular/controllers/fabric_details.js 2016-04-20 05:12:39 +0000 |
64 | @@ -118,29 +118,10 @@ |
65 | $scope.confirmingDelete = false; |
66 | }; |
67 | |
68 | - // Convert the Python dict error message to displayed message. |
69 | - // We know it's probably a form ValidationError dictionary, so just use |
70 | - // it as such, and recover if that doesn't parse as JSON. |
71 | - $scope.convertPythonDictToErrorMsg = function(pythonError) { |
72 | - var dictionary; |
73 | - try { |
74 | - dictionary = JSON.parse(pythonError); |
75 | - } catch(e) { |
76 | - if(e instanceof SyntaxError) { |
77 | - return pythonError; |
78 | - } else { |
79 | - throw e; |
80 | - } |
81 | - } |
82 | - var result = '', msg = ''; |
83 | - var key; |
84 | - angular.forEach(dictionary, function(value, key) { |
85 | - result += key + ": "; |
86 | - angular.forEach(dictionary[key], function(value) { |
87 | - result += value + " "; |
88 | - }); |
89 | - }); |
90 | - return result; |
91 | + // Called when an error message from the Python world needs to be |
92 | + // rendered in the UI. |
93 | + $scope.convertPythonDictToErrorMsg = function(error) { |
94 | + return ManagerHelperService.parseLikelyValidationError(error); |
95 | }; |
96 | |
97 | // Called when the confirm delete fabric button is pressed. |
98 | |
99 | === modified file 'src/maasserver/static/js/angular/controllers/space_details.js' |
100 | --- src/maasserver/static/js/angular/controllers/space_details.js 2016-03-25 13:52:27 +0000 |
101 | +++ src/maasserver/static/js/angular/controllers/space_details.js 2016-04-20 05:12:39 +0000 |
102 | @@ -95,29 +95,10 @@ |
103 | $scope.confirmingDelete = false; |
104 | }; |
105 | |
106 | - // Convert the Python dict error message to displayed message. |
107 | - // We know it's probably a form ValidationError dictionary, so just use |
108 | - // it as such, and recover if that doesn't parse as JSON. |
109 | - $scope.convertPythonDictToErrorMsg = function(pythonError) { |
110 | - var dictionary; |
111 | - try { |
112 | - dictionary = JSON.parse(pythonError); |
113 | - } catch(e) { |
114 | - if(e instanceof SyntaxError) { |
115 | - return pythonError; |
116 | - } else { |
117 | - throw e; |
118 | - } |
119 | - } |
120 | - var result = '', msg = ''; |
121 | - var key; |
122 | - angular.forEach(dictionary, function(value, key) { |
123 | - result += key + ": "; |
124 | - angular.forEach(dictionary[key], function(value) { |
125 | - result += value + " "; |
126 | - }); |
127 | - }); |
128 | - return result; |
129 | + // Called when an error message from the Python world needs to be |
130 | + // rendered in the UI. |
131 | + $scope.convertPythonDictToErrorMsg = function(error) { |
132 | + return ManagerHelperService.parseLikelyValidationError(error); |
133 | }; |
134 | |
135 | // Called when the confirm delete space button is pressed. |
136 | |
137 | === modified file 'src/maasserver/static/js/angular/controllers/subnet_details.js' |
138 | --- src/maasserver/static/js/angular/controllers/subnet_details.js 2016-03-28 13:54:47 +0000 |
139 | +++ src/maasserver/static/js/angular/controllers/subnet_details.js 2016-04-20 05:12:39 +0000 |
140 | @@ -7,11 +7,11 @@ |
141 | angular.module('MAAS').controller('SubnetDetailsController', [ |
142 | '$scope', '$rootScope', '$routeParams', '$filter', '$location', |
143 | 'SubnetsManager', 'IPRangesManager', 'SpacesManager', 'VLANsManager', |
144 | - 'FabricsManager', 'ManagerHelperService', 'ErrorService', |
145 | + 'UsersManager', 'FabricsManager', 'ManagerHelperService', 'ErrorService', |
146 | function( |
147 | $scope, $rootScope, $routeParams, $filter, $location, SubnetsManager, |
148 | - IPRangesManager, SpacesManager, VLANsManager, FabricsManager, |
149 | - ManagerHelperService, ErrorService) { |
150 | + IPRangesManager, SpacesManager, VLANsManager, UsersManager, |
151 | + FabricsManager, ManagerHelperService, ErrorService) { |
152 | |
153 | // Set title and page. |
154 | $rootScope.title = "Loading..."; |
155 | @@ -63,6 +63,38 @@ |
156 | return FabricsManager.getItemFromList(fabric_id).name; |
157 | }; |
158 | |
159 | + // Return true if the authenticated user is super user. |
160 | + $scope.isSuperUser = function() { |
161 | + return UsersManager.isSuperUser(); |
162 | + }; |
163 | + |
164 | + // Called when the delete subnet button is pressed. |
165 | + $scope.deleteButton = function() { |
166 | + $scope.error = null; |
167 | + $scope.confirmingDelete = true; |
168 | + }; |
169 | + |
170 | + // Called when the cancel delete subnet button is pressed. |
171 | + $scope.cancelDeleteButton = function() { |
172 | + $scope.confirmingDelete = false; |
173 | + }; |
174 | + |
175 | + // Called when an error message from the Python world needs to be |
176 | + // rendered in the UI. |
177 | + $scope.convertPythonDictToErrorMsg = function(error) { |
178 | + return ManagerHelperService.parseLikelyValidationError(error); |
179 | + }; |
180 | + |
181 | + // Called when the confirm delete subnet button is pressed. |
182 | + $scope.deleteConfirmButton = function() { |
183 | + SubnetsManager.deleteSubnet($scope.subnet).then(function() { |
184 | + $scope.confirmingDelete = false; |
185 | + $location.path("/networks"); |
186 | + }, function(error) { |
187 | + $scope.error = $scope.convertPythonDictToErrorMsg(error); |
188 | + }); |
189 | + }; |
190 | + |
191 | // Called when the subnet has been loaded. |
192 | function subnetLoaded(subnet) { |
193 | $scope.subnet = subnet; |
194 | @@ -78,7 +110,7 @@ |
195 | // Load all the required managers. |
196 | ManagerHelperService.loadManagers([ |
197 | SubnetsManager, IPRangesManager, SpacesManager, VLANsManager, |
198 | - FabricsManager |
199 | + UsersManager, FabricsManager |
200 | ]).then(function() { |
201 | // Possibly redirected from another controller that already had |
202 | // this subnet set to active. Only call setActiveItem if not |
203 | |
204 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_add_device.js' |
205 | --- src/maasserver/static/js/angular/controllers/tests/test_add_device.js 2016-03-28 13:54:47 +0000 |
206 | +++ src/maasserver/static/js/angular/controllers/tests/test_add_device.js 2016-04-20 05:12:39 +0000 |
207 | @@ -763,14 +763,14 @@ |
208 | }); |
209 | }); |
210 | |
211 | - describe("convertPythonDictToErrorMsg", function() { |
212 | + describe("devicePythonDictToErrorMsg", function() { |
213 | it("converts hostname error for display", |
214 | function() { |
215 | var controller = makeController(); |
216 | var errorMsg = makeName("error"); |
217 | var error = "{'hostname': ['Node " + errorMsg + "']}"; |
218 | var expected = "Device " + errorMsg + " "; |
219 | - expect($scope.convertPythonDictToErrorMsg( |
220 | + expect($scope.devicePythonDictToErrorMsg( |
221 | error)).toBe(expected); |
222 | }); |
223 | |
224 | @@ -780,7 +780,7 @@ |
225 | var errorMsg = makeName("error"); |
226 | var error = "{'mac_addresses': ['" + errorMsg + "']}"; |
227 | var expected = errorMsg + " "; |
228 | - expect($scope.convertPythonDictToErrorMsg( |
229 | + expect($scope.devicePythonDictToErrorMsg( |
230 | error)).toBe(expected); |
231 | }); |
232 | |
233 | @@ -792,7 +792,7 @@ |
234 | var error = "{'" + errorSegment1 + |
235 | "': ['" + errorSegment2 + "']}"; |
236 | var expected = errorSegment1 + errorSegment2; |
237 | - expect($scope.convertPythonDictToErrorMsg( |
238 | + expect($scope.devicePythonDictToErrorMsg( |
239 | error)).toBe(expected); |
240 | }); |
241 | }); |
242 | |
243 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_add_domain.js' |
244 | --- src/maasserver/static/js/angular/controllers/tests/test_add_domain.js 2016-03-28 13:54:47 +0000 |
245 | +++ src/maasserver/static/js/angular/controllers/tests/test_add_domain.js 2016-04-20 05:12:39 +0000 |
246 | @@ -270,39 +270,4 @@ |
247 | expect($scope.error).toBe(errorMsg); |
248 | }); |
249 | }); |
250 | - |
251 | - describe("convertPythonDictToErrorMsg", function() { |
252 | - it("converts name error for display", |
253 | - function() { |
254 | - var controller = makeController(); |
255 | - var errorMsg = makeName("error"); |
256 | - var error = '{"name": ["' + errorMsg + '"]}'; |
257 | - var expected = errorMsg; |
258 | - expect($scope.convertPythonDictToErrorMsg( |
259 | - error)).toBe(expected); |
260 | - }); |
261 | - |
262 | - it("concatenates array elements", |
263 | - function() { |
264 | - var controller = makeController(); |
265 | - var errorSegment1 = makeName("key"); |
266 | - var errorSegment2 = makeName("error"); |
267 | - var errorSegment3 = makeName("error"); |
268 | - var error = '{"' + errorSegment1 + |
269 | - '": ["' + errorSegment2 + '", "' + |
270 | - errorSegment3 + '"]}'; |
271 | - var expected = errorSegment2 + " " + errorSegment3; |
272 | - expect($scope.convertPythonDictToErrorMsg( |
273 | - error)).toBe(expected); |
274 | - }); |
275 | - |
276 | - it("converts non-dictionary correctly", |
277 | - function() { |
278 | - var controller = makeController(); |
279 | - var errorSegment1 = makeName("error"); |
280 | - var expected = errorSegment1; |
281 | - expect($scope.convertPythonDictToErrorMsg( |
282 | - errorSegment1)).toBe(expected); |
283 | - }); |
284 | - }); |
285 | }); |
286 | |
287 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js' |
288 | --- src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js 2016-03-28 13:54:47 +0000 |
289 | +++ src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js 2016-04-20 05:12:39 +0000 |
290 | @@ -221,4 +221,43 @@ |
291 | expect($scope.subnet.dns_servers).toEqual(["127.0.0.1", "127.0.0.2"]); |
292 | expect($scope.all_dns_servers).toBe("127.0.0.1 127.0.0.2"); |
293 | }); |
294 | + |
295 | + describe("deleteButton", function() { |
296 | + |
297 | + it("confirms delete", function() { |
298 | + var controller = makeControllerResolveSetActiveItem(); |
299 | + $scope.deleteButton(); |
300 | + expect($scope.confirmingDelete).toBe(true); |
301 | + }); |
302 | + |
303 | + it("clears error", function() { |
304 | + var controller = makeControllerResolveSetActiveItem(); |
305 | + $scope.error = makeName("error"); |
306 | + $scope.deleteButton(); |
307 | + expect($scope.error).toBeNull(); |
308 | + }); |
309 | + }); |
310 | + |
311 | + describe("cancelDeleteButton", function() { |
312 | + |
313 | + it("cancels delete", function() { |
314 | + var controller = makeControllerResolveSetActiveItem(); |
315 | + $scope.deleteButton(); |
316 | + $scope.cancelDeleteButton(); |
317 | + expect($scope.confirmingDelete).toBe(false); |
318 | + }); |
319 | + }); |
320 | + |
321 | + describe("deleteSubnet", function() { |
322 | + |
323 | + it("calls deleteSubnet", function() { |
324 | + var controller = makeController(); |
325 | + var deleteSubnet = spyOn(SubnetsManager, "deleteSubnet"); |
326 | + var defer = $q.defer(); |
327 | + deleteSubnet.and.returnValue(defer.promise); |
328 | + $scope.deleteConfirmButton(); |
329 | + expect(deleteSubnet).toHaveBeenCalled(); |
330 | + }); |
331 | + }); |
332 | + |
333 | }); |
334 | |
335 | === modified file 'src/maasserver/static/js/angular/factories/subnets.js' |
336 | --- src/maasserver/static/js/angular/factories/subnets.js 2016-03-28 13:54:47 +0000 |
337 | +++ src/maasserver/static/js/angular/factories/subnets.js 2016-04-20 05:12:39 +0000 |
338 | @@ -75,5 +75,10 @@ |
339 | return RegionConnection.callMethod("subnet.create", subnet); |
340 | }; |
341 | |
342 | + // Delete the subnet. |
343 | + SubnetsManager.prototype.deleteSubnet = function(subnet) { |
344 | + return RegionConnection.callMethod("subnet.delete", subnet); |
345 | + }; |
346 | + |
347 | return new SubnetsManager(); |
348 | }]); |
349 | |
350 | === modified file 'src/maasserver/static/js/angular/factories/tests/test_spaces.js' |
351 | --- src/maasserver/static/js/angular/factories/tests/test_spaces.js 2016-03-28 13:54:47 +0000 |
352 | +++ src/maasserver/static/js/angular/factories/tests/test_spaces.js 2016-04-20 05:12:39 +0000 |
353 | @@ -68,4 +68,17 @@ |
354 | ); |
355 | }); |
356 | }); |
357 | + |
358 | + describe("delete", function() { |
359 | + |
360 | + it("calls the region with expected parameters", function() { |
361 | + var obj = {}; |
362 | + var result = {}; |
363 | + spyOn(RegionConnection, "callMethod").and.returnValue(result); |
364 | + expect(SpacesManager.deleteSpace(obj)).toBe(result); |
365 | + expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
366 | + "space.delete", obj |
367 | + ); |
368 | + }); |
369 | + }); |
370 | }); |
371 | |
372 | === modified file 'src/maasserver/static/js/angular/factories/tests/test_subnets.js' |
373 | --- src/maasserver/static/js/angular/factories/tests/test_subnets.js 2016-03-28 13:54:47 +0000 |
374 | +++ src/maasserver/static/js/angular/factories/tests/test_subnets.js 2016-04-20 05:12:39 +0000 |
375 | @@ -73,4 +73,17 @@ |
376 | ); |
377 | }); |
378 | }); |
379 | + |
380 | + describe("delete", function() { |
381 | + |
382 | + it("calls the region with expected parameters", function() { |
383 | + var obj = {}; |
384 | + var result = {}; |
385 | + spyOn(RegionConnection, "callMethod").and.returnValue(result); |
386 | + expect(SubnetsManager.deleteSubnet(obj)).toBe(result); |
387 | + expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
388 | + "subnet.delete", obj |
389 | + ); |
390 | + }); |
391 | + }); |
392 | }); |
393 | |
394 | === modified file 'src/maasserver/static/js/angular/factories/tests/test_vlans.js' |
395 | --- src/maasserver/static/js/angular/factories/tests/test_vlans.js 2016-03-28 13:54:47 +0000 |
396 | +++ src/maasserver/static/js/angular/factories/tests/test_vlans.js 2016-04-20 05:12:39 +0000 |
397 | @@ -102,4 +102,17 @@ |
398 | ); |
399 | }); |
400 | }); |
401 | + |
402 | + describe("delete", function() { |
403 | + |
404 | + it("calls the region with expected parameters", function() { |
405 | + var obj = { id: "whatever" }; |
406 | + var result = {}; |
407 | + spyOn(RegionConnection, "callMethod").and.returnValue(result); |
408 | + expect(VLANsManager.deleteVLAN(obj)).toBe(result); |
409 | + expect(RegionConnection.callMethod).toHaveBeenCalledWith( |
410 | + "vlan.delete", obj, true |
411 | + ); |
412 | + }); |
413 | + }); |
414 | }); |
415 | |
416 | === modified file 'src/maasserver/static/partials/subnet-details.html' |
417 | --- src/maasserver/static/partials/subnet-details.html 2016-03-28 13:54:47 +0000 |
418 | +++ src/maasserver/static/partials/subnet-details.html 2016-04-20 05:12:39 +0000 |
419 | @@ -1,83 +1,42 @@ |
420 | -<div data-ng-hide="loaded"> |
421 | - <header class="page-header"> |
422 | - <div class="inner-wrapper"> |
423 | - <h1 class="page-header__title twelve-col">Loading...</h1> |
424 | - </div> |
425 | - </header> |
426 | -</div> |
427 | +<header class="page-header margin-bottom" data-maas-sticky-header> |
428 | + <div class="inner-wrapper"> |
429 | + <h1 class="page-header__title eight-col">{$ subnet.cidr $}</h1> |
430 | + <div data-ng-hide="loaded"> |
431 | + <div class="inner-wrapper"> |
432 | + <h1 class="page-header__title twelve-col">Loading...</h1> |
433 | + </div> |
434 | + </div> |
435 | + <div class="page-header__actions ng-hide" data-ng-show="isSuperUser() && !isDefaultSubnet() && !loading"> |
436 | + <div class="right no-margin-bottom last-col"> |
437 | + <button class="cta-ubuntu" |
438 | + data-ng-click="deleteButton()" |
439 | + data-ng-hide="confirmingDelete">Delete Subnet</button> |
440 | + </div> |
441 | + </div> |
442 | + <div class="page-header__dropdown ng-hide" data-ng-show="confirmingDelete"> |
443 | + <div class="page-header__feedback ng-hide" data-ng-show="!error"> |
444 | + <p class="page-header__feedback-message error"> |
445 | + Are you sure you want to delete this subnet and release all |
446 | + IP addresses assigned within its range? |
447 | + </p> |
448 | + <div class="right"> |
449 | + <a href="" class="margin-right" data-ng-click="cancelDeleteButton()">Cancel</a> |
450 | + <button class="cta-ubuntu" data-ng-click="deleteConfirmButton()">Go</button> |
451 | + </div> |
452 | + </div> |
453 | + <div class="page-header__feedback ng-hide" data-ng-show="error"> |
454 | + <p class="page-header__feedback-message error"> |
455 | + {$ error $} |
456 | + </p> |
457 | + <div class="right"> |
458 | + <a href="" class="margin-right" data-ng-click="cancelDeleteButton()">Cancel</a> |
459 | + <button class="cta-ubuntu" data-ng-click="deleteConfirmButton()">Retry</button> |
460 | + </div> |
461 | + </div> |
462 | + </div> |
463 | + </div> |
464 | +</header> |
465 | <div data-ng-if="loaded"> |
466 | - <header class="page-header margin-bottom" data-maas-sticky-header> |
467 | - <div class="inner-wrapper"> |
468 | - <h1 class="page-header__title eight-col">{$ subnet.cidr $}</h1> |
469 | - <div class="page-header__actions"> |
470 | - <div class="page-header__cta"> |
471 | - <div class="right cta-group ng-hide"> |
472 | - <a class="cta-group__link ng-binding" data-ng-click="shown=!shown">Take action</a> |
473 | - <ul class="cta-group__dropdown" role="menu" data-ng-show="shown"> |
474 | - <!-- <li class="cta-group__item ng-scope"> |
475 | - <a>Fabric</a> |
476 | - </li> |
477 | - <li class="cta-group__item ng-scope"> |
478 | - <a>VLAN</a> |
479 | - </li> |
480 | - <li class="cta-group__item ng-scope"> |
481 | - <a>Space</a> |
482 | - </li> |
483 | - <li class="cta-group__item ng-scope"> |
484 | - <a>Subnet</a> |
485 | - </li> --> |
486 | - </ul> |
487 | - </div> |
488 | - </div> |
489 | - <div class="page-header__cta ng-hide"> |
490 | - <span class="page-header__cta-feedback"> |
491 | - 2 Selected |
492 | - </span> |
493 | - <div class="right cta-group"> |
494 | - <a class="cta-group__link ng-binding" data-ng-click="shown=!shown">Take action</a> |
495 | - <ul class="cta-group__dropdown ng-hide" role="menu" data-ng-show="shown"> |
496 | - <li class="cta-group__item ng-scope"> |
497 | - <a>Move subnet</a> |
498 | - </li> |
499 | - <li class="cta-group__item ng-scope"> |
500 | - <a>Remove subnet</a> |
501 | - </li> |
502 | - </ul> |
503 | - </div> |
504 | - </div> |
505 | - </div> |
506 | - <div class="page-header__dropdown border ng-hide"> |
507 | - <form class="form-inline tweleve-col padding-top--ten padding-bottom--ten"> |
508 | - <div class="form__group"> |
509 | - <label for="move-subnet">Move this subnet to</label> |
510 | - <select name="move-subnet" id="move-subnet"> |
511 | - <option value="1">Space 1</option> |
512 | - <option value="2">Space 2</option> |
513 | - <option value="3">Space 3</option> |
514 | - </select> |
515 | - <div class="right"> |
516 | - <button class="cta-ubuntu text-button">Cancel</button> |
517 | - <button class="cta-ubuntu">Move subnet</button> |
518 | - </div> |
519 | - </div> |
520 | - </form> |
521 | - <div class="page-header__feedback"> |
522 | - <p class="page-header__feedback-message warning">Are you sure you want to remove this subnet release all IP addresses assigned within its range?</p> |
523 | - <div class="right"> |
524 | - <button class="cta-ubuntu text-button">Cancel</button> |
525 | - <button class="cta-ubuntu">Remove subnet</button> |
526 | - </div> |
527 | - </div> |
528 | - <div class="page-header__feedback"> |
529 | - <p class="page-header__feedback-message warning">Are you sure you want to remove this subnet?</p> |
530 | - <div class="right"> |
531 | - <button class="cta-ubuntu text-button">Cancel</button> |
532 | - <button class="cta-ubuntu">Remove subnet</button> |
533 | - </div> |
534 | - </div> |
535 | - </div> |
536 | - </div> |
537 | - </header> |
538 | <section class="row"> |
539 | <div class="inner-wrapper"> |
540 | <div class="tweleve-col"> |
541 | @@ -290,11 +249,6 @@ |
542 | --> |
543 | </main> |
544 | </div> |
545 | - <!-- XXX mpontillo re-enable buttons when adding edit mode --> |
546 | - <div class="tweleve-col padding-bottom ng-hide"> |
547 | - <a class="link-cta-ubuntu secondary margin-right">Reserve range</a> |
548 | - <a class="link-cta-ubuntu secondary">Reserve dynamic range</a> |
549 | - </div> |
550 | </div> |
551 | <div class="tweleve-col"> |
552 | <div class="six-col"> |
553 | |
554 | === modified file 'src/maasserver/websockets/handlers/subnet.py' |
555 | --- src/maasserver/websockets/handlers/subnet.py 2016-04-14 15:25:36 +0000 |
556 | +++ src/maasserver/websockets/handlers/subnet.py 2016-04-20 05:12:39 +0000 |
557 | @@ -7,6 +7,7 @@ |
558 | "SubnetHandler", |
559 | ] |
560 | |
561 | +from maasserver.enum import NODE_PERMISSION |
562 | from maasserver.models.subnet import Subnet |
563 | from maasserver.websockets.handlers.timestampedmodel import ( |
564 | TimestampedModelHandler, |
565 | @@ -28,6 +29,7 @@ |
566 | pk = 'id' |
567 | allowed_methods = [ |
568 | 'create', |
569 | + 'delete', |
570 | 'get', |
571 | 'list', |
572 | 'set_active' |
573 | @@ -46,3 +48,10 @@ |
574 | data["ip_addresses"] = subnet.render_json_for_related_ips( |
575 | with_username=True, with_node_summary=True) |
576 | return data |
577 | + |
578 | + def delete(self, parameters): |
579 | + """Delete this Subnet.""" |
580 | + subnet = self.get_object(parameters) |
581 | + assert self.user.has_perm( |
582 | + NODE_PERMISSION.ADMIN, subnet), "Permission denied." |
583 | + subnet.delete() |
584 | |
585 | === modified file 'src/maasserver/websockets/handlers/tests/test_space.py' |
586 | --- src/maasserver/websockets/handlers/tests/test_space.py 2016-03-28 13:54:47 +0000 |
587 | +++ src/maasserver/websockets/handlers/tests/test_space.py 2016-04-20 05:12:39 +0000 |
588 | @@ -8,8 +8,11 @@ |
589 | from maasserver.models.space import Space |
590 | from maasserver.testing.factory import factory |
591 | from maasserver.testing.testcase import MAASServerTestCase |
592 | +from maasserver.utils.orm import reload_object |
593 | from maasserver.websockets.handlers.space import SpaceHandler |
594 | from maasserver.websockets.handlers.timestampedmodel import dehydrate_datetime |
595 | +from testtools import ExpectedException |
596 | +from testtools.matchers import Equals |
597 | |
598 | |
599 | class TestSpaceHandler(MAASServerTestCase): |
600 | @@ -51,3 +54,36 @@ |
601 | self.assertItemsEqual( |
602 | expected_spaces, |
603 | handler.list({})) |
604 | + |
605 | + |
606 | +class TestSpaceHandlerDelete(MAASServerTestCase): |
607 | + |
608 | + def test__delete_as_admin_success(self): |
609 | + user = factory.make_admin() |
610 | + handler = SpaceHandler(user, {}) |
611 | + space = factory.make_Space() |
612 | + handler.delete({ |
613 | + "id": space.id, |
614 | + }) |
615 | + space = reload_object(space) |
616 | + self.assertThat(space, Equals(None)) |
617 | + |
618 | + def test__delete_as_non_admin_asserts(self): |
619 | + user = factory.make_User() |
620 | + handler = SpaceHandler(user, {}) |
621 | + space = factory.make_Space() |
622 | + with ExpectedException(AssertionError, "Permission denied."): |
623 | + handler.delete({ |
624 | + "id": space.id, |
625 | + }) |
626 | + |
627 | + def test__reloads_user(self): |
628 | + user = factory.make_admin() |
629 | + handler = SpaceHandler(user, {}) |
630 | + space = factory.make_Space() |
631 | + user.is_superuser = False |
632 | + user.save() |
633 | + with ExpectedException(AssertionError, "Permission denied."): |
634 | + handler.delete({ |
635 | + "id": space.id, |
636 | + }) |
637 | |
638 | === modified file 'src/maasserver/websockets/handlers/tests/test_subnet.py' |
639 | --- src/maasserver/websockets/handlers/tests/test_subnet.py 2016-04-14 15:25:36 +0000 |
640 | +++ src/maasserver/websockets/handlers/tests/test_subnet.py 2016-04-20 05:12:39 +0000 |
641 | @@ -8,10 +8,12 @@ |
642 | from maasserver.models.subnet import Subnet |
643 | from maasserver.testing.factory import factory |
644 | from maasserver.testing.testcase import MAASServerTestCase |
645 | +from maasserver.utils.orm import reload_object |
646 | from maasserver.websockets.handlers.subnet import SubnetHandler |
647 | from maasserver.websockets.handlers.timestampedmodel import dehydrate_datetime |
648 | from netaddr import IPNetwork |
649 | from provisioningserver.utils.network import IPRangeStatistics |
650 | +from testtools import ExpectedException |
651 | from testtools.matchers import Equals |
652 | |
653 | |
654 | @@ -60,3 +62,36 @@ |
655 | self.assertItemsEqual( |
656 | expected_subnets, |
657 | handler.list({})) |
658 | + |
659 | + |
660 | +class TestSubnetHandlerDelete(MAASServerTestCase): |
661 | + |
662 | + def test__delete_as_admin_success(self): |
663 | + user = factory.make_admin() |
664 | + handler = SubnetHandler(user, {}) |
665 | + subnet = factory.make_Subnet() |
666 | + handler.delete({ |
667 | + "id": subnet.id, |
668 | + }) |
669 | + subnet = reload_object(subnet) |
670 | + self.assertThat(subnet, Equals(None)) |
671 | + |
672 | + def test__delete_as_non_admin_asserts(self): |
673 | + user = factory.make_User() |
674 | + handler = SubnetHandler(user, {}) |
675 | + subnet = factory.make_Subnet() |
676 | + with ExpectedException(AssertionError, "Permission denied."): |
677 | + handler.delete({ |
678 | + "id": subnet.id, |
679 | + }) |
680 | + |
681 | + def test__reloads_user(self): |
682 | + user = factory.make_admin() |
683 | + handler = SubnetHandler(user, {}) |
684 | + subnet = factory.make_Subnet() |
685 | + user.is_superuser = False |
686 | + user.save() |
687 | + with ExpectedException(AssertionError, "Permission denied."): |
688 | + handler.delete({ |
689 | + "id": subnet.id, |
690 | + }) |
Looks good. Not going to block you on anything, but please fix the issues before landing.