Merge lp:~blake-rouse/maas/fix-1592137 into lp:~maas-committers/maas/trunk
- fix-1592137
- Merge into trunk
Proposed by
Blake Rouse
Status: | Merged | ||||
---|---|---|---|---|---|
Approved by: | Blake Rouse | ||||
Approved revision: | no longer in the source branch. | ||||
Merged at revision: | 5127 | ||||
Proposed branch: | lp:~blake-rouse/maas/fix-1592137 | ||||
Merge into: | lp:~maas-committers/maas/trunk | ||||
Diff against target: |
725 lines (+450/-97) 7 files modified
src/maasserver/enum.py (+7/-3) src/maasserver/static/js/angular/controllers/subnet_details.js (+91/-5) src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js (+154/-0) src/maasserver/static/js/angular/services/converter.js (+55/-0) src/maasserver/static/js/angular/services/tests/test_converter.js (+104/-0) src/maasserver/static/js/angular/services/validation.js (+15/-55) src/maasserver/static/partials/subnet-details.html (+24/-34) |
||||
To merge this branch: | bzr merge lp:~blake-rouse/maas/fix-1592137 | ||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Mike Pontillo (community) | Approve | ||
Jeffrey C Jones (community) | Approve | ||
Review via email: mp+297468@code.launchpad.net |
Commit message
Add the ability to sort the IP address table on the subnet details page.
Description of the change
To post a comment you must log in.
Revision history for this message
Mike Pontillo (mpontillo) wrote : | # |
Looks really good!
I made a number of comments below; nothing really blocking, but some things to consider and some minor cleanup.
review:
Approve
Revision history for this message
Blake Rouse (blake-rouse) wrote : | # |
Thanks for the reviews. Addressed all comments.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'src/maasserver/enum.py' | |||
2 | --- src/maasserver/enum.py 2016-04-11 16:23:26 +0000 | |||
3 | +++ src/maasserver/enum.py 2016-06-16 14:50:25 +0000 | |||
4 | @@ -134,6 +134,8 @@ | |||
5 | 134 | REGION_AND_RACK_CONTROLLER = 4 | 134 | REGION_AND_RACK_CONTROLLER = 4 |
6 | 135 | 135 | ||
7 | 136 | 136 | ||
8 | 137 | # This is copied in static/js/angular/controllers/subnet_details.js. If you | ||
9 | 138 | # update any choices you also need to update the controller. | ||
10 | 137 | NODE_TYPE_CHOICES = ( | 139 | NODE_TYPE_CHOICES = ( |
11 | 138 | (NODE_TYPE.MACHINE, "Machine"), | 140 | (NODE_TYPE.MACHINE, "Machine"), |
12 | 139 | (NODE_TYPE.DEVICE, "Device"), | 141 | (NODE_TYPE.DEVICE, "Device"), |
13 | @@ -226,12 +228,14 @@ | |||
14 | 226 | DISCOVERED = 6 | 228 | DISCOVERED = 6 |
15 | 227 | 229 | ||
16 | 228 | 230 | ||
17 | 231 | # This is copied in static/js/angular/controllers/subnet_details.js. If you | ||
18 | 232 | # update any choices you also need to update the controller. | ||
19 | 229 | IPADDRESS_TYPE_CHOICES = ( | 233 | IPADDRESS_TYPE_CHOICES = ( |
22 | 230 | (IPADDRESS_TYPE.AUTO, "Auto"), | 234 | (IPADDRESS_TYPE.AUTO, "Automatic"), |
23 | 231 | (IPADDRESS_TYPE.STICKY, "Sticky"), | 235 | (IPADDRESS_TYPE.STICKY, "Static"), |
24 | 232 | (IPADDRESS_TYPE.USER_RESERVED, "User reserved"), | 236 | (IPADDRESS_TYPE.USER_RESERVED, "User reserved"), |
25 | 233 | (IPADDRESS_TYPE.DHCP, "DHCP"), | 237 | (IPADDRESS_TYPE.DHCP, "DHCP"), |
27 | 234 | (IPADDRESS_TYPE.DISCOVERED, "Discovered"), | 238 | (IPADDRESS_TYPE.DISCOVERED, "Observed"), |
28 | 235 | ) | 239 | ) |
29 | 236 | 240 | ||
30 | 237 | 241 | ||
31 | 238 | 242 | ||
32 | === modified file 'src/maasserver/static/js/angular/controllers/subnet_details.js' | |||
33 | --- src/maasserver/static/js/angular/controllers/subnet_details.js 2016-06-09 17:18:10 +0000 | |||
34 | +++ src/maasserver/static/js/angular/controllers/subnet_details.js 2016-06-16 14:50:25 +0000 | |||
35 | @@ -8,10 +8,11 @@ | |||
36 | 8 | '$scope', '$rootScope', '$routeParams', '$filter', '$location', | 8 | '$scope', '$rootScope', '$routeParams', '$filter', '$location', |
37 | 9 | 'SubnetsManager', 'IPRangesManager', 'SpacesManager', 'VLANsManager', | 9 | 'SubnetsManager', 'IPRangesManager', 'SpacesManager', 'VLANsManager', |
38 | 10 | 'UsersManager', 'FabricsManager', 'ManagerHelperService', 'ErrorService', | 10 | 'UsersManager', 'FabricsManager', 'ManagerHelperService', 'ErrorService', |
39 | 11 | 'ConverterService', | ||
40 | 11 | function( | 12 | function( |
41 | 12 | $scope, $rootScope, $routeParams, $filter, $location, SubnetsManager, | 13 | $scope, $rootScope, $routeParams, $filter, $location, SubnetsManager, |
42 | 13 | IPRangesManager, SpacesManager, VLANsManager, UsersManager, | 14 | IPRangesManager, SpacesManager, VLANsManager, UsersManager, |
44 | 14 | FabricsManager, ManagerHelperService, ErrorService) { | 15 | FabricsManager, ManagerHelperService, ErrorService, ConverterService) { |
45 | 15 | 16 | ||
46 | 16 | // Set title and page. | 17 | // Set title and page. |
47 | 17 | $rootScope.title = "Loading..."; | 18 | $rootScope.title = "Loading..."; |
48 | @@ -29,10 +30,29 @@ | |||
49 | 29 | $scope.spaces = SpacesManager.getItems(); | 30 | $scope.spaces = SpacesManager.getItems(); |
50 | 30 | $scope.vlans = VLANsManager.getItems(); | 31 | $scope.vlans = VLANsManager.getItems(); |
51 | 31 | $scope.fabrics = FabricsManager.getItems(); | 32 | $scope.fabrics = FabricsManager.getItems(); |
52 | 33 | $scope.reverse = false; | ||
53 | 32 | $scope.newRange = null; | 34 | $scope.newRange = null; |
54 | 33 | $scope.editIPRange = null; | 35 | $scope.editIPRange = null; |
55 | 34 | $scope.deleteIPRange = null; | 36 | $scope.deleteIPRange = null; |
56 | 35 | 37 | ||
57 | 38 | // Alloc type mapping. | ||
58 | 39 | var ALLOC_TYPES = { | ||
59 | 40 | 0: 'Automatic', | ||
60 | 41 | 1: 'Static', | ||
61 | 42 | 4: 'User reserved', | ||
62 | 43 | 5: 'DHCP', | ||
63 | 44 | 6: 'Observed' | ||
64 | 45 | }; | ||
65 | 46 | |||
66 | 47 | // Node type mapping. | ||
67 | 48 | var NODE_TYPES = { | ||
68 | 49 | 0: 'Machine', | ||
69 | 50 | 1: 'Device', | ||
70 | 51 | 2: 'Rack controller', | ||
71 | 52 | 3: 'Region controller', | ||
72 | 53 | 4: 'Rack and region controller' | ||
73 | 54 | }; | ||
74 | 55 | |||
75 | 36 | // Updates the page title. | 56 | // Updates the page title. |
76 | 37 | function updateTitle() { | 57 | function updateTitle() { |
77 | 38 | subnet = $scope.subnet; | 58 | subnet = $scope.subnet; |
78 | @@ -44,10 +64,75 @@ | |||
79 | 44 | } | 64 | } |
80 | 45 | } | 65 | } |
81 | 46 | 66 | ||
86 | 47 | $scope.isSuperUser = function() { | 67 | // Update the IP version of the CIDR. |
87 | 48 | return UsersManager.isSuperUser(); | 68 | function updateIPVersion() { |
88 | 49 | }; | 69 | var ip = $scope.subnet.cidr.split('/')[0]; |
89 | 50 | 70 | if(ip.indexOf(':') === -1) { | |
90 | 71 | $scope.ipVersion = 4; | ||
91 | 72 | } else { | ||
92 | 73 | $scope.ipVersion = 6; | ||
93 | 74 | } | ||
94 | 75 | } | ||
95 | 76 | |||
96 | 77 | // Sort for IP address. | ||
97 | 78 | $scope.ipSort = function(ipAddress) { | ||
98 | 79 | if($scope.ipVersion === 4) { | ||
99 | 80 | return ConverterService.ipv4ToInteger(ipAddress.ip); | ||
100 | 81 | } else { | ||
101 | 82 | return ConverterService.ipv6Expand(ipAddress.ip); | ||
102 | 83 | } | ||
103 | 84 | }; | ||
104 | 85 | |||
105 | 86 | // Set default predicate to the ipSort function. | ||
106 | 87 | $scope.predicate = $scope.ipSort; | ||
107 | 88 | |||
108 | 89 | // Return the name of the allocation type. | ||
109 | 90 | $scope.getAllocType = function(allocType) { | ||
110 | 91 | var str = ALLOC_TYPES[allocType]; | ||
111 | 92 | if(angular.isString(str)) { | ||
112 | 93 | return str; | ||
113 | 94 | } else { | ||
114 | 95 | return "Unknown"; | ||
115 | 96 | } | ||
116 | 97 | }; | ||
117 | 98 | |||
118 | 99 | // Sort based on the name of the allocation type. | ||
119 | 100 | $scope.allocTypeSort = function(ipAddress) { | ||
120 | 101 | return $scope.getAllocType(ipAddress.alloc_type); | ||
121 | 102 | }; | ||
122 | 103 | |||
123 | 104 | // Return the name of the node type. | ||
124 | 105 | $scope.getNodeType = function(nodeType) { | ||
125 | 106 | var str = NODE_TYPES[nodeType]; | ||
126 | 107 | if(angular.isString(str)) { | ||
127 | 108 | return str; | ||
128 | 109 | } else { | ||
129 | 110 | return "Unknown"; | ||
130 | 111 | } | ||
131 | 112 | }; | ||
132 | 113 | |||
133 | 114 | // Sort based on the node type string. | ||
134 | 115 | $scope.nodeTypeSort = function(ipAddress) { | ||
135 | 116 | return $scope.getNodeType(ipAddress.node_summary.node_type); | ||
136 | 117 | }; | ||
137 | 118 | |||
138 | 119 | // Sort based on the owner name. | ||
139 | 120 | $scope.ownerSort = function(ipAddress) { | ||
140 | 121 | var owner = ipAddress.user; | ||
141 | 122 | if(angular.isString(owner) && owner.length > 0) { | ||
142 | 123 | return owner; | ||
143 | 124 | } else { | ||
144 | 125 | return "MAAS"; | ||
145 | 126 | } | ||
146 | 127 | }; | ||
147 | 128 | |||
148 | 129 | // Called to change the sort order of the IP table. | ||
149 | 130 | $scope.sortIPTable = function(predicate) { | ||
150 | 131 | $scope.predicate = predicate; | ||
151 | 132 | $scope.reverse = !$scope.reverse; | ||
152 | 133 | }; | ||
153 | 134 | |||
154 | 135 | // Return the name of the VLAN. | ||
155 | 51 | $scope.getVLANName = function(vlan) { | 136 | $scope.getVLANName = function(vlan) { |
156 | 52 | return VLANsManager.getName(vlan); | 137 | return VLANsManager.getName(vlan); |
157 | 53 | }; | 138 | }; |
158 | @@ -179,6 +264,7 @@ | |||
159 | 179 | }; | 264 | }; |
160 | 180 | $scope.$watch("subnet.fabric", updateFabric); | 265 | $scope.$watch("subnet.fabric", updateFabric); |
161 | 181 | $scope.$watch("subnet.vlan", updateFabric); | 266 | $scope.$watch("subnet.vlan", updateFabric); |
162 | 267 | $scope.$watch("subnet.cidr", updateIPVersion); | ||
163 | 182 | } | 268 | } |
164 | 183 | 269 | ||
165 | 184 | // Load all the required managers. | 270 | // Load all the required managers. |
166 | 185 | 271 | ||
167 | === modified file 'src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js' | |||
168 | --- src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js 2016-06-13 14:21:20 +0000 | |||
169 | +++ src/maasserver/static/js/angular/controllers/tests/test_subnet_details.js 2016-06-16 14:50:25 +0000 | |||
170 | @@ -72,6 +72,7 @@ | |||
171 | 72 | // Load any injected managers and services. | 72 | // Load any injected managers and services. |
172 | 73 | var SubnetsManager, IPRangesManager, SpacesManager, VLANsManager; | 73 | var SubnetsManager, IPRangesManager, SpacesManager, VLANsManager; |
173 | 74 | var FabricsManager, UsersManager, HelperService, ErrorService; | 74 | var FabricsManager, UsersManager, HelperService, ErrorService; |
174 | 75 | var ConverterService; | ||
175 | 75 | beforeEach(inject(function($injector) { | 76 | beforeEach(inject(function($injector) { |
176 | 76 | SubnetsManager = $injector.get("SubnetsManager"); | 77 | SubnetsManager = $injector.get("SubnetsManager"); |
177 | 77 | IPRangesManager = $injector.get("IPRangesManager"); | 78 | IPRangesManager = $injector.get("IPRangesManager"); |
178 | @@ -81,6 +82,7 @@ | |||
179 | 81 | UsersManager = $injector.get("UsersManager"); | 82 | UsersManager = $injector.get("UsersManager"); |
180 | 82 | ManagerHelperService = $injector.get("ManagerHelperService"); | 83 | ManagerHelperService = $injector.get("ManagerHelperService"); |
181 | 83 | ErrorService = $injector.get("ErrorService"); | 84 | ErrorService = $injector.get("ErrorService"); |
182 | 85 | ConverterService = $injector.get("ConverterService"); | ||
183 | 84 | })); | 86 | })); |
184 | 85 | 87 | ||
185 | 86 | var fabric, vlan, space, subnet; | 88 | var fabric, vlan, space, subnet; |
186 | @@ -209,6 +211,158 @@ | |||
187 | 209 | expect($rootScope.title).toBe(subnet.cidr + " (" + subnet.name + ")"); | 211 | expect($rootScope.title).toBe(subnet.cidr + " (" + subnet.name + ")"); |
188 | 210 | }); | 212 | }); |
189 | 211 | 213 | ||
190 | 214 | describe("ipSort", function() { | ||
191 | 215 | |||
192 | 216 | it("calls ipv4ToInteger when ipVersion == 4", function() { | ||
193 | 217 | var controller = makeControllerResolveSetActiveItem(); | ||
194 | 218 | $scope.ipVersion = 4; | ||
195 | 219 | var expected = {}; | ||
196 | 220 | spyOn(ConverterService, "ipv4ToInteger").and.returnValue(expected); | ||
197 | 221 | var ipAddress = { | ||
198 | 222 | ip: {} | ||
199 | 223 | }; | ||
200 | 224 | var observed = $scope.ipSort(ipAddress); | ||
201 | 225 | expect(ConverterService.ipv4ToInteger).toHaveBeenCalledWith( | ||
202 | 226 | ipAddress.ip); | ||
203 | 227 | expect(observed).toBe(expected); | ||
204 | 228 | }); | ||
205 | 229 | |||
206 | 230 | it("calls ipv6Expand when ipVersion == 6", function() { | ||
207 | 231 | var controller = makeControllerResolveSetActiveItem(); | ||
208 | 232 | $scope.ipVersion = 6; | ||
209 | 233 | var expected = {}; | ||
210 | 234 | spyOn(ConverterService, "ipv6Expand").and.returnValue(expected); | ||
211 | 235 | var ipAddress = { | ||
212 | 236 | ip: {} | ||
213 | 237 | }; | ||
214 | 238 | var observed = $scope.ipSort(ipAddress); | ||
215 | 239 | expect(ConverterService.ipv6Expand).toHaveBeenCalledWith( | ||
216 | 240 | ipAddress.ip); | ||
217 | 241 | expect(observed).toBe(expected); | ||
218 | 242 | }); | ||
219 | 243 | |||
220 | 244 | it("is predicate default", function() { | ||
221 | 245 | var controller = makeControllerResolveSetActiveItem(); | ||
222 | 246 | expect($scope.predicate).toBe($scope.ipSort); | ||
223 | 247 | }); | ||
224 | 248 | }); | ||
225 | 249 | |||
226 | 250 | describe("getAllocType", function() { | ||
227 | 251 | |||
228 | 252 | var scenarios = { | ||
229 | 253 | 0: 'Automatic', | ||
230 | 254 | 1: 'Static', | ||
231 | 255 | 4: 'User reserved', | ||
232 | 256 | 5: 'DHCP', | ||
233 | 257 | 6: 'Observed', | ||
234 | 258 | 7: 'Unknown' | ||
235 | 259 | }; | ||
236 | 260 | |||
237 | 261 | angular.forEach(scenarios, function(expected, allocType) { | ||
238 | 262 | it("allocType( " + allocType + ") = " + expected, function() { | ||
239 | 263 | var controller = makeControllerResolveSetActiveItem(); | ||
240 | 264 | expect($scope.getAllocType(allocType)).toBe(expected); | ||
241 | 265 | }); | ||
242 | 266 | }); | ||
243 | 267 | }); | ||
244 | 268 | |||
245 | 269 | describe("allocTypeSort", function() { | ||
246 | 270 | |||
247 | 271 | it("calls getAllocType", function() { | ||
248 | 272 | var controller = makeControllerResolveSetActiveItem(); | ||
249 | 273 | var expected = {}; | ||
250 | 274 | spyOn($scope, "getAllocType").and.returnValue(expected); | ||
251 | 275 | var ipAddress = { | ||
252 | 276 | alloc_type: {} | ||
253 | 277 | }; | ||
254 | 278 | var observed = $scope.allocTypeSort(ipAddress); | ||
255 | 279 | expect($scope.getAllocType).toHaveBeenCalledWith( | ||
256 | 280 | ipAddress.alloc_type); | ||
257 | 281 | expect(observed).toBe(expected); | ||
258 | 282 | }); | ||
259 | 283 | }); | ||
260 | 284 | |||
261 | 285 | describe("getNodeType", function() { | ||
262 | 286 | |||
263 | 287 | var scenarios = { | ||
264 | 288 | 0: 'Machine', | ||
265 | 289 | 1: 'Device', | ||
266 | 290 | 2: 'Rack controller', | ||
267 | 291 | 3: 'Region controller', | ||
268 | 292 | 4: 'Rack and region controller', | ||
269 | 293 | 5: 'Unknown' | ||
270 | 294 | }; | ||
271 | 295 | |||
272 | 296 | angular.forEach(scenarios, function(expected, nodeType) { | ||
273 | 297 | it("nodeType( " + nodeType + ") = " + expected, function() { | ||
274 | 298 | var controller = makeControllerResolveSetActiveItem(); | ||
275 | 299 | expect($scope.getNodeType(nodeType)).toBe(expected); | ||
276 | 300 | }); | ||
277 | 301 | }); | ||
278 | 302 | }); | ||
279 | 303 | |||
280 | 304 | describe("nodeTypeSort", function() { | ||
281 | 305 | |||
282 | 306 | it("calls getNodeType", function() { | ||
283 | 307 | var controller = makeControllerResolveSetActiveItem(); | ||
284 | 308 | var expected = {}; | ||
285 | 309 | spyOn($scope, "getNodeType").and.returnValue(expected); | ||
286 | 310 | var ipAddress = { | ||
287 | 311 | node_summary: { | ||
288 | 312 | node_type: {} | ||
289 | 313 | } | ||
290 | 314 | }; | ||
291 | 315 | var observed = $scope.nodeTypeSort(ipAddress); | ||
292 | 316 | expect($scope.getNodeType).toHaveBeenCalledWith( | ||
293 | 317 | ipAddress.node_summary.node_type); | ||
294 | 318 | expect(observed).toBe(expected); | ||
295 | 319 | }); | ||
296 | 320 | }); | ||
297 | 321 | |||
298 | 322 | describe("ownerSort", function() { | ||
299 | 323 | |||
300 | 324 | it("returns owner", function() { | ||
301 | 325 | var controller = makeControllerResolveSetActiveItem(); | ||
302 | 326 | var ipAddress = { | ||
303 | 327 | user: makeName("owner") | ||
304 | 328 | }; | ||
305 | 329 | var observed = $scope.ownerSort(ipAddress); | ||
306 | 330 | expect(observed).toBe(ipAddress.user); | ||
307 | 331 | }); | ||
308 | 332 | |||
309 | 333 | it("returns MAAS for empty string", function() { | ||
310 | 334 | var controller = makeControllerResolveSetActiveItem(); | ||
311 | 335 | var ipAddress = { | ||
312 | 336 | user: "" | ||
313 | 337 | }; | ||
314 | 338 | var observed = $scope.ownerSort(ipAddress); | ||
315 | 339 | expect(observed).toBe("MAAS"); | ||
316 | 340 | }); | ||
317 | 341 | |||
318 | 342 | it("returns MAAS for null", function() { | ||
319 | 343 | var controller = makeControllerResolveSetActiveItem(); | ||
320 | 344 | var ipAddress = { | ||
321 | 345 | user: null | ||
322 | 346 | }; | ||
323 | 347 | var observed = $scope.ownerSort(ipAddress); | ||
324 | 348 | expect(observed).toBe("MAAS"); | ||
325 | 349 | }); | ||
326 | 350 | }); | ||
327 | 351 | |||
328 | 352 | describe("sortIPTable", function() { | ||
329 | 353 | |||
330 | 354 | it("sets predicate and inverts reverse", function() { | ||
331 | 355 | var controller = makeControllerResolveSetActiveItem(); | ||
332 | 356 | $scope.reverse = true; | ||
333 | 357 | var predicate = {}; | ||
334 | 358 | $scope.sortIPTable(predicate); | ||
335 | 359 | expect($scope.predicate).toBe(predicate); | ||
336 | 360 | expect($scope.reverse).toBe(false); | ||
337 | 361 | $scope.sortIPTable(predicate); | ||
338 | 362 | expect($scope.reverse).toBe(true); | ||
339 | 363 | }); | ||
340 | 364 | }); | ||
341 | 365 | |||
342 | 212 | describe("deleteButton", function() { | 366 | describe("deleteButton", function() { |
343 | 213 | 367 | ||
344 | 214 | it("confirms delete", function() { | 368 | it("confirms delete", function() { |
345 | 215 | 369 | ||
346 | === modified file 'src/maasserver/static/js/angular/services/converter.js' | |||
347 | --- src/maasserver/static/js/angular/services/converter.js 2016-03-28 13:54:47 +0000 | |||
348 | +++ src/maasserver/static/js/angular/services/converter.js 2016-06-16 14:50:25 +0000 | |||
349 | @@ -86,4 +86,59 @@ | |||
350 | 86 | this.roundByBlockSize = function(bytes, block_size) { | 86 | this.roundByBlockSize = function(bytes, block_size) { |
351 | 87 | return block_size * Math.floor(bytes / block_size); | 87 | return block_size * Math.floor(bytes / block_size); |
352 | 88 | }; | 88 | }; |
353 | 89 | |||
354 | 90 | // Convert string ipv4 address into octets array. | ||
355 | 91 | this.ipv4ToOctets = function(ipAddress) { | ||
356 | 92 | var parts = ipAddress.split('.'); | ||
357 | 93 | var octets = []; | ||
358 | 94 | angular.forEach(parts, function(part) { | ||
359 | 95 | octets.push(parseInt(part, 10)); | ||
360 | 96 | }); | ||
361 | 97 | return octets; | ||
362 | 98 | }; | ||
363 | 99 | |||
364 | 100 | // Convert string ipv4 address into integer. | ||
365 | 101 | this.ipv4ToInteger = function(ipAddress) { | ||
366 | 102 | var octets = this.ipv4ToOctets(ipAddress); | ||
367 | 103 | return ( | ||
368 | 104 | (octets[0] * Math.pow(256,3)) + | ||
369 | 105 | (octets[1] * Math.pow(256,2)) + | ||
370 | 106 | (octets[2] * 256) + octets[3]); | ||
371 | 107 | }; | ||
372 | 108 | |||
373 | 109 | // Convert ipv6 address to a full ipv6 address, removing the | ||
374 | 110 | // '::' shortcut and padding each group with zeros. | ||
375 | 111 | this.ipv6Expand = function(ipAddress) { | ||
376 | 112 | var i, expandedAddress = ipAddress; | ||
377 | 113 | if(expandedAddress.indexOf("::") !== -1) { | ||
378 | 114 | // '::' is present so replace it with the required | ||
379 | 115 | // number of '0000:' based on its location in the string. | ||
380 | 116 | var split = ipAddress.split("::"); | ||
381 | 117 | var groups = 0; | ||
382 | 118 | for(i = 0; i < split.length; i++) { | ||
383 | 119 | groups += split[i].split(":").length; | ||
384 | 120 | } | ||
385 | 121 | expandedAddress = split[0] + ":"; | ||
386 | 122 | for(i = 0; i < 8 - groups; i++) { | ||
387 | 123 | expandedAddress += "0000:"; | ||
388 | 124 | } | ||
389 | 125 | expandedAddress += split[1]; | ||
390 | 126 | } | ||
391 | 127 | // Pad the output of each part with zeros. | ||
392 | 128 | var output = [], parts = expandedAddress.split(":"); | ||
393 | 129 | angular.forEach(parts, function(part) { | ||
394 | 130 | output.push("0000".substr(part.length) + part); | ||
395 | 131 | }); | ||
396 | 132 | return output.join(":"); | ||
397 | 133 | }; | ||
398 | 134 | |||
399 | 135 | // Convert string ipv6 into groups array. | ||
400 | 136 | this.ipv6ToGroups = function(ipAddress) { | ||
401 | 137 | var groups = []; | ||
402 | 138 | var parts = this.ipv6Expand(ipAddress).split(":"); | ||
403 | 139 | angular.forEach(parts, function(part) { | ||
404 | 140 | groups.push(parseInt(part, 16)); | ||
405 | 141 | }); | ||
406 | 142 | return groups; | ||
407 | 143 | }; | ||
408 | 89 | }); | 144 | }); |
409 | 90 | 145 | ||
410 | === modified file 'src/maasserver/static/js/angular/services/tests/test_converter.js' | |||
411 | --- src/maasserver/static/js/angular/services/tests/test_converter.js 2016-03-28 13:54:47 +0000 | |||
412 | +++ src/maasserver/static/js/angular/services/tests/test_converter.js 2016-06-16 14:50:25 +0000 | |||
413 | @@ -201,4 +201,108 @@ | |||
414 | 201 | 1024 * 1024 * 1024); | 201 | 1024 * 1024 * 1024); |
415 | 202 | }); | 202 | }); |
416 | 203 | }); | 203 | }); |
417 | 204 | |||
418 | 205 | describe("ipv4ToOctets", function() { | ||
419 | 206 | |||
420 | 207 | var scenarios = [ | ||
421 | 208 | { | ||
422 | 209 | input: "192.168.1.1", | ||
423 | 210 | output: [192,168,1,1] | ||
424 | 211 | }, | ||
425 | 212 | { | ||
426 | 213 | input: "172.16.1.1", | ||
427 | 214 | output: [172,16,1,1] | ||
428 | 215 | }, | ||
429 | 216 | { | ||
430 | 217 | input: "10.1.1.1", | ||
431 | 218 | output: [10,1,1,1] | ||
432 | 219 | } | ||
433 | 220 | ]; | ||
434 | 221 | |||
435 | 222 | angular.forEach(scenarios, function(scenario) { | ||
436 | 223 | it("converts: " + scenario.input, function() { | ||
437 | 224 | var result = ConverterService.ipv4ToOctets(scenario.input); | ||
438 | 225 | expect(result).toEqual(scenario.output); | ||
439 | 226 | }); | ||
440 | 227 | }); | ||
441 | 228 | }); | ||
442 | 229 | |||
443 | 230 | describe("ipv4ToInteger", function() { | ||
444 | 231 | |||
445 | 232 | var scenarios = [ | ||
446 | 233 | { | ||
447 | 234 | input: "192.168.1.1", | ||
448 | 235 | output: 3232235777 | ||
449 | 236 | }, | ||
450 | 237 | { | ||
451 | 238 | input: "172.16.1.1", | ||
452 | 239 | output: 2886729985 | ||
453 | 240 | }, | ||
454 | 241 | { | ||
455 | 242 | input: "10.1.1.1", | ||
456 | 243 | output: 167837953 | ||
457 | 244 | } | ||
458 | 245 | ]; | ||
459 | 246 | |||
460 | 247 | angular.forEach(scenarios, function(scenario) { | ||
461 | 248 | it("converts: " + scenario.input, function() { | ||
462 | 249 | var result = ConverterService.ipv4ToInteger(scenario.input); | ||
463 | 250 | expect(result).toEqual(scenario.output); | ||
464 | 251 | }); | ||
465 | 252 | }); | ||
466 | 253 | }); | ||
467 | 254 | |||
468 | 255 | describe("ipv6Expand", function() { | ||
469 | 256 | |||
470 | 257 | var scenarios = [ | ||
471 | 258 | { | ||
472 | 259 | input: "::1", | ||
473 | 260 | output: "0000:0000:0000:0000:0000:0000:0000:0001" | ||
474 | 261 | }, | ||
475 | 262 | { | ||
476 | 263 | input: "2001:db8::1", | ||
477 | 264 | output: "2001:0db8:0000:0000:0000:0000:0000:0001" | ||
478 | 265 | }, | ||
479 | 266 | { | ||
480 | 267 | input: "2001:db8:1::1", | ||
481 | 268 | output: "2001:0db8:0001:0000:0000:0000:0000:0001" | ||
482 | 269 | }, | ||
483 | 270 | { | ||
484 | 271 | input: "2001:db8::", | ||
485 | 272 | output: "2001:0db8:0000:0000:0000:0000:0000:0000" | ||
486 | 273 | } | ||
487 | 274 | ]; | ||
488 | 275 | |||
489 | 276 | angular.forEach(scenarios, function(scenario) { | ||
490 | 277 | it("expands: " + scenario.input, function() { | ||
491 | 278 | var result = ConverterService.ipv6Expand(scenario.input); | ||
492 | 279 | expect(result).toBe(scenario.output); | ||
493 | 280 | }); | ||
494 | 281 | }); | ||
495 | 282 | }); | ||
496 | 283 | |||
497 | 284 | describe("ipv6ToGroups", function() { | ||
498 | 285 | |||
499 | 286 | var scenarios = [ | ||
500 | 287 | { | ||
501 | 288 | input: "::1", | ||
502 | 289 | output: [0, 0, 0, 0, 0, 0, 0, 1] | ||
503 | 290 | }, | ||
504 | 291 | { | ||
505 | 292 | input: "2001:db8::1", | ||
506 | 293 | output: [8193, 3512, 0, 0, 0, 0, 0, 1] | ||
507 | 294 | }, | ||
508 | 295 | { | ||
509 | 296 | input: "2001:db8:1::1", | ||
510 | 297 | output: [8193, 3512, 1, 0, 0, 0, 0, 1] | ||
511 | 298 | } | ||
512 | 299 | ]; | ||
513 | 300 | |||
514 | 301 | angular.forEach(scenarios, function(scenario) { | ||
515 | 302 | it("converts: " + scenario.input, function() { | ||
516 | 303 | var result = ConverterService.ipv6ToGroups(scenario.input); | ||
517 | 304 | expect(result).toEqual(scenario.output); | ||
518 | 305 | }); | ||
519 | 306 | }); | ||
520 | 307 | }); | ||
521 | 204 | }); | 308 | }); |
522 | 205 | 309 | ||
523 | === modified file 'src/maasserver/static/js/angular/services/validation.js' | |||
524 | --- src/maasserver/static/js/angular/services/validation.js 2016-03-28 13:54:47 +0000 | |||
525 | +++ src/maasserver/static/js/angular/services/validation.js 2016-06-16 14:50:25 +0000 | |||
526 | @@ -6,7 +6,8 @@ | |||
527 | 6 | * Used by controllers to validate user inputs. | 6 | * Used by controllers to validate user inputs. |
528 | 7 | */ | 7 | */ |
529 | 8 | 8 | ||
531 | 9 | angular.module('MAAS').service('ValidationService', function() { | 9 | angular.module('MAAS').service('ValidationService', [ |
532 | 10 | 'ConverterService', function(ConverterService) { | ||
533 | 10 | 11 | ||
534 | 11 | // Pattern that matches a domainname. | 12 | // Pattern that matches a domainname. |
535 | 12 | // XXX 2016-02-24 lamont: This also matches "example.com.", | 13 | // XXX 2016-02-24 lamont: This also matches "example.com.", |
536 | @@ -50,47 +51,6 @@ | |||
537 | 50 | return true; | 51 | return true; |
538 | 51 | } | 52 | } |
539 | 52 | 53 | ||
540 | 53 | // Convert string ipv4 address into octets array. | ||
541 | 54 | function ipv4ToOctets(ipAddress) { | ||
542 | 55 | var parts = ipAddress.split('.'); | ||
543 | 56 | var octets = []; | ||
544 | 57 | angular.forEach(parts, function(part) { | ||
545 | 58 | octets.push(parseInt(part, 10)); | ||
546 | 59 | }); | ||
547 | 60 | return octets; | ||
548 | 61 | } | ||
549 | 62 | |||
550 | 63 | // Convert ipv6 address to a full ipv6 address, removing the | ||
551 | 64 | // '::' shortcut. | ||
552 | 65 | function ipv6Expand(ipAddress) { | ||
553 | 66 | var i, expandedAddress = ipAddress; | ||
554 | 67 | if(expandedAddress.indexOf("::") !== -1) { | ||
555 | 68 | // '::' is present so replace it with the required | ||
556 | 69 | // number of '0000:' based on its location in the string. | ||
557 | 70 | var split = ipAddress.split("::"); | ||
558 | 71 | var groups = 0; | ||
559 | 72 | for(i = 0; i < split.length; i++) { | ||
560 | 73 | groups += split[i].split(":").length; | ||
561 | 74 | } | ||
562 | 75 | expandedAddress = split[0] + ":"; | ||
563 | 76 | for(i = 0; i < 8 - groups; i++) { | ||
564 | 77 | expandedAddress += "0000:"; | ||
565 | 78 | } | ||
566 | 79 | expandedAddress += split[1]; | ||
567 | 80 | } | ||
568 | 81 | return expandedAddress; | ||
569 | 82 | } | ||
570 | 83 | |||
571 | 84 | // Convert string ipv6 into octets array. | ||
572 | 85 | function ipv6ToOctets(ipAddress) { | ||
573 | 86 | var octets = []; | ||
574 | 87 | var parts = ipv6Expand(ipAddress).split(":"); | ||
575 | 88 | angular.forEach(parts, function(part) { | ||
576 | 89 | octets.push(parseInt(part, 16)); | ||
577 | 90 | }); | ||
578 | 91 | return octets; | ||
579 | 92 | } | ||
580 | 93 | |||
581 | 94 | // Return true if the domainname is valid, false otherwise. | 54 | // Return true if the domainname is valid, false otherwise. |
582 | 95 | this.validateDomainName = function(domainname) { | 55 | this.validateDomainName = function(domainname) { |
583 | 96 | // Invalid if the domain is not a string, empty, or more than | 56 | // Invalid if the domain is not a string, empty, or more than |
584 | @@ -140,8 +100,8 @@ | |||
585 | 140 | ipAddress.indexOf(':') === -1) { | 100 | ipAddress.indexOf(':') === -1) { |
586 | 141 | return false; | 101 | return false; |
587 | 142 | } | 102 | } |
590 | 143 | var expandedAddress = ipv6Expand(ipAddress); | 103 | var expandedAddress = ConverterService.ipv6Expand(ipAddress); |
591 | 144 | var octets = ipv6ToOctets(expandedAddress); | 104 | var octets = ConverterService.ipv6ToGroups(expandedAddress); |
592 | 145 | if(octets.length !== 8) { | 105 | if(octets.length !== 8) { |
593 | 146 | return false; | 106 | return false; |
594 | 147 | } | 107 | } |
595 | @@ -180,14 +140,14 @@ | |||
596 | 180 | if(this.validateIPv4(ipAddress) && | 140 | if(this.validateIPv4(ipAddress) && |
597 | 181 | this.validateIPv4(networkAddress)) { | 141 | this.validateIPv4(networkAddress)) { |
598 | 182 | return cidrMatcher( | 142 | return cidrMatcher( |
601 | 183 | ipv4ToOctets(ipAddress), | 143 | ConverterService.ipv4ToOctets(ipAddress), |
602 | 184 | ipv4ToOctets(networkAddress), | 144 | ConverterService.ipv4ToOctets(networkAddress), |
603 | 185 | 8, cidrBits); | 145 | 8, cidrBits); |
604 | 186 | } else if(this.validateIPv6(ipAddress) && | 146 | } else if(this.validateIPv6(ipAddress) && |
605 | 187 | this.validateIPv6(networkAddress)) { | 147 | this.validateIPv6(networkAddress)) { |
606 | 188 | return cidrMatcher( | 148 | return cidrMatcher( |
609 | 189 | ipv6ToOctets(ipAddress), | 149 | ConverterService.ipv6ToGroups(ipAddress), |
610 | 190 | ipv6ToOctets(networkAddress), | 150 | ConverterService.ipv6ToGroups(networkAddress), |
611 | 191 | 16, cidrBits); | 151 | 16, cidrBits); |
612 | 192 | } | 152 | } |
613 | 193 | return false; | 153 | return false; |
614 | @@ -210,9 +170,9 @@ | |||
615 | 210 | 170 | ||
616 | 211 | // Check that each octet is of the ip address is more or equal | 171 | // Check that each octet is of the ip address is more or equal |
617 | 212 | // to the low address and less or equal to the high address. | 172 | // to the low address and less or equal to the high address. |
621 | 213 | ipOctets = ipv4ToOctets(ipAddress); | 173 | ipOctets = ConverterService.ipv4ToOctets(ipAddress); |
622 | 214 | lowOctets = ipv4ToOctets(lowAddress); | 174 | lowOctets = ConverterService.ipv4ToOctets(lowAddress); |
623 | 215 | highOctets = ipv4ToOctets(highAddress); | 175 | highOctets = ConverterService.ipv4ToOctets(highAddress); |
624 | 216 | for(i = 0; i < 4; i++) { | 176 | for(i = 0; i < 4; i++) { |
625 | 217 | if(ipOctets[i] > highOctets[i] || | 177 | if(ipOctets[i] > highOctets[i] || |
626 | 218 | ipOctets[i] < lowOctets[i]) { | 178 | ipOctets[i] < lowOctets[i]) { |
627 | @@ -226,9 +186,9 @@ | |||
628 | 226 | 186 | ||
629 | 227 | // Check that each octet is of the ip address is more or equal | 187 | // Check that each octet is of the ip address is more or equal |
630 | 228 | // to the low address and less or equal to the high address. | 188 | // to the low address and less or equal to the high address. |
634 | 229 | ipOctets = ipv6ToOctets(ipAddress); | 189 | ipOctets = ConverterService.ipv6ToGroups(ipAddress); |
635 | 230 | lowOctets = ipv6ToOctets(lowAddress); | 190 | lowOctets = ConverterService.ipv6ToGroups(lowAddress); |
636 | 231 | highOctets = ipv6ToOctets(highAddress); | 191 | highOctets = ConverterService.ipv6ToGroups(highAddress); |
637 | 232 | for(i = 0; i < 8; i++) { | 192 | for(i = 0; i < 8; i++) { |
638 | 233 | if(ipOctets[i] > highOctets[i] || | 193 | if(ipOctets[i] > highOctets[i] || |
639 | 234 | ipOctets[i] < lowOctets[i]) { | 194 | ipOctets[i] < lowOctets[i]) { |
640 | @@ -239,4 +199,4 @@ | |||
641 | 239 | } | 199 | } |
642 | 240 | return false; | 200 | return false; |
643 | 241 | }; | 201 | }; |
645 | 242 | }); | 202 | }]); |
646 | 243 | 203 | ||
647 | === modified file 'src/maasserver/static/partials/subnet-details.html' | |||
648 | --- src/maasserver/static/partials/subnet-details.html 2016-06-09 20:08:35 +0000 | |||
649 | +++ src/maasserver/static/partials/subnet-details.html 2016-06-16 14:50:25 +0000 | |||
650 | @@ -250,36 +250,33 @@ | |||
651 | 250 | <table class="table-listing"> | 250 | <table class="table-listing"> |
652 | 251 | <thead> | 251 | <thead> |
653 | 252 | <tr class="table-listing__row"> | 252 | <tr class="table-listing__row"> |
671 | 253 | <th class="table-listing__header table-col--20">IP Address</th> | 253 | <th class="table-listing__header table-col--20"> |
672 | 254 | <th class="table-listing__header table-col--10">Type</th> | 254 | <a href="" data-ng-click="sortIPTable(ipSort)" data-ng-class="{sort: predicate === ipSort, 'sort-asc': reverse === false, 'sort-desc': reverse === true}">IP Address</a> |
673 | 255 | <th class="table-listing__header table-col--15">Node</th> | 255 | </th> |
674 | 256 | <th class="table-listing__header table-col--10">Interface</th> | 256 | <th class="table-listing__header table-col--10"> |
675 | 257 | <th class="table-listing__header table-col--10">Usage</th> | 257 | <a href="" data-ng-click="sortIPTable(allocTypeSort)" data-ng-class="{sort: predicate === allocTypeSort, 'sort-asc': reverse === false, 'sort-desc': reverse === true}">Type</a> |
676 | 258 | <th class="table-listing__header table-col--10">Owner</th> | 258 | </th> |
677 | 259 | <th class="table-listing__header table-col--25">Last seen</th> | 259 | <th class="table-listing__header table-col--15"> |
678 | 260 | <!-- XXX mpontillo need data for this --> | 260 | <a href="" data-ng-click="sortIPTable('node_summary.hostname')" data-ng-class="{sort: predicate === 'node_summary.hostname', 'sort-asc': reverse === false, 'sort-desc': reverse === true}">Node</a> |
679 | 261 | <!-- | 261 | </th> |
680 | 262 | <th class="table-listing__header table-col--25" colspan="2"> | 262 | <th class="table-listing__header table-col--10"> |
681 | 263 | <a class="table-listing__header-link" href="#">Purpose</a> | 263 | <a href="" data-ng-click="sortIPTable('node_summary.via')" data-ng-class="{sort: predicate === 'node_summary.via', 'sort-asc': reverse === false, 'sort-desc': reverse === true}">Interface</a> |
682 | 264 | <span class="divide"></span> | 264 | </th> |
683 | 265 | <a class="table-listing__header-link active" href="#">Last seen</a> | 265 | <th class="table-listing__header table-col--10"> |
684 | 266 | <span class="divide"></span> | 266 | <a href="" data-ng-click="sortIPTable(nodeTypeSort)" data-ng-class="{sort: predicate === nodeTypeSort, 'sort-asc': reverse === false, 'sort-desc': reverse === true}">Usage</a> |
685 | 267 | <a class="table-listing__header-link" href="#">Detected</a> | 267 | </th> |
686 | 268 | </th> | 268 | <th class="table-listing__header table-col--10"> |
687 | 269 | --> | 269 | <a href="" data-ng-click="sortIPTable(ownerSort)" data-ng-class="{sort: predicate === ownerSort, 'sort-asc': reverse === false, 'sort-desc': reverse === true}">Owner</a> |
688 | 270 | </th> | ||
689 | 271 | <th class="table-listing__header table-col--25"> | ||
690 | 272 | <a href="" data-ng-click="sortIPTable('updated')" data-ng-class="{sort: predicate === 'updated', 'sort-asc': reverse === false, 'sort-desc': reverse === true}">Last seen</a> | ||
691 | 273 | </th> | ||
692 | 270 | </tr> | 274 | </tr> |
693 | 271 | </thead> | 275 | </thead> |
694 | 272 | <tbody> | 276 | <tbody> |
696 | 273 | <tr data-ng-repeat="ip in subnet.ip_addresses"> | 277 | <tr data-ng-repeat="ip in subnet.ip_addresses | orderBy:predicate:reverse track by ip.ip"> |
697 | 274 | <td class="table-col--20">{$ ip.ip $}</td> | 278 | <td class="table-col--20">{$ ip.ip $}</td> |
706 | 275 | <td class="table-col--10" data-ng-switch="ip.alloc_type"> | 279 | <td class="table-col--10">{$ getAllocType(ip.alloc_type) $}</td> |
699 | 276 | <span data-ng-switch-when="0">Automatic</span> | ||
700 | 277 | <span data-ng-switch-when="1">Static</span> | ||
701 | 278 | <span data-ng-switch-when="4">User reserved</span> | ||
702 | 279 | <span data-ng-switch-when="5">DHCP</span> | ||
703 | 280 | <span data-ng-switch-when="6">Observed</span> | ||
704 | 281 | <span data-ng-switch-default>Unknown</span> | ||
705 | 282 | </td> | ||
707 | 283 | <td class="table-col--15" data-ng-switch="ip.node_summary.node_type"> | 280 | <td class="table-col--15" data-ng-switch="ip.node_summary.node_type"> |
708 | 284 | <span data-ng-switch-when="0"><a href="#/node/{$ ip.node_summary.system_id $}">{$ ip.node_summary.hostname $}</a></span> | 281 | <span data-ng-switch-when="0"><a href="#/node/{$ ip.node_summary.system_id $}">{$ ip.node_summary.hostname $}</a></span> |
709 | 285 | <span data-ng-switch-when="1">{$ ip.node_summary.hostname $}</span> | 282 | <span data-ng-switch-when="1">{$ ip.node_summary.hostname $}</span> |
710 | @@ -295,14 +292,7 @@ | |||
711 | 295 | <span data-ng-switch-when="4">{$ ip.node_summary.via $}</span> | 292 | <span data-ng-switch-when="4">{$ ip.node_summary.via $}</span> |
712 | 296 | <span data-ng-switch-default>Unknown</span> | 293 | <span data-ng-switch-default>Unknown</span> |
713 | 297 | </td> | 294 | </td> |
722 | 298 | <td class="table-col--10" data-ng-switch="ip.node_summary.node_type"> | 295 | <td class="table-col--10">{$ getNodeType(ip.node_summary.node_type) $}</td> |
715 | 299 | <span data-ng-switch-when="0">Machine</span> | ||
716 | 300 | <span data-ng-switch-when="1">Device</span> | ||
717 | 301 | <span data-ng-switch-when="2">Rack controller</span> | ||
718 | 302 | <span data-ng-switch-when="3">Region controller</span> | ||
719 | 303 | <span data-ng-switch-when="4">Rack and region controller</span> | ||
720 | 304 | <span data-ng-switch-default>Unknown</span> | ||
721 | 305 | </td> | ||
723 | 306 | <td class="table-col--10">{$ ip.user ? ip.user : "MAAS" $}</td> | 296 | <td class="table-col--10">{$ ip.user ? ip.user : "MAAS" $}</td> |
724 | 307 | <td class="table-col--25"> | 297 | <td class="table-col--25"> |
725 | 308 | <time>{$ ip.updated $}</time> | 298 | <time>{$ ip.updated $}</time> |
LGTM, might want to move some display strings to a central location, comments below.