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
diff --git a/debian/changelog b/debian/changelog
index 35034de..725fbd6 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,4 @@
1<<<<<<< debian/changelog
1maas (2.4.0~beta2-0ubuntu1) UNRELEASED; urgency=medium2maas (2.4.0~beta2-0ubuntu1) UNRELEASED; urgency=medium
23
3 * UNRELEASED4 * UNRELEASED
@@ -36,6 +37,27 @@ maas (2.4.0~alpha1-6573-g12ee2331b-0ubuntu1) bionic; urgency=medium
36 -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 09 Feb 2018 18:50:10 -050037 -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 09 Feb 2018 18:50:10 -0500
3738
38maas (2.3.0-6434-gd354690-0ubuntu1) bionic; urgency=medium39maas (2.3.0-6434-gd354690-0ubuntu1) bionic; urgency=medium
40=======
41maas (2.3.2-0ubuntu1) UNRELEASED; urgency=medium
42
43 * UNRELEASED
44
45 -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 06 Apr 2018 09:56:53 -0400
46
47maas (2.3.2-6485-ge93e044-0ubuntu1) artful; urgency=medium
48
49 * New upstream release, MAAS 2.3.2
50
51 -- Andres Rodriguez <andreserl@ubuntu.com> Fri, 06 Apr 2018 09:55:14 -0400
52
53maas (2.3.1-6470-g036d646-0ubuntu1) artful; urgency=medium
54
55 * New upstream release, MAAS 2.3.1
56
57 -- Andres Rodriguez <andreserl@ubuntu.com> Mon, 05 Mar 2018 10:25:44 -0500
58
59maas (2.3.0-6434-gd354690-0ubuntu1) artful; urgency=medium
60>>>>>>> debian/changelog
3961
40 * New upstream release, MAAS 2.3.0:62 * New upstream release, MAAS 2.3.0:
41 - Add support for CentOS & Windows networking.63 - Add support for CentOS & Windows networking.
diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml
index 913fb0b..e277a0c 100644
--- a/snap/snapcraft.yaml
+++ b/snap/snapcraft.yaml
@@ -131,6 +131,15 @@ parts:
131 source: src/maasserver/static131 source: src/maasserver/static
132 organize:132 organize:
133 '*': usr/share/maas/web/static/133 '*': usr/share/maas/web/static/
134<<<<<<< snap/snapcraft.yaml
135=======
136 twisted-plugins:
137 plugin: dump
138 source: twisted/plugins
139 organize:
140 maasrackd.py: usr/lib/python3/dist-packages/twisted/plugins/maasrackd.py
141 maasregiond.py: usr/lib/python3/dist-packages/twisted/plugins/maasregiond.py
142>>>>>>> snap/snapcraft.yaml
134 snap:143 snap:
135 plugin: dump144 plugin: dump
136 source: snap145 source: snap
diff --git a/src/maasserver/bootsources.py b/src/maasserver/bootsources.py
index f71040f..1e11784 100644
--- a/src/maasserver/bootsources.py
+++ b/src/maasserver/bootsources.py
@@ -26,7 +26,10 @@ from maasserver.models import (
26 Config,26 Config,
27 Notification,27 Notification,
28)28)
29<<<<<<< src/maasserver/bootsources.py
29from maasserver.models.timestampedmodel import now30from maasserver.models.timestampedmodel import now
31=======
32>>>>>>> src/maasserver/bootsources.py
30from maasserver.utils import get_maas_user_agent33from maasserver.utils import get_maas_user_agent
31from maasserver.utils.orm import transactional34from maasserver.utils.orm import transactional
32from maasserver.utils.threads import deferToDatabase35from maasserver.utils.threads import deferToDatabase
diff --git a/src/maasserver/models/tests/test_userprofile.py b/src/maasserver/models/tests/test_userprofile.py
index 8a0efac..4bf4730 100644
--- a/src/maasserver/models/tests/test_userprofile.py
+++ b/src/maasserver/models/tests/test_userprofile.py
@@ -139,6 +139,7 @@ class UserProfileTest(MAASServerTestCase):
139 self.assertEqual(reload_object(node).owner, new_user)139 self.assertEqual(reload_object(node).owner, new_user)
140 self.assertEqual(reload_object(ipaddress).user, new_user)140 self.assertEqual(reload_object(ipaddress).user, new_user)
141 self.assertEqual(reload_object(iprange).user, new_user)141 self.assertEqual(reload_object(iprange).user, new_user)
142<<<<<<< src/maasserver/models/tests/test_userprofile.py
142143
143 def test_transfer_resources_missing_target_access(self):144 def test_transfer_resources_missing_target_access(self):
144 user = factory.make_User()145 user = factory.make_User()
@@ -153,6 +154,8 @@ class UserProfileTest(MAASServerTestCase):
153 " resource pool(s)")154 " resource pool(s)")
154 # owner didn't change155 # owner didn't change
155 self.assertEqual(reload_object(node).owner, user)156 self.assertEqual(reload_object(node).owner, user)
157=======
158>>>>>>> src/maasserver/models/tests/test_userprofile.py
156159
157 def test_manager_all_users(self):160 def test_manager_all_users(self):
158 users = set(factory.make_User() for _ in range(3))161 users = set(factory.make_User() for _ in range(3))
diff --git a/src/maasserver/rpc/boot.py b/src/maasserver/rpc/boot.py
index 5b5f1de..9a51bf3 100644
--- a/src/maasserver/rpc/boot.py
+++ b/src/maasserver/rpc/boot.py
@@ -280,10 +280,15 @@ def get_config(
280 osystem = configs['commissioning_osystem']280 osystem = configs['commissioning_osystem']
281 series = configs['commissioning_distro_series']281 series = configs['commissioning_distro_series']
282 else:282 else:
283<<<<<<< src/maasserver/rpc/boot.py
283 osystem = machine.get_osystem(284 osystem = machine.get_osystem(
284 default=configs['default_osystem'])285 default=configs['default_osystem'])
285 series = machine.get_distro_series(286 series = machine.get_distro_series(
286 default=configs['default_distro_series'])287 default=configs['default_distro_series'])
288=======
289 osystem = machine.get_osystem()
290 series = machine.get_distro_series()
291>>>>>>> src/maasserver/rpc/boot.py
287 # XXX: roaksoax LP: #1739761 - Since the switch to squashfs (and292 # XXX: roaksoax LP: #1739761 - Since the switch to squashfs (and
288 # drop of iscsi), precise is no longer deployable. To address a293 # drop of iscsi), precise is no longer deployable. To address a
289 # squashfs image is made available allowing it to be deployed in294 # squashfs image is made available allowing it to be deployed in
diff --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
index 90845d5..101cefd 100644
--- a/src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
+++ b/src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
@@ -1584,6 +1584,7 @@ describe("NodesListController", function() {
1584 it("sets showing_confirmation with testOptions",1584 it("sets showing_confirmation with testOptions",
1585 function() {1585 function() {
1586 var controller = makeController();1586 var controller = makeController();
1587<<<<<<< src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
1587 var object = makeObject("machines");1588 var object = makeObject("machines");
1588 object.status_code = 6;1589 object.status_code = 6;
1589 var spy = spyOn(1590 var spy = spyOn(
@@ -1598,6 +1599,22 @@ describe("NodesListController", function() {
1598 true);1599 true);
1599 expect($scope.tabs[1600 expect($scope.tabs[
1600 "machines"].actionProgress.affected_nodes).toBe(1);1601 "machines"].actionProgress.affected_nodes).toBe(1);
1602=======
1603 var object = makeObject("nodes");
1604 object.status_code = 6;
1605 var spy = spyOn(
1606 $scope.tabs.nodes.manager,
1607 "performAction").and.returnValue(
1608 $q.defer().promise);
1609 $scope.tabs.nodes.actionOption = { name: "test" };
1610 $scope.tabs.nodes.selectedItems = [object];
1611 $scope.actionGo("nodes");
1612 expect($scope.tabs[
1613 "nodes"].actionProgress.showing_confirmation).toBe(
1614 true);
1615 expect($scope.tabs[
1616 "nodes"].actionProgress.affected_nodes).toBe(1);
1617>>>>>>> src/maasserver/static/js/angular/controllers/tests/test_nodes_list.js
1601 expect(spy).not.toHaveBeenCalled();1618 expect(spy).not.toHaveBeenCalled();
1602 });1619 });
16031620
diff --git a/src/maasserver/static/partials/ipranges.html b/src/maasserver/static/partials/ipranges.html
index b159b50..a8cb02b 100755
--- a/src/maasserver/static/partials/ipranges.html
+++ b/src/maasserver/static/partials/ipranges.html
@@ -15,6 +15,7 @@
15 <tbody>15 <tbody>
16 <tr data-ng-repeat="iprange in (subnetIPRanges = ipranges | filterBySubnetOrVlan:subnet:vlan) | orderBy:ipRangeSort"16 <tr data-ng-repeat="iprange in (subnetIPRanges = ipranges | filterBySubnetOrVlan:subnet:vlan) | orderBy:ipRangeSort"
17 data-ng-class="{ 'is-active': isIPRangeInEditMode(iprange) || isIPRangeInDeleteMode(iprange)}">17 data-ng-class="{ 'is-active': isIPRangeInEditMode(iprange) || isIPRangeInDeleteMode(iprange)}">
18<<<<<<< src/maasserver/static/partials/ipranges.html
18 <td class="col-2" aria-label="Start IP Address">{$ iprange.start_ip $}</td>19 <td class="col-2" aria-label="Start IP Address">{$ iprange.start_ip $}</td>
19 <td class="col-2" aria-label="End IP Address">{$ iprange.end_ip $}</td>20 <td class="col-2" aria-label="End IP Address">{$ iprange.end_ip $}</td>
20 <td class="col-1" aria-label="Owner">{$ iprange.type == "dynamic" ? "MAAS" : iprange.user $}</td>21 <td class="col-1" aria-label="Owner">{$ iprange.type == "dynamic" ? "MAAS" : iprange.user $}</td>
@@ -30,6 +31,22 @@
30 data-ng-click="toggleMenu(); ipRangeEnterDeleteMode(iprange)">31 data-ng-click="toggleMenu(); ipRangeEnterDeleteMode(iprange)">
31 <i class="p-icon--delete">Remove</i>32 <i class="p-icon--delete">Remove</i>
32 </button>33 </button>
34=======
35 <div class="table__data table-col--20" aria-label="Start IP Address">{$ iprange.start_ip $}</div>
36 <div class="table__data table-col--20" aria-label="End IP Address">{$ iprange.end_ip $}</div>
37 <div class="table__data table-col--10" aria-label="Owner">{$ iprange.type == "dynamic" ? "MAAS" : iprange.user $}</div>
38 <div class="table__data table-col--10" aria-label="Type">{$ iprange.type == "dynamic" ? "Dynamic" : "Reserved" $}</div>
39 <div class="table__data table-col--31" aria-label="Comment">{$ iprange.type == "dynamic" ? "Dynamic" : iprange.comment $}</div>
40 <div class="table__data table-col--9 table--mobile-controls">
41 <div class="table__controls" toggle-ctrl data-ng-if="ipRangeCanBeModified(iprange)">
42 <button class="table__controls-toggle" data-ng-click="toggleMenu()">View actions</button>
43 <div class="table__controls-menu" role="menu" data-ng-show="isToggled">
44 <button class="table__controls-action" aria-label="Edit row"
45 data-ng-click="toggleMenu(); ipRangeToggleEditMode(iprange)">Edit reserved range</button>
46 <button class="table__controls-action u-text--error" aria-label="Remove"
47 data-ng-click="toggleMenu(); ipRangeEnterDeleteMode(iprange)">Remove range</button>
48 </div>
49>>>>>>> src/maasserver/static/partials/ipranges.html
33 </div>50 </div>
34 </td>51 </td>
35 <td class="is-active p-table-expanding__panel col-12" col-span="6" data-ng-if="isIPRangeInDeleteMode(iprange)">52 <td class="is-active p-table-expanding__panel col-12" col-span="6" data-ng-if="isIPRangeInDeleteMode(iprange)">
diff --git a/src/maasserver/static/partials/node-details.html b/src/maasserver/static/partials/node-details.html
index ed62f6f..c52716d 100755
--- a/src/maasserver/static/partials/node-details.html
+++ b/src/maasserver/static/partials/node-details.html
@@ -98,6 +98,7 @@
98 </ul>98 </ul>
99 </div>99 </div>
100100
101<<<<<<< src/maasserver/static/partials/node-details.html
101 <div class="col-12 u-no-margin--left" data-ng-show="action.option.name === 'deploy' || action.option.name === 'release'">102 <div class="col-12 u-no-margin--left" data-ng-show="action.option.name === 'deploy' || action.option.name === 'release'">
102 <div class="ng-hide" data-ng-show="action.option.name === 'deploy'">103 <div class="ng-hide" data-ng-show="action.option.name === 'deploy'">
103 <div data-maas-os-select="osinfo" data-ng-model="osSelection"></div>104 <div data-maas-os-select="osinfo" data-ng-model="osSelection"></div>
@@ -235,6 +236,139 @@
235 </div>236 </div>
236 </div>237 </div>
237 </div>238 </div>
239=======
240 <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
241 <div class="page-header__dropdown" data-ng-class="{ 'is-open': action.option }">
242 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-if="!isDevice && !node.dhcp_on">
243 <p class="page-header__message page-header__message--warning">MAAS is not providing DHCP.</p>
244 </div>
245
246 <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
247 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError() || isDeployError() || isSSHKeyError() || hasActionPowerError(action.option.name)">
248 <form class="form form--inline u-display--inline">
249 <fieldset class="form__fieldset ng-hide" data-ng-show="action.option.name === 'commission' || action.option.name === 'test'">
250 <div class="form__group">
251 <input class="checkbox" id="enableSSH" type="checkbox"
252 data-ng-model="commissionOptions.enableSSH">
253 <label for="enableSSH">Allow SSH access and prevent machine from powering off</label>
254 </div>
255 </fieldset>
256 <fieldset class="form__fieldset ng-hide" data-ng-show="action.option.name === 'commission'">
257 <div class="form__group">
258 <input class="checkbox" id="skipNetworking" type="checkbox"
259 data-ng-model="commissionOptions.skipNetworking">
260 <label for="skipNetworking">Retain network configuration</label>
261 </div>
262 <div class="form__group">
263 <input class="checkbox" id="skipStorage" type="checkbox"
264 data-ng-model="commissionOptions.skipStorage">
265 <label for="skipStorage">Retain storage configuration</label>
266 </div>
267 </fieldset>
268 <fieldset class="form__fieldset ng-hide" data-ng-show="action.option.name === 'deploy'">
269 <div class="form__group">
270 <label class="form__group-label">Choose your image</label>
271 <div class="form__group-input" data-maas-os-select="osinfo" data-ng-model="osSelection"></div>
272 </div>
273 </fieldset>
274 <fieldset class="form__fieldset" data-ng-if="action.option.name === 'release'">
275 <div class="form__group">
276 <div data-maas-release-options="releaseOptions"></div>
277 </div>
278 </fieldset>
279 <div class="page-header__controls" data-ng-if="action.option.name !== 'commission' && action.option.name !== 'test'">
280 <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
281 <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')">
282 <span data-ng-if="action.option.name === 'acquire'">Acquire {$ type_name $}</span>
283 <span data-ng-if="action.option.name === 'deploy'">Deploy {$ type_name $}</span>
284 <span data-ng-if="action.option.name === 'release'">Release {$ type_name $}</span>
285 <span data-ng-if="action.option.name === 'set-zone'">Set zone for {$ type_name $}</span>
286 <span data-ng-if="action.option.name === 'on'">Power on {$ type_name $}</span>
287 <span data-ng-if="action.option.name === 'off'">Power off {$ type_name $}</span>
288 <span data-ng-if="action.option.name === 'abort'">Abort action on {$ type_name $}</span>
289 <span data-ng-if="action.option.name === 'rescue-mode'">Rescue {$ type_name $}</span>
290 <span data-ng-if="action.option.name === 'exit-rescue-mode'">Exit rescue mode</span>
291 <span data-ng-if="action.option.name === 'mark-broken'">Mark {$ type_name $}</span>
292 <span data-ng-if="action.option.name === 'mark-fixed'">Mark {$ type_name $}</span>
293 <span data-ng-if="action.option.name === 'override-failed-testing'">Override failed testing</span>
294 <span data-ng-if="action.option.name === 'delete'">Delete {$ type_name $}</span>
295 <span data-ng-if="action.option.name === 'import-images'">Import images</span>
296 </button>
297 </div>
298 </form>
299 </div>
300 <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()">
301 <form class="form form--stack">
302 <fieldset class="form__fieldset eight-col u-margin--bottom-small">
303 <div class="form__group">
304 <label for="commissioning-scripts">Additional commissioning scripts</label>
305 <span id="commissioning-scripts" data-maas-script-select="script" data-script-type="0" data-ng-model="commissioningSelection" class="tags--inline"></span>
306 </div>
307 </fieldset>
308 </form>
309 </div>
310 <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')">
311 <form class="form form--stack">
312 <fieldset class="form__fieldset eight-col u-margin--bottom-small">
313 <div class="form__group">
314 <label>Hardware tests</label>
315 <span id="testing-scripts" data-maas-script-select="script" data-script-type="2" data-ng-model="testSelection" class="tags--inline"></span>
316 </div>
317 </fieldset>
318 </form>
319 </div>
320 <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'">
321 <form class="form form--inline">
322 <div class="page-header__controls">
323 <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
324 <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')">
325 <span data-ng-if="action.option.name === 'commission'">Commission {$ type_name $}</span>
326 <span data-ng-if="action.option.name === 'test'">Test {$ type_name $}</span>
327 </button>
328 </div>
329 </form>
330 </div>
331 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="action.showing_confirmation && action.option.name === 'test'">
332 <p class="page-header__message page-header__message--warning">
333 Node is currently deployed. Are you sure you want to continue to test hardware?
334 </p>
335 <div class="page-header__controls">
336 <button class="button--base button--inline" data-ng-click="actionCancel()">No</button>
337 <button class="button--secondary button--inline" data-ng-click="actionGo()">Yes</button>
338 </div>
339 </div>
340 <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
341 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError()">
342 <p class="page-header__message page-header__message--error">
343 Node failed to be {$ action.option.sentence $}, because of the following error: {$ action.error $}
344 </p>
345 <div class="page-header__controls">
346 <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
347 <button class="button--secondary button--inline" data-ng-click="actionGo()">Retry</button>
348 </div>
349 </div>
350
351 <!-- XXX blake_r 2015-02-19 - Need to add e2e test. -->
352 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isDeployError()">
353 <p class="page-header__message page-header__message--error">
354 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>.
355 </p>
356 <div class="page-header__controls">
357 <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
358 </div>
359 </div>
360
361 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionPowerError(action.option.name)">
362 <p class="page-header__message page-header__message--error">
363 Node cannot be {$ action.option.sentence $}, because power control software for the
364 node is missing from its rack controller. To proceed, install the
365 {$ getPowerErrors() $} on the rack controller.
366 </p>
367 <div class="page-header__controls">
368 <button class="button--base button--inline" data-ng-click="actionCancel()">Cancel</button>
369 </div>
370 </div>
371>>>>>>> src/maasserver/static/partials/node-details.html
238372
239 <nav class="p-tabs">373 <nav class="p-tabs">
240 <ul class="p-tabs__list" role="tablist" data-ng-class="{ 'u-hide': action.option }">374 <ul class="p-tabs__list" role="tablist" data-ng-class="{ 'u-hide': action.option }">
diff --git a/src/maasserver/static/partials/nodes-list.html b/src/maasserver/static/partials/nodes-list.html
index d49f11c..e0ff5be 100644
--- a/src/maasserver/static/partials/nodes-list.html
+++ b/src/maasserver/static/partials/nodes-list.html
@@ -1,3 +1,4 @@
1<<<<<<< src/maasserver/static/partials/nodes-list.html
1<header class="p-strip--light is-shallow is-bordered page-header" media-query="min-width: 769px">2<header class="p-strip--light is-shallow is-bordered page-header" media-query="min-width: 769px">
2 <div class="row">3 <div class="row">
3 <div class="col-8">4 <div class="col-8">
@@ -1072,3 +1073,1019 @@ sudo maas-rack register --url {$ tabs.controllers.registerUrl $} --secret {$ tab
1072 on-check-all="toggleCheckAll('switches')" on-check="toggleChecked($switch_, 'switches')"></maas-switches-table>1073 on-check-all="toggleCheckAll('switches')" on-check="toggleChecked($switch_, 'switches')"></maas-switches-table>
1073 </div>1074 </div>
1074</div>1075</div>
1076=======
1077<header class="page-header u-margin--bottom-none" sticky media-query="min-width: 769px">
1078 <div class="wrapper--inner">
1079 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1080 <h1 class="page-header__title">Nodes</h1>
1081
1082 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1083 <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>
1084
1085 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1086 <div class="page-header__controls u-float--right ng-hide" data-ng-show="currentpage === 'nodes'">
1087 <div data-ng-hide="tabs.nodes.selectedItems.length">
1088 <div data-maas-cta="addHardwareOptions"
1089 data-ng-model="addHardwareOption"
1090 data-ng-change="addHardwareOptionChanged()" data-default-title="Add hardware">
1091 </div>
1092 </div>
1093 <div class="ng-hide" data-ng-show="tabs.nodes.selectedItems.length">
1094 <a class="u-display--inline u-margin--right" data-ng-click="showSelected('nodes')">{$ tabs.nodes.selectedItems.length $} Selected</a>
1095 <div data-maas-cta="tabs.nodes.takeActionOptions"
1096 data-ng-model="tabs.nodes.actionOption"
1097 data-ng-change="actionOptionSelected('nodes')">
1098 </div>
1099 </div>
1100 </div>
1101
1102 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1103 <div class="page-header__controls u-float--rightng-hide" data-ng-show="currentpage === 'devices'">
1104 <div data-ng-hide="tabs.devices.selectedItems.length">
1105 <button class="button--secondary button--inline"
1106 data-ng-click="addDevice()"
1107 data-ng-hide="addDeviceScope.viewable">Add device</button>
1108 <button class="button--secondary button--inline ng-hide"
1109 data-ng-click="cancelAddDevice()"
1110 data-ng-show="addDeviceScope.viewable">Cancel add device</button>
1111 </div>
1112 <div data-ng-show="tabs.devices.selectedItems.length">
1113 <a class="u-display--inline u-margin--right" data-ng-click="showSelected('devices')">{$ tabs.devices.selectedItems.length $} Selected</a>
1114 <div data-maas-cta="tabs.devices.takeActionOptions"
1115 data-ng-model="tabs.devices.actionOption"
1116 data-ng-change="actionOptionSelected('devices')">
1117 </div>
1118 </div>
1119 </div>
1120
1121 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1122 <div class="page-header__controls u-float--rightng-hide" data-ng-show="currentpage === 'controllers'">
1123 <div data-ng-if="!tabs.controllers.selectedItems.length">
1124 <button class="button--secondary button--inline"
1125 data-ng-click="tabs.controllers.addController = true"
1126 data-ng-hide="tabs.controllers.addController">Add rack controller</button>
1127 <button class="button--secondary button--inline ng-hide"
1128 data-ng-click="tabs.controllers.addController = false"
1129 data-ng-show="tabs.controllers.addController">Close add rack controller</button>
1130 </div>
1131 <div data-ng-show="tabs.controllers.selectedItems.length">
1132 <a class="u-display--inline u-margin--right" data-ng-click="showSelected('controllers')">{$ tabs.controllers.selectedItems.length $} Selected</a>
1133 <div data-maas-cta="tabs.controllers.takeActionOptions"
1134 data-ng-model="tabs.controllers.actionOption"
1135 data-ng-change="actionOptionSelected('controllers')">
1136 </div>
1137 </div>
1138 </div>
1139
1140 <div class="page-header__controls u-float--right ng-hide" data-ng-show="currentpage === 'switches'">
1141 <div class="ng-hide" data-ng-show="tabs.switches.selectedItems.length">
1142 <a class="u-display--inline u-margin--right" data-ng-click="showSelected('switches')">{$ tabs.switches.selectedItems.length $} Selected</a>
1143 <div data-maas-cta="tabs.switches.takeActionOptions"
1144 data-ng-model="tabs.switches.actionOption"
1145 data-ng-change="actionOptionSelected('switches')">
1146 </div>
1147 </div>
1148 </div>
1149
1150 <div data-ng-repeat="tab in ['nodes', 'switches']" data-ng-show="currentpage == tab">
1151 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1152 <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs[tab].actionOption }">
1153 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1154 <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="isActionError(tab) || hasActionsInProgress(tab)">
1155 <form class="form form--inline u-display--inline">
1156 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'commission' || tabs[tab].actionOption.name === 'test'">
1157 <div class="form__group">
1158 <input class="form__group-label" id="{$ tab $}-enableSSH" type="checkbox"
1159 data-ng-model="tabs[tab].commissionOptions.enableSSH">
1160 <label class="checkbox-label" for="{$ tab $}-enableSSH">Allow SSH access and prevent machine from powering off</label>
1161 </div>
1162 </fieldset>
1163 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'commission'">
1164 <div class="form__group">
1165 <input class="form__group-label" id="{$ tab $}-skipNetworking" type="checkbox"
1166 data-ng-model="tabs[tab].commissionOptions.skipNetworking">
1167 <label class="checkbox-label" for="{$ tab $}-skipNetworking">Retain network configuration</label>
1168 </div>
1169 <div class="form__group">
1170 <input class="form__group-label" id="{$ tab $}-skipStorage" type="checkbox"
1171 data-ng-model="tabs[tab].commissionOptions.skipStorage">
1172 <label class="checkbox-label" for="{$ tab $}-skipStorage">Retain storage configuration</label>
1173 </div>
1174 </fieldset>
1175 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'deploy'">
1176 <div class="form__group">
1177 <label for="image" class="form__group-label">Choose your image</label>
1178 <div class="form__group-input" data-maas-os-select="osinfo" data-ng-model="tabs[tab].osSelection"></div>
1179 </div>
1180 </fieldset>
1181 <fieldset class="form__fieldset" data-ng-if="tabs[tab].actionOption.name === 'release'">
1182 <div class="form__group">
1183 <div data-maas-release-options="tabs[tab].releaseOptions"></div>
1184 </div>
1185 </fieldset>
1186 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs[tab].actionOption.name === 'set-zone'">
1187 <div class="form__group">
1188 <label for="{$ tab $}-zone3" class="form__group-label">Select Zone</label>
1189 <div class="form__group-input">
1190 <select name="zone" id="{$ tab $}-zone3"
1191 data-ng-model="tabs[tab].zoneSelection"
1192 data-ng-options="zone as zone.name for zone in zones">
1193 <option value="" disabled="disabled">Choose a zone</option>
1194 </select>
1195 </div>
1196 </div>
1197 </fieldset>
1198 </form>
1199 <div class="page-header__controls" data-ng-if="tabs[tab].actionOption.name !== 'commission' && tabs[tab].actionOption.name !== 'test'">
1200 <button class="button--base button--inline" data-ng-click="actionCancel(tab)">Cancel</button>
1201 <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)">
1202 <span data-ng-if="tabs[tab].actionOption.name === 'acquire'">Acquire {$ tabs[tab].selectedItems.length $}
1203 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1204 </span>
1205 <span data-ng-if="tabs[tab].actionOption.name === 'deploy'">Deploy {$ tabs[tab].selectedItems.length $}
1206 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1207 </span>
1208 <span data-ng-if="tabs[tab].actionOption.name === 'release'">Release {$ tabs[tab].selectedItems.length $}
1209 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1210 </span>
1211 <span data-ng-if="tabs[tab].actionOption.name === 'set-zone'">Set zone for {$ tabs[tab].selectedItems.length $}
1212 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1213 </span>
1214 <span data-ng-if="tabs[tab].actionOption.name === 'on'">Power on {$ tabs[tab].selectedItems.length $}
1215 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1216 </span>
1217 <span data-ng-if="tabs[tab].actionOption.name === 'off'">Power off {$ tabs[tab].selectedItems.length $}
1218 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1219 </span>
1220 <span data-ng-if="tabs[tab].actionOption.name === 'abort'">Abort action for {$ tabs[tab].selectedItems.length $}
1221 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1222 </span>
1223 <span data-ng-if="tabs[tab].actionOption.name === 'rescue-mode'">Set rescue mode for {$ tabs[tab].selectedItems.length $}
1224 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1225 </span>
1226 <span data-ng-if="tabs[tab].actionOption.name === 'exit-rescue-mode'">Exit rescue mode for {$ tabs[tab].selectedItems.length $}
1227 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1228 </span>
1229 <span data-ng-if="tabs[tab].actionOption.name === 'mark-broken'">Mark {$ tabs[tab].selectedItems.length $}
1230 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span> as broken
1231 </span>
1232 <span data-ng-if="tabs[tab].actionOption.name === 'mark-fixed'">Mark {$ tabs[tab].selectedItems.length $}
1233 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span> as fixed
1234 </span>
1235 <span data-ng-if="tabs[tab].actionOption.name === 'override-failed-testing'">Override failed testing on {$ tabs[tab].selectedItems.length $}
1236 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1237 </span>
1238 <span data-ng-if="tabs[tab].actionOption.name === 'delete'">Delete {$ tabs[tab].selectedItems.length $}
1239 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1240 </span>
1241 </button>
1242 <button class="button--secondary button--inline" data-ng-click="actionGo(tab)" data-ng-show="hasActionsFailed(tab)">Retry</button>
1243 </div>
1244 </section>
1245 <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()">
1246 <form class="form form--stack">
1247 <fieldset class="form__fieldset eight-col u-margin--bottom-small">
1248 <div class="form__group">
1249 <label for="{$ tab $}-commissiong-scripts">Additional commissioning Scripts</label>
1250 <span id="{$ tab $}-commissioning-scripts" data-maas-script-select="script" data-script-type="0" data-ng-model="tabs[tab].commissioningSelection" class="tags--inline"></span>
1251 </div>
1252 </fieldset>
1253 </form>
1254 </section>
1255 <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'">
1256 <form class="form form--stack">
1257 <fieldset class="form__fieldset eight-col u-margin--bottom-small">
1258 <div class="form__group">
1259 <label for="{$ tab $}-testing-scripts">Hardware Tests</label>
1260 <span id="{$ tab $}-testing-scripts" data-maas-script-select="script" data-script-type="2" data-ng-model="tabs[tab].testSelection" class="tags--inline"></span>
1261 </div>
1262 </fieldset>
1263 </form>
1264 </section>
1265 <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'">
1266 <div class="page-header__controls">
1267 <button class="button--base button--inline" data-ng-click="actionCancel(tab)">Cancel</button>
1268 <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)">
1269 <span data-ng-if="tabs[tab].actionOption.name === 'commission'">Commission {$ tabs[tab].selectedItems.length $}
1270 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1271 </span>
1272 <span data-ng-if="tabs[tab].actionOption.name === 'test'">Test {$ tabs[tab].selectedItems.length $}
1273 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'machine', 'other': 'machines'}"></span>
1274 </span>
1275 </button>
1276 <button class="button--secondary button--inline" data-ng-click="actionGo(tab)" data-ng-show="hasActionsFailed(tab)">Retry</button>
1277 </div>
1278 </section>
1279
1280 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1281 <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError(tab)">
1282 <p data-ng-hide="isDeployError(tab) || isSSHKeyError(tab)"
1283 class="page-header__message page-header__message--error ng-hide">
1284 {$ tabs[tab].actionErrorCount $}
1285 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
1286 cannot be {$ tabs[tab].actionOption.sentence $}. To proceed, update your selection.
1287 </p>
1288 <p class="page-header__message page-header__message--error ng-hide" data-ng-show="isDeployError(tab)">
1289 {$ tabs[tab].selectedItems.length $}
1290 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
1291 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>.
1292 </p>
1293 <p class="page-header__message page-header__message--error ng-hide" data-ng-show="isSSHKeyError(tab)">
1294 {$ tabs[tab].selectedItems.length $}
1295 <span data-ng-pluralize count="tabs[tab].selectedItems.length" when="{'one': 'node', 'other': 'nodes'}"></span>
1296 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>.
1297 </p>
1298 </section>
1299 <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="tabs[tab].actionProgress.showing_confirmation">
1300 <p class="page-header__message page-header__message--warning">
1301 {$ 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?
1302 </p>
1303 <div class="page-header__controls">
1304 <button class="button--base button--inline" data-ng-click="actionCancel(tab)">No</button>
1305 <button class="button--secondary button--inline" data-ng-click="actionGo(tab)">Yes</button>
1306 </div>
1307 </section>
1308 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1309 <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionsInProgress(tab) || hasActionsFailed(tab)">
1310 <p class="page-header__message" data-ng-show="hasActionsInProgress(tab)">
1311 <i class="icon icon--loading u-animation--spin"></i>
1312 {$ tabs[tab].actionProgress.completed $} of {$ tabs[tab].actionProgress.total $}
1313 nodes are transitioning to {$ tabs[tab].actionOption.sentence $}.
1314 </p>
1315 <p class="page-header__message page-header__message--error"
1316 data-ng-repeat="(error, nodes) in tabs[tab].actionProgress.errors">
1317 The {$ tabs[tab].actionOption.title.toLowerCase() $} action for {$ nodes.length $}
1318 <span data-ng-pluralize count="nodes.length" when="{'one': 'node', 'other': 'nodes'}"></span>
1319 failed with error: {$ error $}
1320 </p>
1321 </section>
1322 </div>
1323
1324 </div>
1325
1326 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1327 <div class="page-header__dropdown" data-ng-class="{ 'is-open': addHardwareScope.viewable }" data-ng-controller="AddHardwareController">
1328 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1329 <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="showMachine()">
1330 <h3 class="page-header__dropdown-title">Add machine</h3>
1331 <form class="form form--stack">
1332 <fieldset class="form__fieldset six-col">
1333 <div class="form__group">
1334 <label for="machine-name" class="form__label two-col">Machine name</label>
1335 <div class="form__group-input three-col">
1336 <input type="text" id="machine-name" placeholder="Choose a machine name (optional)"
1337 data-ng-model="machine.name">
1338 </div>
1339 </div>
1340 <div class="form__group">
1341 <label for="domain" class="form__group-label two-col">Domain</label>
1342 <div class="form__group-input three-col">
1343 <select name="domain" id="domain"
1344 data-ng-model="machine.domain"
1345 data-ng-options="domain as domain.name for domain in domains">
1346 <option value="" disabled>Choose a domain</option>
1347 </select>
1348 </div>
1349 </div>
1350 <div class="form__group">
1351 <label for="architecture" class="form__group-label two-col">Architecture</label>
1352 <div class="form__group-input three-col">
1353 <select name="architecture" id="architecture"
1354 data-ng-model="machine.architecture"
1355 data-ng-options="arch for arch in architectures">
1356 <option value="" disabled>Choose an architecture</option>
1357 </select>
1358 </div>
1359 </div>
1360 <div class="form__group">
1361 <label for="min_hwe_kernel" class="form__group-label two-col">Minimum Kernel</label>
1362 <div class="form__group-input three-col">
1363 <select name="min_hwe_kernel" id="min_hwe_kernel"
1364 data-ng-model="machine.min_hwe_kernel"
1365 data-ng-options="hwe_kernel[0] as hwe_kernel[1] for hwe_kernel in hwe_kernels">
1366 <option value="">No minimum kernel</option>
1367 </select>
1368 </div>
1369 </div>
1370 <div class="form__group">
1371 <label for="zone4" class="form__group-label two-col">Zone</label>
1372 <select name="zone" id="zone4" class="three-col"
1373 data-ng-model="machine.zone"
1374 data-ng-options="zone as zone.name for zone in zones">
1375 </select>
1376 </div>
1377 <div class="form__group" data-ng-repeat="mac in machine.macs">
1378 <label for="mac-address" class="form__group-label two-col"><span data-ng-hide="mac !== machine.macs[0]">MAC Address</span>&nbsp;</label>
1379 <div class="form__group-input three-col">
1380 <input type="text" id="mac-address" placeholder="00:00:00:00:00:00"
1381 maxlength="17"
1382 data-ng-class="{ 'has-error': mac.error }"
1383 data-ng-model="mac.mac"
1384 data-ng-pattern="macAddressRegex"
1385 data-ng-change="validateMac(mac)"
1386 mac-address>
1387 <span class="form__group-remove" title="Delete MAC"
1388 data-ng-hide="mac === machine.macs[0]" data-ng-click="removeMac(mac)"></span>
1389 </div>
1390 </div>
1391 <div class="form__group">
1392 <div class="five-col">
1393 <button class="button--secondary button--inline u-float--right" data-ng-click="addMac()">+ Add MAC Address</a>
1394 </div>
1395 </div>
1396 </fieldset>
1397 <fieldset class="form__fieldset six-col last-col"
1398 data-maas-power-parameters="power_types"
1399 data-ng-model="machine.power">
1400 </fieldset>
1401 </form>
1402 </section>
1403 <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="showChassis()">
1404 <h3 class="page-header__dropdown-title">Add chassis</h3>
1405 <form action="post" class="form form--stack">
1406 <fieldset class="form__fieldset six-col"
1407 data-maas-power-parameters="chassisPowerTypes"
1408 data-ng-model="chassis.power">
1409 </fieldset>
1410 <fieldset class="form__fieldset six-col last-col">
1411 <div class="form__group">
1412 <label for="domain2" class="form__group-label two-col">Domain</label>
1413 <div class="form__group-input three-col">
1414 <select name="domain" id="domain2"
1415 data-ng-model="chassis.domain"
1416 data-ng-options="domain as domain.name for domain in domains">
1417 </select>
1418 </div>
1419 </div>
1420 </fieldset>
1421 </form>
1422 </section>
1423
1424 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1425 <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-show="showMachine()">
1426 <p class="page-header__message page-header__message--error ng-hide" data-ng-show="error">{$ error $}</p>
1427 <div class="page-header__controls">
1428 <button class="button--base button--inline" data-ng-click="cancel()">Cancel</button>
1429 <button class="button--secondary button--inline"
1430 data-ng-disabled="machineHasError()"
1431 data-ng-click="saveMachine(true)">Save and add another</button>
1432 <button class="button--positive button--inline"
1433 data-ng-disabled="machineHasError()"
1434 data-ng-click="saveMachine(false)">Save machine</button>
1435 </div>
1436 </section>
1437
1438 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1439 <section class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="showChassis()">
1440 <p class="page-header__message page-header__message--error ng-hide" data-ng-show="error">{$ error $}</p>
1441 <div class="page-header__controls">
1442 <button class="button--base button--inline" data-ng-click="cancel()">Cancel</button>
1443 <button class="button--secondary button--inline"
1444 data-ng-disabled="chassisHasErrors()"
1445 data-ng-click="saveChassis(true)">Save and add another</button>
1446 <button class="button--positive button--inline"
1447 data-ng-disabled="chassisHasErrors()"
1448 data-ng-click="saveChassis(false)">Save chassis</button>
1449 </div>
1450 </section>
1451
1452 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1453 <section class="page-header__section twelve-col u-margin--bottom-none" data-ng-hide="architectures.length">
1454 <p class="page-header__message page-header__message--error">
1455 Cannot add {$ mode $} until boot images have been imported. To fix, visit the <a href="#/images">images page</a>.
1456 </p>
1457 </section>
1458 </div>
1459
1460 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1461 <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs.devices.actionOption }">
1462 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1463 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError('devices') || hasActionsInProgress('devices')">
1464 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1465 <form class="form form--inline u-display--inline">
1466 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs.devices.actionOption.name === 'set-zone'">
1467 <div class="form__group">
1468 <label class="form__group-label" for="zone1">Set zone</label>
1469 <select name="zone" id="zone1"
1470 data-ng-model="tabs.devices.zoneSelection"
1471 data-ng-options="zone as zone.name for zone in zones">
1472 <option value="" disabled="disabled">Choose a zone</option>
1473 </select>
1474 </div>
1475 </fieldset>
1476 <div class="page-header__controls">
1477 <button class="button--base button--inline" data-ng-click="actionCancel('devices')">Cancel</button>
1478 <button class="button--positive button--inline" data-ng-click="actionGo('devices')">
1479 <span data-ng-if="tabs.devices.actionOption.name === 'set-zone'">Set zone for {$ tabs.devices.selectedItems.length $}
1480 <span data-ng-pluralize count="tabs.devices.selectedItems.length" when="{'one': 'device', 'other': 'devices'}"></span>
1481 </span>
1482 <span data-ng-if="tabs.devices.actionOption.name === 'delete'">Delete {$ tabs.devices.selectedItems.length $}
1483 <span data-ng-pluralize count="tabs.devices.selectedItems.length" when="{'one': 'device', 'other': 'devices'}"></span>
1484 </span>
1485 </button>
1486 </div>
1487 </form>
1488 </div>
1489
1490 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1491 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError('devices')">
1492 <p class="page-header__message page-header__message--error">
1493 {$ tabs.devices.actionErrorCount $}
1494 <span data-ng-pluralize count="tabs.devices.selectedItems.length" when="{'one': 'device', 'other': 'devices'}"></span>
1495 cannot be {$ tabs.devices.actionOption.sentence $}. To proceed, update your selection.
1496 </p>
1497 </div>
1498
1499 <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
1500 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionsInProgress('devices') || hasActionsFailed('devices')">
1501 <p class="page-header__message" data-ng-show="hasActionsInProgress('devices')">
1502 <i class="icon icon--loading u-animation--spin"></i>
1503 {$ tabs.devices.actionProgress.completed $} of {$ tabs.devices.actionProgress.total $}
1504 devices have been {$ tabs.devices.actionOption.sentence $}.
1505 </p>
1506 <p class="page-header__message page-header__message--error"
1507 data-ng-repeat="(error, devices) in tabs.devices.actionProgress.errors">
1508 The {$ tabs.devices.actionOption.title.toLowerCase() $} action for {$ devices.length $}
1509 <span data-ng-pluralize count="devices.length" when="{'one': 'device', 'other': 'devices'}"></span>
1510 failed with error: {$ error $}
1511 </p>
1512 </div>
1513 </div>
1514
1515 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1516 <div class="page-header__dropdown" data-ng-class="{ 'is-open': addDeviceScope.viewable }" data-ng-controller="AddDeviceController">
1517 <section class="page-header__section twelve-col u-margin--bottom-none">
1518 <h3 class="page-header__dropdown-title">Add device</h3>
1519 <form class="form form--stack">
1520 <fieldset class="form__fieldset six-col">
1521 <div class="form__group">
1522 <label for="device-name" class="form__group-label two-col">Name</label>
1523 <div class="form__group-input three-col">
1524 <input type="text" id="device-name" placeholder="Name your device"
1525 data-ng-model="device.name"
1526 data-ng-class="{ 'has-error': nameHasError() }">
1527 </div>
1528
1529 </div>
1530 <div class="form__group">
1531 <label for="domain3" class="form__group-label two-col">Domain</label>
1532 <div class="form__group-input three-col">
1533 <select name="domain" id="domain3"
1534 data-ng-model="device.domain"
1535 data-ng-options="domain as domain.name for domain in domains">
1536 </select>
1537 </div>
1538 </div>
1539 </fieldset>
1540 </form>
1541 <table>
1542 <thead>
1543 <tr>
1544 <th class="table-col--20">MAC address</th>
1545 <th class="table-col--20">IP assignment</th>
1546 <th class="table-col--20">Subnet</th>
1547 <th class="table-col--35">IP address</th>
1548 <th class="table-col--5"></th>
1549 </tr>
1550 </thead>
1551 <tbody vs-repeat vs-scroll-parent="window">
1552 <tr data-ng-repeat="interface in device.interfaces">
1553 <td class="table-col--20" aria-label="MAC address">
1554 <input type="text" id="mac-address1" placeholder="00:00:00:00:00:00"
1555 data-ng-model="interface.mac"
1556 data-ng-class="{ 'has-error': macHasError(interface) }">
1557 </td>
1558 <td class="table-col--20" aria-label="IP assignment">
1559 <select name="ip-assignment" id="ip-assignment"
1560 data-ng-model="interface.ipAssignment"
1561 data-ng-options="assigment.title for assigment in ipAssignments">
1562 <option value="" disabled selected>Select IP assignment</option>
1563 </select>
1564 </td>
1565 <td class="table-col--20" aria-label="Subnet">
1566 <select name="subnet" id="subnet"
1567 data-ng-model="interface.subnetId"
1568 data-ng-options="subnet.id as subnet.name for subnet in subnets"
1569 data-ng-show="interface.ipAssignment.name === 'static'">
1570 <option value="" disabled selected>Select subnet</option>
1571 </select>
1572 </td>
1573 <td class="table-col--35" aria-label="IP address">
1574 <input type="text" id="ip-address" placeholder="000.000.000.000"
1575 data-ng-model="interface.ipAddress"
1576 data-ng-class="{ 'has-error': ipHasError(interface) }"
1577 data-ng-show="interface.ipAssignment.name === 'external'">
1578 <input type="text" id="ip-address"
1579 data-ng-model="interface.ipAddress"
1580 data-ng-class="{ 'has-error': ipHasError(interface) }"
1581 data-ng-show="interface.ipAssignment.name === 'static'">
1582 </td>
1583 <td class="table-col--5 u-u-align---right table--mobile-controls">
1584 <!-- space needed to set correct height -->
1585 <span data-ng-show="isPrimaryInterface(interface)">&nbsp;</span>
1586 <a title="Delete" class="icon icon--remove u-display--desktop"
1587 data-ng-click="deleteInterface(interface)"
1588 data-ng-hide="isPrimaryInterface(interface)">delete</a>
1589 <button class="button--secondary u-display--mobile"
1590 data-ng-click="deleteInterface(interface)"
1591 data-ng-hide="isPrimaryInterface(interface)">delete</a>
1592 </td>
1593 </tr>
1594 </tbody>
1595 </table>
1596 <button class="button--secondary button--inline" data-ng-click="addInterface()">+ Add another interface</button>
1597 </section>
1598 <section class="page-header__section twelve-col u-margin--bottom-none twelve-col u-margin--bottom-none">
1599 <form class="form form--inline">
1600 <p class="page-header__message page-header__message--error ng-hide" data-ng-show="error">{$ error $}</p>
1601 <div class="page-header__controls">
1602 <button class="button--base button--inline" data-ng-click="cancel()">Cancel</button>
1603 <button class="button--secondary button--inline"
1604 data-ng-class="{ disabled: deviceHasError() }"
1605 data-ng-click="save(true)">Save and add another</button>
1606 <button class="button--positive button--inline"
1607 data-ng-class="{ disabled: deviceHasError() }"
1608 data-ng-click="save(false)">Save device</button>
1609 </div>
1610 </form>
1611 </section>
1612 </div>
1613
1614 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1615 <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs.controllers.actionOption }">
1616 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1617 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-hide="isActionError('controllers') || hasActionsInProgress('controllers')">
1618 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1619 <form class="form form--inline u-display--inline">
1620 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs.controllers.actionOption.name === 'set-zone'">
1621 <div class="form__group">
1622 <label for="zone2" class="form__group-label">Select Zone</label>
1623 <div class="form__group-input">
1624 <select name="zone" id="zone2"
1625 data-ng-model="tabs.controllers.zoneSelection"
1626 data-ng-options="zone as zone.name for zone in zones">
1627 <option value="" disabled="disabled">Choose a zone</option>
1628 </select>
1629 </div>
1630 </div>
1631 </fieldset>
1632 <fieldset class="form__fieldset ng-hide" data-ng-show="tabs.controllers.actionOption.name === 'test'">
1633 <div class="form__group">
1634 <input class="form__group-label" id="enable_SSH" type="checkbox"
1635 data-ng-model="tabs.controllers.commissionOptions.enableSSH">
1636 <label class="checkbox-label" for="enable_SSH">Allow SSH access and prevent machine from powering off</label>
1637 </div>
1638 </fieldset>
1639 </form>
1640 <div class="page-header__controls" data-ng-if="tabs.controllers.actionOption.name !== 'test'">
1641 <button class="button--base button--inline" data-ng-click="actionCancel('controllers')">Cancel</button>
1642 <button class="button--positive button--inline" data-ng-click="actionGo('controllers')">
1643 <span data-ng-if="tabs.controllers.actionOption.name === 'set-zone'">Set zone for {$ tabs.controllers.selectedItems.length $}
1644 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1645 </span>
1646 <span data-ng-if="tabs.controllers.actionOption.name === 'on'">Power on {$ tabs.controllers.selectedItems.length $}
1647 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1648 </span>
1649 <span data-ng-if="tabs.controllers.actionOption.name === 'off'">Power off {$ tabs.controllers.selectedItems.length $}
1650 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1651 </span>
1652 <span data-ng-if="tabs.controllers.actionOption.name === 'delete'">Delete {$ tabs.controllers.selectedItems.length $}
1653 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1654 </span>
1655 <span data-ng-if="tabs.controllers.actionOption.name === 'import-images'">Import images for {$ tabs.controllers.selectedItems.length $}
1656 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1657 </span>
1658 </button>
1659 </div>
1660 </div>
1661 <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'">
1662 <form class="form form--stack">
1663 <fieldset class="form__fieldset eight-col u-margin--bottom-small">
1664 <div class="form__group">
1665 <label for="testing-scripts">Hardware Tests</label>
1666 <span id="testing-scripts" data-maas-script-select="script" data-script-type="2" data-ng-model="tabs.nodes.testSelection" class="tags--inline"></span>
1667 </div>
1668 </fieldset>
1669 </form>
1670 </div>
1671 <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'">
1672 <div class="page-header__controls">
1673 <button class="button--base button--inline" data-ng-click="actionCancel('controllers')">Cancel</button>
1674 <button class="button--positive button--inline" data-ng-click="actionGo('controllers')">
1675 <span>Test {$ tabs.controllers.selectedItems.length $}
1676 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1677 </span>
1678 </button>
1679 </div>
1680 </div>
1681
1682 <!-- XXX blake_r 2015-04-02 - Need to add e2e test. -->
1683 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="isActionError('controllers')">
1684 <form class="form form--inline">
1685 <p class="page-header__message page-header__message--error">
1686 {$ tabs.controllers.actionErrorCount $}
1687 <span data-ng-pluralize count="tabs.controllers.selectedItems.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1688 cannot be {$ tabs.controllers.actionOption.sentence $}. To proceed, update your selection.
1689 </p>
1690 </form>
1691 </div>
1692
1693 <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
1694 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="hasActionsInProgress('controllers') || hasActionsFailed('controllers')">
1695 <form class="form form--inline">
1696 <p class="page-header__message" data-ng-show="hasActionsInProgress('controllers')">
1697 <i class="icon icon--loading u-animation--spin u-margin--right-small"></i>
1698 {$ tabs.controllers.actionProgress.completed $} of {$ tabs.controllers.actionProgress.total $}
1699 controllers have been {$ tabs.controllers.actionOption.sentence $}.
1700 </p>
1701 <p class="page-header__message page-header__message--error"
1702 data-ng-repeat="(error, controllers) in tabs.controllers.actionProgress.errors">
1703 The {$ tabs.controllers.actionOption.title.toLowerCase() $} action for {$ controllers.length $}
1704 <span data-ng-pluralize count="controllers.length" when="{'one': 'controller', 'other': 'controllers'}"></span>
1705 failed with error: {$ error $}
1706 </p>
1707 </form>
1708 </div>
1709 </div>
1710
1711 <div class="page-header__dropdown" data-ng-class="{ 'is-open': tabs.controllers.addController }">
1712 <!-- XXX blake_r 2015-05-07 - Need to add e2e test. -->
1713 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="tabs.controllers.addController">
1714 <h3 class="page-header__dropdown-title">Add rack controller</h3>
1715 <pre class="u-margin--none">
1716 <code># To add a new rack controller, SSH into the rack controller.
1717# Install the maas-rack-controller package.
1718sudo apt install maas-rack-controller
1719# Register the rack controller with this MAAS. If the rack controller (and machines)
1720# don't have access to the URL, use a different IP address to allow connection.
1721sudo maas-rack register --url {$ tabs.controllers.registerUrl $} --secret {$ tabs.controllers.registerSecret $}</code>
1722 </pre>
1723 </div>
1724 <div class="page-header__section twelve-col u-margin--bottom-none ng-hide" data-ng-show="tabs.controllers.addController">
1725 <form class="form form--inline">
1726 <div class="page-header__controls">
1727 <button class="button--secondary button--inline"
1728 data-ng-click="tabs.controllers.addController = false">Close</button>
1729 </div>
1730 </form>
1731 </div>
1732 </div>
1733 </div>
1734</header>
1735<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 }">
1736 <div class="wrapper--inner">
1737 <nav class="page-navigation__links">
1738 <button class="page-navigation__link tooltip tooltip--right"
1739 aria-label="A deployable node managed by MAAS."
1740 data-ng-class="{ 'is-active': currentpage === 'nodes'}"
1741 data-ng-click="toggleTab('nodes')">{$ nodes.length $} <ng-pluralize count="nodes.length" when="{'one': 'Machine', 'other': 'Machines'}"></ng-pluralize></button>
1742 <button class="page-navigation__link tooltip"
1743 aria-label="A node known to MAAS, but is not deployable."
1744 data-ng-class="{ 'is-active': currentpage === 'devices'}"
1745 data-ng-click="toggleTab('devices')">{$ devices.length $} <ng-pluralize count="devices.length" when="{'one': 'Device', 'other': 'Devices'}"></ng-pluralize></button>
1746 <button class="page-navigation__link tooltip"
1747 data-ng-if="isSuperUser()"
1748 aria-label="A node that provides MAAS services."
1749 data-ng-class="{ 'is-active': currentpage === 'controllers'}"
1750 data-ng-click="toggleTab('controllers')">{$ controllers.length $} <ng-pluralize count="controllers.length" when="{'one': 'Controller', 'other': 'Controllers'}"></ng-pluralize></button>
1751 <button class="page-navigation__link tooltip"
1752 data-ng-if="showswitches"
1753 aria-label="A node that is a network switch."
1754 data-ng-class="{ 'is-active': currentpage === 'switches'}"
1755 data-ng-click="toggleTab('switches')">{$ switches.length $} <ng-pluralize count="switches.length" when="{'one': 'Switch', 'other': 'Switches'}"></ng-pluralize></button>
1756 </nav>
1757 </div>
1758</div>
1759<div class="u-padding--top row">
1760 <div class="wrapper--inner">
1761 <maas-notifications></maas-notifications>
1762 <aside class="three-col">
1763 <div class="accordion maas-accordion ng-hide" data-ng-show="currentpage === 'nodes'"
1764 data-ng-class="{ 'is-disabled': tabs.nodes.actionOption }">
1765 <h3 class="accordion__title"
1766 data-ng-class="{'is-active': isActive}"
1767 data-ng-init="isActive = false"
1768 data-ng-click="isActive = !isActive">Filter by</h3>
1769 <div class="accordion__content">
1770 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1771 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.status.length">
1772 <button class="accordion__tab-title maas-accordion-tab is-active">Status</button>
1773 <div class="accordion__tab-content">
1774 <ul class="accordion__tab-list">
1775 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1776 <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') }">
1777 <button class="accordion__tab-link" data-ng-click="toggleFilter('status', status.name, 'nodes')">{$ status.name $} ({$ status.count $})</button>
1778 </li>
1779 </ul>
1780 </div>
1781 </div>
1782 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.owner.length">
1783 <button class="accordion__tab-title maas-accordion-tab">Owner</button>
1784 <div class="accordion__tab-content">
1785 <ul class="accordion__tab-list">
1786 <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') }">
1787 <button class="accordion__tab-link" data-ng-click="toggleFilter('owner', owner.name, 'nodes')">{$ owner.name $} ({$ owner.count $})</button>
1788 </li>
1789 </ul>
1790 </div>
1791 </div>
1792 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.architecture.length">
1793 <button class="accordion__tab-title maas-accordion-tab">Architectures</button>
1794 <div class="accordion__tab-content">
1795 <ul class="accordion__tab-list">
1796 <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') }">
1797 <button class="accordion__tab-link" data-ng-click="toggleFilter('architecture', architecture.name, 'nodes')"><span data-maas-release-name="architecture.name"></span> ({$ architecture.count $})</button>
1798 </li>
1799 </ul>
1800 </div>
1801 </div>
1802 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.release.length">
1803 <button class="accordion__tab-title maas-accordion-tab">OS/Release</button>
1804 <div class="accordion__tab-content">
1805 <ul class="accordion__tab-list">
1806 <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') }">
1807 <button class="accordion__tab-link" data-ng-click="toggleFilter('release', release.name, 'nodes')"><span data-maas-release-name="release.name"></span> ({$ release.count $})</button>
1808 </li>
1809 </ul>
1810 </div>
1811 </div>
1812 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.tags.length">
1813 <button class="accordion__tab-title maas-accordion-tab">Tags</button>
1814 <div class="accordion__tab-content">
1815 <ul class="accordion__tab-list">
1816 <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') }">
1817 <button class="accordion__tab-link" data-ng-click="toggleFilter('tags', tag.name, 'nodes')">{$ tag.name $} ({$ tag.count $})</button>
1818 </li>
1819 </ul>
1820 </div>
1821 </div>
1822 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.storage_tags.length">
1823 <button class="accordion__tab-title maas-accordion-tab">Storage Tags</button>
1824 <div class="accordion__tab-content">
1825 <ul class="accordion__tab-list">
1826 <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') }">
1827 <button class="accordion__tab-link" data-ng-click="toggleFilter('storage_tags', tag.name, 'nodes')">{$ tag.name $} ({$ tag.count $})</button>
1828 </li>
1829 </ul>
1830 </div>
1831 </div>
1832 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.subnets.length">
1833 <button class="accordion__tab-title maas-accordion-tab">Subnets</button>
1834 <div class="accordion__tab-content">
1835 <ul class="accordion__tab-list">
1836 <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') }">
1837 <button class="accordion__tab-link" data-ng-click="toggleFilter('subnets', subnet.name, 'nodes')">{$ subnet.name $} ({$ subnet.count $})</button>
1838 </li>
1839 </ul>
1840 </div>
1841 </div>
1842 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.fabrics.length">
1843 <button class="accordion__tab-title maas-accordion-tab">Fabrics</button>
1844 <div class="accordion__tab-content">
1845 <ul class="accordion__tab-list">
1846 <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') }">
1847 <button class="accordion__tab-link" data-ng-click="toggleFilter('fabrics', fabric.name, 'nodes')">{$ fabric.name $} ({$ fabric.count $})</button>
1848 </li>
1849 </ul>
1850 </div>
1851 </div>
1852 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.spaces.length">
1853 <button class="accordion__tab-title maas-accordion-tab">Spaces</button>
1854 <div class="accordion__tab-content">
1855 <ul class="accordion__tab-list">
1856 <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') }">
1857 <button class="accordion__tab-link" data-ng-click="toggleFilter('spaces', space.name, 'nodes')">{$ space.name $} ({$ space.count $})</button>
1858 </li>
1859 </ul>
1860 </div>
1861 </div>
1862 <div class="ng-hide accordion__tab" data-ng-show="tabs.nodes.metadata.zone.length">
1863 <button class="accordion__tab-title maas-accordion-tab">Zones</button>
1864 <div class="accordion__tab-content">
1865 <ul class="accordion__tab-list">
1866 <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') }">
1867 <button class="accordion__tab-link" data-ng-click="toggleFilter('zone', zone.name, 'nodes')">{$ zone.name $} ({$ zone.count $})</button>
1868 </li>
1869 </ul>
1870 </div>
1871 </div>
1872 </div>
1873 </div>
1874 <div class="accordion three-col maas-accordion ng-hide" data-ng-show="currentpage === 'devices'"
1875 data-ng-class="{ 'is-disabled': tabs.devices.actionOption }">
1876 <h3 class="accordion__title">Filter by</h3>
1877 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1878 <div class="ng-hide accordion__tab" data-ng-show="tabs.devices.metadata.owner.length">
1879 <button class="accordion__tab-title maas-accordion-tab active">Owner</button>
1880 <div class="accordion__tab-content">
1881 <ul class="accordion__tab-list">
1882 <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') }">
1883 <button class="accordion__tab-link" data-ng-click="toggleFilter('owner', owner.name, 'devices')">{$ owner.name $} ({$ owner.count $})</button>
1884 </li>
1885 </ul>
1886 </div>
1887 </div>
1888 <div class="ng-hide accordion__tab" data-ng-show="tabs.devices.metadata.tags.length">
1889 <button class="accordion__tab-title maas-accordion-tab">Tags</button>
1890 <div class="accordion__tab-content">
1891 <ul class="accordion__tab-list">
1892 <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') }">
1893 <button class="accordion__tab-link" data-ng-click="toggleFilter('tags', tag.name, 'devices')">{$ tag.name $} ({$ tag.count $})</button>
1894 </li>
1895 </ul>
1896 </div>
1897 </div>
1898 <div class="ng-hide accordion__tab" data-ng-show="tabs.devices.metadata.zone.length">
1899 <button class="accordion__tab-title maas-accordion-tab">Zones</button>
1900 <div class="accordion__tab-content">
1901 <ul class="accordion__tab-list">
1902 <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') }">
1903 <button class="accordion__tab-link" data-ng-click="toggleFilter('zone', zone.name, 'devices')">{$ zone.name $} ({$ zone.count $})</button>
1904 </li>
1905 </ul>
1906 </div>
1907 </div>
1908 </div>
1909 </aside>
1910 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1911 <div class="nine-col last-col ng-hide" data-ng-show="currentpage === 'nodes'">
1912 <form>
1913 <div id="search-bar" class="search nine-col">
1914 <!-- XXX ricgard 2016-06-16 - Need to add e2e test. -->
1915 <input
1916 type="search" placeholder="Search nodes" class="search__input"
1917 data-ng-model="tabs.nodes.search" data-ng-change="updateFilters('nodes')"
1918 data-ng-class="{ error: !tabs.nodes.searchValid }"
1919 data-ng-disabled="tabs.nodes.actionOption" />
1920 <input type="reset" class="search__submit"
1921 data-ng-class="{ 'search__submit--close': tabs.nodes.search.length > 0 }"
1922 data-ng-click="clearSearch('nodes')" />
1923 </div>
1924 <maas-machines-table id="nodes-listing" search="tabs.nodes.search" ng-disabled="hasActionsInProgress('nodes')"
1925 machine-has-error="!supportsAction($machine, 'nodes')" on-listing-change="onNodeListingChanged($machines, 'nodes')"
1926 on-check-all="toggleCheckAll('nodes')" on-check="toggleChecked($machine, 'nodes')"></maas-machines-table>
1927 </form>
1928 </div>
1929 <div class="nine-col last-col" class="ng-hide" data-ng-show="currentpage === 'devices'">
1930 <div class="search nine-col">
1931 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1932 <input
1933 type="search" placeholder="Search devices" class="search__input"
1934 data-ng-model="tabs.devices.search" data-ng-change="updateFilters('devices')"
1935 data-ng-class="{ error: !tabs.devices.searchValid }"
1936 data-ng-disabled="tabs.devices.actionOption" />
1937 <input type="reset" class="search__submit"
1938 data-ng-class="{ 'search__submit--close': tabs.devices.search.length > 0 }"
1939 data-ng-click="clearSearch('devices')" />
1940 </div>
1941 <table>
1942 <thead>
1943 <tr>
1944 <th class="table-col--3">
1945 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1946 <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')" />
1947 <label for="check-all-devices" class="checkbox-label"></label>
1948 </th>
1949 <th class="table-col--24">
1950 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1951 <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}">
1952 <span title="Fully Qualified Domain Name">FQDN</span>
1953 </a>
1954 </th>
1955 <th class="table-col--25">
1956 <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}">
1957 <span title="Media Access Control addresses">MAC</span>
1958 </a>
1959 </th>
1960 <th class="table-col--15">
1961 <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>
1962 </th>
1963 <th class="table-col--20">
1964 <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>
1965 </th>
1966 <th class="table-col--12">
1967 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1968 <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>
1969 </th>
1970 </tr>
1971 </thead>
1972 <tbody vs-repeat vs-scroll-parent="window">
1973 <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->
1974 <tr
1975 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"
1976 data-ng-class="{ 'table--error': !supportsAction(device, 'devices'), selected: device.$selected }">
1977 <td class="table-col--3" aria-label="Select device">
1978 <input type="checkbox" class="checkbox" data-ng-click="toggleChecked(device, 'devices')" data-ng-checked="device.$selected" id="{$ device.fqdn $}" data-ng-disabled="hasActionsInProgress('devices')"/>
1979 <label for="{$ device.fqdn $}" class="checkbox-label"></label>
1980 </td>
1981 <td class="table-col--24" aria-label="FQDN">
1982 <a href="#/node/device/{$ device.system_id $}">{$ device.fqdn $}</a>
1983 </td>
1984 <td class="table-col--25" aria-label="MAC">
1985 {$ device.primary_mac $}
1986 <span class="extra-macs" data-ng-show="device.extra_macs.length">(+{$ device.extra_macs.length $})</span>
1987 </td>
1988 <td class="table-col--15" aria-label="IP Assignment">{$ getDeviceIPAssignment(device.ip_assignment) $}</td>
1989 <td class="table-col--20" aria-label="IP Address">{$ device.ip_address $}</td>
1990 <td class="table-col--12" aria-label="Owner">{$ device.owner $}</td>
1991 </tr>
1992 </tbody>
1993 </table>
1994 </div>
1995 <div class="twelve-col last-col" class="ng-hide" data-ng-show="currentpage === 'controllers'">
1996 <div class="search twelve-col">
1997 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
1998 <input
1999 type="search" placeholder="Search controllers" class="search__input"
2000 data-ng-model="tabs.controllers.search" data-ng-change="updateFilters('controllers')"
2001 data-ng-class="{ error: !tabs.controllers.searchValid }"
2002 data-ng-disabled="tabs.controllers.actionOption" />
2003 <input type="submit" class="search__submit"
2004 data-ng-class="{ 'search__submit--close': tabs.controllers.search.length > 0 }"
2005 data-ng-click="clearSearch('controllers')" />
2006 </div>
2007 <table>
2008 <thead>
2009 <tr>
2010 <th class="table-col--2">
2011 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
2012 <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')" />
2013 <label for="check-all-controllers" class="checkbox-label"></label>
2014 </th>
2015 <th class="table-col--19">
2016 <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}">
2017 <span title="Fully Qualified Domain Name">Name</span>
2018 </a>
2019 </th>
2020 <th class="table-col--4 u-align--center">
2021 <span title="Status">Status</span>
2022 </th>
2023 <th class="table-col--15">
2024 <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}">
2025 <span title="Controller Type">Type</span>
2026 </a>
2027 </th>
2028 <th class="table-col--10">
2029 <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}">
2030 <span title="Version">Version</span>
2031 </a>
2032 </th>
2033 <th class="table-col--15">
2034 <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>
2035 </th>
2036 <th class="table-col--15">
2037 <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>
2038 </th>
2039 </tr>
2040 </thead>
2041 <tbody vs-repeat vs-scroll-parent="window">
2042 <!-- XXX rvba 2015-02-25 - Need to add e2e test. This really needs lots of tests. -->
2043 <tr
2044 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"
2045 data-ng-class="{ 'table--error': !supportsAction(controller, 'controllers'), selected: controller.$selected }">
2046 <td class="table-col--2" aria-label="Select controller">
2047 <input type="checkbox" class="checkbox" data-ng-click="toggleChecked(controller, 'controllers')" data-ng-checked="controller.$selected" id="{$ controller.fqdn $}" data-ng-disabled="hasActionsInProgress('controllers')"/>
2048 <label for="{$ controller.fqdn $}" class="checkbox-label"></label>
2049 </td>
2050 <td class="table-col--19" aria-label="FQDN">
2051 <a href="#/node/controller/{$ controller.system_id $}">{$ controller.fqdn $}</a>
2052 </td>
2053 <td class="table-col--4 u-align--center" data-maas-controller-status="controller" data-maas-services="services" aria-label="Status"></td>
2054 <td class="table-col--15" aria-label="Type">{$ controller.node_type_display $}</td>
2055 <td class="table-col--10" aria-label="Version">
2056 <div data-ng-show="controller.version">
2057 {$ controller.version__short $}
2058 </div>
2059 <div data-ng-show="!controller.version" class="u-text--subtle">
2060 Unknown
2061 <i class="icon icon--info u-margin--left-tiny tooltip"
2062 aria-label="Less than 2.3.0"></i>
2063 </div>
2064 </td>
2065 <td class="table-col--15" aria-label="Last image sync">{$ controller.last_image_sync || 'Never' $}</td>
2066 <td class="table-col--15" aria-label="Image status">
2067 <maas-controller-image-status system-id="controller.system_id"></maas-controller-image-status>
2068 </td>
2069 </tr>
2070 </tbody>
2071 </table>
2072 </div>
2073 <div class="twelve-col last-col" class="ng-hide" data-ng-show="currentpage === 'switches'">
2074 <div class="search twelve-col">
2075 <!-- XXX rvba 2015-02-25 - Need to add e2e test. -->
2076 <input
2077 type="search" placeholder="Search switches" class="search__input"
2078 data-ng-model="tabs.switches.search" data-ng-change="updateFilters('switches')"
2079 data-ng-class="{ error: !tabs.switches.searchValid }"
2080 data-ng-disabled="tabs.switches.actionOption" />
2081 <input type="submit" class="search__submit"
2082 data-ng-class="{ 'search__submit--close': tabs.switches.search.length > 0 }"
2083 data-ng-click="clearSearch('switches')" />
2084 </div>
2085 <maas-switches-table id="switches-listing" search="tabs.switches.search" ng-disabled="hasActionsInProgress('switches')"
2086 switch-has-error="!supportsAction($switch_, 'switches')" on-listing-change="onNodeListingChanged($switches, 'switches')"
2087 on-check-all="toggleCheckAll('switches')" on-check="toggleChecked($switch_, 'switches')"></maas-switches-table>
2088 </div>
2089 </div>
2090</div>
2091>>>>>>> src/maasserver/static/partials/nodes-list.html
diff --git a/src/maasserver/static/partials/script-results-list.html b/src/maasserver/static/partials/script-results-list.html
index 12d1175..bcf7b40 100644
--- a/src/maasserver/static/partials/script-results-list.html
+++ b/src/maasserver/static/partials/script-results-list.html
@@ -4,6 +4,7 @@
4 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>4 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>
5 </div>5 </div>
6 </div>6 </div>
7<<<<<<< src/maasserver/static/partials/script-results-list.html
7 <div class="row">8 <div class="row">
8 <div data-ng-repeat="hardware_type in results">9 <div data-ng-repeat="hardware_type in results">
9 <div data-ng-if="resultsLoaded && (hardware_type.results | json) != '{}'">10 <div data-ng-if="resultsLoaded && (hardware_type.results | json) != '{}'">
@@ -49,12 +50,59 @@
49 <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>50 <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>
50 </div>51 </div>
51 </div>52 </div>
53=======
54 <div data-ng-repeat="hardware_type in results">
55 <div data-ng-if="resultsLoaded && (hardware_type.results | json) != '{}'">
56 <h2 data-ng-if="hardware_type.title !== 'null'">{$ hardware_type.title $}</h2>
57 <div data-ng-repeat="(title, results) in hardware_type.results">
58 <h3 data-ng-if="title !== 'null'">{$ title $}</h3>
59 <section class="table u-margin--bottom">
60 <header class="table__head">
61 <div class="table__row">
62 <div class="table__header table-col--2 u-padding--left-none"></div>
63 <div class="table__header table-col--24">Name</div>
64 <div class="table__header table-col--22">Tags</div>
65 <div class="table__header table-col--15">Runtime</div>
66 <div class="table__header table-col--20">Date</div>
67 <div class="table__header table-col--12">Result</div>
68 <div class="table__header table-col--5 u-align--right">Actions</div>
69 </div>
70 </header>
71 <main class="table__body">
72 <div data-ng-repeat="result in results">
73 <div class="table__row" data-ng-class="{'is-active': result.showing_results || result.showing_history}">
74 <div class="table__data table-col--2 u-padding--left-none" aria-label="Status">
75 <span data-maas-script-status="script-status" data-script-status="result.status"></span>
76 </div>
77 <div class="table__data table-col--24" data-ng-click="result.showing_results = !result.showing_results" aria-label="Name">
78 {$ result.name $}
79 <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>
80 </div>
81 <div class="table__data table-col--22" aria-label="Tags"><span data-ng-hide="result.showing_history">{$ result.tags $}</span></div>
82 <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>
83 <div class="table__data table-col--20" aria-label="Date"><span data-ng-hide="result.showing_history">{$ result.updated $}</span></div>
84 <div class="table__data table-col--12" aria-label="Status">
85 <span data-ng-hide="result.showing_history">
86 <!-- 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)-->
87 {$ 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>
88 </span>
89 </div>
90 <div class="table__data table-col--5 table--mobile-controls">
91 <div class="table__controls u-align--right" toggle-ctrl>
92 <button class="table__controls-toggle" data-ng-click="toggleMenu()">View actions</button>
93 <div class="table__controls-menu ng-hide" role="menu" data-ng-show="isToggled">
94 <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>
95 <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>
96 <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>
97 <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>
98>>>>>>> src/maasserver/static/partials/script-results-list.html
52 </div>99 </div>
53 </td>100 </td>
54 <td class="p-table-expanding__panel col-12" aria-label="results" data-ng-if="result.showing_results && !result.showing_history">101 <td class="p-table-expanding__panel col-12" aria-label="results" data-ng-if="result.showing_results && !result.showing_history">
55 <div class="row">102 <div class="row">
56 <div class="col-12" data-ng-if="result.results.length === 0">No metrics provided</div>103 <div class="col-12" data-ng-if="result.results.length === 0">No metrics provided</div>
57 </div>104 </div>
105<<<<<<< src/maasserver/static/partials/script-results-list.html
58 <div class="row" data-ng-if="result.results">106 <div class="row" data-ng-if="result.results">
59 <dl data-ng-repeat="item in result.results">107 <dl data-ng-repeat="item in result.results">
60 <dt class="p-tooltip p-tooltip--top-center">108 <dt class="p-tooltip p-tooltip--top-center">
@@ -68,6 +116,34 @@
68 <td class="p-table-expanding__panel col-12" aria-label="loading history" data-ng-if="result.loading_history">116 <td class="p-table-expanding__panel col-12" aria-label="loading history" data-ng-if="result.loading_history">
69 <div class="col-12">117 <div class="col-12">
70 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>118 <p class="u-text--loading"><i class="p-icon--spinner u-animation--spin"></i>&nbsp;&nbsp;Loading...</p>
119=======
120 </div>
121 </div>
122
123 <div class="table__dropdown" aria-label="history" data-ng-if="result.showing_history">
124 <div class="table__row is-active">
125 <div class="table__data table-col--100">
126 <section class="table u-margin--bottom">
127 <main class="table__body">
128 <div class="table__row is-active u-border--none" data-ng-repeat="item in result.history_list">
129 <div class="table__data table-col--2 u-padding--left-none" aria-label="Status">
130 <span data-maas-script-status="script-status" data-script-status="item.status"></span>
131 </div>
132 <div class="table__data table-col--24" aria-label="Name">{$ result.name $}</div>
133 <div class="table__data table-col--24" aria-label="Tags">{$ result.tags $}</div>
134 <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>
135 <div class="table__data table-col--20" aria-label="Date">{$ item.updated $}</div>
136 <div class="table__data table-col--10" aria-label="Status">
137 <!-- 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)-->
138 {$ 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>
139 </div>
140 </div>
141 </main>
142 </section>
143 <p class="u-align--center u-margin--bottom">
144 <button class="button--secondary button--inline" data-ng-click="result.showing_history = false">Hide previous {$ result.result_section $}</button>
145 </p>
146>>>>>>> src/maasserver/static/partials/script-results-list.html
71 </div>147 </div>
72 </div>148 </div>
73 <td class="p-table-expanding__panel col-12" aria-label="history" data-ng-if="result.showing_history">149 <td class="p-table-expanding__panel col-12" aria-label="history" data-ng-if="result.showing_history">
diff --git a/src/maasserver/static/partials/subnet-details.html b/src/maasserver/static/partials/subnet-details.html
index dc22777..5415f96 100755
--- a/src/maasserver/static/partials/subnet-details.html
+++ b/src/maasserver/static/partials/subnet-details.html
@@ -201,6 +201,7 @@
201 <maas-obj-field type="text" key="cidr" label="CIDR" placeholder="Subnet CIDR"201 <maas-obj-field type="text" key="cidr" label="CIDR" placeholder="Subnet CIDR"
202 label-width="2" input-width="4"></maas-obj-field>202 label-width="2" input-width="4"></maas-obj-field>
203 <maas-obj-field type="text" key="gateway_ip" label="Gateway IP" placeholder="Gateway IP"203 <maas-obj-field type="text" key="gateway_ip" label="Gateway IP" placeholder="Gateway IP"
204<<<<<<< src/maasserver/static/partials/subnet-details.html
204 label-width="2" input-width="4"></maas-obj-field>205 label-width="2" input-width="4"></maas-obj-field>
205 <maas-obj-field type="text" key="dns_servers" label="DNS" placeholder="DNS nameservers for subnet"206 <maas-obj-field type="text" key="dns_servers" label="DNS" placeholder="DNS nameservers for subnet"
206 label-width="2" input-width="4"></maas-obj-field>207 label-width="2" input-width="4"></maas-obj-field>
@@ -237,6 +238,45 @@
237 <p>Space</p>238 <p>Space</p>
238 </div>239 </div>
239 <div class="p-form__control">240 <div class="p-form__control">
241=======
242 label-width="two" input-width="three" class="u-margin--bottom-none"></maas-obj-field>
243 </maas-obj-field-group>
244 <maas-obj-field type="text" key="dns_servers" label="DNS" placeholder="DNS nameservers for subnet"
245 label-width="two" input-width="three"></maas-obj-field>
246 <maas-obj-field type="textarea" key="description" label="Description" placeholder="Subnet description"
247 label-width="two" input-width="three"></maas-obj-field>
248 </fieldset>
249 <fieldset class="form__fieldset six-col last-col">
250 <maas-obj-field type="onoffswitch" key="managed" label="Managed allocation"
251 label-width="two" input-width="three"
252 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.">
253
254 </maas-obj-field>
255 <span data-ng-if="subnet.$maasForm.getValue('managed') != subnet.managed">
256 <div class="form__group-label two-col">&nbsp;</div>
257 <div class="form__group-input three-col last-col">
258 <span data-ng-if="subnet.$maasForm.getValue('managed') == false" class="u-text--subtle">
259 <i class="icon icon--warning"></i>
260 <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.
261 </span>
262 <span data-ng-if="subnet.$maasForm.getValue('managed') == true" class="u-text--subtle">
263 <i class="icon icon--warning"></i>
264 <b>Warning:</b> MAAS will now allocate IP addresses from the entire subnet, excluding any reserved ranges.
265 </span>
266 </div>
267 </span>
268 <maas-obj-field data-ng-if="subnet.version === 4" type="onoffswitch" key="active_discovery" label="Active mapping"
269 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>
270 <maas-obj-field type="options" key="fabric" label="Fabric" placeholder="Choose fabric"
271 options="fabric.id as fabric.name for fabric in fabrics | orderBy:'name'"
272 label-width="two" input-width="three"></maas-obj-field>
273 <maas-obj-field type="options" key="vlan" class="u-margin--bottom" label="VLAN" placeholder="Choose VLAN"
274 options="v.id as getVLANName(v) for v in vlans | filterByFabric:subnet.$maasForm.getValue('fabric')"
275 label-width="two" input-width="three"></maas-obj-field>
276 <dl>
277 <dt class="two-col u-text--subtle">Space</dt>
278 <dd class="four-col last-col">
279>>>>>>> src/maasserver/static/partials/subnet-details.html
240 <a data-ng-if="space !== null" href="#/space/{$ space.id $}">{$ space.name $}</a>280 <a data-ng-if="space !== null" href="#/space/{$ space.id $}">{$ space.name $}</a>
241 <span data-ng-if="space === null">281 <span data-ng-if="space === null">
242 (undefined)282 (undefined)
diff --git a/src/maasserver/static/partials/vlan-details.html b/src/maasserver/static/partials/vlan-details.html
index 8a2826a..404a6e6 100644
--- a/src/maasserver/static/partials/vlan-details.html
+++ b/src/maasserver/static/partials/vlan-details.html
@@ -231,9 +231,15 @@
231 <!-- End of "Take action" dropdown -->231 <!-- End of "Take action" dropdown -->
232 </div>232 </div>
233 </header>233 </header>
234<<<<<<< src/maasserver/static/partials/vlan-details.html
234 <section class="p-strip is-bordered">235 <section class="p-strip is-bordered">
235 <div class="row">236 <div class="row">
236 <div class="col-6">237 <div class="col-6">
238=======
239 <section class="row">
240 <fieldset class="wrapper--inner">
241 <div class="twelve-col">
242>>>>>>> src/maasserver/static/partials/vlan-details.html
237 <h2 class="u-float--left">VLAN Summary</h2>243 <h2 class="u-float--left">VLAN Summary</h2>
238 </div>244 </div>
239 <div class="col-6">245 <div class="col-6">
@@ -310,6 +316,7 @@
310 </div>316 </div>
311 <div class="row">317 <div class="row">
312 <maas-obj-form obj="vlanDetails.vlan" manager="vlanDetails.vlanManager" data-ng-disabled="!vlanDetails.isSuperUser()" data-ng-if="vlanDetails.editSummary"318 <maas-obj-form obj="vlanDetails.vlan" manager="vlanDetails.vlanManager" data-ng-disabled="!vlanDetails.isSuperUser()" data-ng-if="vlanDetails.editSummary"
319<<<<<<< src/maasserver/static/partials/vlan-details.html
313 table-form="true" save-on-blur="false" after-save="vlanDetails.exitEditSummary">320 table-form="true" save-on-blur="false" after-save="vlanDetails.exitEditSummary">
314 <div class="row">321 <div class="row">
315 <div class="col-6">322 <div class="col-6">
@@ -337,11 +344,35 @@
337 </span></p>344 </span></p>
338 </div>345 </div>
339 <div class="p-form__control">346 <div class="p-form__control">
347=======
348 table-form="true" save-on-blur="false" after-save="vlanDetails.exitEditSummary">
349 <fieldset class="form__fieldset six-col">
350 <maas-obj-field type="text" key="vid" label="VID" placeholder="VLAN VID"
351 label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
352 <maas-obj-field type="text" key="name" label="Name" placeholder="VLAN name"
353 label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
354 <maas-obj-field type="text" key="mtu" label="MTU" placeholder="VLAN MTU"
355 label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
356 <maas-obj-field type="options" key="space" label="Space" placeholder="(undefined)" placeholder-enabled="true"
357 options="space.id as space.name for space in vlanDetails.spaces"
358 label-width="two" input-width="three"></maas-obj-field>
359 <maas-obj-field type="textarea" key="description" label="Description" placeholder="VLAN description"
360 label-width="two" input-width="three" blur-on-enter="true"></maas-obj-field>
361 </fieldset>
362 <fieldset class="form__fieldset six-col last-col">
363 <dl>
364 <maas-obj-field type="options" key="fabric" label="Fabric" label-width="two" input-width="three"
365 options="fabric.id as fabric.name for fabric in vlanDetails.fabrics"></maas-obj-field>
366 <div data-ng-if="vlanDetails.relatedControllers">
367 <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>
368 <dd class="four-col last-col">
369>>>>>>> src/maasserver/static/partials/vlan-details.html
340 <span data-ng-repeat="rack in vlanDetails.relatedControllers">370 <span data-ng-repeat="rack in vlanDetails.relatedControllers">
341 <a href="#/controller/{$ rack.system_id $}">{$ rack.hostname $}</a>,371 <a href="#/controller/{$ rack.system_id $}">{$ rack.hostname $}</a>,
342 </span>372 </span>
343 </div>373 </div>
344 </div>374 </div>
375<<<<<<< src/maasserver/static/partials/vlan-details.html
345 </div>376 </div>
346 </div>377 </div>
347 <div class="row u-no-margin--top">378 <div class="row u-no-margin--top">
@@ -353,6 +384,17 @@
353 data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>384 data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>
354 <button class="p-button--positive" maas-obj-save>Save summary</button>385 <button class="p-button--positive" maas-obj-save>Save summary</button>
355 </div>386 </div>
387=======
388 </dl>
389 </fieldset>
390 <div class="six-col">
391 <maas-obj-errors></maas-obj-errors>
392 </div>
393 <div class="six-col last-col u-align--right">
394 <button class="button--base button--inline"
395 data-ng-click="vlanDetails.exitEditSummary()">Cancel</button>
396 <button class="button--positive button--inline" maas-obj-save>Save summary</button>
397>>>>>>> src/maasserver/static/partials/vlan-details.html
356 </div>398 </div>
357 </maas-obj-form>399 </maas-obj-form>
358 </div>400 </div>
diff --git a/src/maasserver/tests/test_bootsources.py b/src/maasserver/tests/test_bootsources.py
index a83c9c1..3629529 100644
--- a/src/maasserver/tests/test_bootsources.py
+++ b/src/maasserver/tests/test_bootsources.py
@@ -44,7 +44,10 @@ from maasserver.testing.testcase import (
44)44)
45from maasserver.tests.test_bootresources import SimplestreamsEnvFixture45from maasserver.tests.test_bootresources import SimplestreamsEnvFixture
46from maasserver.utils import get_maas_user_agent46from maasserver.utils import get_maas_user_agent
47<<<<<<< src/maasserver/tests/test_bootsources.py
47from maastesting.djangotestcase import count_queries48from maastesting.djangotestcase import count_queries
49=======
50>>>>>>> src/maasserver/tests/test_bootsources.py
48from maastesting.matchers import MockCalledOnceWith51from maastesting.matchers import MockCalledOnceWith
49from provisioningserver.config import DEFAULT_IMAGES_URL52from provisioningserver.config import DEFAULT_IMAGES_URL
50from provisioningserver.import_images import (53from provisioningserver.import_images import (
diff --git a/src/maasserver/tests/test_stats.py b/src/maasserver/tests/test_stats.py
index d0200f7..43aae70 100644
--- a/src/maasserver/tests/test_stats.py
+++ b/src/maasserver/tests/test_stats.py
@@ -69,7 +69,7 @@ class TestMAASStats(MAASServerTestCase):
69 "total_storage": total_storage,69 "total_storage": total_storage,
70 },70 },
71 }71 }
72 self.assertEquals(stats, json.dumps(compare))72 self.assertEquals(json.loads(stats), compare)
7373
74 def test_get_request_params_returns_params(self):74 def test_get_request_params_returns_params(self):
75 factory.make_RegionRackController()75 factory.make_RegionRackController()
diff --git a/src/maasserver/triggers/tests/test_websocket_listener.py b/src/maasserver/triggers/tests/test_websocket_listener.py
index 5e69c2d..5594db9 100644
--- a/src/maasserver/triggers/tests/test_websocket_listener.py
+++ b/src/maasserver/triggers/tests/test_websocket_listener.py
@@ -1714,8 +1714,13 @@ class TestIPRangeListener(
1714 self.create_subnet, {'cidr': str(network)})1714 self.create_subnet, {'cidr': str(network)})
1715 params = {1715 params = {
1716 'subnet': subnet,1716 'subnet': subnet,
1717<<<<<<< src/maasserver/triggers/tests/test_websocket_listener.py
1717 'start_ip': IPAddress(network.first + 2),1718 'start_ip': IPAddress(network.first + 2),
1718 'end_ip': IPAddress(network.last - 1)}1719 'end_ip': IPAddress(network.last - 1)}
1720=======
1721 'start_ip': str(IPAddress(network.first + 2)),
1722 'end_ip': str(IPAddress(network.last - 1))}
1723>>>>>>> src/maasserver/triggers/tests/test_websocket_listener.py
1719 yield listener.startService()1724 yield listener.startService()
1720 try:1725 try:
1721 iprange = yield deferToDatabase(1726 iprange = yield deferToDatabase(
diff --git a/src/metadataserver/user_data/templates/commissioning.template b/src/metadataserver/user_data/templates/commissioning.template
index 1ba2510..a1f6ce9 100644
--- a/src/metadataserver/user_data/templates/commissioning.template
+++ b/src/metadataserver/user_data/templates/commissioning.template
@@ -31,6 +31,10 @@ main() {
31 # the BMC username and password.31 # the BMC username and password.
32 signal WORKING "Starting [maas-bmc-autodetect]" || exit 132 signal WORKING "Starting [maas-bmc-autodetect]" || exit 1
3333
34 # LP:1730524 - Make sure we can signal the metadata service before updating
35 # the BMC username and password.
36 signal WORKING "Starting [maas-bmc-autodetect]" || exit 1
37
34 # Install IPMI deps38 # Install IPMI deps
35 aptget install freeipmi-tools openipmi ipmitool sshpass39 aptget install freeipmi-tools openipmi ipmitool sshpass
3640
diff --git a/src/provisioningserver/drivers/power/ipmi.py b/src/provisioningserver/drivers/power/ipmi.py
index e3b1561..3783152 100644
--- a/src/provisioningserver/drivers/power/ipmi.py
+++ b/src/provisioningserver/drivers/power/ipmi.py
@@ -219,7 +219,11 @@ class IPMIPowerDriver(PowerDriver):
219 @staticmethod219 @staticmethod
220 def _issue_ipmi_chassis_config_command(220 def _issue_ipmi_chassis_config_command(
221 command, power_change, power_address, power_boot_type=None):221 command, power_change, power_address, power_boot_type=None):
222<<<<<<< src/provisioningserver/drivers/power/ipmi.py
222 env = shell.get_env_with_locale()223 env = shell.get_env_with_locale()
224=======
225 env = shell.select_c_utf8_locale()
226>>>>>>> src/provisioningserver/drivers/power/ipmi.py
223 with NamedTemporaryFile("w+", encoding="utf-8") as tmp_config:227 with NamedTemporaryFile("w+", encoding="utf-8") as tmp_config:
224 # Write out the chassis configuration.228 # Write out the chassis configuration.
225 if (power_boot_type is None or229 if (power_boot_type is None or
diff --git a/src/provisioningserver/import_images/boot_resources.py b/src/provisioningserver/import_images/boot_resources.py
index f985df6..0a5b3c2 100644
--- a/src/provisioningserver/import_images/boot_resources.py
+++ b/src/provisioningserver/import_images/boot_resources.py
@@ -9,6 +9,7 @@ __all__ = [
9 ]9 ]
1010
11from argparse import ArgumentParser11from argparse import ArgumentParser
12import copy
12import errno13import errno
13from io import StringIO14from io import StringIO
14import os15import os
@@ -41,6 +42,18 @@ from provisioningserver.utils.fs import (
41 read_text_file,42 read_text_file,
42 tempdir,43 tempdir,
43)44)
45<<<<<<< src/provisioningserver/import_images/boot_resources.py
46=======
47from provisioningserver.utils.service_monitor import ServiceActionError
48from provisioningserver.utils.shell import (
49 call_and_check,
50 ExternalProcessError,
51)
52from provisioningserver.utils.snappy import (
53 get_snap_data_path,
54 running_in_snap,
55)
56>>>>>>> src/provisioningserver/import_images/boot_resources.py
44from twisted.internet.defer import inlineCallbacks57from twisted.internet.defer import inlineCallbacks
45from twisted.python.filepath import FilePath58from twisted.python.filepath import FilePath
4659
@@ -109,6 +122,54 @@ def write_snapshot_metadata(snapshot, meta_file_content):
109 atomic_write(meta_file_content.encode("ascii"), meta_file, mode=0o644)122 atomic_write(meta_file_content.encode("ascii"), meta_file, mode=0o644)
110123
111124
125<<<<<<< src/provisioningserver/import_images/boot_resources.py
126=======
127def write_targets_conf(snapshot):
128 """Write "maas.tgt" file."""
129 targets_conf = os.path.join(snapshot, 'maas.tgt')
130 targets_conf_content = compose_targets_conf(snapshot)
131 atomic_write(targets_conf_content, targets_conf, mode=0o644)
132
133
134def update_targets_conf(snapshot):
135 """Runs tgt-admin to update the new targets from "maas.tgt"."""
136 # Ensure that tgt is running before tgt-admin is used.
137 try:
138 service_monitor.ensureService("tgt").wait(30)
139 except ServiceActionError:
140 msg = "Unable to start tgt"
141 try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
142 maaslog.warning(msg)
143 return
144
145 # Update the tgt config.
146 targets_conf = os.path.join(snapshot, 'maas.tgt')
147
148 # The targets_conf may not exist in the event the BootSource is broken
149 # and images havn't been imported yet. This fixes LP:1655721
150 if not os.path.exists(targets_conf):
151 return
152
153 try:
154 env = copy.deepcopy(os.environ)
155 # LP:1718706 - When TGT is run in a snap the socket is stored in the
156 # SNAP_DATA path. Define it before calling tgt-admin otherwise the
157 # standard path is used and the call will fail.
158 if running_in_snap():
159 env['TGT_IPC_SOCKET'] = os.path.join(
160 get_snap_data_path(), 'tgtd-socket')
161 call_and_check(sudo([
162 get_path('/usr/sbin/tgt-admin'),
163 '--conf', targets_conf,
164 '--update', 'ALL',
165 ]), env=env)
166 except ExternalProcessError as e:
167 msg = "Unable to update TGT config: %s" % e
168 try_send_rack_event(EVENT_TYPES.RACK_IMPORT_WARNING, msg)
169 maaslog.warning(msg)
170
171
172>>>>>>> src/provisioningserver/import_images/boot_resources.py
112def read_sources(sources_yaml):173def read_sources(sources_yaml):
113 """Read boot resources config file.174 """Read boot resources config file.
114175
diff --git a/src/provisioningserver/import_images/tests/test_boot_resources.py b/src/provisioningserver/import_images/tests/test_boot_resources.py
index 28635e7..31fa955 100644
--- a/src/provisioningserver/import_images/tests/test_boot_resources.py
+++ b/src/provisioningserver/import_images/tests/test_boot_resources.py
@@ -43,7 +43,18 @@ from provisioningserver.testing.config import (
43 BootSourcesFixture,43 BootSourcesFixture,
44 ClusterConfigurationFixture,44 ClusterConfigurationFixture,
45)45)
46<<<<<<< src/provisioningserver/import_images/tests/test_boot_resources.py
46from provisioningserver.utils.fs import write_text_file47from provisioningserver.utils.fs import write_text_file
48=======
49from provisioningserver.utils.fs import (
50 tempdir,
51 write_text_file,
52)
53from provisioningserver.utils.service_monitor import ServiceActionError
54from provisioningserver.utils.shell import ExternalProcessError
55from testtools.content import Content
56from testtools.content_type import UTF8_TEXT
57>>>>>>> src/provisioningserver/import_images/tests/test_boot_resources.py
47from testtools.matchers import (58from testtools.matchers import (
48 DirExists,59 DirExists,
49 FileExists,60 FileExists,
@@ -477,6 +488,87 @@ class TestMain(MAASTestCase):
477 boot_resources.NoConfigFile,488 boot_resources.NoConfigFile,
478 boot_resources.main, self.make_args(sources="", sources_file=""))489 boot_resources.main, self.make_args(sources="", sources_file=""))
479490
491<<<<<<< src/provisioningserver/import_images/tests/test_boot_resources.py
492=======
493 def test_update_targets_conf_ensures_tgt_service(self):
494 mock_ensureService = self.patch(
495 boot_resources.service_monitor, "ensureService")
496 self.patch(boot_resources, "call_and_check")
497 boot_resources.update_targets_conf(factory.make_name("snapshot"))
498 self.assertThat(mock_ensureService, MockCalledOnceWith("tgt"))
499
500 def test_update_targets_conf_logs_tgt_service_check_error(self):
501 # Regression test for LP:1735025
502 mock_ensureService = self.patch(
503 boot_resources.service_monitor, "ensureService")
504 mock_ensureService.side_effect = ServiceActionError()
505 mock_try_send_rack_event = self.patch(
506 boot_resources, 'try_send_rack_event')
507 mock_maaslog = self.patch(boot_resources.maaslog, 'warning')
508 boot_resources.update_targets_conf(factory.make_name("snapshot"))
509 self.assertThat(mock_try_send_rack_event, MockCalledOnce())
510 self.assertThat(mock_maaslog, MockCalledOnce())
511
512 def test_update_targets_conf_logs_error(self):
513 self.patch(boot_resources.service_monitor, "ensureService")
514 mock_try_send_rack_event = self.patch(
515 boot_resources, 'try_send_rack_event')
516 mock_maaslog = self.patch(boot_resources.maaslog, 'warning')
517 self.patch(boot_resources.os.path, 'exists').return_value = True
518 self.patch(boot_resources, 'call_and_check').side_effect = (
519 ExternalProcessError(
520 returncode=2, cmd=('tgt-admin',), output='error'))
521 snapshot = factory.make_name("snapshot")
522 boot_resources.update_targets_conf(snapshot)
523 self.assertThat(mock_try_send_rack_event, MockCalledOnce())
524 self.assertThat(mock_maaslog, MockCalledOnce())
525 self.assertThat(
526 boot_resources.call_and_check,
527 MockCalledOnceWith([
528 'sudo', '-n', '/usr/sbin/tgt-admin',
529 '--conf', os.path.join(snapshot, 'maas.tgt'),
530 '--update', 'ALL'], env=os.environ))
531
532 def test_update_targets_only_runs_when_conf_exists(self):
533 # Regression test for LP:1655721
534 temp_dir = self.useFixture(TempDirectory()).path
535 self.useFixture(ClusterConfigurationFixture(tftp_root=temp_dir))
536 mock_ensureService = self.patch(
537 boot_resources.service_monitor, "ensureService")
538 mock_call_and_check = self.patch(boot_resources, "call_and_check")
539 mock_path_exists = self.patch(boot_resources.os.path, 'exists')
540 mock_path_exists.return_value = False
541 boot_resources.update_targets_conf(temp_dir)
542 self.assertThat(mock_ensureService, MockCalledOnceWith("tgt"))
543 self.assertThat(
544 mock_path_exists,
545 MockCalledOnceWith(os.path.join(temp_dir, 'maas.tgt')))
546 self.assertThat(mock_call_and_check, MockNotCalled())
547
548 def test_update_targets_conf_sets_env_var_in_snap(self):
549 # Regression test for LP:1718706
550 self.patch(boot_resources.service_monitor, "ensureService")
551 self.patch(boot_resources.os.path, 'exists').return_value = True
552 self.patch(boot_resources, 'call_and_check')
553 self.patch(boot_resources, 'running_in_snap').return_value = True
554 snap_data_path = factory.make_name('snap_data_path')
555 self.patch(boot_resources, 'get_snap_data_path').return_value = (
556 snap_data_path)
557 snapshot = factory.make_name("snapshot")
558 boot_resources.update_targets_conf(snapshot)
559 self.assertThat(
560 boot_resources.call_and_check,
561 MockCalledOnceWith([
562 'sudo', '-n', '/usr/sbin/tgt-admin',
563 '--conf', os.path.join(snapshot, 'maas.tgt'),
564 '--update', 'ALL'
565 ], env={
566 'TGT_IPC_SOCKET': os.path.join(
567 snap_data_path, 'tgtd-socket'),
568 **os.environ,
569 }))
570
571>>>>>>> src/provisioningserver/import_images/tests/test_boot_resources.py
480572
481class TestMetaContains(MAASTestCase):573class TestMetaContains(MAASTestCase):
482 """Tests for the `meta_contains` function."""574 """Tests for the `meta_contains` function."""
diff --git a/src/provisioningserver/import_images/tests/test_download_resources.py b/src/provisioningserver/import_images/tests/test_download_resources.py
index 9f40e29..81bc3c1 100644
--- a/src/provisioningserver/import_images/tests/test_download_resources.py
+++ b/src/provisioningserver/import_images/tests/test_download_resources.py
@@ -359,6 +359,41 @@ class TestRepoWriter(MAASTestCase):
359 label=product['label'], subarches={'ga-16.04', 'generic'},359 label=product['label'], subarches={'ga-16.04', 'generic'},
360 bootloader_type=None))360 bootloader_type=None))
361361
362<<<<<<< src/provisioningserver/import_images/tests/test_download_resources.py
363 def test_inserts_no_generic_link_for_generic_non_ga_kflavor(self):
364 # Regression test for LP:1749246
365=======
366 def test_inserts_generic_link_for_generic_ga_kflavor(self):
367>>>>>>> src/provisioningserver/import_images/tests/test_download_resources.py
368 product_mapping = ProductMapping()
369 product = self.make_product(subarch='hwe-16.04', kflavor='generic')
370 product_mapping.add(product, 'hwe-16.04')
371 repo_writer = download_resources.RepoWriter(
372 None, None, product_mapping)
373 self.patch(
374 download_resources, 'products_exdata').return_value = product
375 # Prevent MAAS from trying to actually write the file.
376 mock_insert_file = self.patch(download_resources, 'insert_file')
377 mock_link_resources = self.patch(download_resources, 'link_resources')
378 # We only need to provide the product as the other fields are only used
379 # when writing the actual files to disk.
380 repo_writer.insert_item(product, None, None, None, None)
381 # None is used for the store and the content source as we're not
382 # writing anything to disk.
383 self.assertThat(
384 mock_insert_file,
385 MockCalledOnceWith(
386 None, os.path.basename(product['path']), product['sha256'],
387 {'sha256': product['sha256']}, product['size'], None))
388 # links are mocked out by the mock_insert_file above.
389 self.assertThat(
390 mock_link_resources,
391 MockCalledOnceWith(
392 snapshot_path=None, links=mock.ANY, osystem=product['os'],
393 arch=product['arch'], release=product['release'],
394 label=product['label'], subarches={'hwe-16.04'},
395 bootloader_type=None))
396
362 def test_inserts_no_generic_link_for_generic_non_ga_kflavor(self):397 def test_inserts_no_generic_link_for_generic_non_ga_kflavor(self):
363 # Regression test for LP:1749246398 # Regression test for LP:1749246
364 product_mapping = ProductMapping()399 product_mapping = ProductMapping()
diff --git a/src/provisioningserver/utils/tests/test_network.py b/src/provisioningserver/utils/tests/test_network.py
index bda51e1..3b3c6b0 100644
--- a/src/provisioningserver/utils/tests/test_network.py
+++ b/src/provisioningserver/utils/tests/test_network.py
@@ -83,7 +83,10 @@ from provisioningserver.utils.network import (
83 resolves_to_loopback_address,83 resolves_to_loopback_address,
84 reverseResolve,84 reverseResolve,
85)85)
86<<<<<<< src/provisioningserver/utils/tests/test_network.py
86from provisioningserver.utils.shell import get_env_with_locale87from provisioningserver.utils.shell import get_env_with_locale
88=======
89>>>>>>> src/provisioningserver/utils/tests/test_network.py
87from testtools import ExpectedException90from testtools import ExpectedException
88from testtools.matchers import (91from testtools.matchers import (
89 Contains,92 Contains,
diff --git a/utilities/release-build b/utilities/release-build
index 3c0a5af..49f32cc 100755
--- a/utilities/release-build
+++ b/utilities/release-build
@@ -15,7 +15,7 @@ usage () {
15 exit 115 exit 1
16}16}
1717
18DEFAULT_DISTROS="$(ubuntu-distro-info --supported | grep -v trusty)"18DEFAULT_DISTROS="$(ubuntu-distro-info --supported | grep -v trusty | grep -v bionic)"
19PACKAGE_DISTROS=""19PACKAGE_DISTROS=""
20KEEP_CHANGELOG=020KEEP_CHANGELOG=0
2121
@@ -61,7 +61,7 @@ dch -a "" --release-heuristic log --nomultimaint
61PKG=$(head -n1 debian/changelog | awk '{print $1}')61PKG=$(head -n1 debian/changelog | awk '{print $1}')
62MAJOR_VER=$(head -n 1 debian/changelog | sed 's/^.*(//' | sed 's/).*//' | sed 's/-.*//')62MAJOR_VER=$(head -n 1 debian/changelog | sed 's/^.*(//' | sed 's/).*//' | sed 's/-.*//')
63REV_COUNT=$(git rev-list --count $COMMIT)63REV_COUNT=$(git rev-list --count $COMMIT)
64REV_SHORT=$(git rev-parse --short $COMMIT)64REV_SHORT=$(git rev-parse --short=7 $COMMIT)
65FULL_VER="$MAJOR_VER-$REV_COUNT-g$REV_SHORT"65FULL_VER="$MAJOR_VER-$REV_COUNT-g$REV_SHORT"
66TARBALL="maas_$FULL_VER.orig.tar.gz"66TARBALL="maas_$FULL_VER.orig.tar.gz"
6767
@@ -89,3 +89,12 @@ if echo "$PACKAGE_DISTROS" | grep -q "bionic"; then
89 sed -i "s/) UNRELEASED;/~18.04.1) bionic;/i" debian/changelog89 sed -i "s/) UNRELEASED;/~18.04.1) bionic;/i" debian/changelog
90 debuild -S -sa90 debuild -S -sa
91fi91fi
92<<<<<<< utilities/release-build
93=======
94
95if echo "$PACKAGE_DISTROS" | grep -q "artful"; then
96 cp $ROOTDIR/debian/changelog debian/changelog
97 sed -i "s/) UNRELEASED;/~17.10.1) artful;/" debian/changelog
98 debuild -S
99fi
100>>>>>>> utilities/release-build

Subscribers

People subscribed via source and target branches