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

Proposed by Blake Rouse on 2015-10-28
Status: Merged
Approved by: Blake Rouse on 2015-10-29
Approved revision: 4426
Merged at revision: 4431
Proposed branch: lp:~blake-rouse/maas/fix-1509417
Merge into: lp:maas/trunk
Diff against target: 396 lines (+179/-111)
3 files modified
src/maasserver/static/js/angular/controllers/node_details_storage.js (+43/-31)
src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js (+125/-77)
src/maasserver/static/partials/node-details.html (+11/-3)
To merge this branch: bzr merge lp:~blake-rouse/maas/fix-1509417
Reviewer Review Type Date Requested Status
Mike Pontillo (community) 2015-10-28 Approve on 2015-10-28
Lee Trager 2015-10-28 Approve on 2015-10-28
Review via email: mp+276041@code.launchpad.net

Commit message

Enable modification of storage tags on physical and virtual block devices in the available section.

Description of the change

This does not fully fix the bug. Has Rich still needs to fix the styling, so landing this branch will not mark the bug "Fix Committed".

To post a comment you must log in.
Lee Trager (ltrager) wrote :

LGTM!

My only thought is that we should allow viewing and editing tags in either the filesystem or used disks and partitions section. Its a bit of a pain to have to make a disk available just to be able to view and edit its tags.

review: Approve
Blake Rouse (blake-rouse) wrote :

Thanks for the review.

Yeah I agree. I will be enabling that, just in a different branch.

Mike Pontillo (mpontillo) wrote :

Looks pretty good to me; I may have spotted an issue or two that you might want to look at. Take a look below and let me know what you think.

review: Approve
Blake Rouse (blake-rouse) wrote :

Thanks for the review. I will add the request comment.

MAAS Lander (maas-lander) wrote :
Download full text (1003.7 KiB)

The attempt to merge lp:~blake-rouse/maas/fix-1509417 into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com trusty-security InRelease [64.4 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:3 http://security.ubuntu.com trusty-security/main Sources [98.0 kB]
Get:4 http://security.ubuntu.com trusty-security/universe Sources [31.0 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [241 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [357 kB]
Get:7 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [143 kB]
Get:8 http://security.ubuntu.com trusty-security/universe amd64 Packages [117 kB]
Get:9 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [637 kB]
Get:10 http://security.ubuntu.com trusty-security/main Translation-en [194 kB]
Get:11 http://security.ubuntu.com trusty-security/universe Translation-en [68.4 kB]
Get:12 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [326 kB]
Get:13 http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en [309 kB]
Get:14 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en [172 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 2,823 kB in 4s (652 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita py...

Mike Pontillo (mpontillo) wrote :
Download full text (3.3 KiB)

Digging through that log is annoying. But I found this:

maasserver.tests.test_forms_interface.BondInterfaceFormTest.test__creates_bond_interface_with_parent_mac_address ... ERROR
maasserver.tests.test_populate_tags.TestPopulateTags.test__calls_do_populate_tags_with_clusters ... FAIL

======================================================================
ERROR: maasserver.tests.test_forms_interface.BondInterfaceFormTest.test__creates_bond_interface_with_parent_mac_address
----------------------------------------------------------------------
_StringException: Traceback (most recent call last):
  File "/tmp/tarmac/branch.IXGl4c/src/maastesting/runtest.py", line 68, in _run_user
    result = function(*args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/testtools/testcase.py", line 616, in _run_test_method
    return self._get_test_method()()
  File "/tmp/tarmac/branch.IXGl4c/src/maasserver/tests/test_forms_interface.py", line 516, in test__creates_bond_interface_with_parent_mac_address
    vlan = factory.make_VLAN(vid=10)
  File "/tmp/tarmac/branch.IXGl4c/src/maasserver/testing/factory.py", line 739, in make_VLAN
    vlan.save()
  File "/tmp/tarmac/branch.IXGl4c/src/maasserver/models/cleansave.py", line 37, in save
    self.full_clean()
  File "/usr/lib/python2.7/dist-packages/django/db/models/base.py", line 950, in full_clean
    raise ValidationError(errors)
ValidationError: {'__all__': [u'VLAN with this Vid and Fabric already exists.']}

======================================================================
FAIL: maasserver.tests.test_populate_tags.TestPopulateTags.test__calls_do_populate_tags_with_clusters
----------------------------------------------------------------------
_StringException: Traceback (most recent call last):
  File "/tmp/tarmac/branch.IXGl4c/src/maastesting/runtest.py", line 68, in _run_user
    result = function(*args, **kwargs)
  File "/usr/lib/python2.7/dist-packages/testtools/testcase.py", line 616, in _run_test_method
    return self._get_test_method()()
  File "/tmp/tarmac/branch.IXGl4c/src/maasserver/tests/test_populate_tags.py", line 244, in test__calls_do_populate_tags_with_clusters
    populate_tags_module.tag_nsmap))
  File "/usr/lib/python2.7/dist-packages/testtools/testcase.py", line 406, in assertThat
    raise mismatch_error
MismatchError: Expected call: mock(((u'd3892b66-7db1-11e5-a501-fa163e312456', u'cluster-kKQCvD', u'nSaYxW5vfF7Ch4hpS6:QyP7Kw8baU347hG7r8:2yWUpBW23SLrmhzta49NND73Rtks5ndm'), (u'd38bb390-7db1-11e5-a501-fa163e312456', u'cluster-GMo8FL', u'N76yENgGPFVjbzXhSE:H3MNtwh824fB9ezxj6:N4XHy6nELjjNC26yZEvBMjdc3RmqeXJu'), (u'd38d73f6-7db1-11e5-a501-fa163e312456', u'cluster-bgk6Vv', u'dReXSBYBrD82JkcCpv:wApPKrTsbp3rsBLAfj:Eu77CdEkfYg8SJ4NZeGWzuk8EUdS8hQR')), u'tag-6TAjrG', u'//node', {u'lldp': u'lldp', u'lshw': u'lshw'})
Actual call: mock(((u'd38d73f6-7db1-11e5-a501-fa163e312456', u'cluster-bgk6Vv', u'dReXSBYBrD82JkcCpv:wApPKrTsbp3rsBLAfj:Eu77CdEkfYg8SJ4NZeGWzuk8EUdS8hQR'), (u'd3892b66-7db1-11e5-a501-fa163e312456', u'cluster-kKQCvD', u'nSaYxW5vfF7Ch4hpS6:QyP7Kw8baU347hG7r8:2yWUpBW23SLrmhzta49NND73Rtks5ndm'), (u'd38bb390-7db1-11e5-a501-fa163e312456', u'cluster-GMo8FL', u'N76yENgGPFVjbzXhSE:H3MN...

Read more...

MAAS Lander (maas-lander) wrote :
Download full text (1003.7 KiB)

The attempt to merge lp:~blake-rouse/maas/fix-1509417 into lp:maas failed. Below is the output from the failed tests.

Get:1 http://security.ubuntu.com trusty-security InRelease [64.4 kB]
Ign http://nova.clouds.archive.ubuntu.com trusty InRelease
Get:2 http://nova.clouds.archive.ubuntu.com trusty-updates InRelease [64.4 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty Release.gpg
Hit http://nova.clouds.archive.ubuntu.com trusty Release
Get:3 http://security.ubuntu.com trusty-security/main Sources [98.0 kB]
Get:4 http://security.ubuntu.com trusty-security/universe Sources [31.0 kB]
Get:5 http://nova.clouds.archive.ubuntu.com trusty-updates/main Sources [241 kB]
Get:6 http://security.ubuntu.com trusty-security/main amd64 Packages [357 kB]
Get:7 http://nova.clouds.archive.ubuntu.com trusty-updates/universe Sources [143 kB]
Get:8 http://nova.clouds.archive.ubuntu.com trusty-updates/main amd64 Packages [637 kB]
Get:9 http://security.ubuntu.com trusty-security/universe amd64 Packages [117 kB]
Hit http://security.ubuntu.com trusty-security/main Translation-en
Hit http://security.ubuntu.com trusty-security/universe Translation-en
Get:10 http://nova.clouds.archive.ubuntu.com trusty-updates/universe amd64 Packages [326 kB]
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty-updates/universe Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/main Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Sources
Hit http://nova.clouds.archive.ubuntu.com trusty/main amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/universe amd64 Packages
Hit http://nova.clouds.archive.ubuntu.com trusty/main Translation-en
Hit http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en
Ign http://nova.clouds.archive.ubuntu.com trusty/main Translation-en_US
Ign http://nova.clouds.archive.ubuntu.com trusty/universe Translation-en_US
Fetched 2,080 kB in 3s (527 kB/s)
Reading package lists...
sudo DEBIAN_FRONTEND=noninteractive apt-get -y \
     --no-install-recommends install apache2 authbind bind9 bind9utils build-essential bzr-builddeb chromium-browser chromium-chromedriver curl daemontools debhelper dh-apport dh-systemd distro-info dnsutils firefox freeipmi-tools git gjs ipython isc-dhcp-common libjs-angularjs libjs-jquery libjs-jquery-hotkeys libjs-yui3-full libjs-yui3-min libpq-dev make nodejs-legacy npm pep8 phantomjs postgresql pyflakes python-apt python-bson python-bzrlib python-convoy python-coverage python-crochet python-cssselect python-curtin python-dev python-distro-info python-django python-django-piston python-django-south python-djorm-ext-pgarray python-docutils python-extras python-fixtures python-flake8 python-formencode python-hivex python-httplib2 python-jinja2 python-jsonschema python-lxml python-mock python-netaddr python-netifaces python-nose python-oauth python-openssl python-paramiko python-pexpect python-pip python-pocket-lint python-psycopg2 python-pyinotify python-pyparsing python-seamicroclient python-simplejson python-simplestreams python-sphinx python-subunit python-tempita python-testresources python-testscenarios python-te...

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/maasserver/static/js/angular/controllers/node_details_storage.js'
2--- src/maasserver/static/js/angular/controllers/node_details_storage.js 2015-10-23 16:34:24 +0000
3+++ src/maasserver/static/js/angular/controllers/node_details_storage.js 2015-10-28 20:03:05 +0000
4@@ -105,8 +105,6 @@
5 }
6 ];
7
8- $scope.editing = false;
9- $scope.editing_tags = false;
10 $scope.column = 'model';
11 $scope.has_disks = false;
12 $scope.filesystems = [];
13@@ -492,6 +490,10 @@
14
15 // Return true if another disk exists with name.
16 function isNameAlreadyInUse(name, exclude_disk) {
17+ if(!angular.isArray($scope.node.disks)) {
18+ return false;
19+ }
20+
21 var i, j;
22 for(i = 0; i < $scope.node.disks.length; i++) {
23 var disk = $scope.node.disks[i];
24@@ -631,6 +633,7 @@
25 return false;
26 };
27
28+ // Return true if the free space label should be shown.
29 $scope.showFreeSpace = function(disk) {
30 if(disk.type === "lvm-vg") {
31 return true;
32@@ -1202,6 +1205,7 @@
33 var selected = $scope.getSelectedAvailable();
34 if(selected.length === 1) {
35 return (
36+ !selected[0].has_partitions &&
37 !$scope.hasUnmountedFilesystem(selected[0]) &&
38 selected[0].type !== "lvm-vg");
39 }
40@@ -1471,7 +1475,11 @@
41 $scope.availableNew.spares.length);
42 var minSize = Number.MAX_VALUE;
43 angular.forEach($scope.availableNew.devices, function(device) {
44- minSize = Math.min(minSize, device.original.available_size);
45+ // Get the size of the device. For a block device it will be
46+ // at available_size and for a partition it will be at size.
47+ var deviceSize = (
48+ device.original.available_size || device.original.size);
49+ minSize = Math.min(minSize, deviceSize);
50 });
51
52 // Calculate the new size.
53@@ -1546,7 +1554,9 @@
54 if(selected.length > 0) {
55 var i;
56 for(i = 0; i < selected.length; i++) {
57- if($scope.hasUnmountedFilesystem(selected[i])) {
58+ if(selected[i].has_partitions) {
59+ return false;
60+ } else if($scope.hasUnmountedFilesystem(selected[i])) {
61 return false;
62 } else if(selected[i].type === "lvm-vg") {
63 return false;
64@@ -1573,7 +1583,9 @@
65 $scope.getNewVolumeGroupSize = function() {
66 var total = 0;
67 angular.forEach($scope.availableNew.devices, function(device) {
68- total += device.original.available_size;
69+ // Add available_size or size if available_size is not set.
70+ total += (
71+ device.original.available_size || device.original.size);
72 });
73 return ConverterService.bytesToUnits(total).string;
74 };
75@@ -1703,33 +1715,33 @@
76 $scope.updateAvailableSelection(true);
77 };
78
79+ // Return true when tags can be edited.
80+ $scope.canEditTags = function(disk) {
81+ return disk.type !== "partition" && disk.type !== "lvm-vg";
82+ };
83+
84 // Called to enter tag editing mode
85- $scope.editTags = function() {
86- if($scope.$parent.canEdit() && !$scope.editing) {
87- $scope.editing = true;
88- $scope.editing_tags = true;
89- }
90- };
91-
92- // Called to cancel editing.
93- $scope.cancelTags = function() {
94- $scope.editing = false;
95- $scope.editing_tags = false;
96- updateDisks();
97- };
98-
99- // Called to save the changes.
100- $scope.saveTags = function() {
101- $scope.editing = false;
102- $scope.editing_tags = false;
103-
104- angular.forEach($scope.available, function(disk) {
105- var tags = [];
106- angular.forEach(disk.tags, function(tag) {
107- tags.push(tag.text);
108- });
109- NodesManager.updateDiskTags(
110- $scope.node, disk.block_id, tags);
111+ $scope.availableEditTags = function(disk) {
112+ disk.$options = {
113+ editingTags: true,
114+ tags: angular.copy(disk.tags)
115+ };
116+ };
117+
118+ // Called to cancel editing tags.
119+ $scope.availableCancelTags = function(disk) {
120+ disk.$options = {};
121+ };
122+
123+ // Called to save the tag changes.
124+ $scope.availableSaveTags = function(disk) {
125+ var tags = [];
126+ angular.forEach(disk.$options.tags, function(tag) {
127+ tags.push(tag.text);
128 });
129+ NodesManager.updateDiskTags(
130+ $scope.node, disk.block_id, tags);
131+ disk.tags = disk.$options.tags;
132+ disk.$options = {};
133 };
134 }]);
135
136=== modified file 'src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js'
137--- src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2015-10-23 18:39:01 +0000
138+++ src/maasserver/static/js/angular/controllers/tests/test_node_details_storage.js 2015-10-28 20:03:05 +0000
139@@ -252,7 +252,6 @@
140
141 it("sets initial values", function() {
142 var controller = makeController();
143- expect($scope.editing).toBe(false);
144 expect($scope.column).toBe('model');
145 expect($scope.has_disks).toBe(false);
146 expect($scope.filesystems).toEqual([]);
147@@ -3277,6 +3276,25 @@
148 expect($scope.getNewRAIDSize()).toBe("2.0 MB");
149 });
150
151+ it("gets proper raid-0 size using size", function() {
152+ var controller = makeController();
153+ var disk0 = {
154+ original: {
155+ size: 1000 * 1000
156+ }
157+ };
158+ var disk1 = {
159+ original: {
160+ size: 1000 * 1000
161+ }
162+ };
163+ $scope.availableNew.spares = [];
164+ $scope.availableNew.devices = [disk0, disk1];
165+ $scope.availableNew.mode = $scope.getAvailableRAIDModes()[0];
166+
167+ expect($scope.getNewRAIDSize()).toBe("2.0 MB");
168+ });
169+
170 it("gets proper raid-1 size", function() {
171 var controller = makeController();
172 var disk0 = {
173@@ -3632,6 +3650,29 @@
174
175 expect($scope.getNewVolumeGroupSize()).toBe("3.0 MB");
176 });
177+
178+ it("return the total of all devices using size", function() {
179+ var controller = makeController();
180+ $scope.availableNew.devices = [
181+ {
182+ original: {
183+ size: 1000 * 1000
184+ }
185+ },
186+ {
187+ original: {
188+ size: 1000 * 1000
189+ }
190+ },
191+ {
192+ original: {
193+ size: 1000 * 1000
194+ }
195+ }
196+ ];
197+
198+ expect($scope.getNewVolumeGroupSize()).toBe("3.0 MB");
199+ });
200 });
201
202 describe("createVolumeGroupCanSave", function() {
203@@ -3925,83 +3966,90 @@
204 });
205 });
206
207- describe("editTags", function() {
208-
209- it("doesnt sets editing to true if cannot edit", function() {
210- var controller = makeController();
211- canEditSpy.and.returnValue(false);
212- $scope.editing = false;
213- $scope.editing_tags = false;
214- $scope.editTags();
215- expect($scope.editing).toBe(false);
216- expect($scope.editing_tags).toBe(false);
217- });
218-
219- it("sets editing to true", function() {
220- var controller = makeController();
221- canEditSpy.and.returnValue(true);
222- $scope.editing = false;
223- $scope.editing_tags = false;
224- $scope.editTags();
225- expect($scope.editing).toBe(true);
226- expect($scope.editing_tags).toBe(true);
227- });
228- });
229-
230- describe("cancelTags", function() {
231-
232- it("sets editing to false", function() {
233- var controller = makeController();
234- $scope.editing = true;
235- $scope.editing_tags = true;
236- $scope.cancelTags();
237- expect($scope.editing).toBe(false);
238- expect($scope.editing_tags).toBe(false);
239- });
240-
241- it("calls updateDisks", function() {
242- var controller = makeController();
243-
244- // Updates disks so we can check that updateStorage
245- // is called.
246- node.disks = [
247- {
248- id: 0,
249- model: makeName("model"),
250- tags: [],
251- available_size: 0,
252- filesystem: null,
253- partitions: null
254+ describe("canEditTags", function() {
255+
256+ it("returns false for partition", function() {
257+ var controller = makeController();
258+ expect($scope.canEditTags({
259+ type: "partition"
260+ })).toBe(false);
261+ });
262+
263+ it("returns false for lvm-vg", function() {
264+ var controller = makeController();
265+ expect($scope.canEditTags({
266+ type: "lvm-vg"
267+ })).toBe(false);
268+ });
269+
270+ it("returns true for physical", function() {
271+ var controller = makeController();
272+ expect($scope.canEditTags({
273+ type: "physical"
274+ })).toBe(true);
275+ });
276+
277+ it("returns true for virtual", function() {
278+ var controller = makeController();
279+ expect($scope.canEditTags({
280+ type: "virtual"
281+ })).toBe(true);
282+ });
283+ });
284+
285+ describe("availableEditTags", function() {
286+
287+ it("sets $options", function() {
288+ var controller = makeController();
289+ var tags = [{}, {}];
290+ var disk = {
291+ tags: tags
292+ };
293+
294+ $scope.availableEditTags(disk);
295+ expect(disk.$options.editingTags).toBe(true);
296+ expect(disk.$options.tags).toEqual(tags);
297+ expect(disk.$options.tags).not.toBe(tags);
298+ });
299+ });
300+
301+ describe("availableCancelTags", function() {
302+
303+ it("clears $options", function() {
304+ var controller = makeController();
305+ var options = {};
306+ var disk = { $options: options };
307+
308+ $scope.availableCancelTags(disk);
309+ expect(disk.$options).toEqual({});
310+ expect(disk.$options).not.toBe(options);
311+ });
312+ });
313+
314+ describe("availableSaveTags", function() {
315+
316+ it("calls NodesManager.updateDiskTags", function() {
317+ var controller = makeController();
318+ var tags = [
319+ { text: "new" },
320+ { text: "old" }
321+ ];
322+ var disk = {
323+ block_id: makeInteger(0, 100),
324+ tags: [],
325+ $options: {
326+ editingTags:true,
327+ tags: tags
328 }
329- ];
330-
331- $scope.nodeLoaded();
332- $rootScope.$digest();
333- var filesystems = $scope.filesystems;
334- var available = $scope.available;
335- var used = $scope.used;
336- $scope.editing = true;
337- $scope.editing_tags = true;
338- $scope.cancelTags();
339-
340- // Verify cancel calls updateStorage but doesn't change any data
341- expect($scope.filesystems).toEqual(filesystems);
342- expect($scope.available).toEqual(available);
343- expect($scope.used).toEqual(used);
344- });
345- });
346-
347- describe("saveTags", function() {
348-
349- it("sets editing to false", function() {
350- var controller = makeController();
351-
352- $scope.editing = true;
353- $scope.editing_tags = true;
354- $scope.saveTags();
355-
356- expect($scope.editing).toBe(false);
357- expect($scope.editing_tags).toBe(false);
358+ };
359+ spyOn(NodesManager, "updateDiskTags");
360+
361+ $scope.availableSaveTags(disk);
362+
363+ expect(NodesManager.updateDiskTags).toHaveBeenCalledWith(
364+ node, disk.block_id, ["new", "old"]);
365+ expect(disk.$options).toEqual({});
366+ expect(disk.tags).toEqual(tags);
367 });
368 });
369 });
370
371=== modified file 'src/maasserver/static/partials/node-details.html'
372--- src/maasserver/static/partials/node-details.html 2015-10-28 18:53:35 +0000
373+++ src/maasserver/static/partials/node-details.html 2015-10-28 20:03:05 +0000
374@@ -828,11 +828,19 @@
375 <div class="table__data table__column--15" colspan="2" data-ng-show="column === 'model'">{$ item.model $}</div>
376 <div class="table__data table__column--15 ng-hide" colspan="2" data-ng-show="column === 'serial'">{$ item.serial $}</div>
377 <div class="table__data table__tags table__column--15">
378- <span class="table__tag" data-ng-repeat="tag in item.tags" data-ng-hide="editing_tags">
379+ <span class="table__tag" data-ng-repeat="tag in item.tags" data-ng-hide="item.$options.editingTags">
380 <a href="#/node/?query=storage_tags:({$ tag.text $})">{$ tag.text $}</a>
381 </span>
382- <tags-input ng-model="item.tags" data-ng-show="editing_tags" allow-tags-pattern="[\w-]+"></tags-input>
383- <a class="table__controls table__controls--secondary">edit</a>
384+ <tags-input ng-model="item.$options.tags" data-ng-show="item.$options.editingTags" allow-tags-pattern="[\w-]+"></tags-input>
385+ <a class="table__controls table__controls--secondary"
386+ data-ng-hide="!canEditTags(item) || item.$options.editingTags"
387+ data-ng-click="availableEditTags(item)">edit</a>
388+ <a class="link-cta-ubuntu text-button"
389+ data-ng-show="item.$options.editingTags"
390+ data-ng-click="availableCancelTags(item)">Cancel</a>
391+ <button class="cta-ubuntu"
392+ data-ng-show="item.$options.editingTags"
393+ data-ng-click="availableSaveTags(item)">Save</button>
394 </div>
395 <div class="table__data table__column--7">
396 <div class="table__controls">