Merge ~andreserl/maas:2.3_chassis_config_on_power_on into maas:master

Proposed by Andres Rodriguez
Status: Superseded
Proposed branch: ~andreserl/maas:2.3_chassis_config_on_power_on
Merge into: maas:master
Diff against target: 1981 lines (+1604/-3) (has conflicts)
22 files modified
debian/changelog (+22/-0)
snap/snapcraft.yaml (+9/-0)
src/maasserver/bootsources.py (+3/-0)
src/maasserver/models/tests/test_userprofile.py (+3/-0)
src/maasserver/rpc/boot.py (+5/-0)
src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js (+17/-0)
src/maasserver/static/partials/ipranges.html (+17/-0)
src/maasserver/static/partials/node-details.html (+134/-0)
src/maasserver/static/partials/nodes-list.html (+1017/-0)
src/maasserver/static/partials/script-results-list.html (+76/-0)
src/maasserver/static/partials/subnet-details.html (+40/-0)
src/maasserver/static/partials/vlan-details.html (+42/-0)
src/maasserver/tests/test_bootsources.py (+3/-0)
src/maasserver/tests/test_stats.py (+1/-1)
src/maasserver/triggers/tests/test_websocket_listener.py (+5/-0)
src/metadataserver/user_data/templates/commissioning.template (+4/-0)
src/provisioningserver/drivers/power/ipmi.py (+4/-0)
src/provisioningserver/import_images/boot_resources.py (+61/-0)
src/provisioningserver/import_images/tests/test_boot_resources.py (+92/-0)
src/provisioningserver/import_images/tests/test_download_resources.py (+35/-0)
src/provisioningserver/utils/tests/test_network.py (+3/-0)
utilities/release-build (+11/-2)
Conflict in debian/changelog
Conflict in snap/snapcraft.yaml
Conflict in src/maasserver/bootsources.py
Conflict in src/maasserver/models/tests/test_userprofile.py
Conflict in src/maasserver/rpc/boot.py
Conflict in src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
Conflict in src/maasserver/static/partials/ipranges.html
Conflict in src/maasserver/static/partials/node-details.html
Conflict in src/maasserver/static/partials/nodes-list.html
Conflict in src/maasserver/static/partials/script-results-list.html
Conflict in src/maasserver/static/partials/subnet-details.html
Conflict in src/maasserver/static/partials/vlan-details.html
Conflict in src/maasserver/tests/test_bootsources.py
Conflict in src/maasserver/triggers/tests/test_websocket_listener.py
Conflict in src/provisioningserver/drivers/power/ipmi.py
Conflict in src/provisioningserver/import_images/boot_resources.py
Conflict in src/provisioningserver/import_images/tests/test_boot_resources.py
Conflict in src/provisioningserver/import_images/tests/test_download_resources.py
Conflict in src/provisioningserver/utils/tests/test_network.py
Conflict in utilities/release-build
Reviewer Review Type Date Requested Status
MAAS Maintainers Pending
Review via email: mp+343188@code.launchpad.net
To post a comment you must log in.

Unmerged commits

8c67adc... by Andres Rodriguez

Only issue ipmi_chassis_config on power on.

914c0b2... by Mike Pontillo

LP: #1753493 - Make region IP addresses consistent for HA.

Backports: 5000bb58ea91af41b2465dc3d17aa2388a5edbaa

f745516... by Andres Rodriguez

Open 2.3.3; Update changelog to reflect release

e93e044... by Lee Trager

Backport: 89f12d9 LP: #1738127 Add favicon to the UI

c948da2... by Andres Rodriguez

Backport c70ed068a and 84a9fd27e

LP: #1750622 - Add ability to force the BIOS boot method, and auto discover it during enlistment/commissioning

7b7104a... by Mike Pontillo

Fix random failures in test_stats.py.

007a4fe... by Andres Rodriguez

Backport d2f6dd9cff9072c27549fd9aff6e82994a5f0ebe from master

Calculate the total amount of machine resources.-

7eb70a8... by Mike Pontillo

LP: #1704501 - Allow users to change which Fabric a VLAN is on.

Backports: 4083dc0635958109a28ef44a84df63e855007335

06b71f4... by Mike Pontillo

LP #1755587 - Allow moving a subnet to a different fabric in the UI.

Backports: 544aaae99fdb5954762980c2d93f5ae5b12d0b8a

13492bb... by Andres Rodriguez

debian/changelog: Update to correctly create dailybuilds

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/debian/changelog b/debian/changelog
2index 35034de..725fbd6 100644
3--- a/debian/changelog
4+++ b/debian/changelog
5@@ -1,3 +1,4 @@
6+<<<<<<< debian/changelog
7 maas (2.4.0~beta2-0ubuntu1) UNRELEASED; urgency=medium
8
9 * UNRELEASED
10@@ -36,6 +37,27 @@ maas (2.4.0~alpha1-6573-g12ee2331b-0ubuntu1) bionic; urgency=medium
11 -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 09 Feb 2018 18:50:10 -0500
12
13 maas (2.3.0-6434-gd354690-0ubuntu1) bionic; urgency=medium
14+=======
15+maas (2.3.2-0ubuntu1) UNRELEASED; urgency=medium
16+
17+ * UNRELEASED
18+
19+ -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 06 Apr 2018 09:56:53 -0400
20+
21+maas (2.3.2-6485-ge93e044-0ubuntu1) artful; urgency=medium
22+
23+ * New upstream release, MAAS 2.3.2
24+
25+ -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 06 Apr 2018 09:55:14 -0400
26+
27+maas (2.3.1-6470-g036d646-0ubuntu1) artful; urgency=medium
28+
29+ * New upstream release, MAAS 2.3.1
30+
31+ -- Andres Rodriguez <andreserl@ubuntu.com> Mon, 05 Mar 2018 10:25:44 -0500
32+
33+maas (2.3.0-6434-gd354690-0ubuntu1) artful; urgency=medium
34+>>>>>>> debian/changelog
35
36 * New upstream release, MAAS 2.3.0:
37 - Add support for CentOS & Windows networking.
38diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
39index 913fb0b..e277a0c 100644
40--- a/snap/snapcraft.yaml
41+++ b/snap/snapcraft.yaml
42@@ -131,6 +131,15 @@ parts:
43 source: src/maasserver/static
44 organize:
45 '*': usr/share/maas/web/static/
46+<<<<<<< snap/snapcraft.yaml
47+=======
48+ twisted-plugins:
49+ plugin: dump
50+ source: twisted/plugins
51+ organize:
52+ maasrackd.py: usr/lib/python3/dist-packages/twisted/plugins/maasrackd.py
53+ maasregiond.py: usr/lib/python3/dist-packages/twisted/plugins/maasregiond.py
54+>>>>>>> snap/snapcraft.yaml
55 snap:
56 plugin: dump
57 source: snap
58diff --git a/src/maasserver/bootsources.py b/src/maasserver/bootsources.py
59index f71040f..1e11784 100644
60--- a/src/maasserver/bootsources.py
61+++ b/src/maasserver/bootsources.py
62@@ -26,7 +26,10 @@ from maasserver.models import (
63 Config,
64 Notification,
65 )
66+<<<<<<< src/maasserver/bootsources.py
67 from maasserver.models.timestampedmodel import now
68+=======
69+>>>>>>> src/maasserver/bootsources.py
70 from maasserver.utils import get_maas_user_agent
71 from maasserver.utils.orm import transactional
72 from maasserver.utils.threads import deferToDatabase
73diff --git a/src/maasserver/models/tests/test_userprofile.py b/src/maasserver/models/tests/test_userprofile.py
74index 8a0efac..4bf4730 100644
75--- a/src/maasserver/models/tests/test_userprofile.py
76+++ b/src/maasserver/models/tests/test_userprofile.py
77@@ -139,6 +139,7 @@ class UserProfileTest(MAASServerTestCase):
78 self.assertEqual(reload_object(node).owner, new_user)
79 self.assertEqual(reload_object(ipaddress).user, new_user)
80 self.assertEqual(reload_object(iprange).user, new_user)
81+<<<<<<< src/maasserver/models/tests/test_userprofile.py
82
83 def test_transfer_resources_missing_target_access(self):
84 user = factory.make_User()
85@@ -153,6 +154,8 @@ class UserProfileTest(MAASServerTestCase):
86 " resource pool(s)")
87 # owner didn't change
88 self.assertEqual(reload_object(node).owner, user)
89+=======
90+>>>>>>> src/maasserver/models/tests/test_userprofile.py
91
92 def test_manager_all_users(self):
93 users = set(factory.make_User() for _ in range(3))
94diff --git a/src/maasserver/rpc/boot.py b/src/maasserver/rpc/boot.py
95index 5b5f1de..9a51bf3 100644
96--- a/src/maasserver/rpc/boot.py
97+++ b/src/maasserver/rpc/boot.py
98@@ -280,10 +280,15 @@ def get_config(
99 osystem = configs['commissioning_osystem']
100 series = configs['commissioning_distro_series']
101 else:
102+<<<<<<< src/maasserver/rpc/boot.py
103 osystem = machine.get_osystem(
104 default=configs['default_osystem'])
105 series = machine.get_distro_series(
106 default=configs['default_distro_series'])
107+=======
108+ osystem = machine.get_osystem()
109+ series = machine.get_distro_series()
110+>>>>>>> src/maasserver/rpc/boot.py
111 # XXX: roaksoax LP: #1739761 - Since the switch to squashfs (and
112 # drop of iscsi), precise is no longer deployable. To address a
113 # squashfs image is made available allowing it to be deployed in
114diff --git a/src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js b/src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
115index 90845d5..101cefd 100644
116--- a/src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
117+++ b/src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
118@@ -1584,6 +1584,7 @@ describe("NodesListController", function() {
119 it("sets showing_confirmation with testOptions",
120 function() {
121 var controller = makeController();
122+<<<<<<< src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
123 var object = makeObject("machines");
124 object.status_code = 6;
125 var spy = spyOn(
126@@ -1598,6 +1599,22 @@ describe("NodesListController", function() {
127 true);
128 expect($scope.tabs[
129 "machines"].actionProgress.affected_nodes).toBe(1);
130+=======
131+ var object = makeObject("nodes");
132+ object.status_code = 6;
133+ var spy = spyOn(
134+ $scope.tabs.nodes.manager,
135+ "performAction").and.returnValue(
136+ $q.defer().promise);
137+ $scope.tabs.nodes.actionOption = { name: "test" };
138+ $scope.tabs.nodes.selectedItems = [object];
139+ $scope.actionGo("nodes");
140+ expect($scope.tabs[
141+ "nodes"].actionProgress.showing_confirmation).toBe(
142+ true);
143+ expect($scope.tabs[
144+ "nodes"].actionProgress.affected_nodes).toBe(1);
145+>>>>>>> src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
146 expect(spy).not.toHaveBeenCalled();
147 });
148
149diff --git a/src/maasserver/static/partials/ipranges.html b/src/maasserver/static/partials/ipranges.html
150index b159b50..a8cb02b 100755
151--- a/src/maasserver/static/partials/ipranges.html
152+++ b/src/maasserver/static/partials/ipranges.html
153@@ -15,6 +15,7 @@
154 <tbody>
155 <tr data-ng-repeat="iprange in (subnetIPRanges = ipranges | filterBySubnetOrVlan:subnet:vlan) | orderBy:ipRangeSort"
156 data-ng-class="{ 'is-active': isIPRangeInEditMode(iprange) || isIPRangeInDeleteMode(iprange)}">
157+<<<<<<< src/maasserver/static/partials/ipranges.html
158 <td class="col-2" aria-label="Start IP Address">{$ iprange.start_ip $}</td>
159 <td class="col-2" aria-label="End IP Address">{$ iprange.end_ip $}</td>
160 <td class="col-1" aria-label="Owner">{$ iprange.type == "dynamic" ? "MAAS" : iprange.user $}</td>
161@@ -30,6 +31,22 @@
162 data-ng-click="toggleMenu(); ipRangeEnterDeleteMode(iprange)">
163 <i class="p-icon--delete">Remove</i>
164 </button>
165+=======
166+ <div class="table__data table-col--20" aria-label="Start IP Address">{$ iprange.start_ip $}</div>
167+ <div class="table__data table-col--20" aria-label="End IP Address">{$ iprange.end_ip $}</div>
168+ <div class="table__data table-col--10" aria-label="Owner">{$ iprange.type == "dynamic" ? "MAAS" : iprange.user $}</div>
169+ <div class="table__data table-col--10" aria-label="Type">{$ iprange.type == "dynamic" ? "Dynamic" : "Reserved" $}</div>
170+ <div class="table__data table-col--31" aria-label="Comment">{$ iprange.type == "dynamic" ? "Dynamic" : iprange.comment $}</div>
171+ <div class="table__data table-col--9 table--mobile-controls">
172+ <div class="table__controls" toggle-ctrl data-ng-if="ipRangeCanBeModified(iprange)">
173+ <button class="table__controls-toggle" data-ng-click="toggleMenu()">View actions</button>
174+ <div class="table__controls-menu" role="menu" data-ng-show="isToggled">
175+ <button class="table__controls-action" aria-label="Edit row"
176+ data-ng-click="toggleMenu(); ipRangeToggleEditMode(iprange)">Edit reserved range</button>
177+ <button class="table__controls-action u-text--error" aria-label="Remove"
178+ data-ng-click="toggleMenu(); ipRangeEnterDeleteMode(iprange)">Remove range</button>
179+ </div>
180+>>>>>>> src/maasserver/static/partials/ipranges.html
181 </div>
182 </td>
183 <td class="is-active p-table-expanding__panel col-12" col-span="6" data-ng-if="isIPRangeInDeleteMode(iprange)">
184diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html
185index ed62f6f..c52716d 100755
186--- a/src/maasserver/static/partials/node-details.html
187+++ b/src/maasserver/static/partials/node-details.html
188@@ -98,6 +98,7 @@
189 </ul>
190 </div>
191
192+<<<<<<< src/maasserver/static/partials/node-details.html
193 <div class="col-12 u-no-margin--left" data-ng-show="action.option.name === 'deploy' || action.option.name === 'release'">
194 <div class="ng-hide" data-ng-show="action.option.name === 'deploy'">
195 <div data-maas-os-select="osinfo" data-ng-model="osSelection"></div>
196@@ -235,6 +236,139 @@
197 </div>
198 </div>
199 </div>
200+=======
201+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
202+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': action.option }">
203+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-if="!isDevice && !node.dhcp_on">
204+ <p class="page-header__message page-header__message--warning">MAAS is not providing DHCP.</p>
205+ </div>
206+
207+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
208+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name)">
209+ <form class="form form--inline u-display--inline">
210+ <fieldset class="form__fieldset ng-hide" data-ng-show="action.option.name === 'commission' || action.option.name === 'test'">
211+ <div class="form__group">
212+ <input class="checkbox" id="enableSSH" type="checkbox"
213+ data-ng-model="commissionOptions.enableSSH">
214+ <label for="enableSSH">Allow SSH access and prevent machine from powering off</label>
215+ </div>
216+ </fieldset>
217+ <fieldset class="form__fieldset ng-hide" data-ng-show="action.option.name === 'commission'">
218+ <div class="form__group">
219+ <input class="checkbox" id="skipNetworking" type="checkbox"
220+ data-ng-model="commissionOptions.skipNetworking">
221+ <label for="skipNetworking">Retain network configuration</label>
222+ </div>
223+ <div class="form__group">
224+ <input class="checkbox" id="skipStorage" type="checkbox"
225+ data-ng-model="commissionOptions.skipStorage">
226+ <label for="skipStorage">Retain storage configuration</label>
227+ </div>
228+ </fieldset>
229+ <fieldset class="form__fieldset ng-hide" data-ng-show="action.option.name === 'deploy'">
230+ <div class="form__group">
231+ <label class="form__group-label">Choose your image</label>
232+ <div class="form__group-input" data-maas-os-select="osinfo" data-ng-model="osSelection"></div>
233+ </div>
234+ </fieldset>
235+ <fieldset class="form__fieldset" data-ng-if="action.option.name === 'release'">
236+ <div class="form__group">
237+ <div data-maas-release-options="releaseOptions"></div>
238+ </div>
239+ </fieldset>
240+ <div class="page-header__controls" data-ng-if="action.option.name !== 'commission' && action.option.name !== 'test'">
241+ <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
242+ <button class="button--inline" data-ng-class="action.option.name === 'delete' ? 'button--destructive' : 'button--positive'" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')">
243+ <span data-ng-if="action.option.name === 'acquire'">Acquire {$ type_name $}</span>
244+ <span data-ng-if="action.option.name === 'deploy'">Deploy {$ type_name $}</span>
245+ <span data-ng-if="action.option.name === 'release'">Release {$ type_name $}</span>
246+ <span data-ng-if="action.option.name === 'set-zone'">Set zone for {$ type_name $}</span>
247+ <span data-ng-if="action.option.name === 'on'">Power on {$ type_name $}</span>
248+ <span data-ng-if="action.option.name === 'off'">Power off {$ type_name $}</span>
249+ <span data-ng-if="action.option.name === 'abort'">Abort action on {$ type_name $}</span>
250+ <span data-ng-if="action.option.name === 'rescue-mode'">Rescue {$ type_name $}</span>
251+ <span data-ng-if="action.option.name === 'exit-rescue-mode'">Exit rescue mode</span>
252+ <span data-ng-if="action.option.name === 'mark-broken'">Mark {$ type_name $}</span>
253+ <span data-ng-if="action.option.name === 'mark-fixed'">Mark {$ type_name $}</span>
254+ <span data-ng-if="action.option.name === 'override-failed-testing'">Override failed testing</span>
255+ <span data-ng-if="action.option.name === 'delete'">Delete {$ type_name $}</span>
256+ <span data-ng-if="action.option.name === 'import-images'">Import images</span>
257+ </button>
258+ </div>
259+ </form>
260+ </div>
261+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name) || action.option.name !== 'commission'" data-ng-if="hasCustomCommissioningScripts()">
262+ <form class="form form--stack">
263+ <fieldset class="form__fieldset eight-col u-margin--bottom-small">
264+ <div class="form__group">
265+ <label for="commissioning-scripts">Additional commissioning scripts</label>
266+ <span id="commissioning-scripts" data-maas-script-select="script" data-script-type="0" data-ng-model="commissioningSelection" class="tags--inline"></span>
267+ </div>
268+ </fieldset>
269+ </form>
270+ </div>
271+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name) || (action.option.name !== 'commission' && action.option.name !== 'test')">
272+ <form class="form form--stack">
273+ <fieldset class="form__fieldset eight-col u-margin--bottom-small">
274+ <div class="form__group">
275+ <label>Hardware tests</label>
276+ <span id="testing-scripts" data-maas-script-select="script" data-script-type="2" data-ng-model="testSelection" class="tags--inline"></span>
277+ </div>
278+ </fieldset>
279+ </form>
280+ </div>
281+ <div class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name)" data-ng-if="action.option.name === 'commission' || action.option.name === 'test'">
282+ <form class="form form--inline">
283+ <div class="page-header__controls">
284+ <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
285+ <button class="button--inline" data-ng-class="action.option.name === 'delete' ? 'button--destructive' : 'button--positive'" data-ng-click="actionGo('nodes')" data-ng-hide="hasActionsFailed('nodes')">
286+ <span data-ng-if="action.option.name === 'commission'">Commission {$ type_name $}</span>
287+ <span data-ng-if="action.option.name === 'test'">Test {$ type_name $}</span>
288+ </button>
289+ </div>
290+ </form>
291+ </div>
292+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="action.showing_confirmation && action.option.name === 'test'">
293+ <p class="page-header__message page-header__message--warning">
294+ Node is currently deployed. Are you sure you want to continue to test hardware?
295+ </p>
296+ <div class="page-header__controls">
297+ <button class="button--base button--inline" data-ng-click="actionCancel()">No</button>
298+ <button class="button--secondary button--inline" data-ng-click="actionGo()">Yes</button>
299+ </div>
300+ </div>
301+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
302+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError()">
303+ <p class="page-header__message page-header__message--error">
304+ Node failed to be {$ action.option.sentence $}, because of the following error: {$ action.error $}
305+ </p>
306+ <div class="page-header__controls">
307+ <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
308+ <button class="button--secondary button--inline" data-ng-click="actionGo()">Retry</button>
309+ </div>
310+ </div>
311+
312+ <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
313+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isDeployError()">
314+ <p class="page-header__message page-header__message--error">
315+ Node cannot be {$ action.option.sentence $}, because the required boot images have not been imported. To import boot images, visit the <a href="images/">images page</a>.
316+ </p>
317+ <div class="page-header__controls">
318+ <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
319+ </div>
320+ </div>
321+
322+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionPowerError(action.option.name)">
323+ <p class="page-header__message page-header__message--error">
324+ Node cannot be {$ action.option.sentence $}, because power control software for the
325+ node is missing from its rack controller. To proceed, install the
326+ {$ getPowerErrors() $} on the rack controller.
327+ </p>
328+ <div class="page-header__controls">
329+ <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
330+ </div>
331+ </div>
332+>>>>>>> src/maasserver/static/partials/node-details.html
333
334 <nav class="p-tabs">
335 <ul class="p-tabs__list" role="tablist" data-ng-class="{ 'u-hide': action.option }">
336diff --git a/src/maasserver/static/partials/nodes-list.html b/src/maasserver/static/partials/nodes-list.html
337index d49f11c..e0ff5be 100644
338--- a/src/maasserver/static/partials/nodes-list.html
339+++ b/src/maasserver/static/partials/nodes-list.html
340@@ -1,3 +1,4 @@
341+<<<<<<< src/maasserver/static/partials/nodes-list.html
342 <header class="p-strip--light is-shallow is-bordered page-header" media-query="min-width: 769px">
343 <div class="row">
344 <div class="col-8">
345@@ -1072,3 +1073,1019 @@ sudo maas-rack register --url {$ tabs.controllers.registerUrl $} --secret {$ tab
346 on-check-all="toggleCheckAll('switches')" on-check="toggleChecked($switch_, 'switches')"></maas-switches-table>
347 </div>
348 </div>
349+=======
350+<header class="page-header u-margin--bottom-none" sticky media-query="min-width: 769px">
351+ <div class="wrapper--inner">
352+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
353+ <h1 class="page-header__title">Nodes</h1>
354+
355+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
356+ <p class="page-header__status" data-ng-show="loading"><span class="u-text--loading"><i class="icon icon--loading u-animation--spin"></i> Loading...</span></p>
357+
358+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
359+ <div class="page-header__controls u-float--right ng-hide" data-ng-show="currentpage === 'nodes'">
360+ <div data-ng-hide="tabs.nodes.selectedItems.length">
361+ <div data-maas-cta="addHardwareOptions"
362+ data-ng-model="addHardwareOption"
363+ data-ng-change="addHardwareOptionChanged()" data-default-title="Add hardware">
364+ </div>
365+ </div>
366+ <div class="ng-hide" data-ng-show="tabs.nodes.selectedItems.length">
367+ <a class="u-display--inline u-margin--right" data-ng-click="showSelected('nodes')">{$ tabs.nodes.selectedItems.length $} Selected</a>
368+ <div data-maas-cta="tabs.nodes.takeActionOptions"
369+ data-ng-model="tabs.nodes.actionOption"
370+ data-ng-change="actionOptionSelected('nodes')">
371+ </div>
372+ </div>
373+ </div>
374+
375+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
376+ <div class="page-header__controls u-float--rightng-hide" data-ng-show="currentpage === 'devices'">
377+ <div data-ng-hide="tabs.devices.selectedItems.length">
378+ <button class="button--secondary button--inline"
379+ data-ng-click="addDevice()"
380+ data-ng-hide="addDeviceScope.viewable">Add device</button>
381+ <button class="button--secondary button--inline ng-hide"
382+ data-ng-click="cancelAddDevice()"
383+ data-ng-show="addDeviceScope.viewable">Cancel add device</button>
384+ </div>
385+ <div data-ng-show="tabs.devices.selectedItems.length">
386+ <a class="u-display--inline u-margin--right" data-ng-click="showSelected('devices')">{$ tabs.devices.selectedItems.length $} Selected</a>
387+ <div data-maas-cta="tabs.devices.takeActionOptions"
388+ data-ng-model="tabs.devices.actionOption"
389+ data-ng-change="actionOptionSelected('devices')">
390+ </div>
391+ </div>
392+ </div>
393+
394+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
395+ <div class="page-header__controls u-float--rightng-hide" data-ng-show="currentpage === 'controllers'">
396+ <div data-ng-if="!tabs.controllers.selectedItems.length">
397+ <button class="button--secondary button--inline"
398+ data-ng-click="tabs.controllers.addController = true"
399+ data-ng-hide="tabs.controllers.addController">Add rack controller</button>
400+ <button class="button--secondary button--inline ng-hide"
401+ data-ng-click="tabs.controllers.addController = false"
402+ data-ng-show="tabs.controllers.addController">Close add rack controller</button>
403+ </div>
404+ <div data-ng-show="tabs.controllers.selectedItems.length">
405+ <a class="u-display--inline u-margin--right" data-ng-click="showSelected('controllers')">{$ tabs.controllers.selectedItems.length $} Selected</a>
406+ <div data-maas-cta="tabs.controllers.takeActionOptions"
407+ data-ng-model="tabs.controllers.actionOption"
408+ data-ng-change="actionOptionSelected('controllers')">
409+ </div>
410+ </div>
411+ </div>
412+
413+ <div class="page-header__controls u-float--right ng-hide" data-ng-show="currentpage === 'switches'">
414+ <div class="ng-hide" data-ng-show="tabs.switches.selectedItems.length">
415+ <a class="u-display--inline u-margin--right" data-ng-click="showSelected('switches')">{$ tabs.switches.selectedItems.length $} Selected</a>
416+ <div data-maas-cta="tabs.switches.takeActionOptions"
417+ data-ng-model="tabs.switches.actionOption"
418+ data-ng-change="actionOptionSelected('switches')">
419+ </div>
420+ </div>
421+ </div>
422+
423+ <div data-ng-repeat="tab in ['nodes', 'switches']" data-ng-show="currentpage == tab">
424+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
425+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs[tab].actionOption }">
426+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
427+ <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError(tab) || hasActionsInProgress(tab)">
428+ <form class="form form--inline u-display--inline">
429+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'commission' || tabs[tab].actionOption.name === 'test'">
430+ <div class="form__group">
431+ <input class="form__group-label" id="{$ tab $}-enableSSH" type="checkbox"
432+ data-ng-model="tabs[tab].commissionOptions.enableSSH">
433+ <label class="checkbox-label" for="{$ tab $}-enableSSH">Allow SSH access and prevent machine from powering off</label>
434+ </div>
435+ </fieldset>
436+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'commission'">
437+ <div class="form__group">
438+ <input class="form__group-label" id="{$ tab $}-skipNetworking" type="checkbox"
439+ data-ng-model="tabs[tab].commissionOptions.skipNetworking">
440+ <label class="checkbox-label" for="{$ tab $}-skipNetworking">Retain network configuration</label>
441+ </div>
442+ <div class="form__group">
443+ <input class="form__group-label" id="{$ tab $}-skipStorage" type="checkbox"
444+ data-ng-model="tabs[tab].commissionOptions.skipStorage">
445+ <label class="checkbox-label" for="{$ tab $}-skipStorage">Retain storage configuration</label>
446+ </div>
447+ </fieldset>
448+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'deploy'">
449+ <div class="form__group">
450+ <label for="image" class="form__group-label">Choose your image</label>
451+ <div class="form__group-input" data-maas-os-select="osinfo" data-ng-model="tabs[tab].osSelection"></div>
452+ </div>
453+ </fieldset>
454+ <fieldset class="form__fieldset" data-ng-if="tabs[tab].actionOption.name === 'release'">
455+ <div class="form__group">
456+ <div data-maas-release-options="tabs[tab].releaseOptions"></div>
457+ </div>
458+ </fieldset>
459+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'set-zone'">
460+ <div class="form__group">
461+ <label for="{$ tab $}-zone3" class="form__group-label">Select Zone</label>
462+ <div class="form__group-input">
463+ <select name="zone" id="{$ tab $}-zone3"
464+ data-ng-model="tabs[tab].zoneSelection"
465+ data-ng-options="zone as zone.name for zone in zones">
466+ <option value="" disabled="disabled">Choose a zone</option>
467+ </select>
468+ </div>
469+ </div>
470+ </fieldset>
471+ </form>
472+ <div class="page-header__controls" data-ng-if="tabs[tab].actionOption.name !== 'commission' && tabs[tab].actionOption.name !== 'test'">
473+ <button class="button--base button--inline" data-ng-click="actionCancel(tab)">Cancel</button>
474+ <button class="button--inline" data-ng-class="tabs[tab].actionOption.name === 'delete' ? 'button--destructive' : 'button--positive'" data-ng-click="actionGo(tab)" data-ng-hide="hasActionsFailed(tab)">
475+ <span data-ng-if="tabs[tab].actionOption.name === 'acquire'">Acquire {$ tabs[tab].selectedItems.length $}
476+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
477+ </span>
478+ <span data-ng-if="tabs[tab].actionOption.name === 'deploy'">Deploy {$ tabs[tab].selectedItems.length $}
479+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
480+ </span>
481+ <span data-ng-if="tabs[tab].actionOption.name === 'release'">Release {$ tabs[tab].selectedItems.length $}
482+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
483+ </span>
484+ <span data-ng-if="tabs[tab].actionOption.name === 'set-zone'">Set zone for {$ tabs[tab].selectedItems.length $}
485+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
486+ </span>
487+ <span data-ng-if="tabs[tab].actionOption.name === 'on'">Power on {$ tabs[tab].selectedItems.length $}
488+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
489+ </span>
490+ <span data-ng-if="tabs[tab].actionOption.name === 'off'">Power off {$ tabs[tab].selectedItems.length $}
491+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
492+ </span>
493+ <span data-ng-if="tabs[tab].actionOption.name === 'abort'">Abort action for {$ tabs[tab].selectedItems.length $}
494+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
495+ </span>
496+ <span data-ng-if="tabs[tab].actionOption.name === 'rescue-mode'">Set rescue mode for {$ tabs[tab].selectedItems.length $}
497+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
498+ </span>
499+ <span data-ng-if="tabs[tab].actionOption.name === 'exit-rescue-mode'">Exit rescue mode for {$ tabs[tab].selectedItems.length $}
500+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
501+ </span>
502+ <span data-ng-if="tabs[tab].actionOption.name === 'mark-broken'">Mark {$ tabs[tab].selectedItems.length $}
503+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span> as broken
504+ </span>
505+ <span data-ng-if="tabs[tab].actionOption.name === 'mark-fixed'">Mark {$ tabs[tab].selectedItems.length $}
506+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span> as fixed
507+ </span>
508+ <span data-ng-if="tabs[tab].actionOption.name === 'override-failed-testing'">Override failed testing on {$ tabs[tab].selectedItems.length $}
509+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
510+ </span>
511+ <span data-ng-if="tabs[tab].actionOption.name === 'delete'">Delete {$ tabs[tab].selectedItems.length $}
512+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
513+ </span>
514+ </button>
515+ <button class="button--secondary button--inline" data-ng-click="actionGo(tab)" data-ng-show="hasActionsFailed(tab)">Retry</button>
516+ </div>
517+ </section>
518+ <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError(tab) || hasActionsInProgress(tab)" data-ng-if="tabs[tab].actionOption.name === 'commission' && hasCustomCommissioningScripts()">
519+ <form class="form form--stack">
520+ <fieldset class="form__fieldset eight-col u-margin--bottom-small">
521+ <div class="form__group">
522+ <label for="{$ tab $}-commissiong-scripts">Additional commissioning Scripts</label>
523+ <span id="{$ tab $}-commissioning-scripts" data-maas-script-select="script" data-script-type="0" data-ng-model="tabs[tab].commissioningSelection" class="tags--inline"></span>
524+ </div>
525+ </fieldset>
526+ </form>
527+ </section>
528+ <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError(tab) || hasActionsInProgress(tab)" data-ng-if="tabs[tab].actionOption.name === 'commission' || tabs[tab].actionOption.name ==='test'">
529+ <form class="form form--stack">
530+ <fieldset class="form__fieldset eight-col u-margin--bottom-small">
531+ <div class="form__group">
532+ <label for="{$ tab $}-testing-scripts">Hardware Tests</label>
533+ <span id="{$ tab $}-testing-scripts" data-maas-script-select="script" data-script-type="2" data-ng-model="tabs[tab].testSelection" class="tags--inline"></span>
534+ </div>
535+ </fieldset>
536+ </form>
537+ </section>
538+ <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError(tab) || hasActionsInProgress(tab)" data-ng-if="tabs[tab].actionOption.name === 'commission' || tabs[tab].actionOption.name === 'test'">
539+ <div class="page-header__controls">
540+ <button class="button--base button--inline" data-ng-click="actionCancel(tab)">Cancel</button>
541+ <button class="button--inline" data-ng-class="tabs[tab].actionOption.name === 'delete' ? 'button--destructive' : 'button--positive'" data-ng-click="actionGo(tab)" data-ng-hide="hasActionsFailed(tab)">
542+ <span data-ng-if="tabs[tab].actionOption.name === 'commission'">Commission {$ tabs[tab].selectedItems.length $}
543+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
544+ </span>
545+ <span data-ng-if="tabs[tab].actionOption.name === 'test'">Test {$ tabs[tab].selectedItems.length $}
546+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
547+ </span>
548+ </button>
549+ <button class="button--secondary button--inline" data-ng-click="actionGo(tab)" data-ng-show="hasActionsFailed(tab)">Retry</button>
550+ </div>
551+ </section>
552+
553+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
554+ <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError(tab)">
555+ <p data-ng-hide="isDeployError(tab) || isSSHKeyError(tab)"
556+ class="page-header__message page-header__message--error ng-hide">
557+ {$ tabs[tab].actionErrorCount $}
558+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
559+ cannot be {$ tabs[tab].actionOption.sentence $}. To proceed, update your selection.
560+ </p>
561+ <p class="page-header__message page-header__message--error ng-hide" data-ng-show="isDeployError(tab)">
562+ {$ tabs[tab].selectedItems.length $}
563+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
564+ cannot be {$ tabs[tab].actionOption.sentence $}, because the required boot images have not been imported. To import boot images, visit the <a href="#/images">images page</a>.
565+ </p>
566+ <p class="page-header__message page-header__message--error ng-hide" data-ng-show="isSSHKeyError(tab)">
567+ {$ tabs[tab].selectedItems.length $}
568+ <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
569+ cannot be {$ tabs[tab].actionOption.sentence $}, because an SSH key has not been added to your account. To add an SSH key, visit <a href="account/prefs/">your account page</a>.
570+ </p>
571+ </section>
572+ <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="tabs[tab].actionProgress.showing_confirmation">
573+ <p class="page-header__message page-header__message--warning">
574+ {$ tabs[tab].actionProgress.affected_nodes $} of {$ tabs[tab].selectedItems.length $} are in a deployed state. Are you sure you want to continue to test hardware?
575+ </p>
576+ <div class="page-header__controls">
577+ <button class="button--base button--inline" data-ng-click="actionCancel(tab)">No</button>
578+ <button class="button--secondary button--inline" data-ng-click="actionGo(tab)">Yes</button>
579+ </div>
580+ </section>
581+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
582+ <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionsInProgress(tab) || hasActionsFailed(tab)">
583+ <p class="page-header__message" data-ng-show="hasActionsInProgress(tab)">
584+ <i class="icon icon--loading u-animation--spin"></i>
585+ {$ tabs[tab].actionProgress.completed $} of {$ tabs[tab].actionProgress.total $}
586+ nodes are transitioning to {$ tabs[tab].actionOption.sentence $}.
587+ </p>
588+ <p class="page-header__message page-header__message--error"
589+ data-ng-repeat="(error, nodes) in tabs[tab].actionProgress.errors">
590+ The {$ tabs[tab].actionOption.title.toLowerCase() $} action for {$ nodes.length $}
591+ <span data-ng-pluralize count="nodes.length" when="{'one': 'node', 'other': 'nodes'}"></span>
592+ failed with error: {$ error $}
593+ </p>
594+ </section>
595+ </div>
596+
597+ </div>
598+
599+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
600+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': addHardwareScope.viewable }" data-ng-controller="AddHardwareController">
601+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
602+ <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="showMachine()">
603+ <h3 class="page-header__dropdown-title">Add machine</h3>
604+ <form class="form form--stack">
605+ <fieldset class="form__fieldset six-col">
606+ <div class="form__group">
607+ <label for="machine-name" class="form__label two-col">Machine name</label>
608+ <div class="form__group-input three-col">
609+ <input type="text" id="machine-name" placeholder="Choose a machine name (optional)"
610+ data-ng-model="machine.name">
611+ </div>
612+ </div>
613+ <div class="form__group">
614+ <label for="domain" class="form__group-label two-col">Domain</label>
615+ <div class="form__group-input three-col">
616+ <select name="domain" id="domain"
617+ data-ng-model="machine.domain"
618+ data-ng-options="domain as domain.name for domain in domains">
619+ <option value="" disabled>Choose a domain</option>
620+ </select>
621+ </div>
622+ </div>
623+ <div class="form__group">
624+ <label for="architecture" class="form__group-label two-col">Architecture</label>
625+ <div class="form__group-input three-col">
626+ <select name="architecture" id="architecture"
627+ data-ng-model="machine.architecture"
628+ data-ng-options="arch for arch in architectures">
629+ <option value="" disabled>Choose an architecture</option>
630+ </select>
631+ </div>
632+ </div>
633+ <div class="form__group">
634+ <label for="min_hwe_kernel" class="form__group-label two-col">Minimum Kernel</label>
635+ <div class="form__group-input three-col">
636+ <select name="min_hwe_kernel" id="min_hwe_kernel"
637+ data-ng-model="machine.min_hwe_kernel"
638+ data-ng-options="hwe_kernel[0] as hwe_kernel[1] for hwe_kernel in hwe_kernels">
639+ <option value="">No minimum kernel</option>
640+ </select>
641+ </div>
642+ </div>
643+ <div class="form__group">
644+ <label for="zone4" class="form__group-label two-col">Zone</label>
645+ <select name="zone" id="zone4" class="three-col"
646+ data-ng-model="machine.zone"
647+ data-ng-options="zone as zone.name for zone in zones">
648+ </select>
649+ </div>
650+ <div class="form__group" data-ng-repeat="mac in machine.macs">
651+ <label for="mac-address" class="form__group-label two-col"><span data-ng-hide="mac !== machine.macs[0]">MAC Address</span>&nbsp;</label>
652+ <div class="form__group-input three-col">
653+ <input type="text" id="mac-address" placeholder="00:00:00:00:00:00"
654+ maxlength="17"
655+ data-ng-class="{ 'has-error': mac.error }"
656+ data-ng-model="mac.mac"
657+ data-ng-pattern="macAddressRegex"
658+ data-ng-change="validateMac(mac)"
659+ mac-address>
660+ <span class="form__group-remove" title="Delete MAC"
661+ data-ng-hide="mac === machine.macs[0]" data-ng-click="removeMac(mac)"></span>
662+ </div>
663+ </div>
664+ <div class="form__group">
665+ <div class="five-col">
666+ <button class="button--secondary button--inline u-float--right" data-ng-click="addMac()">+ Add MAC Address</a>
667+ </div>
668+ </div>
669+ </fieldset>
670+ <fieldset class="form__fieldset six-col last-col"
671+ data-maas-power-parameters="power_types"
672+ data-ng-model="machine.power">
673+ </fieldset>
674+ </form>
675+ </section>
676+ <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="showChassis()">
677+ <h3 class="page-header__dropdown-title">Add chassis</h3>
678+ <form action="post" class="form form--stack">
679+ <fieldset class="form__fieldset six-col"
680+ data-maas-power-parameters="chassisPowerTypes"
681+ data-ng-model="chassis.power">
682+ </fieldset>
683+ <fieldset class="form__fieldset six-col last-col">
684+ <div class="form__group">
685+ <label for="domain2" class="form__group-label two-col">Domain</label>
686+ <div class="form__group-input three-col">
687+ <select name="domain" id="domain2"
688+ data-ng-model="chassis.domain"
689+ data-ng-options="domain as domain.name for domain in domains">
690+ </select>
691+ </div>
692+ </div>
693+ </fieldset>
694+ </form>
695+ </section>
696+
697+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
698+ <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-show="showMachine()">
699+ <p class="page-header__message page-header__message--error ng-hide" data-ng-show="error">{$ error $}</p>
700+ <div class="page-header__controls">
701+ <button class="button--base button--inline" data-ng-click="cancel()">Cancel</button>
702+ <button class="button--secondary button--inline"
703+ data-ng-disabled="machineHasError()"
704+ data-ng-click="saveMachine(true)">Save and add another</button>
705+ <button class="button--positive button--inline"
706+ data-ng-disabled="machineHasError()"
707+ data-ng-click="saveMachine(false)">Save machine</button>
708+ </div>
709+ </section>
710+
711+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
712+ <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="showChassis()">
713+ <p class="page-header__message page-header__message--error ng-hide" data-ng-show="error">{$ error $}</p>
714+ <div class="page-header__controls">
715+ <button class="button--base button--inline" data-ng-click="cancel()">Cancel</button>
716+ <button class="button--secondary button--inline"
717+ data-ng-disabled="chassisHasErrors()"
718+ data-ng-click="saveChassis(true)">Save and add another</button>
719+ <button class="button--positive button--inline"
720+ data-ng-disabled="chassisHasErrors()"
721+ data-ng-click="saveChassis(false)">Save chassis</button>
722+ </div>
723+ </section>
724+
725+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
726+ <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="architectures.length">
727+ <p class="page-header__message page-header__message--error">
728+ Cannot add {$ mode $} until boot images have been imported. To fix, visit the <a href="#/images">images page</a>.
729+ </p>
730+ </section>
731+ </div>
732+
733+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
734+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs.devices.actionOption }">
735+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
736+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError('devices') || hasActionsInProgress('devices')">
737+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
738+ <form class="form form--inline u-display--inline">
739+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs.devices.actionOption.name === 'set-zone'">
740+ <div class="form__group">
741+ <label class="form__group-label" for="zone1">Set zone</label>
742+ <select name="zone" id="zone1"
743+ data-ng-model="tabs.devices.zoneSelection"
744+ data-ng-options="zone as zone.name for zone in zones">
745+ <option value="" disabled="disabled">Choose a zone</option>
746+ </select>
747+ </div>
748+ </fieldset>
749+ <div class="page-header__controls">
750+ <button class="button--base button--inline" data-ng-click="actionCancel('devices')">Cancel</button>
751+ <button class="button--positive button--inline" data-ng-click="actionGo('devices')">
752+ <span data-ng-if="tabs.devices.actionOption.name === 'set-zone'">Set zone for {$ tabs.devices.selectedItems.length $}
753+ <span data-ng-pluralize count="tabs.devices.selectedItems.length" when="{'one': 'device', 'other': 'devices'}"></span>
754+ </span>
755+ <span data-ng-if="tabs.devices.actionOption.name === 'delete'">Delete {$ tabs.devices.selectedItems.length $}
756+ <span data-ng-pluralize count="tabs.devices.selectedItems.length" when="{'one': 'device', 'other': 'devices'}"></span>
757+ </span>
758+ </button>
759+ </div>
760+ </form>
761+ </div>
762+
763+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
764+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError('devices')">
765+ <p class="page-header__message page-header__message--error">
766+ {$ tabs.devices.actionErrorCount $}
767+ <span data-ng-pluralize count="tabs.devices.selectedItems.length" when="{'one': 'device', 'other': 'devices'}"></span>
768+ cannot be {$ tabs.devices.actionOption.sentence $}. To proceed, update your selection.
769+ </p>
770+ </div>
771+
772+ <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
773+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionsInProgress('devices') || hasActionsFailed('devices')">
774+ <p class="page-header__message" data-ng-show="hasActionsInProgress('devices')">
775+ <i class="icon icon--loading u-animation--spin"></i>
776+ {$ tabs.devices.actionProgress.completed $} of {$ tabs.devices.actionProgress.total $}
777+ devices have been {$ tabs.devices.actionOption.sentence $}.
778+ </p>
779+ <p class="page-header__message page-header__message--error"
780+ data-ng-repeat="(error, devices) in tabs.devices.actionProgress.errors">
781+ The {$ tabs.devices.actionOption.title.toLowerCase() $} action for {$ devices.length $}
782+ <span data-ng-pluralize count="devices.length" when="{'one': 'device', 'other': 'devices'}"></span>
783+ failed with error: {$ error $}
784+ </p>
785+ </div>
786+ </div>
787+
788+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
789+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': addDeviceScope.viewable }" data-ng-controller="AddDeviceController">
790+ <section class="page-header__section twelve-col u-margin--bottom-none">
791+ <h3 class="page-header__dropdown-title">Add device</h3>
792+ <form class="form form--stack">
793+ <fieldset class="form__fieldset six-col">
794+ <div class="form__group">
795+ <label for="device-name" class="form__group-label two-col">Name</label>
796+ <div class="form__group-input three-col">
797+ <input type="text" id="device-name" placeholder="Name your device"
798+ data-ng-model="device.name"
799+ data-ng-class="{ 'has-error': nameHasError() }">
800+ </div>
801+
802+ </div>
803+ <div class="form__group">
804+ <label for="domain3" class="form__group-label two-col">Domain</label>
805+ <div class="form__group-input three-col">
806+ <select name="domain" id="domain3"
807+ data-ng-model="device.domain"
808+ data-ng-options="domain as domain.name for domain in domains">
809+ </select>
810+ </div>
811+ </div>
812+ </fieldset>
813+ </form>
814+ <table>
815+ <thead>
816+ <tr>
817+ <th class="table-col--20">MAC address</th>
818+ <th class="table-col--20">IP assignment</th>
819+ <th class="table-col--20">Subnet</th>
820+ <th class="table-col--35">IP address</th>
821+ <th class="table-col--5"></th>
822+ </tr>
823+ </thead>
824+ <tbody vs-repeat vs-scroll-parent="window">
825+ <tr data-ng-repeat="interface in device.interfaces">
826+ <td class="table-col--20" aria-label="MAC address">
827+ <input type="text" id="mac-address1" placeholder="00:00:00:00:00:00"
828+ data-ng-model="interface.mac"
829+ data-ng-class="{ 'has-error': macHasError(interface) }">
830+ </td>
831+ <td class="table-col--20" aria-label="IP assignment">
832+ <select name="ip-assignment" id="ip-assignment"
833+ data-ng-model="interface.ipAssignment"
834+ data-ng-options="assigment.title for assigment in ipAssignments">
835+ <option value="" disabled selected>Select IP assignment</option>
836+ </select>
837+ </td>
838+ <td class="table-col--20" aria-label="Subnet">
839+ <select name="subnet" id="subnet"
840+ data-ng-model="interface.subnetId"
841+ data-ng-options="subnet.id as subnet.name for subnet in subnets"
842+ data-ng-show="interface.ipAssignment.name === 'static'">
843+ <option value="" disabled selected>Select subnet</option>
844+ </select>
845+ </td>
846+ <td class="table-col--35" aria-label="IP address">
847+ <input type="text" id="ip-address" placeholder="000.000.000.000"
848+ data-ng-model="interface.ipAddress"
849+ data-ng-class="{ 'has-error': ipHasError(interface) }"
850+ data-ng-show="interface.ipAssignment.name === 'external'">
851+ <input type="text" id="ip-address"
852+ data-ng-model="interface.ipAddress"
853+ data-ng-class="{ 'has-error': ipHasError(interface) }"
854+ data-ng-show="interface.ipAssignment.name === 'static'">
855+ </td>
856+ <td class="table-col--5 u-u-align---right table--mobile-controls">
857+ <!-- space needed to set correct height -->
858+ <span data-ng-show="isPrimaryInterface(interface)">&nbsp;</span>
859+ <a title="Delete" class="icon icon--remove u-display--desktop"
860+ data-ng-click="deleteInterface(interface)"
861+ data-ng-hide="isPrimaryInterface(interface)">delete</a>
862+ <button class="button--secondary u-display--mobile"
863+ data-ng-click="deleteInterface(interface)"
864+ data-ng-hide="isPrimaryInterface(interface)">delete</a>
865+ </td>
866+ </tr>
867+ </tbody>
868+ </table>
869+ <button class="button--secondary button--inline" data-ng-click="addInterface()">+ Add another interface</button>
870+ </section>
871+ <section class="page-header__section twelve-col u-margin--bottom-none twelve-col u-margin--bottom-none">
872+ <form class="form form--inline">
873+ <p class="page-header__message page-header__message--error ng-hide" data-ng-show="error">{$ error $}</p>
874+ <div class="page-header__controls">
875+ <button class="button--base button--inline" data-ng-click="cancel()">Cancel</button>
876+ <button class="button--secondary button--inline"
877+ data-ng-class="{ disabled: deviceHasError() }"
878+ data-ng-click="save(true)">Save and add another</button>
879+ <button class="button--positive button--inline"
880+ data-ng-class="{ disabled: deviceHasError() }"
881+ data-ng-click="save(false)">Save device</button>
882+ </div>
883+ </form>
884+ </section>
885+ </div>
886+
887+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
888+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs.controllers.actionOption }">
889+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
890+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError('controllers') || hasActionsInProgress('controllers')">
891+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
892+ <form class="form form--inline u-display--inline">
893+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs.controllers.actionOption.name === 'set-zone'">
894+ <div class="form__group">
895+ <label for="zone2" class="form__group-label">Select Zone</label>
896+ <div class="form__group-input">
897+ <select name="zone" id="zone2"
898+ data-ng-model="tabs.controllers.zoneSelection"
899+ data-ng-options="zone as zone.name for zone in zones">
900+ <option value="" disabled="disabled">Choose a zone</option>
901+ </select>
902+ </div>
903+ </div>
904+ </fieldset>
905+ <fieldset class="form__fieldset ng-hide" data-ng-show="tabs.controllers.actionOption.name === 'test'">
906+ <div class="form__group">
907+ <input class="form__group-label" id="enable_SSH" type="checkbox"
908+ data-ng-model="tabs.controllers.commissionOptions.enableSSH">
909+ <label class="checkbox-label" for="enable_SSH">Allow SSH access and prevent machine from powering off</label>
910+ </div>
911+ </fieldset>
912+ </form>
913+ <div class="page-header__controls" data-ng-if="tabs.controllers.actionOption.name !== 'test'">
914+ <button class="button--base button--inline" data-ng-click="actionCancel('controllers')">Cancel</button>
915+ <button class="button--positive button--inline" data-ng-click="actionGo('controllers')">
916+ <span data-ng-if="tabs.controllers.actionOption.name === 'set-zone'">Set zone for {$ tabs.controllers.selectedItems.length $}
917+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
918+ </span>
919+ <span data-ng-if="tabs.controllers.actionOption.name === 'on'">Power on {$ tabs.controllers.selectedItems.length $}
920+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
921+ </span>
922+ <span data-ng-if="tabs.controllers.actionOption.name === 'off'">Power off {$ tabs.controllers.selectedItems.length $}
923+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
924+ </span>
925+ <span data-ng-if="tabs.controllers.actionOption.name === 'delete'">Delete {$ tabs.controllers.selectedItems.length $}
926+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
927+ </span>
928+ <span data-ng-if="tabs.controllers.actionOption.name === 'import-images'">Import images for {$ tabs.controllers.selectedItems.length $}
929+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
930+ </span>
931+ </button>
932+ </div>
933+ </div>
934+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError('controllers') || hasActionsInProgress('controllers')" data-ng-if="tabs.controllers.actionOption.name === 'test'">
935+ <form class="form form--stack">
936+ <fieldset class="form__fieldset eight-col u-margin--bottom-small">
937+ <div class="form__group">
938+ <label for="testing-scripts">Hardware Tests</label>
939+ <span id="testing-scripts" data-maas-script-select="script" data-script-type="2" data-ng-model="tabs.nodes.testSelection" class="tags--inline"></span>
940+ </div>
941+ </fieldset>
942+ </form>
943+ </div>
944+ <div class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError('controllers') || hasActionsInProgress('controllers')" data-ng-if="tabs.controllers.actionOption.name === 'test'">
945+ <div class="page-header__controls">
946+ <button class="button--base button--inline" data-ng-click="actionCancel('controllers')">Cancel</button>
947+ <button class="button--positive button--inline" data-ng-click="actionGo('controllers')">
948+ <span>Test {$ tabs.controllers.selectedItems.length $}
949+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
950+ </span>
951+ </button>
952+ </div>
953+ </div>
954+
955+ <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
956+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError('controllers')">
957+ <form class="form form--inline">
958+ <p class="page-header__message page-header__message--error">
959+ {$ tabs.controllers.actionErrorCount $}
960+ <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
961+ cannot be {$ tabs.controllers.actionOption.sentence $}. To proceed, update your selection.
962+ </p>
963+ </form>
964+ </div>
965+
966+ <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
967+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionsInProgress('controllers') || hasActionsFailed('controllers')">
968+ <form class="form form--inline">
969+ <p class="page-header__message" data-ng-show="hasActionsInProgress('controllers')">
970+ <i class="icon icon--loading u-animation--spin u-margin--right-small"></i>
971+ {$ tabs.controllers.actionProgress.completed $} of {$ tabs.controllers.actionProgress.total $}
972+ controllers have been {$ tabs.controllers.actionOption.sentence $}.
973+ </p>
974+ <p class="page-header__message page-header__message--error"
975+ data-ng-repeat="(error, controllers) in tabs.controllers.actionProgress.errors">
976+ The {$ tabs.controllers.actionOption.title.toLowerCase() $} action for {$ controllers.length $}
977+ <span data-ng-pluralize count="controllers.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
978+ failed with error: {$ error $}
979+ </p>
980+ </form>
981+ </div>
982+ </div>
983+
984+ <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs.controllers.addController }">
985+ <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
986+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="tabs.controllers.addController">
987+ <h3 class="page-header__dropdown-title">Add rack controller</h3>
988+ <pre class="u-margin--none">
989+ <code># To add a new rack controller, SSH into the rack controller.
990+# Install the maas-rack-controller package.
991+sudo apt install maas-rack-controller
992+# Register the rack controller with this MAAS. If the rack controller (and machines)
993+# don't have access to the URL, use a different IP address to allow connection.
994+sudo maas-rack register --url {$ tabs.controllers.registerUrl $} --secret {$ tabs.controllers.registerSecret $}</code>
995+ </pre>
996+ </div>
997+ <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="tabs.controllers.addController">
998+ <form class="form form--inline">
999+ <div class="page-header__controls">
1000+ <button class="button--secondary button--inline"
1001+ data-ng-click="tabs.controllers.addController = false">Close</button>
1002+ </div>
1003+ </form>
1004+ </div>
1005+ </div>
1006+ </div>
1007+</header>
1008+<div sticky media-query="min-width: 769px" offset="89" class="page-navigation" data-ng-class="{ 'u-visibility--hidden': tabs.nodes.actionOption || addHardwareScope.viewable || tabs.devices.actionOption || addDeviceScope.viewable || tabs.controllers.actionOption || tabs.controllers.addController }">
1009+ <div class="wrapper--inner">
1010+ <nav class="page-navigation__links">
1011+ <button class="page-navigation__link tooltip tooltip--right"
1012+ aria-label="A deployable node managed by MAAS."
1013+ data-ng-class="{ 'is-active': currentpage === 'nodes'}"
1014+ data-ng-click="toggleTab('nodes')">{$ nodes.length $} <ng-pluralize count="nodes.length" when="{'one': 'Machine', 'other': 'Machines'}"></ng-pluralize></button>
1015+ <button class="page-navigation__link tooltip"
1016+ aria-label="A node known to MAAS, but is not deployable."
1017+ data-ng-class="{ 'is-active': currentpage === 'devices'}"
1018+ data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize></button>
1019+ <button class="page-navigation__link tooltip"
1020+ data-ng-if="isSuperUser()"
1021+ aria-label="A node that provides MAAS services."
1022+ data-ng-class="{ 'is-active': currentpage === 'controllers'}"
1023+ data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize></button>
1024+ <button class="page-navigation__link tooltip"
1025+ data-ng-if="showswitches"
1026+ aria-label="A node that is a network switch."
1027+ data-ng-class="{ 'is-active': currentpage === 'switches'}"
1028+ data-ng-click="toggleTab('switches')">{$ switches.length $} <ng-pluralize count="switches.length" when="{'one': 'Switch', 'other': 'Switches'}"></ng-pluralize></button>
1029+ </nav>
1030+ </div>
1031+</div>
1032+<div class="u-padding--top row">
1033+ <div class="wrapper--inner">
1034+ <maas-notifications></maas-notifications>
1035+ <aside class="three-col">
1036+ <div class="accordion maas-accordion ng-hide" data-ng-show="currentpage === 'nodes'"
1037+ data-ng-class="{ 'is-disabled': tabs.nodes.actionOption }">
1038+ <h3 class="accordion__title"
1039+ data-ng-class="{'is-active': isActive}"
1040+ data-ng-init="isActive = false"
1041+ data-ng-click="isActive = !isActive">Filter by</h3>
1042+ <div class="accordion__content">
1043+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1044+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.status.length">
1045+ <button class="accordion__tab-title maas-accordion-tab is-active">Status</button>
1046+ <div class="accordion__tab-content">
1047+ <ul class="accordion__tab-list">
1048+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1049+ <li class="accordion__tab-item" data-ng-repeat="status in tabs.nodes.metadata.status | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('status', status.name, 'nodes') }">
1050+ <button class="accordion__tab-link" data-ng-click="toggleFilter('status', status.name, 'nodes')">{$ status.name $} ({$ status.count $})</button>
1051+ </li>
1052+ </ul>
1053+ </div>
1054+ </div>
1055+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.owner.length">
1056+ <button class="accordion__tab-title maas-accordion-tab">Owner</button>
1057+ <div class="accordion__tab-content">
1058+ <ul class="accordion__tab-list">
1059+ <li class="accordion__tab-item" data-ng-repeat="owner in tabs.nodes.metadata.owner | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('owner', owner.name, 'nodes') }">
1060+ <button class="accordion__tab-link" data-ng-click="toggleFilter('owner', owner.name, 'nodes')">{$ owner.name $} ({$ owner.count $})</button>
1061+ </li>
1062+ </ul>
1063+ </div>
1064+ </div>
1065+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.architecture.length">
1066+ <button class="accordion__tab-title maas-accordion-tab">Architectures</button>
1067+ <div class="accordion__tab-content">
1068+ <ul class="accordion__tab-list">
1069+ <li class="accordion__tab-item" data-ng-repeat="architecture in tabs.nodes.metadata.architecture | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('architecture', architecture.name, 'nodes') }">
1070+ <button class="accordion__tab-link" data-ng-click="toggleFilter('architecture', architecture.name, 'nodes')"><span data-maas-release-name="architecture.name"></span> ({$ architecture.count $})</button>
1071+ </li>
1072+ </ul>
1073+ </div>
1074+ </div>
1075+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.release.length">
1076+ <button class="accordion__tab-title maas-accordion-tab">OS/Release</button>
1077+ <div class="accordion__tab-content">
1078+ <ul class="accordion__tab-list">
1079+ <li class="accordion__tab-item" data-ng-repeat="release in tabs.nodes.metadata.release | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('release', release.name, 'nodes') }">
1080+ <button class="accordion__tab-link" data-ng-click="toggleFilter('release', release.name, 'nodes')"><span data-maas-release-name="release.name"></span> ({$ release.count $})</button>
1081+ </li>
1082+ </ul>
1083+ </div>
1084+ </div>
1085+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.tags.length">
1086+ <button class="accordion__tab-title maas-accordion-tab">Tags</button>
1087+ <div class="accordion__tab-content">
1088+ <ul class="accordion__tab-list">
1089+ <li class="accordion__tab-item" data-ng-repeat="tag in tabs.nodes.metadata.tags | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('tags', tag.name, 'nodes') }">
1090+ <button class="accordion__tab-link" data-ng-click="toggleFilter('tags', tag.name, 'nodes')">{$ tag.name $} ({$ tag.count $})</button>
1091+ </li>
1092+ </ul>
1093+ </div>
1094+ </div>
1095+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.storage_tags.length">
1096+ <button class="accordion__tab-title maas-accordion-tab">Storage Tags</button>
1097+ <div class="accordion__tab-content">
1098+ <ul class="accordion__tab-list">
1099+ <li class="accordion__tab-item" data-ng-repeat="tag in tabs.nodes.metadata.storage_tags | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('storage_tags', tag.name, 'nodes') }">
1100+ <button class="accordion__tab-link" data-ng-click="toggleFilter('storage_tags', tag.name, 'nodes')">{$ tag.name $} ({$ tag.count $})</button>
1101+ </li>
1102+ </ul>
1103+ </div>
1104+ </div>
1105+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.subnets.length">
1106+ <button class="accordion__tab-title maas-accordion-tab">Subnets</button>
1107+ <div class="accordion__tab-content">
1108+ <ul class="accordion__tab-list">
1109+ <li class="accordion__tab-item" data-ng-repeat="subnet in tabs.nodes.metadata.subnets | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('subnets', subnet.name, 'nodes') }">
1110+ <button class="accordion__tab-link" data-ng-click="toggleFilter('subnets', subnet.name, 'nodes')">{$ subnet.name $} ({$ subnet.count $})</button>
1111+ </li>
1112+ </ul>
1113+ </div>
1114+ </div>
1115+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.fabrics.length">
1116+ <button class="accordion__tab-title maas-accordion-tab">Fabrics</button>
1117+ <div class="accordion__tab-content">
1118+ <ul class="accordion__tab-list">
1119+ <li class="accordion__tab-item" data-ng-repeat="fabric in tabs.nodes.metadata.fabrics | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('fabrics', fabric.name, 'nodes') }">
1120+ <button class="accordion__tab-link" data-ng-click="toggleFilter('fabrics', fabric.name, 'nodes')">{$ fabric.name $} ({$ fabric.count $})</button>
1121+ </li>
1122+ </ul>
1123+ </div>
1124+ </div>
1125+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.spaces.length">
1126+ <button class="accordion__tab-title maas-accordion-tab">Spaces</button>
1127+ <div class="accordion__tab-content">
1128+ <ul class="accordion__tab-list">
1129+ <li class="accordion__tab-item" data-ng-repeat="space in tabs.nodes.metadata.spaces | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('spaces', space.name, 'nodes') }">
1130+ <button class="accordion__tab-link" data-ng-click="toggleFilter('spaces', space.name, 'nodes')">{$ space.name $} ({$ space.count $})</button>
1131+ </li>
1132+ </ul>
1133+ </div>
1134+ </div>
1135+ <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.zone.length">
1136+ <button class="accordion__tab-title maas-accordion-tab">Zones</button>
1137+ <div class="accordion__tab-content">
1138+ <ul class="accordion__tab-list">
1139+ <li class="accordion__tab-item" data-ng-repeat="zone in tabs.nodes.metadata.zone | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('zone', zone.name, 'nodes') }">
1140+ <button class="accordion__tab-link" data-ng-click="toggleFilter('zone', zone.name, 'nodes')">{$ zone.name $} ({$ zone.count $})</button>
1141+ </li>
1142+ </ul>
1143+ </div>
1144+ </div>
1145+ </div>
1146+ </div>
1147+ <div class="accordion three-col maas-accordion ng-hide" data-ng-show="currentpage === 'devices'"
1148+ data-ng-class="{ 'is-disabled': tabs.devices.actionOption }">
1149+ <h3 class="accordion__title">Filter by</h3>
1150+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1151+ <div class="ng-hide accordion__tab" data-ng-show="tabs.devices.metadata.owner.length">
1152+ <button class="accordion__tab-title maas-accordion-tab active">Owner</button>
1153+ <div class="accordion__tab-content">
1154+ <ul class="accordion__tab-list">
1155+ <li class="accordion__tab-item" data-ng-repeat="owner in tabs.devices.metadata.owner | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('owner', owner.name, 'devices') }">
1156+ <button class="accordion__tab-link" data-ng-click="toggleFilter('owner', owner.name, 'devices')">{$ owner.name $} ({$ owner.count $})</button>
1157+ </li>
1158+ </ul>
1159+ </div>
1160+ </div>
1161+ <div class="ng-hide accordion__tab" data-ng-show="tabs.devices.metadata.tags.length">
1162+ <button class="accordion__tab-title maas-accordion-tab">Tags</button>
1163+ <div class="accordion__tab-content">
1164+ <ul class="accordion__tab-list">
1165+ <li class="accordion__tab-item" data-ng-repeat="tag in tabs.devices.metadata.tags | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('tags', tag.name, 'devices') }">
1166+ <button class="accordion__tab-link" data-ng-click="toggleFilter('tags', tag.name, 'devices')">{$ tag.name $} ({$ tag.count $})</button>
1167+ </li>
1168+ </ul>
1169+ </div>
1170+ </div>
1171+ <div class="ng-hide accordion__tab" data-ng-show="tabs.devices.metadata.zone.length">
1172+ <button class="accordion__tab-title maas-accordion-tab">Zones</button>
1173+ <div class="accordion__tab-content">
1174+ <ul class="accordion__tab-list">
1175+ <li class="accordion__tab-item" data-ng-repeat="zone in tabs.devices.metadata.zone | orderBy:['name', '-count']" data-ng-class="{ 'is-active': isFilterActive('zone', zone.name, 'devices') }">
1176+ <button class="accordion__tab-link" data-ng-click="toggleFilter('zone', zone.name, 'devices')">{$ zone.name $} ({$ zone.count $})</button>
1177+ </li>
1178+ </ul>
1179+ </div>
1180+ </div>
1181+ </div>
1182+ </aside>
1183+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1184+ <div class="nine-col last-col ng-hide" data-ng-show="currentpage === 'nodes'">
1185+ <form>
1186+ <div id="search-bar" class="search nine-col">
1187+ <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1188+ <input
1189+ type="search" placeholder="Search nodes" class="search__input"
1190+ data-ng-model="tabs.nodes.search" data-ng-change="updateFilters('nodes')"
1191+ data-ng-class="{ error: !tabs.nodes.searchValid }"
1192+ data-ng-disabled="tabs.nodes.actionOption" />
1193+ <input type="reset" class="search__submit"
1194+ data-ng-class="{ 'search__submit--close': tabs.nodes.search.length > 0 }"
1195+ data-ng-click="clearSearch('nodes')" />
1196+ </div>
1197+ <maas-machines-table id="nodes-listing" search="tabs.nodes.search" ng-disabled="hasActionsInProgress('nodes')"
1198+ machine-has-error="!supportsAction($machine, 'nodes')" on-listing-change="onNodeListingChanged($machines, 'nodes')"
1199+ on-check-all="toggleCheckAll('nodes')" on-check="toggleChecked($machine, 'nodes')"></maas-machines-table>
1200+ </form>
1201+ </div>
1202+ <div class="nine-col last-col" class="ng-hide" data-ng-show="currentpage === 'devices'">
1203+ <div class="search nine-col">
1204+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1205+ <input
1206+ type="search" placeholder="Search devices" class="search__input"
1207+ data-ng-model="tabs.devices.search" data-ng-change="updateFilters('devices')"
1208+ data-ng-class="{ error: !tabs.devices.searchValid }"
1209+ data-ng-disabled="tabs.devices.actionOption" />
1210+ <input type="reset" class="search__submit"
1211+ data-ng-class="{ 'search__submit--close': tabs.devices.search.length > 0 }"
1212+ data-ng-click="clearSearch('devices')" />
1213+ </div>
1214+ <table>
1215+ <thead>
1216+ <tr>
1217+ <th class="table-col--3">
1218+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1219+ <input type="checkbox" class="checkbox" data-ng-click="toggleCheckAll('devices')" data-ng-checked="tabs.devices.allViewableChecked" id="check-all-devices" data-ng-disabled="hasActionsInProgress('devices')" />
1220+ <label for="check-all-devices" class="checkbox-label"></label>
1221+ </th>
1222+ <th class="table-col--24">
1223+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1224+ <a href="" data-ng-click="sortTable('fqdn', 'devices')" data-ng-class="{'is-sorted': tabs.devices.predicate === 'fqdn', 'sort-asc': tabs.devices.reverse === false, 'sort-desc': tabs.devices.reverse === true}">
1225+ <span title="Fully Qualified Domain Name">FQDN</span>
1226+ </a>
1227+ </th>
1228+ <th class="table-col--25">
1229+ <a href="" data-ng-click="sortTable('primary_mac', 'devices')" data-ng-class="{'is-sorted': tabs.devices.predicate === 'primary_mac', 'sort-asc': tabs.devices.reverse === false, 'sort-desc': tabs.devices.reverse === true}">
1230+ <span title="Media Access Control addresses">MAC</span>
1231+ </a>
1232+ </th>
1233+ <th class="table-col--15">
1234+ <a href="" data-ng-click="sortTable('ip_assignment', 'devices')" data-ng-class="{'is-sorted': tabs.devices.predicate === 'ip_assignment', 'sort-asc': tabs.devices.reverse === false, 'sort-desc': tabs.devices.reverse === true}">IP Assignment</a>
1235+ </th>
1236+ <th class="table-col--20">
1237+ <a href="" data-ng-click="sortTable('ip_address', 'devices')" data-ng-class="{'is-sorted': tabs.devices.predicate === 'ip_address', 'sort-asc': tabs.devices.reverse === false, 'sort-desc': tabs.devices.reverse === true}">IP Address</a>
1238+ </th>
1239+ <th class="table-col--12">
1240+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1241+ <a href="" data-ng-click="sortTable('owner', 'devices')" data-ng-class="{'is-sorted': tabs.devices.predicate === 'owner', 'sort-asc': tabs.devices.reverse === false, 'sort-desc': tabs.devices.reverse === true}">Owner</a>
1242+ </th>
1243+ </tr>
1244+ </thead>
1245+ <tbody vs-repeat vs-scroll-parent="window">
1246+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->
1247+ <tr
1248+ data-ng-repeat="device in tabs.devices.filtered_items = (devices | nodesFilter:tabs.devices.search | orderBy:tabs.devices.predicate:tabs.devices.reverse) track by device.system_id"
1249+ data-ng-class="{ 'table--error': !supportsAction(device, 'devices'), selected: device.$selected }">
1250+ <td class="table-col--3" aria-label="Select device">
1251+ <input type="checkbox" class="checkbox" data-ng-click="toggleChecked(device, 'devices')" data-ng-checked="device.$selected" id="{$ device.fqdn $}" data-ng-disabled="hasActionsInProgress('devices')"/>
1252+ <label for="{$ device.fqdn $}" class="checkbox-label"></label>
1253+ </td>
1254+ <td class="table-col--24" aria-label="FQDN">
1255+ <a href="#/node/device/{$ device.system_id $}">{$ device.fqdn $}</a>
1256+ </td>
1257+ <td class="table-col--25" aria-label="MAC">
1258+ {$ device.primary_mac $}
1259+ <span class="extra-macs" data-ng-show="device.extra_macs.length">(+{$ device.extra_macs.length $})</span>
1260+ </td>
1261+ <td class="table-col--15" aria-label="IP Assignment">{$ getDeviceIPAssignment(device.ip_assignment) $}</td>
1262+ <td class="table-col--20" aria-label="IP Address">{$ device.ip_address $}</td>
1263+ <td class="table-col--12" aria-label="Owner">{$ device.owner $}</td>
1264+ </tr>
1265+ </tbody>
1266+ </table>
1267+ </div>
1268+ <div class="twelve-col last-col" class="ng-hide" data-ng-show="currentpage === 'controllers'">
1269+ <div class="search twelve-col">
1270+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1271+ <input
1272+ type="search" placeholder="Search controllers" class="search__input"
1273+ data-ng-model="tabs.controllers.search" data-ng-change="updateFilters('controllers')"
1274+ data-ng-class="{ error: !tabs.controllers.searchValid }"
1275+ data-ng-disabled="tabs.controllers.actionOption" />
1276+ <input type="submit" class="search__submit"
1277+ data-ng-class="{ 'search__submit--close': tabs.controllers.search.length > 0 }"
1278+ data-ng-click="clearSearch('controllers')" />
1279+ </div>
1280+ <table>
1281+ <thead>
1282+ <tr>
1283+ <th class="table-col--2">
1284+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1285+ <input type="checkbox" class="checkbox" data-ng-click="toggleCheckAll('controllers')" data-ng-checked="tabs.controllers.allViewableChecked" id="check-all-controllers" data-ng-disabled="hasActionsInProgress('controllers')" />
1286+ <label for="check-all-controllers" class="checkbox-label"></label>
1287+ </th>
1288+ <th class="table-col--19">
1289+ <a href="" data-ng-click="sortTable('fqdn', 'controllers')" data-ng-class="{'is-sorted': tabs.controllers.predicate === 'fqdn', 'sort-asc': tabs.controllers.reverse === false, 'sort-desc': tabs.controllers.reverse === true}">
1290+ <span title="Fully Qualified Domain Name">Name</span>
1291+ </a>
1292+ </th>
1293+ <th class="table-col--4 u-align--center">
1294+ <span title="Status">Status</span>
1295+ </th>
1296+ <th class="table-col--15">
1297+ <a href="" data-ng-click="sortTable('node_type_display', 'controllers')" data-ng-class="{'is-sorted': tabs.controllers.predicate === 'node_type_display', 'sort-asc': tabs.controllers.reverse === false, 'sort-desc': tabs.controllers.reverse === true}">
1298+ <span title="Controller Type">Type</span>
1299+ </a>
1300+ </th>
1301+ <th class="table-col--10">
1302+ <a href="" data-ng-click="sortTable('node_type_display', 'controllers')" data-ng-class="{'is-sorted': tabs.controllers.predicate === 'version', 'sort-asc': tabs.controllers.reverse === false, 'sort-desc': tabs.controllers.reverse === true}">
1303+ <span title="Version">Version</span>
1304+ </a>
1305+ </th>
1306+ <th class="table-col--15">
1307+ <a href="" data-ng-click="sortTable('updated', 'controllers')" data-ng-class="{'is-sorted': tabs.controllers.predicate === 'updated', 'sort-asc': tabs.controllers.reverse === false, 'sort-desc': tabs.controllers.reverse === true}">Last Image Sync</a>
1308+ </th>
1309+ <th class="table-col--15">
1310+ <a href="" data-ng-click="sortTable('updated', 'controllers')" data-ng-class="{'is-sorted': tabs.controllers.predicate === 'updated', 'sort-asc': tabs.controllers.reverse === false, 'sort-desc': tabs.controllers.reverse === true}">Images Status</a>
1311+ </th>
1312+ </tr>
1313+ </thead>
1314+ <tbody vs-repeat vs-scroll-parent="window">
1315+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->
1316+ <tr
1317+ data-ng-repeat="controller in tabs.controllers.filtered_items = (controllers | nodesFilter:tabs.controllers.search | orderBy:tabs.controllers.predicate:tabs.controllers.reverse) track by controller.system_id"
1318+ data-ng-class="{ 'table--error': !supportsAction(controller, 'controllers'), selected: controller.$selected }">
1319+ <td class="table-col--2" aria-label="Select controller">
1320+ <input type="checkbox" class="checkbox" data-ng-click="toggleChecked(controller, 'controllers')" data-ng-checked="controller.$selected" id="{$ controller.fqdn $}" data-ng-disabled="hasActionsInProgress('controllers')"/>
1321+ <label for="{$ controller.fqdn $}" class="checkbox-label"></label>
1322+ </td>
1323+ <td class="table-col--19" aria-label="FQDN">
1324+ <a href="#/node/controller/{$ controller.system_id $}">{$ controller.fqdn $}</a>
1325+ </td>
1326+ <td class="table-col--4 u-align--center" data-maas-controller-status="controller" data-maas-services="services" aria-label="Status"></td>
1327+ <td class="table-col--15" aria-label="Type">{$ controller.node_type_display $}</td>
1328+ <td class="table-col--10" aria-label="Version">
1329+ <div data-ng-show="controller.version">
1330+ {$ controller.version__short $}
1331+ </div>
1332+ <div data-ng-show="!controller.version" class="u-text--subtle">
1333+ Unknown
1334+ <i class="icon icon--info u-margin--left-tiny tooltip"
1335+ aria-label="Less than 2.3.0"></i>
1336+ </div>
1337+ </td>
1338+ <td class="table-col--15" aria-label="Last image sync">{$ controller.last_image_sync || 'Never' $}</td>
1339+ <td class="table-col--15" aria-label="Image status">
1340+ <maas-controller-image-status system-id="controller.system_id"></maas-controller-image-status>
1341+ </td>
1342+ </tr>
1343+ </tbody>
1344+ </table>
1345+ </div>
1346+ <div class="twelve-col last-col" class="ng-hide" data-ng-show="currentpage === 'switches'">
1347+ <div class="search twelve-col">
1348+ <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1349+ <input
1350+ type="search" placeholder="Search switches" class="search__input"
1351+ data-ng-model="tabs.switches.search" data-ng-change="updateFilters('switches')"
1352+ data-ng-class="{ error: !tabs.switches.searchValid }"
1353+ data-ng-disabled="tabs.switches.actionOption" />
1354+ <input type="submit" class="search__submit"
1355+ data-ng-class="{ 'search__submit--close': tabs.switches.search.length > 0 }"
1356+ data-ng-click="clearSearch('switches')" />
1357+ </div>
1358+ <maas-switches-table id="switches-listing" search="tabs.switches.search" ng-disabled="hasActionsInProgress('switches')"
1359+ switch-has-error="!supportsAction($switch_, 'switches')" on-listing-change="onNodeListingChanged($switches, 'switches')"
1360+ on-check-all="toggleCheckAll('switches')" on-check="toggleChecked($switch_, 'switches')"></maas-switches-table>
1361+ </div>
1362+ </div>
1363+</div>
1364+>>>>>>> src/maasserver/static/partials/nodes-list.html
1365diff --git a/src/maasserver/static/partials/script-results-list.html b/src/maasserver/static/partials/script-results-list.html
1366index 12d1175..bcf7b40 100644
1367--- a/src/maasserver/static/partials/script-results-list.html
1368+++ b/src/maasserver/static/partials/script-results-list.html
1369@@ -4,6 +4,7 @@
1370 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>
1371 </div>
1372 </div>
1373+<<<<<<< src/maasserver/static/partials/script-results-list.html
1374 <div class="row">
1375 <div data-ng-repeat="hardware_type in results">
1376 <div data-ng-if="resultsLoaded && (hardware_type.results | json) != '{}'">
1377@@ -49,12 +50,59 @@
1378 <button class="p-contextual-menu__link" aria-label="Hide previous {$ result.result_section $}" data-ng-if="result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = false">Hide previous {$ result.result_section $}</button>
1379 </div>
1380 </div>
1381+=======
1382+ <div data-ng-repeat="hardware_type in results">
1383+ <div data-ng-if="resultsLoaded && (hardware_type.results | json) != '{}'">
1384+ <h2 data-ng-if="hardware_type.title !== 'null'">{$ hardware_type.title $}</h2>
1385+ <div data-ng-repeat="(title, results) in hardware_type.results">
1386+ <h3 data-ng-if="title !== 'null'">{$ title $}</h3>
1387+ <section class="table u-margin--bottom">
1388+ <header class="table__head">
1389+ <div class="table__row">
1390+ <div class="table__header table-col--2 u-padding--left-none"></div>
1391+ <div class="table__header table-col--24">Name</div>
1392+ <div class="table__header table-col--22">Tags</div>
1393+ <div class="table__header table-col--15">Runtime</div>
1394+ <div class="table__header table-col--20">Date</div>
1395+ <div class="table__header table-col--12">Result</div>
1396+ <div class="table__header table-col--5 u-align--right">Actions</div>
1397+ </div>
1398+ </header>
1399+ <main class="table__body">
1400+ <div data-ng-repeat="result in results">
1401+ <div class="table__row" data-ng-class="{'is-active': result.showing_results || result.showing_history}">
1402+ <div class="table__data table-col--2 u-padding--left-none" aria-label="Status">
1403+ <span data-maas-script-status="script-status" data-script-status="result.status"></span>
1404+ </div>
1405+ <div class="table__data table-col--24" data-ng-click="result.showing_results = !result.showing_results" aria-label="Name">
1406+ {$ result.name $}
1407+ <button class="icon u-margin--top-tiny u-float--right" data-ng-class="{'icon--open': !result.showing_results, 'icon--close': result.showing_results}" data-ng-if="!result.showing_history"></button>
1408+ </div>
1409+ <div class="table__data table-col--22" aria-label="Tags"><span data-ng-hide="result.showing_history">{$ result.tags $}</span></div>
1410+ <div class="table__data table-col--15" aria-label="Runtime"><span data-ng-hide="result.showing_history" data-maas-script-run-time="script-runtime" data-start-time="result.starttime" data-run-time="{{result.runtime}}" data-estimated-run-time="{{result.estimated_runtime}}" data-script-status="result.status"></span></div>
1411+ <div class="table__data table-col--20" aria-label="Date"><span data-ng-hide="result.showing_history">{$ result.updated $}</span></div>
1412+ <div class="table__data table-col--12" aria-label="Status">
1413+ <span data-ng-hide="result.showing_history">
1414+ <!-- Only link to the testing result when we've received it. This is indicated with status 2(passed), 3(failed), 4(timedout), 6(degraded), 8(failed installing)-->
1415+ {$ result.status_name $} <a data-ng-if="result.status === 2 || result.status === 3 || result.status === 4 || result.status === 6 || result.status === 8" href="#/node/{$ type_name $}/{$ node.system_id $}/{$ section.area $}/{$ result.id $}">View log</a>
1416+ </span>
1417+ </div>
1418+ <div class="table__data table-col--5 table--mobile-controls">
1419+ <div class="table__controls u-align--right" toggle-ctrl>
1420+ <button class="table__controls-toggle" data-ng-click="toggleMenu()">View actions</button>
1421+ <div class="table__controls-menu ng-hide" role="menu" data-ng-show="isToggled">
1422+ <button class="table__controls-action" aria-label="View metrics" data-ng-if="!result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = true">View metrics</button>
1423+ <button class="table__controls-action" aria-label="Hide metrics" data-ng-if="result.showing_results" data-ng-click="toggleMenu(); result.showing_history = false; result.showing_results = false">Hide metrics</button>
1424+ <button class="table__controls-action" aria-label="View previous {$ result.result_section $}" data-ng-if="!result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = true">View previous {$ result.result_section $}</button>
1425+ <button class="table__controls-action" aria-label="Hide previous {$ result.result_section $}" data-ng-if="result.showing_history" data-ng-click="toggleMenu(); result.showing_results = false; result.showing_history = false">Hide previous {$ result.result_section $}</button>
1426+>>>>>>> src/maasserver/static/partials/script-results-list.html
1427 </div>
1428 </td>
1429 <td class="p-table-expanding__panel col-12" aria-label="results" data-ng-if="result.showing_results && !result.showing_history">
1430 <div class="row">
1431 <div class="col-12" data-ng-if="result.results.length === 0">No metrics provided</div>
1432 </div>
1433+<<<<<<< src/maasserver/static/partials/script-results-list.html
1434 <div class="row" data-ng-if="result.results">
1435 <dl data-ng-repeat="item in result.results">
1436 <dt class="p-tooltip p-tooltip--top-center">
1437@@ -68,6 +116,34 @@
1438 <td class="p-table-expanding__panel col-12" aria-label="loading history" data-ng-if="result.loading_history">
1439 <div class="col-12">
1440 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>
1441+=======
1442+ </div>
1443+ </div>
1444+
1445+ <div class="table__dropdown" aria-label="history" data-ng-if="result.showing_history">
1446+ <div class="table__row is-active">
1447+ <div class="table__data table-col--100">
1448+ <section class="table u-margin--bottom">
1449+ <main class="table__body">
1450+ <div class="table__row is-active u-border--none" data-ng-repeat="item in result.history_list">
1451+ <div class="table__data table-col--2 u-padding--left-none" aria-label="Status">
1452+ <span data-maas-script-status="script-status" data-script-status="item.status"></span>
1453+ </div>
1454+ <div class="table__data table-col--24" aria-label="Name">{$ result.name $}</div>
1455+ <div class="table__data table-col--24" aria-label="Tags">{$ result.tags $}</div>
1456+ <div class="table__data table-col--15" aria-label="Runtime"><span data-maas-script-run-time="script-runtime" data-start-time="item.starttime" data-run-time="{{item.runtime}}" data-estimated-run-time="{{item.estimated_runtime}}" data-script-status="item.status"></span></div>
1457+ <div class="table__data table-col--20" aria-label="Date">{$ item.updated $}</div>
1458+ <div class="table__data table-col--10" aria-label="Status">
1459+ <!-- Only link to the testing result when we've received it. This is indicated with status 2(passed), 3(failed), 4(timedout), 6(degraded), 8(failed installing)-->
1460+ {$ item.status_name $} <a data-ng-if="item.status === 2 || item.status === 3 || item.status === 4 || item.status === 6 || item.status === 8" href="#/node/{$ type_name $}/{$ node.system_id $}/{$ section.area $}/{$ item.id $}">View log</a>
1461+ </div>
1462+ </div>
1463+ </main>
1464+ </section>
1465+ <p class="u-align--center u-margin--bottom">
1466+ <button class="button--secondary button--inline" data-ng-click="result.showing_history = false">Hide previous {$ result.result_section $}</button>
1467+ </p>
1468+>>>>>>> src/maasserver/static/partials/script-results-list.html
1469 </div>
1470 </div>
1471 <td class="p-table-expanding__panel col-12" aria-label="history" data-ng-if="result.showing_history">
1472diff --git a/src/maasserver/static/partials/subnet-details.html b/src/maasserver/static/partials/subnet-details.html
1473index dc22777..5415f96 100755
1474--- a/src/maasserver/static/partials/subnet-details.html
1475+++ b/src/maasserver/static/partials/subnet-details.html
1476@@ -201,6 +201,7 @@
1477 <maas-obj-field type="text" key="cidr" label="CIDR" placeholder="Subnet CIDR"
1478 label-width="2" input-width="4"></maas-obj-field>
1479 <maas-obj-field type="text" key="gateway_ip" label="Gateway IP" placeholder="Gateway IP"
1480+<<<<<<< src/maasserver/static/partials/subnet-details.html
1481 label-width="2" input-width="4"></maas-obj-field>
1482 <maas-obj-field type="text" key="dns_servers" label="DNS" placeholder="DNS nameservers for subnet"
1483 label-width="2" input-width="4"></maas-obj-field>
1484@@ -237,6 +238,45 @@
1485 <p>Space</p>
1486 </div>
1487 <div class="p-form__control">
1488+=======
1489+ label-width="two" input-width="three" class="u-margin--bottom-none"></maas-obj-field>
1490+ </maas-obj-field-group>
1491+ <maas-obj-field type="text" key="dns_servers" label="DNS" placeholder="DNS nameservers for subnet"
1492+ label-width="two" input-width="three"></maas-obj-field>
1493+ <maas-obj-field type="textarea" key="description" label="Description" placeholder="Subnet description"
1494+ label-width="two" input-width="three"></maas-obj-field>
1495+ </fieldset>
1496+ <fieldset class="form__fieldset six-col last-col">
1497+ <maas-obj-field type="onoffswitch" key="managed" label="Managed allocation"
1498+ label-width="two" input-width="three"
1499+ label-info="When enabled, MAAS will assume it may take full control of DHCP and&#xa;IP address management on this subnet. When disabled, MAAS will only&#xa;allocate addresses from reserved IP ranges on this subnet, and will not&#xa;include this subnet's dynamic ranges in the DHCP configuration.">
1500+
1501+ </maas-obj-field>
1502+ <span data-ng-if="subnet.$maasForm.getValue('managed') != subnet.managed">
1503+ <div class="form__group-label two-col">&nbsp;</div>
1504+ <div class="form__group-input three-col last-col">
1505+ <span data-ng-if="subnet.$maasForm.getValue('managed') == false" class="u-text--subtle">
1506+ <i class="icon icon--warning"></i>
1507+ <b>Warning:</b> MAAS will now start allocating IP addresses from reserved ranges. If a reserved range has been defined to prevent MAAS from allocating IP addresses, this behavior will change.
1508+ </span>
1509+ <span data-ng-if="subnet.$maasForm.getValue('managed') == true" class="u-text--subtle">
1510+ <i class="icon icon--warning"></i>
1511+ <b>Warning:</b> MAAS will now allocate IP addresses from the entire subnet, excluding any reserved ranges.
1512+ </span>
1513+ </div>
1514+ </span>
1515+ <maas-obj-field data-ng-if="subnet.version === 4" type="onoffswitch" key="active_discovery" label="Active mapping"
1516+ label-width="two" input-width="three" label-info="When enabled, MAAS will scan this subnet {$ active_discovery_interval | lowercase $}&#xa;to discover hosts that have not been discovered passively."></maas-obj-field>
1517+ <maas-obj-field type="options" key="fabric" label="Fabric" placeholder="Choose fabric"
1518+ options="fabric.id as fabric.name for fabric in fabrics | orderBy:'name'"
1519+ label-width="two" input-width="three"></maas-obj-field>
1520+ <maas-obj-field type="options" key="vlan" class="u-margin--bottom" label="VLAN" placeholder="Choose VLAN"
1521+ options="v.id as getVLANName(v) for v in vlans | filterByFabric:subnet.$maasForm.getValue('fabric')"
1522+ label-width="two" input-width="three"></maas-obj-field>
1523+ <dl>
1524+ <dt class="two-col u-text--subtle">Space</dt>
1525+ <dd class="four-col last-col">
1526+>>>>>>> src/maasserver/static/partials/subnet-details.html
1527 <a data-ng-if="space !== null" href="#/space/{$ space.id $}">{$ space.name $}</a>
1528 <span data-ng-if="space === null">
1529 (undefined)
1530diff --git a/src/maasserver/static/partials/vlan-details.html b/src/maasserver/static/partials/vlan-details.html
1531index 8a2826a..404a6e6 100644
1532--- a/src/maasserver/static/partials/vlan-details.html
1533+++ b/src/maasserver/static/partials/vlan-details.html
1534@@ -231,9 +231,15 @@
1535 <!-- End of "Take action" dropdown -->
1536 </div>
1537 </header>
1538+<<<<<<< src/maasserver/static/partials/vlan-details.html
1539 <section class="p-strip is-bordered">
1540 <div class="row">
1541 <div class="col-6">
1542+=======
1543+ <section class="row">
1544+ <fieldset class="wrapper--inner">
1545+ <div class="twelve-col">
1546+>>>>>>> src/maasserver/static/partials/vlan-details.html
1547 <h2 class="u-float--left">VLAN Summary</h2>
1548 </div>
1549 <div class="col-6">
1550@@ -310,6 +316,7 @@
1551 </div>
1552 <div class="row">
1553 <maas-obj-form obj="vlanDetails.vlan" manager="vlanDetails.vlanManager" data-ng-disabled="!vlanDetails.isSuperUser()" data-ng-if="vlanDetails.editSummary"
1554+<<<<<<< src/maasserver/static/partials/vlan-details.html
1555 table-form="true" save-on-blur="false" after-save="vlanDetails.exitEditSummary">
1556 <div class="row">
1557 <div class="col-6">
1558@@ -337,11 +344,35 @@
1559 </span></p>
1560 </div>
1561 <div class="p-form__control">
1562+=======
1563+ table-form="true" save-on-blur="false" after-save="vlanDetails.exitEditSummary">
1564+ <fieldset class="form__fieldset six-col">
1565+ <maas-obj-field type="text" key="vid" label="VID" placeholder="VLAN VID"
1566+ label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
1567+ <maas-obj-field type="text" key="name" label="Name" placeholder="VLAN name"
1568+ label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
1569+ <maas-obj-field type="text" key="mtu" label="MTU" placeholder="VLAN MTU"
1570+ label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
1571+ <maas-obj-field type="options" key="space" label="Space" placeholder="(undefined)" placeholder-enabled="true"
1572+ options="space.id as space.name for space in vlanDetails.spaces"
1573+ label-width="two" input-width="three"></maas-obj-field>
1574+ <maas-obj-field type="textarea" key="description" label="Description" placeholder="VLAN description"
1575+ label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
1576+ </fieldset>
1577+ <fieldset class="form__fieldset six-col last-col">
1578+ <dl>
1579+ <maas-obj-field type="options" key="fabric" label="Fabric" label-width="two" input-width="three"
1580+ options="fabric.id as fabric.name for fabric in vlanDetails.fabrics"></maas-obj-field>
1581+ <div data-ng-if="vlanDetails.relatedControllers">
1582+ <dt class="two-col">Rack controllers <span class="icon icon--help tooltip" aria-label="A rack controller controls hosts and images and runs network services&#xa;like DHCP for connected VLANs."></span></dt>
1583+ <dd class="four-col last-col">
1584+>>>>>>> src/maasserver/static/partials/vlan-details.html
1585 <span data-ng-repeat="rack in vlanDetails.relatedControllers">
1586 <a href="#/controller/{$ rack.system_id $}">{$ rack.hostname $}</a>,
1587 </span>
1588 </div>
1589 </div>
1590+<<<<<<< src/maasserver/static/partials/vlan-details.html
1591 </div>
1592 </div>
1593 <div class="row u-no-margin--top">
1594@@ -353,6 +384,17 @@
1595 data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>
1596 <button class="p-button--positive" maas-obj-save>Save summary</button>
1597 </div>
1598+=======
1599+ </dl>
1600+ </fieldset>
1601+ <div class="six-col">
1602+ <maas-obj-errors></maas-obj-errors>
1603+ </div>
1604+ <div class="six-col last-col u-align--right">
1605+ <button class="button--base button--inline"
1606+ data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>
1607+ <button class="button--positive button--inline" maas-obj-save>Save summary</button>
1608+>>>>>>> src/maasserver/static/partials/vlan-details.html
1609 </div>
1610 </maas-obj-form>
1611 </div>
1612diff --git a/src/maasserver/tests/test_bootsources.py b/src/maasserver/tests/test_bootsources.py
1613index a83c9c1..3629529 100644
1614--- a/src/maasserver/tests/test_bootsources.py
1615+++ b/src/maasserver/tests/test_bootsources.py
1616@@ -44,7 +44,10 @@ from maasserver.testing.testcase import (
1617 )
1618 from maasserver.tests.test_bootresources import SimplestreamsEnvFixture
1619 from maasserver.utils import get_maas_user_agent
1620+<<<<<<< src/maasserver/tests/test_bootsources.py
1621 from maastesting.djangotestcase import count_queries
1622+=======
1623+>>>>>>> src/maasserver/tests/test_bootsources.py
1624 from maastesting.matchers import MockCalledOnceWith
1625 from provisioningserver.config import DEFAULT_IMAGES_URL
1626 from provisioningserver.import_images import (
1627diff --git a/src/maasserver/tests/test_stats.py b/src/maasserver/tests/test_stats.py
1628index d0200f7..43aae70 100644
1629--- a/src/maasserver/tests/test_stats.py
1630+++ b/src/maasserver/tests/test_stats.py
1631@@ -69,7 +69,7 @@ class TestMAASStats(MAASServerTestCase):
1632 "total_storage": total_storage,
1633 },
1634 }
1635- self.assertEquals(stats, json.dumps(compare))
1636+ self.assertEquals(json.loads(stats), compare)
1637
1638 def test_get_request_params_returns_params(self):
1639 factory.make_RegionRackController()
1640diff --git a/src/maasserver/triggers/tests/test_websocket_listener.py b/src/maasserver/triggers/tests/test_websocket_listener.py
1641index 5e69c2d..5594db9 100644
1642--- a/src/maasserver/triggers/tests/test_websocket_listener.py
1643+++ b/src/maasserver/triggers/tests/test_websocket_listener.py
1644@@ -1714,8 +1714,13 @@ class TestIPRangeListener(
1645 self.create_subnet, {'cidr': str(network)})
1646 params = {
1647 'subnet': subnet,
1648+<<<<<<< src/maasserver/triggers/tests/test_websocket_listener.py
1649 'start_ip': IPAddress(network.first + 2),
1650 'end_ip': IPAddress(network.last - 1)}
1651+=======
1652+ 'start_ip': str(IPAddress(network.first + 2)),
1653+ 'end_ip': str(IPAddress(network.last - 1))}
1654+>>>>>>> src/maasserver/triggers/tests/test_websocket_listener.py
1655 yield listener.startService()
1656 try:
1657 iprange = yield deferToDatabase(
1658diff --git a/src/metadataserver/user_data/templates/commissioning.template b/src/metadataserver/user_data/templates/commissioning.template
1659index 1ba2510..a1f6ce9 100644
1660--- a/src/metadataserver/user_data/templates/commissioning.template
1661+++ b/src/metadataserver/user_data/templates/commissioning.template
1662@@ -31,6 +31,10 @@ main() {
1663 # the BMC username and password.
1664 signal WORKING "Starting [maas-bmc-autodetect]" || exit 1
1665
1666+ # LP:1730524 - Make sure we can signal the metadata service before updating
1667+ # the BMC username and password.
1668+ signal WORKING "Starting [maas-bmc-autodetect]" || exit 1
1669+
1670 # Install IPMI deps
1671 aptget install freeipmi-tools openipmi ipmitool sshpass
1672
1673diff --git a/src/provisioningserver/drivers/power/ipmi.py b/src/provisioningserver/drivers/power/ipmi.py
1674index e3b1561..3783152 100644
1675--- a/src/provisioningserver/drivers/power/ipmi.py
1676+++ b/src/provisioningserver/drivers/power/ipmi.py
1677@@ -219,7 +219,11 @@ class IPMIPowerDriver(PowerDriver):
1678 @staticmethod
1679 def _issue_ipmi_chassis_config_command(
1680 command, power_change, power_address, power_boot_type=None):
1681+<<<<<<< src/provisioningserver/drivers/power/ipmi.py
1682 env = shell.get_env_with_locale()
1683+=======
1684+ env = shell.select_c_utf8_locale()
1685+>>>>>>> src/provisioningserver/drivers/power/ipmi.py
1686 with NamedTemporaryFile("w+", encoding="utf-8") as tmp_config:
1687 # Write out the chassis configuration.
1688 if (power_boot_type is None or
1689diff --git a/src/provisioningserver/import_images/boot_resources.py b/src/provisioningserver/import_images/boot_resources.py
1690index f985df6..0a5b3c2 100644
1691--- a/src/provisioningserver/import_images/boot_resources.py
1692+++ b/src/provisioningserver/import_images/boot_resources.py
1693@@ -9,6 +9,7 @@ __all__ = [
1694 ]
1695
1696 from argparse import ArgumentParser
1697+import copy
1698 import errno
1699 from io import StringIO
1700 import os
1701@@ -41,6 +42,18 @@ from provisioningserver.utils.fs import (
1702 read_text_file,
1703 tempdir,
1704 )
1705+<<<<<<< src/provisioningserver/import_images/boot_resources.py
1706+=======
1707+from provisioningserver.utils.service_monitor import ServiceActionError
1708+from provisioningserver.utils.shell import (
1709+ call_and_check,
1710+ ExternalProcessError,
1711+)
1712+from provisioningserver.utils.snappy import (
1713+ get_snap_data_path,
1714+ running_in_snap,
1715+)
1716+>>>>>>> src/provisioningserver/import_images/boot_resources.py
1717 from twisted.internet.defer import inlineCallbacks
1718 from twisted.python.filepath import FilePath
1719
1720@@ -109,6 +122,54 @@ def write_snapshot_metadata(snapshot, meta_file_content):
1721 atomic_write(meta_file_content.encode("ascii"), meta_file, mode=0o644)
1722
1723
1724+<<<<<<< src/provisioningserver/import_images/boot_resources.py
1725+=======
1726+def write_targets_conf(snapshot):
1727+ """Write "maas.tgt" file."""
1728+ targets_conf = os.path.join(snapshot, 'maas.tgt')
1729+ targets_conf_content = compose_targets_conf(snapshot)
1730+ atomic_write(targets_conf_content, targets_conf, mode=0o644)
1731+
1732+
1733+def update_targets_conf(snapshot):
1734+ """Runs tgt-admin to update the new targets from "maas.tgt"."""
1735+ # Ensure that tgt is running before tgt-admin is used.
1736+ try:
1737+ service_monitor.ensureService("tgt").wait(30)
1738+ except ServiceActionError:
1739+ msg = "Unable to start tgt"
1740+ try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
1741+ maaslog.warning(msg)
1742+ return
1743+
1744+ # Update the tgt config.
1745+ targets_conf = os.path.join(snapshot, 'maas.tgt')
1746+
1747+ # The targets_conf may not exist in the event the BootSource is broken
1748+ # and images havn't been imported yet. This fixes LP:1655721
1749+ if not os.path.exists(targets_conf):
1750+ return
1751+
1752+ try:
1753+ env = copy.deepcopy(os.environ)
1754+ # LP:1718706 - When TGT is run in a snap the socket is stored in the
1755+ # SNAP_DATA path. Define it before calling tgt-admin otherwise the
1756+ # standard path is used and the call will fail.
1757+ if running_in_snap():
1758+ env['TGT_IPC_SOCKET'] = os.path.join(
1759+ get_snap_data_path(), 'tgtd-socket')
1760+ call_and_check(sudo([
1761+ get_path('/usr/sbin/tgt-admin'),
1762+ '--conf', targets_conf,
1763+ '--update', 'ALL',
1764+ ]), env=env)
1765+ except ExternalProcessError as e:
1766+ msg = "Unable to update TGT config: %s" % e
1767+ try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
1768+ maaslog.warning(msg)
1769+
1770+
1771+>>>>>>> src/provisioningserver/import_images/boot_resources.py
1772 def read_sources(sources_yaml):
1773 """Read boot resources config file.
1774
1775diff --git a/src/provisioningserver/import_images/tests/test_boot_resources.py b/src/provisioningserver/import_images/tests/test_boot_resources.py
1776index 28635e7..31fa955 100644
1777--- a/src/provisioningserver/import_images/tests/test_boot_resources.py
1778+++ b/src/provisioningserver/import_images/tests/test_boot_resources.py
1779@@ -43,7 +43,18 @@ from provisioningserver.testing.config import (
1780 BootSourcesFixture,
1781 ClusterConfigurationFixture,
1782 )
1783+<<<<<<< src/provisioningserver/import_images/tests/test_boot_resources.py
1784 from provisioningserver.utils.fs import write_text_file
1785+=======
1786+from provisioningserver.utils.fs import (
1787+ tempdir,
1788+ write_text_file,
1789+)
1790+from provisioningserver.utils.service_monitor import ServiceActionError
1791+from provisioningserver.utils.shell import ExternalProcessError
1792+from testtools.content import Content
1793+from testtools.content_type import UTF8_TEXT
1794+>>>>>>> src/provisioningserver/import_images/tests/test_boot_resources.py
1795 from testtools.matchers import (
1796 DirExists,
1797 FileExists,
1798@@ -477,6 +488,87 @@ class TestMain(MAASTestCase):
1799 boot_resources.NoConfigFile,
1800 boot_resources.main, self.make_args(sources="", sources_file=""))
1801
1802+<<<<<<< src/provisioningserver/import_images/tests/test_boot_resources.py
1803+=======
1804+ def test_update_targets_conf_ensures_tgt_service(self):
1805+ mock_ensureService = self.patch(
1806+ boot_resources.service_monitor, "ensureService")
1807+ self.patch(boot_resources, "call_and_check")
1808+ boot_resources.update_targets_conf(factory.make_name("snapshot"))
1809+ self.assertThat(mock_ensureService, MockCalledOnceWith("tgt"))
1810+
1811+ def test_update_targets_conf_logs_tgt_service_check_error(self):
1812+ # Regression test for LP:1735025
1813+ mock_ensureService = self.patch(
1814+ boot_resources.service_monitor, "ensureService")
1815+ mock_ensureService.side_effect = ServiceActionError()
1816+ mock_try_send_rack_event = self.patch(
1817+ boot_resources, 'try_send_rack_event')
1818+ mock_maaslog = self.patch(boot_resources.maaslog, 'warning')
1819+ boot_resources.update_targets_conf(factory.make_name("snapshot"))
1820+ self.assertThat(mock_try_send_rack_event, MockCalledOnce())
1821+ self.assertThat(mock_maaslog, MockCalledOnce())
1822+
1823+ def test_update_targets_conf_logs_error(self):
1824+ self.patch(boot_resources.service_monitor, "ensureService")
1825+ mock_try_send_rack_event = self.patch(
1826+ boot_resources, 'try_send_rack_event')
1827+ mock_maaslog = self.patch(boot_resources.maaslog, 'warning')
1828+ self.patch(boot_resources.os.path, 'exists').return_value = True
1829+ self.patch(boot_resources, 'call_and_check').side_effect = (
1830+ ExternalProcessError(
1831+ returncode=2, cmd=('tgt-admin',), output='error'))
1832+ snapshot = factory.make_name("snapshot")
1833+ boot_resources.update_targets_conf(snapshot)
1834+ self.assertThat(mock_try_send_rack_event, MockCalledOnce())
1835+ self.assertThat(mock_maaslog, MockCalledOnce())
1836+ self.assertThat(
1837+ boot_resources.call_and_check,
1838+ MockCalledOnceWith([
1839+ 'sudo', '-n', '/usr/sbin/tgt-admin',
1840+ '--conf', os.path.join(snapshot, 'maas.tgt'),
1841+ '--update', 'ALL'], env=os.environ))
1842+
1843+ def test_update_targets_only_runs_when_conf_exists(self):
1844+ # Regression test for LP:1655721
1845+ temp_dir = self.useFixture(TempDirectory()).path
1846+ self.useFixture(ClusterConfigurationFixture(tftp_root=temp_dir))
1847+ mock_ensureService = self.patch(
1848+ boot_resources.service_monitor, "ensureService")
1849+ mock_call_and_check = self.patch(boot_resources, "call_and_check")
1850+ mock_path_exists = self.patch(boot_resources.os.path, 'exists')
1851+ mock_path_exists.return_value = False
1852+ boot_resources.update_targets_conf(temp_dir)
1853+ self.assertThat(mock_ensureService, MockCalledOnceWith("tgt"))
1854+ self.assertThat(
1855+ mock_path_exists,
1856+ MockCalledOnceWith(os.path.join(temp_dir, 'maas.tgt')))
1857+ self.assertThat(mock_call_and_check, MockNotCalled())
1858+
1859+ def test_update_targets_conf_sets_env_var_in_snap(self):
1860+ # Regression test for LP:1718706
1861+ self.patch(boot_resources.service_monitor, "ensureService")
1862+ self.patch(boot_resources.os.path, 'exists').return_value = True
1863+ self.patch(boot_resources, 'call_and_check')
1864+ self.patch(boot_resources, 'running_in_snap').return_value = True
1865+ snap_data_path = factory.make_name('snap_data_path')
1866+ self.patch(boot_resources, 'get_snap_data_path').return_value = (
1867+ snap_data_path)
1868+ snapshot = factory.make_name("snapshot")
1869+ boot_resources.update_targets_conf(snapshot)
1870+ self.assertThat(
1871+ boot_resources.call_and_check,
1872+ MockCalledOnceWith([
1873+ 'sudo', '-n', '/usr/sbin/tgt-admin',
1874+ '--conf', os.path.join(snapshot, 'maas.tgt'),
1875+ '--update', 'ALL'
1876+ ], env={
1877+ 'TGT_IPC_SOCKET': os.path.join(
1878+ snap_data_path, 'tgtd-socket'),
1879+ **os.environ,
1880+ }))
1881+
1882+>>>>>>> src/provisioningserver/import_images/tests/test_boot_resources.py
1883
1884 class TestMetaContains(MAASTestCase):
1885 """Tests for the `meta_contains` function."""
1886diff --git a/src/provisioningserver/import_images/tests/test_download_resources.py b/src/provisioningserver/import_images/tests/test_download_resources.py
1887index 9f40e29..81bc3c1 100644
1888--- a/src/provisioningserver/import_images/tests/test_download_resources.py
1889+++ b/src/provisioningserver/import_images/tests/test_download_resources.py
1890@@ -359,6 +359,41 @@ class TestRepoWriter(MAASTestCase):
1891 label=product['label'], subarches={'ga-16.04', 'generic'},
1892 bootloader_type=None))
1893
1894+<<<<<<< src/provisioningserver/import_images/tests/test_download_resources.py
1895+ def test_inserts_no_generic_link_for_generic_non_ga_kflavor(self):
1896+ # Regression test for LP:1749246
1897+=======
1898+ def test_inserts_generic_link_for_generic_ga_kflavor(self):
1899+>>>>>>> src/provisioningserver/import_images/tests/test_download_resources.py
1900+ product_mapping = ProductMapping()
1901+ product = self.make_product(subarch='hwe-16.04', kflavor='generic')
1902+ product_mapping.add(product, 'hwe-16.04')
1903+ repo_writer = download_resources.RepoWriter(
1904+ None, None, product_mapping)
1905+ self.patch(
1906+ download_resources, 'products_exdata').return_value = product
1907+ # Prevent MAAS from trying to actually write the file.
1908+ mock_insert_file = self.patch(download_resources, 'insert_file')
1909+ mock_link_resources = self.patch(download_resources, 'link_resources')
1910+ # We only need to provide the product as the other fields are only used
1911+ # when writing the actual files to disk.
1912+ repo_writer.insert_item(product, None, None, None, None)
1913+ # None is used for the store and the content source as we're not
1914+ # writing anything to disk.
1915+ self.assertThat(
1916+ mock_insert_file,
1917+ MockCalledOnceWith(
1918+ None, os.path.basename(product['path']), product['sha256'],
1919+ {'sha256': product['sha256']}, product['size'], None))
1920+ # links are mocked out by the mock_insert_file above.
1921+ self.assertThat(
1922+ mock_link_resources,
1923+ MockCalledOnceWith(
1924+ snapshot_path=None, links=mock.ANY, osystem=product['os'],
1925+ arch=product['arch'], release=product['release'],
1926+ label=product['label'], subarches={'hwe-16.04'},
1927+ bootloader_type=None))
1928+
1929 def test_inserts_no_generic_link_for_generic_non_ga_kflavor(self):
1930 # Regression test for LP:1749246
1931 product_mapping = ProductMapping()
1932diff --git a/src/provisioningserver/utils/tests/test_network.py b/src/provisioningserver/utils/tests/test_network.py
1933index bda51e1..3b3c6b0 100644
1934--- a/src/provisioningserver/utils/tests/test_network.py
1935+++ b/src/provisioningserver/utils/tests/test_network.py
1936@@ -83,7 +83,10 @@ from provisioningserver.utils.network import (
1937 resolves_to_loopback_address,
1938 reverseResolve,
1939 )
1940+<<<<<<< src/provisioningserver/utils/tests/test_network.py
1941 from provisioningserver.utils.shell import get_env_with_locale
1942+=======
1943+>>>>>>> src/provisioningserver/utils/tests/test_network.py
1944 from testtools import ExpectedException
1945 from testtools.matchers import (
1946 Contains,
1947diff --git a/utilities/release-build b/utilities/release-build
1948index 3c0a5af..49f32cc 100755
1949--- a/utilities/release-build
1950+++ b/utilities/release-build
1951@@ -15,7 +15,7 @@ usage () {
1952 exit 1
1953 }
1954
1955-DEFAULT_DISTROS="$(ubuntu-distro-info --supported | grep -v trusty)"
1956+DEFAULT_DISTROS="$(ubuntu-distro-info --supported | grep -v trusty | grep -v bionic)"
1957 PACKAGE_DISTROS=""
1958 KEEP_CHANGELOG=0
1959
1960@@ -61,7 +61,7 @@ dch -a "" --release-heuristic log --nomultimaint
1961 PKG=$(head -n1 debian/changelog | awk '{print $1}')
1962 MAJOR_VER=$(head -n 1 debian/changelog | sed 's/^.*(//' | sed 's/).*//' | sed 's/-.*//')
1963 REV_COUNT=$(git rev-list --count $COMMIT)
1964-REV_SHORT=$(git rev-parse --short $COMMIT)
1965+REV_SHORT=$(git rev-parse --short=7 $COMMIT)
1966 FULL_VER="$MAJOR_VER-$REV_COUNT-g$REV_SHORT"
1967 TARBALL="maas_$FULL_VER.orig.tar.gz"
1968
1969@@ -89,3 +89,12 @@ if echo "$PACKAGE_DISTROS" | grep -q "bionic"; then
1970 sed -i "s/) UNRELEASED;/~18.04.1) bionic;/i" debian/changelog
1971 debuild -S -sa
1972 fi
1973+<<<<<<< utilities/release-build
1974+=======
1975+
1976+if echo "$PACKAGE_DISTROS" | grep -q "artful"; then
1977+ cp $ROOTDIR/debian/changelog debian/changelog
1978+ sed -i "s/) UNRELEASED;/~17.10.1) artful;/" debian/changelog
1979+ debuild -S
1980+fi
1981+>>>>>>> utilities/release-build

Subscribers

People subscribed via source and target branches