diff -Nru maas-2.4.2-7034-g2f5deb8b8/debian/changelog maas-2.4.3-7049-g000f1aa/debian/changelog --- maas-2.4.2-7034-g2f5deb8b8/debian/changelog 2018-08-23 11:29:47.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/debian/changelog 2019-11-01 09:09:16.000000000 -0400 @@ -1,18 +1,54 @@ -maas (2.4.2-7034-g2f5deb8b8-0ubuntu1) bionic-proposed; urgency=medium +maas (2.4.3-7049-g000f1aa-0ubuntu1) UNRELEASED; urgency=medium - * Stable Release Update. New upstream release, MAAS 2.4.2 (LP: #1788641) + * UNRELEASED - -- Andres Rodriguez Thu, 23 Aug 2018 11:29:47 -0400 + -- Blake Rouse Fri, 1 Nov 2019 09:09:16 -0400 -maas (2.4.0-6981-g011e51b7a-0ubuntu1~18.04.1) bionic-proposed; urgency=medium +maas (2.4.3-7049-g000f1aa-0ubuntu1) bionic; urgency=medium - * Stable Release Update. New upstream release, MAAS 2.4.0 (LP: #1773201) - * debian/control: - - Add versioned depends to python3-django. - - Add depends on python3-macaroonbakery for python3-maas-client. - - Move depends on avahi-utils to maas-common from maas-region-controller. + * New upstream release: + - Notice when bind9 is hung, force kill the service, ensure it starts + again, then perform a reload. (LP: #1710278) + - MAAS sometimes get in a state where machine tags aren't updated from + their definitions. (LP: #1845351) - -- Andres Rodriguez Mon, 18 Jun 2018 09:11:11 -0500 + -- Blake Rouse Fri, 31 Oct 2019 14:22:04 -0400 + +maas (2.4.2-7034-g2f5deb8b8-0ubuntu1) bionic; urgency=medium + + * New upstream release. + + -- Andres Rodriguez Fri, 17 Aug 2018 10:44:00 -0400 + +maas (2.4.1-7032-g11e4fa330-0ubuntu1) cosmic; urgency=medium + + * New upstream release. + + -- Andres Rodriguez Wed, 15 Aug 2018 10:00:02 -0400 + +maas (2.4.0-6981-g011e51b7a-0ubuntu1) cosmic; urgency=medium + + * New upstream release. + + -- Andres Rodriguez Thu, 24 May 2018 11:01:06 -0400 + +maas (2.4.0~rc2-6980-gc5ebdd7b1-0ubuntu1) bionic; urgency=medium + + * New upstream release. + + -- Andres Rodriguez Fri, 18 May 2018 17:48:22 -0400 + +maas (2.4.0~rc1-6959-gd7a158fc3-0ubuntu1) bionic; urgency=medium + + * New upstream release. + + -- Andres Rodriguez Fri, 11 May 2018 14:34:52 -0400 + +maas (2.4.0~beta3-6929-g62682abf5-0ubuntu1) bionic; urgency=medium + + * New upstream release. + + -- Andres Rodriguez Thu, 03 May 2018 03:31:14 -0400 maas (2.4.0~beta2-6865-gec43e47e6-0ubuntu1) bionic; urgency=medium diff -Nru maas-2.4.2-7034-g2f5deb8b8/debian/extras/99-maas-common-sudoers maas-2.4.3-7049-g000f1aa/debian/extras/99-maas-common-sudoers --- maas-2.4.2-7034-g2f5deb8b8/debian/extras/99-maas-common-sudoers 2018-08-17 10:44:00.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/debian/extras/99-maas-common-sudoers 2019-11-01 09:09:16.000000000 -0400 @@ -11,9 +11,16 @@ maas ALL= NOPASSWD: /bin/systemctl start chrony maas ALL= NOPASSWD: /bin/systemctl status chrony +# Control of the DNS server: MAAS needs to track status, start it +# again if stopped manually, or if MAAS must brutally kill it! +maas ALL= NOPASSWD: /bin/systemctl status bind9 +maas ALL= NOPASSWD: /bin/systemctl start bind9 +maas ALL= NOPASSWD: /bin/systemctl kill -s SIGKILL bind9 + # Writing and deleting files as root. maas ALL= NOPASSWD: /usr/lib/maas/maas-delete-file maas ALL= NOPASSWD: /usr/lib/maas/maas-write-file # Allow active subnet mapping maas ALL= NOPASSWD: /usr/lib/maas/maas-common scan-network * +maas ALL= NOPASSWD: /usr/lib/maas/maas-common scan-network diff -Nru maas-2.4.2-7034-g2f5deb8b8/Makefile maas-2.4.3-7049-g000f1aa/Makefile --- maas-2.4.2-7034-g2f5deb8b8/Makefile 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/Makefile 2019-10-18 12:06:29.000000000 -0400 @@ -277,7 +277,9 @@ utilities/configure-lxd-profile utilities/create-lxd-bionic-image -test: bin/test.parallel bin/coverage +test: test-js test-py + +test-py: bin/test.parallel bin/coverage @$(RM) .coverage .coverage.* @bin/test.parallel --with-coverage --subprocess-per-core @bin/coverage combine diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/apiclient/maas_client.py maas-2.4.3-7049-g000f1aa/src/apiclient/maas_client.py --- maas-2.4.2-7034-g2f5deb8b8/src/apiclient/maas_client.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/apiclient/maas_client.py 2019-10-18 12:06:29.000000000 -0400 @@ -79,8 +79,14 @@ Be careful when changing its API: this class is designed so that it can be replaced with a Twisted-enabled alternative. See the MAAS provider in Juju for the code this would require. + + @ivar autodetect_proxies: Extract proxy information from the + environment variables (http_proxy, no_proxy). Default True """ + def __init__(self, autodetect_proxies=True): + self.autodetect_proxies = autodetect_proxies + def dispatch_query(self, request_url, headers, method="GET", data=None): """Synchronously dispatch an OAuth-signed request to L{request_url}. @@ -109,7 +115,11 @@ if data is not None and not isinstance(data, bytes): data = bytes(data, 'utf-8') req = RequestWithMethod(request_url, data, headers, method=method) - res = urllib.request.urlopen(req) + handlers = [] + if not self.autodetect_proxies: + handlers.append(urllib.request.ProxyHandler({})) + opener = urllib.request.build_opener(*handlers) + res = opener.open(req) # If we set the Accept-encoding header, then we decode the header for # the caller. is_gzip = ( diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/apiclient/tests/test_maas_client.py maas-2.4.3-7049-g000f1aa/src/apiclient/tests/test_maas_client.py --- maas-2.4.2-7034-g2f5deb8b8/src/apiclient/tests/test_maas_client.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/apiclient/tests/test_maas_client.py 2019-10-18 12:06:29.000000000 -0400 @@ -8,8 +8,13 @@ import gzip from io import BytesIO import json +import os from random import randint -from unittest.mock import ANY +from unittest.mock import ( + ANY, + MagicMock, + patch, +) import urllib.error import urllib.parse from urllib.parse import ( @@ -46,6 +51,22 @@ class TestMAASDispatcher(MAASTestCase): + def setUp(self): + super().setUp() + self.patch_urllib() + + def patch_urllib(self): + def patch_build_opener(*args, **kwargs): + self.opener = real_build_opener(*args, **kwargs) + self.orig_open_func = self.opener.open + if self.open_func is not None: + self.opener.open = self.open_func + return self.opener + + real_build_opener = urllib.request.build_opener + build_opener_mock = self.patch(urllib.request, "build_opener") + build_opener_mock.side_effect = patch_build_opener + self.open_func = None def test_dispatch_query_makes_direct_call(self): contents = factory.make_string().encode("ascii") @@ -56,13 +77,13 @@ def test_dispatch_query_encodes_string_data(self): # urllib, used by MAASDispatcher, requires data encoded into bytes. We # encode into utf-8 in dispatch_query if necessary. - request = self.patch(urllib.request.Request, '__init__') - urlopen = self.patch(urllib.request, 'urlopen') + request = self.patch(urllib.request.Request, "__init__") + self.patch_urllib() + self.open_func = lambda *args: MagicMock() url = factory.make_url() data = factory.make_string(300, spaces=True) MAASDispatcher().dispatch_query(url, {}, method="POST", data=data) request.assert_called_once_with(ANY, url, bytes(data, "utf-8"), ANY) - urlopen.assert_called_once_with(ANY) def test_request_from_http(self): # We can't just call self.make_file because HTTPServerFixture will only @@ -106,12 +127,12 @@ content = factory.make_string(300).encode('ascii') factory.make_file(location='.', name=name, contents=content) called = [] - orig_urllib = urllib.request.urlopen - def logging_urlopen(*args, **kwargs): + def logging_open(*args, **kwargs): called.append((args, kwargs)) - return orig_urllib(*args, **kwargs) - self.patch(urllib.request, 'urlopen', logging_urlopen) + return self.orig_open_func(*args, **kwargs) + + self.open_func = logging_open with HTTPServerFixture() as httpd: url = urljoin(httpd.url, name) res = MAASDispatcher().dispatch_query(url, {}) @@ -139,6 +160,44 @@ mode='rb', fileobj=BytesIO(raw_content)).read() self.assertEqual(content, read_content) + def test_autodetects_proxies(self): + self.open_func = lambda *args: MagicMock() + url = factory.make_url() + proxy_variables = { + "http_proxy": "http://proxy.example.com", + "https_proxy": "https://proxy.example.com", + "no_proxy": "noproxy.example.com", + } + with patch.dict(os.environ, proxy_variables): + MAASDispatcher().dispatch_query(url, {}, method="GET") + for handler in self.opener.handle_open["http"]: + if isinstance(handler, urllib.request.ProxyHandler): + break + else: + raise AssertionError("No ProxyHandler installed") + expected = { + "http": proxy_variables["http_proxy"], + "https": proxy_variables["https_proxy"], + "no": proxy_variables["no_proxy"], + } + for key, value in expected.items(): + self.assertEqual(value, handler.proxies[key]) + + def test_no_autodetects_proxies(self): + self.open_func = lambda *args: MagicMock() + url = factory.make_url() + proxy_variables = { + "http_proxy": "http://proxy.example.com", + "https_proxy": "https://proxy.example.com", + "no_proxy": "noproxy.example.com", + } + with patch.dict(os.environ, proxy_variables): + dispatcher = MAASDispatcher(autodetect_proxies=False) + dispatcher.dispatch_query(url, {}, method="GET") + for handler in self.opener.handle_open["http"]: + if isinstance(handler, urllib.request.ProxyHandler): + raise AssertionError("ProxyHandler shouldn't be there") + def make_path(): """Create an arbitrary resource path.""" diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/auth.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/auth.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/auth.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/auth.py 2019-10-18 12:06:29.000000000 -0400 @@ -69,17 +69,18 @@ if consumer and token: user = token.user - is_local_user = ( - user.username in SYSTEM_USERS or user.userprofile.is_local) - external_auth_info = request.external_auth_info - if external_auth_info: - if is_local_user: + if user.username not in SYSTEM_USERS: + external_auth_info = request.external_auth_info + is_local_user = user.userprofile.is_local + if external_auth_info: + if is_local_user: + return False + if not validate_user_external_auth( + user, + admin_group=external_auth_info.admin_group): + return False + elif not is_local_user: return False - if not validate_user_external_auth( - user, admin_group=external_auth_info.admin_group): - return False - elif not is_local_user: - return False request.user = user request.consumer = consumer diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/rackcontrollers.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/rackcontrollers.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/rackcontrollers.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/rackcontrollers.py 2019-10-18 12:06:29.000000000 -0400 @@ -72,6 +72,7 @@ 'other_test_status', 'other_test_status_name', 'hardware_info', + 'tag_names', ) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/regioncontrollers.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/regioncontrollers.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/regioncontrollers.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/regioncontrollers.py 2019-10-18 12:06:29.000000000 -0400 @@ -55,6 +55,7 @@ 'other_test_status', 'other_test_status_name', 'hardware_info', + 'tag_names', ) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_auth.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_auth.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_auth.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_auth.py 2019-10-18 12:06:29.000000000 -0400 @@ -12,6 +12,7 @@ from unittest import mock from django.contrib.auth.models import AnonymousUser +from maasserver.api import auth as api_auth from maasserver.api.auth import ( MAASAPIAuthentication, OAuthUnauthorized, @@ -21,6 +22,7 @@ from maasserver.testing.factory import factory from maasserver.testing.testcase import MAASServerTestCase from maastesting.testcase import MAASTestCase +from metadataserver.nodeinituser import get_node_init_user from oauth import oauth from testtools.matchers import Contains @@ -56,8 +58,8 @@ auth = MAASAPIAuthentication() self.assertTrue(auth.is_authenticated(request)) - @mock.patch('maasserver.api.auth.validate_user_external_auth') - def test_is_authenticated_external_auth_validate_user(self, mock_validate): + def test_is_authenticated_external_auth_validate_user(self): + mock_validate = self.patch(api_auth, 'validate_user_external_auth') mock_validate.return_value = True auth = MAASAPIAuthentication() @@ -72,8 +74,8 @@ self.assertTrue(auth.is_authenticated(request)) mock_validate.assert_called_with(user, admin_group='admins') - @mock.patch('maasserver.api.auth.validate_user_external_auth') - def test_is_authenticated_external_auth_validate_fail(self, mock_validate): + def test_is_authenticated_external_auth_validate_fail(self): + mock_validate = self.patch(api_auth, 'validate_user_external_auth') mock_validate.return_value = False auth = MAASAPIAuthentication() @@ -88,25 +90,41 @@ # check interval not expired, the user isn't checked mock_validate.assert_called_with(user, admin_group='admins') - @mock.patch('maasserver.api.auth.validate_user_external_auth') - def test_is_authenticated_external_auth_user_local(self, mock_validate): + def test_is_authenticated_external_auth_user_local(self): + mock_validate = self.patch(api_auth, 'validate_user_external_auth') mock_validate.return_value = True auth = MAASAPIAuthentication() user = factory.make_User() user.userprofile.is_local = True user.userprofile.save() - request = self.make_request() + request = self.make_request(user=user) self.assertFalse(auth.is_authenticated(request)) mock_validate.assert_not_called() + def test_is_authenticated_external_auth_system_user(self): + mock_validate = self.patch(api_auth, 'validate_user_external_auth') + mock_validate.return_value = True + + auth = MAASAPIAuthentication() + user = get_node_init_user() + request = self.make_request() + mock_token = mock.Mock(user=user) + auth.is_valid_request = lambda request: True + auth.validate_token = lambda request: (mock.Mock(), mock_token, None) + self.assertTrue(auth.is_authenticated(request)) + mock_validate.assert_not_called() + def test_is_authenticated_false_external_user_no_external_auth(self): Config.objects.set_config('external_auth_url', '') user = factory.make_User() user.userprofile.is_local = False user.userprofile.save() - request = self.make_request(user=user) + mock_token = mock.Mock(user=user) + request = self.make_request() auth = MAASAPIAuthentication() + auth.is_valid_request = lambda request: True + auth.validate_token = lambda request: (mock.Mock(), mock_token, None) self.assertFalse(auth.is_authenticated(request)) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_blockdevice.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_blockdevice.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_blockdevice.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_blockdevice.py 2019-10-18 12:06:29.000000000 -0400 @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Tests for blockdevice API.""" diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_rackcontroller.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_rackcontroller.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_rackcontroller.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_rackcontroller.py 2019-10-18 12:06:29.000000000 -0400 @@ -150,6 +150,7 @@ 'other_test_status', 'other_test_status_name', 'hardware_info', + 'tag_names', ], list(parsed_result[0])) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_raid.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_raid.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_raid.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_raid.py 2019-10-18 12:06:29.000000000 -0400 @@ -17,6 +17,7 @@ ) from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE from maasserver.models.filesystem import Filesystem +from maasserver.models.filesystemgroup import RAID_SUPERBLOCK_OVERHEAD from maasserver.models.partition import MIN_PARTITION_SIZE from maasserver.models.partitiontable import PARTITION_TABLE_EXTRA_SPACE from maasserver.testing.api import APITestCase @@ -29,6 +30,12 @@ Equals, ) +# Use the following block devices and partition sizes in these tests. This +# ensures that there will be enough space available to handle the partition +# table overhead and RAID overhead. +BLOCK_SIZE = MIN_BLOCK_DEVICE_SIZE * 2 +PART_SIZE = MIN_PARTITION_SIZE * 2 + def get_raid_devices_uri(node): """Return a Node's RAID devices URI on the API.""" @@ -117,7 +124,7 @@ bds = [ factory.make_PhysicalBlockDevice( node=node, - size=(MIN_BLOCK_DEVICE_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) + size=(BLOCK_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) for i in range(10) ] for bd in bds[5:]: @@ -127,7 +134,7 @@ for bd in bds[:5] ] partitions = [ - bd.get_partitiontable().add_partition(size=MIN_PARTITION_SIZE).id + bd.get_partitiontable().add_partition(size=PART_SIZE).id for bd in bds[5:] ] uuid4 = str(uuid.uuid4()) @@ -150,7 +157,7 @@ bds = [ factory.make_PhysicalBlockDevice( node=node, - size=(MIN_BLOCK_DEVICE_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) + size=(BLOCK_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) for i in range(10) ] for bd in bds[5:]: @@ -160,7 +167,7 @@ for bd in bds[:5] ] partitions = [ - bd.get_partitiontable().add_partition(size=MIN_PARTITION_SIZE).id + bd.get_partitiontable().add_partition(size=PART_SIZE).id for bd in bds[5:] ] uuid4 = str(uuid.uuid4()) @@ -185,7 +192,7 @@ bds = [ factory.make_PhysicalBlockDevice( node=node, - size=(MIN_BLOCK_DEVICE_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) + size=(BLOCK_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) for i in range(10) ] for bd in bds[5:]: @@ -195,7 +202,7 @@ for bd in bds[:5] ] partitions = [ - bd.get_partitiontable().add_partition(size=MIN_PARTITION_SIZE).id + bd.get_partitiontable().add_partition(size=PART_SIZE).id for bd in bds[5:] ] uuid4 = str(uuid.uuid4()) @@ -216,7 +223,9 @@ (parsed_block_devices, parsed_partitions, parsed_block_device_spares, parsed_partition_spares) = ( get_devices_from_raid(parsed_device)) - self.assertEqual(10 * MIN_PARTITION_SIZE, parsed_device['size']) + self.assertEqual( + (10 * PART_SIZE) - RAID_SUPERBLOCK_OVERHEAD, + parsed_device['size']) self.assertItemsEqual(uuid4, parsed_device['uuid']) self.assertItemsEqual(block_devices, parsed_block_devices) self.assertItemsEqual(partitions, parsed_partitions) @@ -232,7 +241,7 @@ bds = [ factory.make_PhysicalBlockDevice( node=node, - size=(MIN_BLOCK_DEVICE_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) + size=(BLOCK_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) for i in range(10) ] for bd in bds[5:]: @@ -243,7 +252,7 @@ if bd.get_partitiontable() is None ] partitions = [ - bd.get_partitiontable().add_partition(size=MIN_PARTITION_SIZE).id + bd.get_partitiontable().add_partition(size=PART_SIZE).id for bd in bds[5:] ] uuid4 = factory.make_UUID() @@ -268,7 +277,7 @@ bds = [ factory.make_PhysicalBlockDevice( node=node, - size=(MIN_BLOCK_DEVICE_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) + size=(BLOCK_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) for i in range(10) ] for bd in bds[5:]: @@ -278,7 +287,7 @@ for bd in bds[:5] ] partitions = [ - bd.get_partitiontable().add_partition(size=MIN_PARTITION_SIZE).id + bd.get_partitiontable().add_partition(size=PART_SIZE).id for bd in bds[5:] ] uuid4 = str(uuid.uuid4()) @@ -299,7 +308,9 @@ (parsed_block_devices, parsed_partitions, parsed_block_device_spares, parsed_partition_spares) = ( get_devices_from_raid(parsed_device)) - self.assertEqual(MIN_PARTITION_SIZE, parsed_device['size']) + self.assertEqual( + PART_SIZE - RAID_SUPERBLOCK_OVERHEAD, + parsed_device['size']) self.assertItemsEqual(uuid4, parsed_device['uuid']) self.assertItemsEqual(block_devices, parsed_block_devices) self.assertItemsEqual(partitions, parsed_partitions) @@ -314,13 +325,13 @@ bds = [ factory.make_PhysicalBlockDevice( node=node, - size=(MIN_BLOCK_DEVICE_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) + size=(BLOCK_SIZE * 2) + PARTITION_TABLE_EXTRA_SPACE) for i in range(10) ] for bd in bds[5:]: factory.make_PartitionTable(block_device=bd) large_partitions = [ - bd.get_partitiontable().add_partition(size=MIN_PARTITION_SIZE) + bd.get_partitiontable().add_partition(size=PART_SIZE) for bd in bds[5:] ] block_devices = [ @@ -351,7 +362,9 @@ (parsed_block_devices, parsed_partitions, parsed_block_device_spares, parsed_partition_spares) = ( get_devices_from_raid(parsed_device)) - self.assertEqual(MIN_PARTITION_SIZE, parsed_device['size']) + self.assertEqual( + PART_SIZE - RAID_SUPERBLOCK_OVERHEAD, + parsed_device['size']) self.assertItemsEqual(uuid4, parsed_device['uuid']) self.assertItemsEqual(block_devices, parsed_block_devices) self.assertItemsEqual(partitions, parsed_partitions) @@ -406,7 +419,9 @@ parsed_block_device_spares, parsed_partition_spares) = ( get_devices_from_raid(parsed_device)) # Size is equivalent to 7 of the smallest device (the partitions). - self.assertEqual(7 * large_partitions[0].size, parsed_device['size']) + self.assertEqual( + (7 * large_partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + parsed_device['size']) self.assertItemsEqual(block_devices, parsed_block_devices) self.assertItemsEqual(partitions, parsed_partitions) self.assertItemsEqual(spare_devices, parsed_block_device_spares) @@ -453,7 +468,9 @@ parsed_block_device_spares, parsed_partition_spares) = ( get_devices_from_raid(parsed_device)) # Size is equivalent to 6 of the smallest device (the partitions). - self.assertEqual(6 * large_partitions[0].size, parsed_device['size']) + self.assertEqual( + (6 * large_partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + parsed_device['size']) self.assertItemsEqual(block_devices, parsed_block_devices) self.assertItemsEqual(partitions, parsed_partitions) self.assertItemsEqual(spare_devices, parsed_block_device_spares) @@ -500,7 +517,9 @@ parsed_block_device_spares, parsed_partition_spares) = ( get_devices_from_raid(parsed_device)) # Size is equivalent to 4 of the smallest device (the partitions). - self.assertEqual(4 * large_partitions[0].size, parsed_device['size']) + self.assertEqual( + (4 * large_partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + parsed_device['size']) self.assertItemsEqual(block_devices, parsed_block_devices) self.assertItemsEqual(partitions, parsed_partitions) self.assertItemsEqual(spare_devices, parsed_block_device_spares) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_regioncontroller.py maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_regioncontroller.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/api/tests/test_regioncontroller.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/api/tests/test_regioncontroller.py 2019-10-18 12:06:29.000000000 -0400 @@ -97,5 +97,6 @@ 'other_test_status', 'other_test_status_name', 'hardware_info', + 'tag_names', ], list(parsed_result[0])) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/dns/config.py maas-2.4.3-7049-g000f1aa/src/maasserver/dns/config.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/dns/config.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/dns/config.py 2019-10-18 12:06:29.000000000 -0400 @@ -42,7 +42,7 @@ DNSPublication(source="Force reload").save() -def dns_update_all_zones(reload_retry=False): +def dns_update_all_zones(reload_retry=False, reload_timeout=2): """Update all zone files for all domains. Serving these zone files means updating BIND's configuration to include @@ -84,12 +84,12 @@ # actually needed but it seems safer to maintain this behaviour until we # have a better understanding. if reload_retry: - bind_reload_with_retries() + reloaded = bind_reload_with_retries(timeout=reload_timeout) else: - bind_reload() + reloaded = bind_reload(timeout=reload_timeout) # Return the current serial and list of domain names. - return serial, [ + return serial, reloaded, [ domain.name for domain in domains ] diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/dns/tests/test_config.py maas-2.4.3-7049-g000f1aa/src/maasserver/dns/tests/test_config.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/dns/tests/test_config.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/dns/tests/test_config.py 2019-10-18 12:06:29.000000000 -0400 @@ -53,6 +53,7 @@ Contains, Equals, FileContains, + Is, MatchesSetwise, MatchesStructure, ) @@ -255,7 +256,8 @@ bind_reload_with_retries = self.patch_autospec( dns_config_module, "bind_reload_with_retries") dns_update_all_zones(reload_retry=True) - self.assertThat(bind_reload_with_retries, MockCalledOnceWith()) + self.assertThat( + bind_reload_with_retries, MockCalledOnceWith(timeout=2)) def test_dns_update_all_zones_passes_upstream_dns_parameter(self): self.patch(settings, 'DNS_CONNECT', True) @@ -314,8 +316,9 @@ self.patch( dns_config_module, "current_zone_serial").return_value = fake_serial - serial, domains = dns_update_all_zones() + serial, reloaded, domains = dns_update_all_zones() self.assertThat(serial, Equals(fake_serial)) + self.assertThat(reloaded, Is(True)) self.assertThat(domains, MatchesSetwise(*[ Equals(domain.name) for domain in Domain.objects.filter(authoritative=True) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/__init__.py maas-2.4.3-7049-g000f1aa/src/maasserver/forms/__init__.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/__init__.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/forms/__init__.py 2019-10-18 12:06:29.000000000 -0400 @@ -111,6 +111,7 @@ BOOT_RESOURCE_TYPE, CACHE_MODE_TYPE_CHOICES, FILESYSTEM_FORMAT_TYPE_CHOICES, + FILESYSTEM_FORMAT_TYPE_CHOICES_DICT, FILESYSTEM_GROUP_RAID_TYPE_CHOICES, FILESYSTEM_TYPE, INTERFACE_TYPE, @@ -2577,10 +2578,18 @@ This implementation of `save` does not support the `commit` argument. """ - # Remove the previous format if one already exists. - Filesystem.objects.filter( + filesystem = Filesystem.objects.filter( block_device=self.block_device, - acquired=self.node.is_in_allocated_state()).delete() + acquired=self.node.is_in_allocated_state()).first() + if (filesystem is not None and + filesystem.fstype not in FILESYSTEM_FORMAT_TYPE_CHOICES_DICT): + raise ValidationError( + "Cannot format a block device that has a filesystem " + "type of %s." % filesystem.fstype) + + # Remove the previous format if one already exists. + if filesystem is not None: + filesystem.delete() # Create the new filesystem Filesystem.objects.create( @@ -2640,10 +2649,18 @@ This implementation of `save` does not support the `commit` argument. """ - # Remove the previous format if one already exists. - Filesystem.objects.filter( + filesystem = Filesystem.objects.filter( partition=self.partition, - acquired=self.node.is_in_allocated_state()).delete() + acquired=self.node.is_in_allocated_state()).first() + if (filesystem is not None and + filesystem.fstype not in FILESYSTEM_FORMAT_TYPE_CHOICES_DICT): + raise ValidationError( + "Cannot format a partition that has a filesystem " + "type of %s." % filesystem.fstype) + + # Remove the previous format if one already exists. + if filesystem is not None: + filesystem.delete() # Create the new filesystem Filesystem.objects.create( diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/tests/test_blockdevice.py maas-2.4.3-7049-g000f1aa/src/maasserver/forms/tests/test_blockdevice.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/tests/test_blockdevice.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/forms/tests/test_blockdevice.py 2019-10-18 12:06:29.000000000 -0400 @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Tests for all forms that are used with `BlockDevice`.""" @@ -8,6 +8,7 @@ import random import uuid +from django.core.exceptions import ValidationError from maasserver.enum import FILESYSTEM_TYPE from maasserver.forms import ( CreatePhysicalBlockDeviceForm, @@ -109,6 +110,16 @@ "Should be invalid because of an invalid uuid.") self.assertEqual({'uuid': ["Enter a valid value."]}, form._errors) + def test_is_not_valid_if_non_user_format_fstype(self): + block_device = factory.make_PhysicalBlockDevice() + factory.make_Filesystem( + fstype='bcache-backing', block_device=block_device) + data = { + 'fstype': FILESYSTEM_TYPE.EXT4, + } + form = FormatBlockDeviceForm(block_device, data=data) + self.assertRaises(ValidationError, form.save) + def test_creates_filesystem(self): fsuuid = "%s" % uuid.uuid4() fstype = factory.pick_filesystem_type() diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/tests/test_partition.py maas-2.4.3-7049-g000f1aa/src/maasserver/forms/tests/test_partition.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/tests/test_partition.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/forms/tests/test_partition.py 2019-10-18 12:06:29.000000000 -0400 @@ -1,4 +1,4 @@ -# Copyright 2015-2016 Canonical Ltd. This software is licensed under the +# Copyright 2015-2018 Canonical Ltd. This software is licensed under the # GNU Affero General Public License version 3 (see the file LICENSE). """Tests for all forms that are used with `Partition`.""" @@ -7,6 +7,7 @@ import uuid +from django.core.exceptions import ValidationError from maasserver.enum import FILESYSTEM_TYPE from maasserver.forms import ( AddPartitionForm, @@ -159,6 +160,15 @@ ], }, form._errors) + def test_is_not_valid_if_non_user_format_fstype(self): + partition = factory.make_Partition() + factory.make_Filesystem(fstype='bcache-backing', partition=partition) + data = { + 'fstype': FILESYSTEM_TYPE.EXT4, + } + form = FormatPartitionForm(partition, data=data) + self.assertRaises(ValidationError, form.save) + def test_creates_filesystem(self): fsuuid = "%s" % uuid.uuid4() fstype = factory.pick_filesystem_type() diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/tests/test_raid.py maas-2.4.3-7049-g000f1aa/src/maasserver/forms/tests/test_raid.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/forms/tests/test_raid.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/forms/tests/test_raid.py 2019-10-18 12:06:29.000000000 -0400 @@ -16,7 +16,10 @@ CreateRaidForm, UpdateRaidForm, ) -from maasserver.models.filesystemgroup import RAID +from maasserver.models.filesystemgroup import ( + RAID, + RAID_SUPERBLOCK_OVERHEAD, +) from maasserver.testing.factory import factory from maasserver.testing.testcase import MAASServerTestCase @@ -130,7 +133,9 @@ self.assertTrue(form.is_valid(), form.errors) raid = form.save() self.assertEqual('md1', raid.name) - self.assertEqual(8 * partition_objs[0].size, raid.get_size()) + self.assertEqual( + (8 * partition_objs[0].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) self.assertEqual(FILESYSTEM_GROUP_TYPE.RAID_6, raid.group_type) self.assertItemsEqual( block_devices, @@ -181,7 +186,9 @@ self.assertTrue(form.is_valid(), form.errors) raid = form.save() self.assertEqual('md1', raid.name) - self.assertEqual(8 * partitions[0].size, raid.get_size()) + self.assertEqual( + (8 * partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) self.assertEqual(FILESYSTEM_GROUP_TYPE.RAID_6, raid.group_type) self.assertItemsEqual( block_devices_ids, diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/management/commands/configauth.py maas-2.4.3-7049-g000f1aa/src/maasserver/management/commands/configauth.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/management/commands/configauth.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/management/commands/configauth.py 2019-10-18 12:06:29.000000000 -0400 @@ -8,6 +8,7 @@ import json import attr +from django.contrib.sessions.models import Session from django.core.exceptions import ValidationError from django.core.management.base import ( BaseCommand, @@ -85,6 +86,10 @@ 'external_auth_admin_group', auth_details.admin_group) +def clear_user_sessions(): + Session.objects.all().delete() + + class Command(BaseCommand): help = "Configure external authentication." @@ -115,6 +120,7 @@ "Group of users whose members are made admins in MAAS " "(leave blank for empty): ") set_auth_config(config_manager, auth_details) + clear_user_sessions() return auth_details.url = options.get('idm_url') @@ -141,6 +147,7 @@ "(leave blank for empty): ") set_auth_config(config_manager, auth_details) + clear_user_sessions() def _get_or_prompt(options, option, message, replace_none=False): diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/filesystemgroup.py maas-2.4.3-7049-g000f1aa/src/maasserver/models/filesystemgroup.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/filesystemgroup.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/models/filesystemgroup.py 2019-10-18 12:06:29.000000000 -0400 @@ -40,6 +40,9 @@ # Size of LVM physical extent. 4MiB LVM_PE_SIZE = 4 * 1024 * 1024 +# Size of the RAID overhead. 128k is maximum used. +RAID_SUPERBLOCK_OVERHEAD = 128 * 1024 + class BaseFilesystemGroupManager(Manager): """A utility to manage the collection of FilesystemGroup.""" @@ -397,9 +400,11 @@ # Possible when no filesytems are attached to this group. return 0 elif self.group_type == FILESYSTEM_GROUP_TYPE.RAID_0: - return min_size * self.filesystems.count() + return ( + (min_size * self.filesystems.count()) - + RAID_SUPERBLOCK_OVERHEAD) elif self.group_type == FILESYSTEM_GROUP_TYPE.RAID_1: - return min_size + return min_size - RAID_SUPERBLOCK_OVERHEAD else: num_raid = len([ fstype @@ -407,11 +412,14 @@ if fstype == FILESYSTEM_TYPE.RAID ]) if self.group_type == FILESYSTEM_GROUP_TYPE.RAID_5: - return min_size * (num_raid - 1) + return ( + (min_size * (num_raid - 1)) - RAID_SUPERBLOCK_OVERHEAD) elif self.group_type == FILESYSTEM_GROUP_TYPE.RAID_6: - return min_size * (num_raid - 2) + return ( + (min_size * (num_raid - 2)) - RAID_SUPERBLOCK_OVERHEAD) elif self.group_type == FILESYSTEM_GROUP_TYPE.RAID_10: - return min_size * num_raid / 2 + return ( + (min_size * num_raid / 2) - RAID_SUPERBLOCK_OVERHEAD) raise ValidationError("Unknown raid type: %s" % self.group_type) def get_bcache_backing_filesystem(self): diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/interface.py maas-2.4.3-7049-g000f1aa/src/maasserver/models/interface.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/interface.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/models/interface.py 2019-10-18 12:06:29.000000000 -0400 @@ -724,6 +724,10 @@ StaticIPAddress.objects.filter( interface=self, alloc_type=IPADDRESS_TYPE.DISCOVERED).delete() + # Keep track of which subnets were found, in order to avoid linking + # duplicates. + created_on_subnets = set() + # Sort the cidr list by prefixlen. for ip in sorted(cidr_list, key=lambda x: int(x.split('/')[1])): network = IPNetwork(ip) @@ -822,20 +826,41 @@ " on " + node.fqdn if node is not None else '') prev_address.delete() - # XXX lamont 2016-11-03 Bug#1639090 # At the moment, IPv6 autoconf (SLAAC) is required so that we get # the correct subnet block created above. However, if we add SLAAC # addresses to the DB, then we wind up creating 2 autoassigned # addresses on the interface. We need to discuss how to model them # and incorporate the change for 2.2. For now, just drop them with # prejudice. (Bug#1639288) - if address != str(self._eui64_address(subnet.cidr)): - # Create the newly discovered IP address. - new_address = StaticIPAddress( - alloc_type=IPADDRESS_TYPE.DISCOVERED, ip=address, - subnet=subnet) - new_address.save() - self.ip_addresses.add(new_address) + if address == str(self._eui64_address(subnet.cidr)): + maaslog.warning( + "IP address (%s)%s was skipped because " + "it is an EUI-64 (SLAAC) address.", + address, + " on " + self.node.fqdn if self.node is not None else '') + continue + + # Remember which subnets we created addresses on; we don't want to + # link more than one address per subnet in case of a duplicate. + # Duplicates could happen, in theory, if the IP address configured + # in the preboot environment differs from the IP address acquired + # by the DHCP client. See bug #1803188. + if subnet in created_on_subnets: + maaslog.warning( + "IP address (%s)%s was skipped because it was found on " + "the same subnet as a previous address: %s.", + address, + " on " + self.node.fqdn if self.node is not None else '', + network) + continue + + # Create the newly discovered IP address. + new_address = StaticIPAddress( + alloc_type=IPADDRESS_TYPE.DISCOVERED, ip=address, + subnet=subnet) + new_address.save() + self.ip_addresses.add(new_address) + created_on_subnets.add(subnet) def _eui64_address(self, net_cidr): """Return the SLAAC address for this interface.""" diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/partition.py maas-2.4.3-7049-g000f1aa/src/maasserver/models/partition.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/partition.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/models/partition.py 2019-10-18 12:06:29.000000000 -0400 @@ -197,7 +197,12 @@ if (arch == "ppc64el" and block_device.id == boot_disk.id): return idx + 2 elif arch == "amd64" and bios_boot_method != "uefi": - return idx + 2 + # Delay the `type` check because it can cause a query. Only + # physical block devices get the bios_grub partition. + if block_device.type == 'physical': + return idx + 2 + else: + return idx + 1 else: return idx + 1 elif self.partition_table.table_type == PARTITION_TABLE_TYPE.MBR: diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/tests/test_filesystemgroup.py maas-2.4.3-7049-g000f1aa/src/maasserver/models/tests/test_filesystemgroup.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/tests/test_filesystemgroup.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/models/tests/test_filesystemgroup.py 2019-10-18 12:06:29.000000000 -0400 @@ -31,6 +31,7 @@ FilesystemGroup, LVM_PE_SIZE, RAID, + RAID_SUPERBLOCK_OVERHEAD, RAIDManager, VolumeGroup, VolumeGroupManager, @@ -687,7 +688,8 @@ group_type=FILESYSTEM_GROUP_TYPE.RAID_0, filesystems=filesystems) # Size should be twice the smallest device (the rest of the larger # device remains unused. - self.assertEqual(small_size * 2, fsgroup.get_size()) + self.assertEqual( + (small_size * 2) - RAID_SUPERBLOCK_OVERHEAD, fsgroup.get_size()) def test_get_size_returns_smallest_disk_size_for_raid_1(self): node = factory.make_Node() @@ -706,7 +708,8 @@ ] fsgroup = factory.make_FilesystemGroup( group_type=FILESYSTEM_GROUP_TYPE.RAID_1, filesystems=filesystems) - self.assertEqual(small_size, fsgroup.get_size()) + self.assertEqual( + small_size - RAID_SUPERBLOCK_OVERHEAD, fsgroup.get_size()) def test_get_size_returns_correct_disk_size_for_raid_5(self): node = factory.make_Node() @@ -736,7 +739,8 @@ fsgroup = factory.make_FilesystemGroup( group_type=FILESYSTEM_GROUP_TYPE.RAID_5, filesystems=filesystems) self.assertEqual( - small_size * number_of_raid_devices, fsgroup.get_size()) + (small_size * number_of_raid_devices) - RAID_SUPERBLOCK_OVERHEAD, + fsgroup.get_size()) def test_get_size_returns_correct_disk_size_for_raid_6(self): node = factory.make_Node() @@ -766,7 +770,8 @@ fsgroup = factory.make_FilesystemGroup( group_type=FILESYSTEM_GROUP_TYPE.RAID_6, filesystems=filesystems) self.assertEqual( - small_size * (number_of_raid_devices - 1), fsgroup.get_size()) + (small_size * (number_of_raid_devices - 1)) - ( + RAID_SUPERBLOCK_OVERHEAD), fsgroup.get_size()) @skip("XXX: GavinPanella 2015-12-04 bug=1522965: Fails spuriously.") def test_get_size_returns_correct_disk_size_for_raid_10(self): @@ -797,7 +802,8 @@ fsgroup = factory.make_FilesystemGroup( group_type=FILESYSTEM_GROUP_TYPE.RAID_10, filesystems=filesystems) self.assertEqual( - small_size * (number_of_raid_devices + 1) // 2, fsgroup.get_size()) + (small_size * (number_of_raid_devices + 1) // 2) - ( + RAID_SUPERBLOCK_OVERHEAD), fsgroup.get_size()) def test_get_size_returns_0_if_bcache_without_backing(self): fsgroup = FilesystemGroup(group_type=FILESYSTEM_GROUP_TYPE.BCACHE) @@ -1612,7 +1618,9 @@ spare_devices=[spare_block_device], spare_partitions=[spare_partition]) self.assertEqual('md0', raid.name) - self.assertEqual(6 * partitions[1].size, raid.get_size()) + self.assertEqual( + (6 * partitions[1].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) self.assertEqual(FILESYSTEM_GROUP_TYPE.RAID_6, raid.group_type) self.assertEqual(uuid, raid.uuid) self.assertEqual(10, raid.filesystems.count()) @@ -1702,7 +1710,8 @@ spare_devices=[spare_block_device], spare_partitions=[spare_partition]) self.assertEqual('md0', raid.name) - self.assertEqual(partitions[1].size, raid.get_size()) + self.assertEqual( + partitions[1].size - RAID_SUPERBLOCK_OVERHEAD, raid.get_size()) self.assertEqual(FILESYSTEM_GROUP_TYPE.RAID_1, raid.group_type) self.assertEqual(uuid, raid.uuid) self.assertEqual(10, raid.filesystems.count()) @@ -1754,7 +1763,9 @@ spare_devices=[spare_block_device], spare_partitions=[spare_partition]) self.assertEqual('md0', raid.name) - self.assertEqual(7 * partitions[1].size, raid.get_size()) + self.assertEqual( + (7 * partitions[1].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) self.assertEqual(FILESYSTEM_GROUP_TYPE.RAID_5, raid.group_type) self.assertEqual(uuid, raid.uuid) self.assertEqual(10, raid.filesystems.count()) @@ -1868,7 +1879,9 @@ device = factory.make_PhysicalBlockDevice(node=node, size=device_size) raid.add_device(device, FILESYSTEM_TYPE.RAID) self.assertEqual(11, raid.filesystems.count()) - self.assertEqual(10 * device_size, raid.get_size()) + self.assertEqual( + (10 * device_size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_add_spare_device_to_array(self): node = factory.make_Node() @@ -1886,7 +1899,9 @@ device = factory.make_PhysicalBlockDevice(node=node, size=device_size) raid.add_device(device, FILESYSTEM_TYPE.RAID_SPARE) self.assertEqual(11, raid.filesystems.count()) - self.assertEqual(9 * device_size, raid.get_size()) + self.assertEqual( + (9 * device_size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_add_partition_to_array(self): node = factory.make_Node() @@ -1906,7 +1921,9 @@ node=node, size=device_size)).add_partition() raid.add_partition(partition, FILESYSTEM_TYPE.RAID) self.assertEqual(11, raid.filesystems.count()) - self.assertEqual(10 * partition.size, raid.get_size()) + self.assertEqual( + (10 * partition.size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_add_spare_partition_to_array(self): node = factory.make_Node() @@ -1926,7 +1943,9 @@ node=node, size=device_size)).add_partition() raid.add_partition(partition, FILESYSTEM_TYPE.RAID_SPARE) self.assertEqual(11, raid.filesystems.count()) - self.assertEqual(9 * partition.size, raid.get_size()) + self.assertEqual( + (9 * partition.size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_add_device_from_another_node_to_array_fails(self): node = factory.make_Node() @@ -1951,7 +1970,8 @@ "array.']")): raid.add_device(device, FILESYSTEM_TYPE.RAID) self.assertEqual(10, raid.filesystems.count()) # Still 10 devices - self.assertEqual(9 * device_size, raid.get_size()) + self.assertEqual( + (9 * device_size) - RAID_SUPERBLOCK_OVERHEAD, raid.get_size()) def test_add_partition_from_another_node_to_array_fails(self): node = factory.make_Node() @@ -1977,7 +1997,8 @@ "the rest of the array.']")): raid.add_partition(partition, FILESYSTEM_TYPE.RAID) self.assertEqual(10, raid.filesystems.count()) # Nothing added - self.assertEqual(9 * device_size, raid.get_size()) + self.assertEqual( + (9 * device_size) - RAID_SUPERBLOCK_OVERHEAD, raid.get_size()) def test_add_already_used_device_to_array_fails(self): node = factory.make_Node() @@ -2002,7 +2023,8 @@ "['There is another filesystem on this device.']")): raid.add_device(device, FILESYSTEM_TYPE.RAID) self.assertEqual(10, raid.filesystems.count()) # Nothing added. - self.assertEqual(9 * device_size, raid.get_size()) + self.assertEqual( + (9 * device_size) - RAID_SUPERBLOCK_OVERHEAD, raid.get_size()) def test_remove_device_from_array_invalidates_array_fails(self): """Checks it's not possible to remove a device from an RAID in such way @@ -2029,7 +2051,9 @@ "devices and any number of spares.']}")): raid.remove_device(block_devices[0]) self.assertEqual(4, raid.filesystems.count()) - self.assertEqual(2 * device_size, raid.get_size()) + self.assertEqual( + (2 * device_size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) # Ensure the filesystems are the exact same before and after. self.assertItemsEqual( fsids_before, [fs.id for fs in raid.filesystems.all()]) @@ -2062,7 +2086,9 @@ "devices and any number of spares.']}")): raid.remove_partition(partitions[0]) self.assertEqual(4, raid.filesystems.count()) - self.assertEqual(2 * partitions[0].size, raid.get_size()) + self.assertEqual( + (2 * partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) # Ensure the filesystems are the exact same before and after. self.assertItemsEqual( fsids_before, [fs.id for fs in raid.filesystems.all()]) @@ -2083,7 +2109,9 @@ spare_devices=block_devices[-2:]) raid.remove_device(block_devices[0]) self.assertEqual(9, raid.filesystems.count()) - self.assertEqual(6 * device_size, raid.get_size()) + self.assertEqual( + (6 * device_size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_remove_partition_from_array(self): node = factory.make_Node() @@ -2103,7 +2131,9 @@ spare_partitions=partitions[-2:]) raid.remove_partition(partitions[0]) self.assertEqual(9, raid.filesystems.count()) - self.assertEqual(6 * partitions[0].size, raid.get_size()) + self.assertEqual( + (6 * partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_remove_invalid_partition_from_array_fails(self): node = factory.make_Node(bios_boot_method="uefi") @@ -2130,7 +2160,9 @@ block_device=factory.make_PhysicalBlockDevice( node=node, size=device_size)).add_partition()) self.assertEqual(10, raid.filesystems.count()) - self.assertEqual(9 * partitions[0].size, raid.get_size()) + self.assertEqual( + (9 * partitions[0].size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) def test_remove_device_from_array_fails(self): node = factory.make_Node() @@ -2151,7 +2183,9 @@ raid.remove_device( factory.make_PhysicalBlockDevice(node=node, size=device_size)) self.assertEqual(10, raid.filesystems.count()) - self.assertEqual(9 * device_size, raid.get_size()) + self.assertEqual( + (9 * device_size) - RAID_SUPERBLOCK_OVERHEAD, + raid.get_size()) class TestBcache(MAASServerTestCase): @@ -2215,7 +2249,8 @@ cache_mode=CACHE_MODE_TYPE.WRITEAROUND) # Verify the filesystems were properly created on the target devices - self.assertEqual(10 * backing_size, bcache.get_size()) + self.assertEqual( + (10 * backing_size) - RAID_SUPERBLOCK_OVERHEAD, bcache.get_size()) self.assertEqual( FILESYSTEM_TYPE.BCACHE_CACHE, cache_device.get_effective_filesystem().fstype) diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/tests/test_interface.py maas-2.4.3-7049-g000f1aa/src/maasserver/models/tests/test_interface.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/tests/test_interface.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/models/tests/test_interface.py 2019-10-18 12:06:29.000000000 -0400 @@ -1912,6 +1912,24 @@ self.assertEqual(0, iface.ip_addresses.count()) self.assertEqual(1, Subnet.objects.filter(cidr=network.cidr).count()) + def test__does_not_add_addresses_from_duplicate_subnet(self): + # See also LP#1803188. + mac_address = factory.make_MAC() + vlan = factory.make_VLAN() + factory.make_Subnet(cidr="10.0.0.0/8", vlan=vlan) + factory.make_Subnet(cidr="2001::/64", vlan=vlan) + node = factory.make_Node() + iface = factory.make_Interface( + INTERFACE_TYPE.PHYSICAL, mac_address=mac_address, vlan=vlan, + node=node) + iface.update_ip_addresses([ + "10.0.0.1/8", + "10.0.0.2/8", + "2001::1/64", + "2001::2/64", + ]) + self.assertEqual(2, iface.ip_addresses.count()) + def test__finds_ipv6_subnet_regardless_of_order(self): iface = factory.make_Interface(INTERFACE_TYPE.PHYSICAL) network = factory.make_ipv6_network() diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/tests/test_virtualblockdevice.py maas-2.4.3-7049-g000f1aa/src/maasserver/models/tests/test_virtualblockdevice.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/models/tests/test_virtualblockdevice.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/models/tests/test_virtualblockdevice.py 2019-10-18 12:06:29.000000000 -0400 @@ -12,6 +12,7 @@ from django.core.exceptions import ValidationError from maasserver.enum import FILESYSTEM_GROUP_TYPE from maasserver.models.blockdevice import MIN_BLOCK_DEVICE_SIZE +from maasserver.models.filesystemgroup import RAID_SUPERBLOCK_OVERHEAD from maasserver.models.virtualblockdevice import VirtualBlockDevice from maasserver.testing.factory import factory from maasserver.testing.testcase import MAASServerTestCase @@ -77,7 +78,8 @@ # filesystems in it. array_size = new_size * filesystem_group.filesystems.count() self.assertEqual( - array_size, reload_object(filesystem_group.virtual_device).size) + array_size - RAID_SUPERBLOCK_OVERHEAD, + reload_object(filesystem_group.virtual_device).size) def test_create_or_update_for_bcache_updates_block_device(self): # This will create the filesystem group and a virtual block device. diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/region_controller.py maas-2.4.3-7049-g000f1aa/src/maasserver/region_controller.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/region_controller.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/region_controller.py 2019-10-18 12:06:29.000000000 -0400 @@ -26,6 +26,7 @@ from maasserver.dns.config import dns_update_all_zones from maasserver.models.dnspublication import DNSPublication from maasserver.proxyconfig import proxy_update_config +from maasserver.service_monitor import service_monitor from maasserver.utils.orm import transactional from maasserver.utils.threads import deferToDatabase from provisioningserver.logger import LegacyLogger @@ -60,7 +61,7 @@ See module documentation for more details. """ - def __init__(self, postgresListener, clock=reactor): + def __init__(self, postgresListener, clock=reactor, retryOnFailure=True): """Initialise a new `RegionControllerService`. :param postgresListener: The `PostgresListenerService` that is running @@ -68,6 +69,7 @@ """ super(RegionControllerService, self).__init__() self.clock = clock + self.retryOnFailure = retryOnFailure self.processing = LoopingCall(self.process) self.processing.clock = self.clock self.processingDefer = None @@ -118,12 +120,27 @@ def process(self): """Process the DNS and/or proxy update.""" + + def _onFailureRetry(failure, attr): + """Retry update on failure. + + Doesn't mask the failure, the failure is still raised. + """ + if self.retryOnFailure: + setattr(self, attr, True) + return failure + defers = [] if self.needsDNSUpdate: self.needsDNSUpdate = False d = deferToDatabase(transactional(dns_update_all_zones)) d.addCallback(self._checkSerial) d.addCallback(self._logDNSReload) + # Order here matters, first needsDNSUpdate is set then pass the + # failure onto `_onDNSReloadFailure` to do the correct thing + # with the DNS server. + d.addErrback(_onFailureRetry, 'needsDNSUpdate') + d.addErrback(self._onDNSReloadFailure) d.addErrback( log.err, "Failed configuring DNS.") @@ -150,7 +167,10 @@ """Check that the serial of the domain is updated.""" if result is None: return None - serial, domain_names = result + serial, reloaded, domain_names = result + if not reloaded: + raise DNSReloadError( + "Failed to reload DNS; timeout or rdnc command failed.") not_matching_domains = set(domain_names) loop = 0 while len(not_matching_domains) > 0 and loop != 30: @@ -173,13 +193,13 @@ raise DNSReloadError( "Failed to reload DNS; serial mismatch " "on domains %s" % ', '.join(not_matching_domains)) - return serial, domain_names + return result def _logDNSReload(self, result): """Log the reason DNS was reloaded.""" if result is None: return None - serial, domain_names = result + serial, _, domain_names = result if self.previousSerial is None: # This was the first load for starting the service. self.previousSerial = serial @@ -190,7 +210,10 @@ # reason for the reload. def _logReason(reasons): - if len(reasons) == 1: + if len(reasons) == 0: + msg = ( + "Reloaded DNS configuration; previous failure (retry)") + elif len(reasons) == 1: msg = "Reloaded DNS configuration; %s" % reasons[0] else: msg = 'Reloaded DNS configuration: \n' + '\n'.join( @@ -207,6 +230,16 @@ self.previousSerial = serial return d + def _onDNSReloadFailure(self, failure): + """Force kill and restart bind9.""" + failure.trap(DNSReloadError) + if not self.retryOnFailure: + return failure + log.err(failure, "Failed configuring DNS; killing and restarting") + d = service_monitor.killService('bind9') + d.addErrback(log.err, "Failed to kill and restart DNS.") + return d + @transactional def _getReloadReasons(self, previousSerial, currentSerial): return [ diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/service_monitor.py maas-2.4.3-7049-g000f1aa/src/maasserver/service_monitor.py --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/service_monitor.py 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/service_monitor.py 2019-10-18 12:06:29.000000000 -0400 @@ -29,6 +29,9 @@ service_name = "bind9" snap_service_name = "bind9" + # Pass SIGKILL directly to parent. + kill_extra_opts = ('-s', 'SIGKILL') + class NTPServiceOnRegion(AlwaysOnService): """Monitored NTP service on a region controller host.""" diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/static/js/angular/directives/login.js maas-2.4.3-7049-g000f1aa/src/maasserver/static/js/angular/directives/login.js --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/static/js/angular/directives/login.js 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/static/js/angular/directives/login.js 2019-10-18 12:06:29.000000000 -0400 @@ -50,6 +50,7 @@ $scope.errorMessage = ( response.currentTarget.responseText); }); + localStorage.clear(); } else { $window.location.replace(nextPath); } diff -Nru maas-2.4.2-7034-g2f5deb8b8/src/maasserver/static/js/bundle/maas-min.js maas-2.4.3-7049-g000f1aa/src/maasserver/static/js/bundle/maas-min.js --- maas-2.4.2-7034-g2f5deb8b8/src/maasserver/static/js/bundle/maas-min.js 2018-08-17 10:36:45.000000000 -0400 +++ maas-2.4.3-7049-g000f1aa/src/maasserver/static/js/bundle/maas-min.js 2019-10-18 12:06:29.000000000 -0400 @@ -1,2 +1,2 @@ -!function(modules){var installedModules={};function __webpack_require__(moduleId){if(installedModules[moduleId])return installedModules[moduleId].exports;var module=installedModules[moduleId]={i:moduleId,l:!1,exports:{}};return modules[moduleId].call(module.exports,module,module.exports,__webpack_require__),module.l=!0,module.exports}__webpack_require__.m=modules,__webpack_require__.c=installedModules,__webpack_require__.d=function(exports,name,getter){__webpack_require__.o(exports,name)||Object.defineProperty(exports,name,{configurable:!1,enumerable:!0,get:getter})},__webpack_require__.r=function(exports){Object.defineProperty(exports,"__esModule",{value:!0})},__webpack_require__.n=function(module){var getter=module&&module.__esModule?function(){return module.default}:function(){return module};return __webpack_require__.d(getter,"a",getter),getter},__webpack_require__.o=function(object,property){return Object.prototype.hasOwnProperty.call(object,property)},__webpack_require__.p="",__webpack_require__(__webpack_require__.s=1)}({"./src/maasserver/static/js/angular/controllers/add_device.js":function(module,exports){angular.module("MAAS").controller("AddDeviceController",["$scope","DevicesManager","SubnetsManager","DomainsManager","ManagerHelperService","ValidationService",function($scope,DevicesManager,SubnetsManager,DomainsManager,ManagerHelperService,ValidationService){function newDevice(cloneDevice){return angular.isObject(cloneDevice)?{name:"",domain:cloneDevice.domain,interfaces:[{mac:"",ipAssignment:null,subnetId:null,ipAddress:""}]}:{name:"",domain:DomainsManager.getDefaultDomain(),interfaces:[{mac:"",ipAssignment:null,subnetId:null,ipAddress:""}]}}$scope.$parent.addDeviceScope=$scope,$scope.subnets=SubnetsManager.getItems(),$scope.domains=DomainsManager.getItems(),$scope.viewable=!1,$scope.error=null,$scope.ipAssignments=[{name:"external",title:"External"},{name:"dynamic",title:"Dynamic"},{name:"static",title:"Static"}],$scope.device=null,$scope.show=function(){$scope.viewable||ManagerHelperService.loadManagers($scope,[SubnetsManager,DomainsManager]).then(function(){$scope.device=newDevice($scope.device),$scope.viewable=!0})},$scope.hide=function(){$scope.viewable=!1,ManagerHelperService.unloadManagers($scope,[SubnetsManager,DomainsManager]),$scope.$emit("addDeviceHidden")},$scope.nameHasError=function(){return null!==$scope.device&&0!==$scope.device.name.length&&!ValidationService.validateHostname($scope.device.name)},$scope.macHasError=function(deviceInterface){if(0===deviceInterface.mac.length)return!1;if(!ValidationService.validateMAC(deviceInterface.mac))return!0;var i;for(i=0;i<$scope.device.interfaces.length;i++){if(!($scope.device.interfaces[i]===deviceInterface)&&$scope.device.interfaces[i].mac===deviceInterface.mac)return!0}return!1},$scope.ipHasError=function(deviceInterface){if(0===deviceInterface.ipAddress.length)return!1;if(!ValidationService.validateIP(deviceInterface.ipAddress))return!0;var i;if(angular.isObject(deviceInterface.ipAssignment))if("external"===deviceInterface.ipAssignment.name){for(i=0;i<$scope.subnets.length;i++)if(ValidationService.validateIPInNetwork(deviceInterface.ipAddress,$scope.subnets[i].cidr))return!0}else if("static"===deviceInterface.ipAssignment.name&&angular.isNumber(deviceInterface.subnetId)){var subnet=SubnetsManager.getItemFromList(deviceInterface.subnetId);if(!ValidationService.validateIPInNetwork(deviceInterface.ipAddress,subnet.cidr))return!0}return!1},$scope.deviceHasError=function(){if(null===$scope.device||""===$scope.device.name||$scope.nameHasError())return!0;var i;for(i=0;i<$scope.device.interfaces.length;i++){var deviceInterface=$scope.device.interfaces[i];if(""===deviceInterface.mac||$scope.macHasError(deviceInterface)||!angular.isObject(deviceInterface.ipAssignment))return!0;var externalIpError="external"===deviceInterface.ipAssignment.name&&(""===deviceInterface.ipAddress||$scope.ipHasError(deviceInterface)),staticIpError="static"===deviceInterface.ipAssignment.name&&(!angular.isNumber(deviceInterface.subnetId)||$scope.ipHasError(deviceInterface));if(externalIpError||staticIpError)return!0}return!1},$scope.addInterface=function(){$scope.device.interfaces.push({mac:"",ipAssignment:null,subnetId:null,ipAddress:""})},$scope.isPrimaryInterface=function(deviceInterface){return 0===$scope.device.interfaces.indexOf(deviceInterface)},$scope.deleteInterface=function(deviceInterface){$scope.isPrimaryInterface(deviceInterface)||$scope.device.interfaces.splice($scope.device.interfaces.indexOf(deviceInterface),1)},$scope.cancel=function(){$scope.error=null,$scope.device=newDevice(),$scope.hide()},$scope.save=function(addAnother){if(!$scope.deviceHasError()){$scope.error=null;var device=function(device){var i,convertedDevice={hostname:device.name,domain:device.domain,primary_mac:device.interfaces[0].mac,extra_macs:[],interfaces:[]};for(i=1;i-1&&$scope.machine.macs.splice(idx,1)},$scope.invalidName=function(){return 0!==$scope.machine.name.length&&!ValidationService.validateHostname($scope.machine.name)},$scope.validateMac=function(mac){""===mac.mac?mac.error=!1:mac.error=!ValidationService.validateMAC(mac.mac)},$scope.machineHasError=function(){if(in_error=null===$scope.machine||null===$scope.machine.zone||null===$scope.machine.pool||""===$scope.machine.architecture||null===$scope.machine.power.type||$scope.invalidName($scope.machine),in_error)return in_error;if(""===$scope.machine.macs[0].mac||$scope.machine.macs[0].error)return!0;var i;for(i=1;i<$scope.machine.macs.length;i++){var mac=$scope.machine.macs[i];if(""!==mac.mac&&mac.error)return!0}return!1},$scope.chassisHasErrors=function(){return in_error=null===$scope.chassis||null===$scope.chassis.power.type,in_error||function(power_type,parameters){var i;for(i=0;i0?(domain=DomainsManager.getDomainByName(hostname.slice(hostname.indexOf(".")+1)),hostname=hostname.split(".",1)[0],null===domain&&(domain=DomainsManager.getDefaultDomain())):domain=DomainsManager.getDefaultDomain(),$scope.convertTo={type:"device",hostname:hostname,domain:domain,parent:null,ip_assignment:"dynamic",goTo:!1,saved:!1,deviceIPOptions:deviceIPOptions.filter(function(option){return!("static"===option[0]&&!angular.isNumber(discovered.subnet))})},$scope.selectedDevice=deviceId}},$scope.preProcess=function(item){var discovered=DiscoveriesManager.getItemFromList($scope.selectedDevice);return item=angular.copy(item),"device"===$scope.convertTo.type?(item.primary_mac=discovered.mac_address,item.extra_macs=[],item.interfaces=[{mac:discovered.mac_address,ip_assignment:item.ip_assignment,ip_address:discovered.ip,subnet:discovered.subnet}]):"interface"===$scope.convertTo.type&&(item.mac_address=discovered.mac_address,item.ip_address=discovered.ip,item.subnet=discovered.subnet),item},$scope.afterSave=function(obj){DiscoveriesManager._removeItem($scope.selectedDevice),$scope.selectedDevice=null,$scope.convertTo.hostname=obj.hostname,$scope.convertTo.parent=obj.parent,$scope.convertTo.saved=!0,$scope.convertTo.goTo&&(angular.isString(obj.parent)?$location.path("/device/"+obj.parent):$location.path("/devices/"))},ManagerHelperService.loadManagers($scope,[DiscoveriesManager,DomainsManager,MachinesManager,DevicesManager,SubnetsManager,VLANsManager,ConfigsManager]).then(function(){$scope.loaded=!0,$scope.networkDiscovery=ConfigsManager.getItemFromList("network_discovery")})}])},"./src/maasserver/static/js/angular/controllers/domain_details.js":function(module,exports){angular.module("MAAS").controller("DomainDetailsController",["$scope","$rootScope","$routeParams","$location","DomainsManager","UsersManager","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$location,DomainsManager,UsersManager,ManagerHelperService,ErrorService){function domainLoaded(domain){$scope.domain=domain,$scope.loaded=!0,$rootScope.title=$scope.domain.displayname}$rootScope.title="Loading...",$rootScope.page="domains",$scope.loaded=!1,$scope.domain=null,$scope.editSummary=!1,$scope.predicate="name",$scope.reverse=!1,$scope.action=null,$scope.editRow=null,$scope.deleteRow=null,$scope.domainsManager=DomainsManager,$scope.newObject={},$scope.supportedRecordTypes=["A","AAAA","CNAME","MX","NS","SRV","SSHFP","TXT"],$scope.predicate="name",$scope.sortTable=function(predicate){$scope.predicate=predicate,$scope.reverse=!$scope.reverse},$scope.enterEditSummary=function(){$scope.editSummary=!0},$scope.exitEditSummary=function(){$scope.editSummary=!1},$scope.isRecordAutogenerated=function(row){return!row.dnsresource_id},$scope.editRecord=function(row){$scope.editRow=row,row.previous_rrdata=row.rrdata,row.previous_rrtype=row.rrtype,row.previous_name=row.name,row.previous_ttl=row.ttl,$scope.deleteRow=null},$scope.removeRecord=function(row){$scope.deleteRow=row,$scope.editRow=null},$scope.confirmRemoveRecord=function(row){row.domain=$scope.domain.id,$scope.domainsManager.deleteDNSRecord(row),$scope.stopEditingRow()},$scope.stopEditingRow=function(){$scope.editRow=null,$scope.deleteRow=null},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.isDefaultDomain=function(){return!!angular.isObject($scope.domain)&&0===$scope.domain.id},$scope.canBeDeleted=function(){return!!angular.isObject($scope.domain)&&0===$scope.domain.rrsets.length},$scope.deleteButton=function(){$scope.error=null,$scope.actionInProgress=!0,$scope.action="delete"},$scope.addRecordButton=function(){$scope.error=null,$scope.actionInProgress=!0,$scope.action="add_record"},$scope.cancelAction=function(){$scope.actionInProgress=!1},$scope.deleteConfirmButton=function(){DomainsManager.deleteDomain($scope.domain).then(function(){$scope.actionInProgress=!1,$location.path("/domains")},function(error){$scope.error=ManagerHelperService.parseValidationError(error)})},ManagerHelperService.loadManagers($scope,[DomainsManager,UsersManager]).then(function(){var activeDomain=DomainsManager.getActiveItem(),requestedDomain=parseInt($routeParams.domain_id,10);isNaN(requestedDomain)?ErrorService.raiseError("Invalid domain identifier."):angular.isObject(activeDomain)&&activeDomain.id===requestedDomain?domainLoaded(activeDomain):DomainsManager.setActiveItem(requestedDomain).then(function(domain){domainLoaded(domain)},function(error){ErrorService.raiseError(error)})})}])},"./src/maasserver/static/js/angular/controllers/domains_list.js":function(module,exports){angular.module("MAAS").controller("DomainsListController",["$scope","$rootScope","$routeParams","$filter","DomainsManager","UsersManager","ManagerHelperService",function($scope,$rootScope,$routeParams,$filter,DomainsManager,UsersManager,ManagerHelperService){$rootScope.title="DNS",$rootScope.page="domains",$scope.domains=DomainsManager.getItems(),$scope.currentpage="domains",$scope.predicate="name",$scope.reverse=!1,$scope.loading=!0,$scope.addDomainScope=null,$scope.addDomain=function(){$scope.addDomainScope.show()},$scope.cancelAddDomain=function(){$scope.addDomainScope.cancel()},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},ManagerHelperService.loadManagers($scope,[DomainsManager,UsersManager]).then(function(){$scope.loading=!1})}])},"./src/maasserver/static/js/angular/controllers/fabric_details.js":function(module,exports){angular.module("MAAS").controller("FabricDetailsController",["$scope","$rootScope","$routeParams","$filter","$location","FabricsManager","VLANsManager","SubnetsManager","SpacesManager","ControllersManager","UsersManager","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$filter,$location,FabricsManager,VLANsManager,SubnetsManager,SpacesManager,ControllersManager,UsersManager,ManagerHelperService,ErrorService){function fabricLoaded(fabric){angular.isObject(fabric)&&($scope.fabric=fabric,$rootScope.title=$scope.fabric.name,$scope.$watch("vlans",updateVLANs,!0),$scope.$watch("subnets",updateVLANs,!0),$scope.$watch("controllers",updateVLANs,!0),$scope.loaded=!0,$scope.predicate="['vlan_name', 'vlan.id', 'subnet_name']")}function updateVLANs(){var rows=[],racks={};angular.forEach($filter("filter")($scope.vlans,{fabric:$scope.fabric.id},!0),function(vlan){var subnets=$filter("filter")($scope.subnets,{vlan:vlan.id},!0);if(subnets.length>0)angular.forEach(subnets,function(subnet){var space=SpacesManager.getItemFromList(subnet.space),space_name=null===space?"(undefined)":space.name,row={vlan:vlan,vlan_name:VLANsManager.getName(vlan),subnet:subnet,subnet_name:SubnetsManager.getName(subnet),space:space,space_name:space_name};rows.push(row)});else{var row={vlan:vlan,vlan_name:VLANsManager.getName(vlan),subnet:null,subnet_name:null,space:null,space_name:null};rows.push(row)}angular.forEach(vlan.rack_sids,function(rack_sid){var rack=ControllersManager.getItemFromList(rack_sid);angular.isObject(rack)&&(racks[rack.system_id]=rack)})}),$scope.rows=rows,$scope.racks=Object.keys(racks).map(function(key){return racks[key]})}$rootScope.title="Loading...",$rootScope.page="networks",$scope.fabric=null,$scope.fabricManager=FabricsManager,$scope.editSummary=!1,$scope.vlans=VLANsManager.getItems(),$scope.subnets=SubnetsManager.getItems(),$scope.controllers=ControllersManager.getItems(),$scope.loaded=!1,$scope.enterEditSummary=function(){$scope.editSummary=!0},$scope.exitEditSummary=function(){$scope.editSummary=!1},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.isDefaultFabric=function(){return!!angular.isObject($scope.fabric)&&0===$scope.fabric.id},$scope.canBeDeleted=function(){return!!angular.isObject($scope.fabric)&&0!==$scope.fabric.id},$scope.deleteButton=function(){$scope.error=null,$scope.confirmingDelete=!0},$scope.cancelDeleteButton=function(){$scope.confirmingDelete=!1},$scope.deleteConfirmButton=function(){FabricsManager.deleteFabric($scope.fabric).then(function(){$scope.confirmingDelete=!1,$location.path("/networks"),$location.search("by","fabric")},function(reply){$scope.error=ManagerHelperService.parseValidationError(reply.error)})},ManagerHelperService.loadManagers($scope,[FabricsManager,VLANsManager,SubnetsManager,SpacesManager,ControllersManager,UsersManager]).then(function(){var activeFabric=FabricsManager.getActiveItem(),requestedFabric=parseInt($routeParams.fabric_id,10);isNaN(requestedFabric)?ErrorService.raiseError("Invalid fabric identifier."):angular.isObject(activeFabric)&&activeFabric.id===requestedFabric?fabricLoaded(activeFabric):FabricsManager.setActiveItem(requestedFabric).then(function(fabric){fabricLoaded(fabric)},function(error){ErrorService.raiseError(error)})})}])},"./src/maasserver/static/js/angular/controllers/images.js":function(module,exports){angular.module("MAAS").controller("ImagesController",["$rootScope","$scope","BootResourcesManager","ConfigsManager","UsersManager","ManagerHelperService",function($rootScope,$scope,BootResourcesManager,ConfigsManager,UsersManager,ManagerHelperService){$rootScope.page="images",$rootScope.title="Loading...",$scope.loading=!0,$scope.bootResources=BootResourcesManager.getData(),$scope.configManager=ConfigsManager,$scope.autoImport=null,$scope.isSuperUser=function(){return UsersManager.isSuperUser()},ManagerHelperService.loadManagers($scope,[ConfigsManager,UsersManager]).then(function(){$scope.autoImport=ConfigsManager.getItemFromList("boot_images_auto_import")}),$scope.$watch("bootResources.resources",function(){angular.isArray($scope.bootResources.resources)&&($scope.loading=!1,$rootScope.title="Images")})}])},"./src/maasserver/static/js/angular/controllers/intro.js":function(module,exports){angular.module("MAAS").controller("IntroController",["$rootScope","$scope","$window","$location","ConfigsManager","PackageRepositoriesManager","BootResourcesManager","ManagerHelperService",function($rootScope,$scope,$window,$location,ConfigsManager,PackageRepositoriesManager,BootResourcesManager,ManagerHelperService){if($rootScope.page="intro",$rootScope.title="Welcome",$scope.loading=!0,$scope.configManager=ConfigsManager,$scope.repoManager=PackageRepositoriesManager,$scope.bootResources=BootResourcesManager.getData(),$scope.hasImages=!1,$scope.maasName=null,$scope.upstreamDNS=null,$scope.mainArchive=null,$scope.portsArchive=null,$scope.httpProxy=null,$rootScope.skip=function(){$scope.clickContinue(!0)},$scope.welcomeInError=function(){var form=$scope.maasName.$maasForm;return!!angular.isObject(form)&&form.hasErrors()},$scope.networkInError=function(){var inError=!1,objs=[$scope.upstreamDNS,$scope.mainArchive,$scope.portsArchive,$scope.httpProxy];return angular.forEach(objs,function(obj){var form=obj.$maasForm;angular.isObject(form)&&form.hasErrors()&&(inError=!0)}),inError},$scope.canContinue=function(){return!$scope.welcomeInError()&&!$scope.networkInError()&&$scope.hasImages},$scope.clickContinue=function(force){angular.isUndefined(force)&&(force=!1),(force||$scope.canContinue())&&ConfigsManager.updateItem({name:"completed_intro",value:!0}).then(function(){$window.location.reload()})},MAAS_config.completed_intro)$location.path("/");else{var managers=[ConfigsManager,PackageRepositoriesManager];ManagerHelperService.loadManagers($scope,managers).then(function(){$scope.loading=!1,$scope.maasName=ConfigsManager.getItemFromList("maas_name"),$scope.upstreamDNS=ConfigsManager.getItemFromList("upstream_dns"),$scope.httpProxy=ConfigsManager.getItemFromList("http_proxy"),$scope.mainArchive=PackageRepositoriesManager.getItems().filter(function(repo){return repo.default&&"main_archive"===repo.name})[0],$scope.portsArchive=PackageRepositoriesManager.getItems().filter(function(repo){return repo.default&&"ports_archive"===repo.name})[0]}),$scope.$watch("bootResources.resources",function(){angular.isArray($scope.bootResources.resources)&&$scope.bootResources.resources.length>0?$scope.hasImages=!0:$scope.hasImages=!1})}}])},"./src/maasserver/static/js/angular/controllers/intro_user.js":function(module,exports){angular.module("MAAS").controller("IntroUserController",["$rootScope","$scope","$window","$location","UsersManager","ManagerHelperService",function($rootScope,$scope,$window,$location,UsersManager,ManagerHelperService){$rootScope.page="intro",$rootScope.title="Welcome",$scope.loading=!0,$scope.user=null,$rootScope.skip=function(){$scope.clickContinue(!0)},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.canContinue=function(){return $scope.user.sshkeys_count>0},$scope.clickContinue=function(force){angular.isUndefined(force)&&(force=!1),(force||$scope.canContinue())&&UsersManager.markIntroComplete().then(function(){$window.location.reload()})},MAAS_config.user_completed_intro?$location.path("/"):ManagerHelperService.loadManager($scope,UsersManager).then(function(){$scope.loading=!1,$scope.user=UsersManager.getAuthUser()})}])},"./src/maasserver/static/js/angular/controllers/networks_list.js":function(module,exports){angular.module("MAAS").controller("NetworksListController",["$scope","$rootScope","$routeParams","$filter","$location","SubnetsManager","FabricsManager","SpacesManager","VLANsManager","UsersManager","ManagerHelperService",function($scope,$rootScope,$routeParams,$filter,$location,SubnetsManager,FabricsManager,SpacesManager,VLANsManager,UsersManager,ManagerHelperService){var filterByVLAN=$filter("filterByVLAN"),filterByFabric=$filter("filterByFabric"),filterBySpace=$filter("filterBySpace"),filterByNullSpace=$filter("filterByNullSpace");$rootScope.title="Subnets",$rootScope.page="networks",$scope.getURLParameters=function(){angular.isString($location.search().by)?$scope.groupBy=$location.search().by:$scope.groupBy="fabric"},$scope.ADD_FABRIC_ACTION={name:"add_fabric",title:"Fabric",selectedTitle:"Add fabric",objectName:"fabric"},$scope.ADD_VLAN_ACTION={name:"add_vlan",title:"VLAN",selectedTitle:"Add VLAN",objectName:"vlan"},$scope.ADD_SPACE_ACTION={name:"add_space",title:"Space",selectedTitle:"Add space",objectName:"space"},$scope.ADD_SUBNET_ACTION={name:"add_subnet",title:"Subnet",selectedTitle:"Add subnet",objectName:"subnet"},$scope.getURLParameters(),$scope.subnetManager=SubnetsManager,$scope.subnets=SubnetsManager.getItems(),$scope.fabricManager=FabricsManager,$scope.fabrics=FabricsManager.getItems(),$scope.spaceManager=SpacesManager,$scope.spaces=SpacesManager.getItems(),$scope.vlanManager=VLANsManager,$scope.vlans=VLANsManager.getItems(),$scope.loading=!0,$scope.group={},$scope.group.fabrics={},$scope.group.spaces={},$scope.group.spaces.orphanVLANs=[];var actionObjectInitializers={fabric:function(){return{}},vlan:function(){return{fabric:$scope.fabrics[0].id}},space:function(){return{}},subnet:function(){return{vlan:$scope.fabrics[0].vlan_ids[0]}}};function getSubnetName(subnet){return SubnetsManager.getName(subnet)}$scope.updateGroupBy=function(){var rows,spaces,previous_space;"space"===$scope.groupBy?($location.search("by","space"),rows=[],spaces=$filter("orderBy")($scope.spaces,["name"]),previous_space={id:-1},angular.forEach(spaces,function(space){var subnets=filterBySpace($scope.subnets,space);subnets=$filter("orderBy")(subnets,["cidr"]);var index=0;angular.forEach(subnets,function(subnet){index++;var vlan=VLANsManager.getItemFromList(subnet.vlan),row={fabric:FabricsManager.getItemFromList(vlan.fabric),vlan:vlan,vlan_name:$scope.getVLANName(vlan),subnet:subnet,subnet_name:getSubnetName(subnet),space:space,space_name:""};space.id!==previous_space.id&&(previous_space.id=space.id,row.space_name=space.name),rows.push(row)}),0===index&&rows.push({space:space,space_name:space.name})}),$scope.group.spaces.rows=rows,function(){var rows=[],subnets=filterByNullSpace($scope.subnets);subnets=$filter("orderBy")(subnets,["cidr"]),angular.forEach(subnets,function(subnet){var vlan=VLANsManager.getItemFromList(subnet.vlan),row={fabric:FabricsManager.getItemFromList(vlan.fabric),vlan:vlan,vlan_name:$scope.getVLANName(vlan),subnet:subnet,subnet_name:getSubnetName(subnet),space:null};rows.push(row)}),$scope.group.spaces.orphanVLANs=rows}()):($location.search("by","fabric"),function(){var rows=[],previous_fabric={id:-1},previous_vlan={id:-1},fabrics=$filter("orderBy")($scope.fabrics,["name"]);angular.forEach(fabrics,function(fabric){var vlans=filterByFabric($scope.vlans,fabric);vlans=$filter("orderBy")(vlans,["vid"]),angular.forEach(vlans,function(vlan){var subnets=filterByVLAN($scope.subnets,vlan);if(subnets.length>0)angular.forEach(subnets,function(subnet){var space=SpacesManager.getItemFromList(subnet.space),row={fabric:fabric,fabric_name:"",vlan:vlan,vlan_name:"",space:space,subnet:subnet,subnet_name:getSubnetName(subnet)};fabric.id!==previous_fabric.id&&(previous_fabric.id=fabric.id,row.fabric_name=fabric.name),vlan.id!==previous_vlan.id&&(previous_vlan.id=vlan.id,row.vlan_name=$scope.getVLANName(vlan)),rows.push(row)});else{var row={fabric:fabric,fabric_name:"",vlan:vlan,vlan_name:$scope.getVLANName(vlan)};fabric.id!==previous_fabric.id&&(previous_fabric.id=fabric.id,row.fabric_name=fabric.name),rows.push(row)}})}),$scope.group.fabrics.rows=rows}())},$scope.updateActions=function(){UsersManager.isSuperUser()?$scope.actionOptions=[$scope.ADD_FABRIC_ACTION,$scope.ADD_VLAN_ACTION,$scope.ADD_SPACE_ACTION,$scope.ADD_SUBNET_ACTION]:$scope.actionOptions=[]},$scope.actionChanged=function(){$scope.newObject=actionObjectInitializers[$scope.actionOption.objectName]()},$scope.cancelAction=function(){$scope.actionOption=null,$scope.newObject=null},$scope.getVLANName=function(vlan){return VLANsManager.getName(vlan)},$scope.getFabricNameById=function(fabricId){return FabricsManager.getName(FabricsManager.getItemFromList(fabricId))},$scope.actionSubnetPreSave=function(obj){return obj.fabric=VLANsManager.getItemFromList(obj.vlan).fabric,obj},ManagerHelperService.loadManagers($scope,[SubnetsManager,FabricsManager,SpacesManager,VLANsManager,UsersManager]).then(function(){$scope.loading=!1,$scope.updateActions(),$scope.$watch("[subnets, fabrics, spaces, vlans]",$scope.updateGroupBy,!0),$scope.$on("$routeUpdate",function(){$scope.getURLParameters(),$scope.updateGroupBy()}),$scope.updateGroupBy()})}])},"./src/maasserver/static/js/angular/controllers/node_details.js":function(module,exports){angular.module("MAAS").controller("NodeDetailsController",["$scope","$rootScope","$routeParams","$location","$interval","DevicesManager","MachinesManager","ControllersManager","ZonesManager","GeneralManager","UsersManager","TagsManager","DomainsManager","ManagerHelperService","ServicesManager","ErrorService","ValidationService","ScriptsManager","ResourcePoolsManager",function($scope,$rootScope,$routeParams,$location,$interval,DevicesManager,MachinesManager,ControllersManager,ZonesManager,GeneralManager,UsersManager,TagsManager,DomainsManager,ManagerHelperService,ServicesManager,ErrorService,ValidationService,ScriptsManager,ResourcePoolsManager){var page_managers,DEVICE_IP_ASSIGNMENT={external:"External",dynamic:"Dynamic",static:"Static"};function updateTitle(){$scope.node&&$scope.node.fqdn&&($rootScope.title=$scope.node.fqdn)}function updateHeader(){var i;if(!$scope.header.editing&&!$scope.header.editing_domain)for($scope.header.hostname.value=$scope.node.fqdn,i=0;i<$scope.header.domain.options.length;i++){var option=$scope.header.domain.options[i];if(option.id===$scope.node.domain.id){$scope.header.domain.selected=option;break}}}function updateAvailableActionOptions(){if(angular.isObject($scope.node)){var actionTypeForNodeType={0:"machine_actions",1:"device_actions",2:"rack_controller_actions",3:"region_controller_actions",4:"region_and_rack_controller_actions"};if(GeneralManager.isDataLoaded(actionTypeForNodeType[$scope.node.node_type]))$scope.action.allOptions=GeneralManager.getData(actionTypeForNodeType[$scope.node.node_type]),$scope.action.availableOptions.length=0,angular.forEach($scope.action.allOptions,function(option){$scope.node.actions.indexOf(option.name)>=0&&"set-zone"!==option.name&&"set-pool"!==option.name&&$scope.action.availableOptions.push(option)});else{GeneralManager.loadItems([actionTypeForNodeType[$scope.node.node_type]]).then(updateAvailableActionOptions)}}}function updatePower(){if(!$scope.power.editing){var i;for($scope.power.type=null,i=0;i<$scope.power_types.length;i++)if($scope.node.power_type===$scope.power_types[i].name){$scope.power.type=$scope.power_types[i];break}$scope.power.bmc_node_count=$scope.node.power_bmc_node_count,$scope.power.parameters=angular.copy($scope.node.power_parameters),angular.isObject($scope.power.parameters)||($scope.power.parameters={}),$scope.canEdit()&&""===$scope.node.power_type&&0===$scope.node.node_type&&($scope.power.editing=!0),$scope.power.in_pod=angular.isDefined($scope.node.pod)}}function updateSummary(){$scope.summary.editing||(angular.isObject($scope.node.zone)&&($scope.summary.zone.selected=ZonesManager.getItemFromList($scope.node.zone.id)),angular.isObject($scope.node.pool)&&($scope.summary.pool.selected=ResourcePoolsManager.getItemFromList($scope.node.pool.id)),$scope.summary.architecture.selected=$scope.node.architecture,$scope.summary.min_hwe_kernel.selected=$scope.node.min_hwe_kernel,$scope.summary.tags=angular.copy($scope.node.tags),$scope.canEdit()&&$scope.hasUsableArchitectures()&&$scope.hasInvalidArchitecture()&&($scope.summary.editing=!0))}function updateServices(){$scope.isController&&($scope.services={},angular.forEach(ControllersManager.getServices($scope.node),function(service){$scope.services[service.name]=service}))}function updateDevices(){$scope.devices=[],angular.forEach($scope.node.devices,function(child){var device={name:child.fqdn};angular.isArray(child.interfaces)&&child.interfaces.length>0?angular.forEach(child.interfaces,function(nic,nicIdx){var deviceWithMAC=angular.copy(device);deviceWithMAC.mac_address=nic.mac_address,nicIdx>0&&(deviceWithMAC.name=""),angular.isArray(nic.links)&&nic.links.length>0?angular.forEach(nic.links,function(link,lIdx){var deviceWithLink=angular.copy(deviceWithMAC);deviceWithLink.ip_address=link.ip_address,lIdx>0&&(deviceWithLink.mac_address=""),$scope.devices.push(deviceWithLink)}):$scope.devices.push(deviceWithMAC)}):$scope.devices.push(device)})}function nodeLoaded(node){$scope.node=node,$scope.loaded=!0,updateTitle(),updateSummary(),updateServices(),$scope.$watch("node.fqdn",function(){updateTitle(),updateHeader()}),$scope.$watch("node.devices",updateDevices),$scope.$watch("node.actions",updateAvailableActionOptions),$scope.$watch("node.architecture",updateSummary),$scope.$watchCollection($scope.summary.architecture.options,updateSummary),$scope.$watch("node.min_hwe_kernel",updateSummary),$scope.$watchCollection($scope.summary.min_hwe_kernel.options,updateSummary),$scope.$watch("node.zone.id",updateSummary),$scope.$watchCollection($scope.summary.zone.options,updateSummary),$scope.$watch("node.pool.id",updateSummary),$scope.$watchCollection($scope.summary.pool.options,updateSummary),$scope.$watch("node.power_type",updatePower),$scope.$watch("node.power_parameters",updatePower),$scope.$watchCollection("power_types",updatePower),$scope.$watch("node.service_ids",updateServices),angular.isObject($scope.storageController)&&$scope.storageController.nodeLoaded(),angular.isObject($scope.networkingController)&&$scope.networkingController.nodeLoaded()}$rootScope.title="Loading...",$scope.MAAS_config=MAAS_config,$scope.loaded=!1,$scope.node=null,$scope.action={option:null,allOptions:null,availableOptions:[],error:null,showing_confirmation:!1},$scope.power_types=GeneralManager.getData("power_types"),$scope.osinfo=GeneralManager.getData("osinfo"),$scope.section={area:angular.isString($routeParams.area)?$routeParams.area:"summary"},$scope.osSelection={osystem:null,release:null,hwe_kernel:null},$scope.commissionOptions={enableSSH:!1,skipNetworking:!1,skipStorage:!1,updateFirmware:!1,configureHBA:!1},$scope.commissioningSelection=[],$scope.testSelection=[],$scope.releaseOptions={},$scope.checkingPower=!1,$scope.devices=[],$scope.scripts=ScriptsManager.getItems(),$scope.header={editing:!1,editing_domain:!1,hostname:{value:""},domain:{selected:null,options:DomainsManager.getItems()}},$scope.summary={editing:!1,architecture:{selected:null,options:GeneralManager.getData("architectures")},min_hwe_kernel:{selected:null,options:GeneralManager.getData("min_hwe_kernels")},zone:{selected:null,options:ZonesManager.getItems()},pool:{selected:null,options:ResourcePoolsManager.getItems()},tags:[]},$scope.services={},$scope.power={editing:!1,type:null,bmc_node_count:0,parameters:{},in_pod:!1},$scope.getDeviceIPAssignment=function(ipAssignment){return DEVICE_IP_ASSIGNMENT[ipAssignment]},$scope.events={limit:10},$scope.updateNode=function(node,queryPower){return angular.isUndefined(queryPower)&&(queryPower=!1),$scope.nodesManager.updateItem(node).then(function(node){updateHeader(),updateSummary(),queryPower&&$scope.checkPowerState()},function(error){console.log(error),updateHeader(),updateSummary()})},$scope.tagsAutocomplete=function(query){return TagsManager.autocomplete(query)},$scope.getPowerStateClass=function(){return angular.isObject($scope.node)?$scope.checkingPower?"checking":$scope.node.power_state:""},$scope.getPowerStateText=function(){return angular.isObject($scope.node)?$scope.checkingPower?"Checking power":"unknown"===$scope.node.power_state?"":"Power "+$scope.node.power_state:""},$scope.canCheckPowerState=function(){return!!angular.isObject($scope.node)&&("unknown"!==$scope.node.power_state&&!$scope.checkingPower)},$scope.checkPowerState=function(){$scope.checkingPower=!0,$scope.nodesManager.checkPowerState($scope.node).then(function(){$scope.checkingPower=!1})},$scope.isUbuntuOS=function(){return!!angular.isObject($scope.node)&&"ubuntu"===$scope.node.osystem},$scope.isUbuntuCoreOS=function(){return!!angular.isObject($scope.node)&&"ubuntu-core"===$scope.node.osystem},$scope.isCustomOS=function(){return!!angular.isObject($scope.node)&&"custom"===$scope.node.osystem},$scope.isActionError=function(){return null!==$scope.action.error},$scope.isDeployError=function(){if($scope.isActionError())return!1;var missing_osinfo=angular.isUndefined($scope.osinfo.osystems)||0===$scope.osinfo.osystems.length;return!(!angular.isObject($scope.action.option)||"deploy"!==$scope.action.option.name||!missing_osinfo)},$scope.isSSHKeyError=function(){return!$scope.isActionError()&&!(!angular.isObject($scope.action.option)||"deploy"!==$scope.action.option.name||0!==UsersManager.getSSHKeyCount())},$scope.action.optionChanged=function(){$scope.action.error=null,$scope.action.showing_confirmation=!1},$scope.actionCancel=function(){$scope.action.option=null,$scope.action.error=null,$scope.action.showing_confirmation=!1},$scope.actionGo=function(){var i,extra={};if("deploy"===$scope.action.option.name&&angular.isString($scope.osSelection.osystem)&&angular.isString($scope.osSelection.release)){extra.osystem=$scope.osSelection.osystem;var release=$scope.osSelection.release;release=(release=release.split("/"))[release.length-1],extra.distro_series=release,angular.isString($scope.osSelection.hwe_kernel)&&($scope.osSelection.hwe_kernel.indexOf("hwe-")>=0||$scope.osSelection.hwe_kernel.indexOf("ga-")>=0)&&(extra.hwe_kernel=$scope.osSelection.hwe_kernel)}else if("commission"===$scope.action.option.name){for(extra.enable_ssh=$scope.commissionOptions.enableSSH,extra.skip_networking=$scope.commissionOptions.skipNetworking,extra.skip_storage=$scope.commissionOptions.skipStorage,extra.commissioning_scripts=[],i=0;i<$scope.commissioningSelection.length;i++)extra.commissioning_scripts.push($scope.commissioningSelection[i].id);for($scope.commissionOptions.updateFirmware&&extra.commissioning_scripts.push("update_firmware"),$scope.commissionOptions.configureHBA&&extra.commissioning_scripts.push("configure_hba"),0===extra.commissioning_scripts.length&&extra.commissioning_scripts.push("none"),extra.testing_scripts=[],i=0;i<$scope.testSelection.length;i++)extra.testing_scripts.push($scope.testSelection[i].id);0===extra.testing_scripts.length&&extra.testing_scripts.push("none")}else if("test"===$scope.action.option.name){if(6===$scope.node.status_code&&!$scope.action.showing_confirmation)return void($scope.action.showing_confirmation=!0);for(extra.enable_ssh=$scope.commissionOptions.enableSSH,extra.testing_scripts=[],i=0;i<$scope.testSelection.length;i++)extra.testing_scripts.push($scope.testSelection[i].id);0===extra.testing_scripts.length&&extra.testing_scripts.push("none")}else"release"===$scope.action.option.name&&(extra.erase=$scope.releaseOptions.erase,extra.secure_erase=$scope.releaseOptions.secureErase,extra.quick_erase=$scope.releaseOptions.quickErase);$scope.nodesManager.performAction($scope.node,$scope.action.option.name,extra).then(function(){"delete"===$scope.action.option.name&&$location.path("/machines"),$scope.action.option=null,$scope.action.error=null,$scope.action.showing_confirmation=!1,$scope.osSelection.$reset(),$scope.commissionOptions.enableSSH=!1,$scope.commissionOptions.skipNetworking=!1,$scope.commissionOptions.skipStorage=!1,$scope.commissionOptions.updateFirmware=!1,$scope.commissionOptions.configureHBA=!1,$scope.commissioningSelection=[],$scope.testSelection=[]},function(error){$scope.action.error=error})},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.hasUsableArchitectures=function(){return $scope.summary.architecture.options.length>0},$scope.getArchitecturePlaceholder=function(){return $scope.hasUsableArchitectures()?"Choose an architecture":"-- No usable architectures --"},$scope.hasInvalidArchitecture=function(){return!!angular.isObject($scope.node)&&(!$scope.isDevice&&(""===$scope.node.architecture||-1===$scope.summary.architecture.options.indexOf($scope.node.architecture)))},$scope.invalidArchitecture=function(){return!($scope.isDevice||$scope.isController||""!==$scope.summary.architecture.selected&&-1!==$scope.summary.architecture.options.indexOf($scope.summary.architecture.selected))},$scope.isRackControllerConnected=function(){return $scope.power_types.length>0},$scope.isLocked=function(){return null!==$scope.node&&$scope.node.locked},$scope.canEdit=function(){return!!$scope.isDevice||$scope.isRackControllerConnected()&&$scope.isSuperUser()&&!$scope.isLocked()},$scope.editHeaderDomain=function(){$scope.canEdit()||$scope.header.editing_domain||($scope.header.editing=!1,$scope.header.editing_domain=!0,$scope.header.hostname.value=$scope.node.hostname)},$scope.editHeader=function(){$scope.canEdit()&&($scope.header.editing||($scope.header.editing=!0,$scope.header.editing_domain=!1,$scope.header.hostname.value=$scope.node.hostname))},$scope.editHeaderInvalid=function(){if(!$scope.header.editing&&!$scope.header.editing_domain)return!1;var value=$scope.header.hostname.value;return 0===value.length||!ValidationService.validateHostname(value)},$scope.cancelEditHeader=function(){$scope.header.editing=!1,$scope.header.editing_domain=!1,updateHeader()},$scope.saveEditHeader=function(){if(!$scope.editHeaderInvalid()){$scope.header.editing=!1,$scope.header.editing_domain=!1;var node=angular.copy($scope.node);node.hostname=$scope.header.hostname.value,node.domain=$scope.header.domain.selected,$scope.updateNode(node)}},$scope.editSummary=function(){$scope.canEdit()&&($scope.summary.editing=!0)},$scope.cancelEditSummary=function(){$scope.isDevice||$scope.isController?$scope.summary.editing=!1:$scope.hasInvalidArchitecture()||($scope.summary.editing=!1),updateSummary()},$scope.saveEditSummary=function(){if(!$scope.invalidArchitecture()){$scope.summary.editing=!1;var node=angular.copy($scope.node);node.zone=angular.copy($scope.summary.zone.selected),node.pool=angular.copy($scope.summary.pool.selected),node.architecture=$scope.summary.architecture.selected,null===$scope.summary.min_hwe_kernel.selected?node.min_hwe_kernel="":node.min_hwe_kernel=$scope.summary.min_hwe_kernel.selected,node.tags=[],angular.forEach($scope.summary.tags,function(tag){node.tags.push(tag.text)}),$scope.updateNode(node)}},$scope.invalidPowerType=function(){return!angular.isObject($scope.power.type)},$scope.editPower=function(){$scope.canEdit()&&($scope.power.editing=!0)},$scope.cancelEditPower=function(){0===$scope.node.node_type&&""===$scope.node.power_type||($scope.power.editing=!1),updatePower()},$scope.saveEditPower=function(){if(!$scope.invalidPowerType()){$scope.power.editing=!1;var node=angular.copy($scope.node);node.power_type=$scope.power.type.name,node.power_parameters=angular.copy($scope.power.parameters),$scope.updateNode(node,!0)}},$scope.allowShowMoreEvents=function(){return!!angular.isObject($scope.node)&&(!!angular.isArray($scope.node.events)&&($scope.node.events.length>0&&$scope.node.events.length>$scope.events.limit&&$scope.events.limit<50))},$scope.showMoreEvents=function(){$scope.events.limit+=10},$scope.getEventText=function(event){var text=event.type.description;return angular.isString(event.description)&&event.description.length>0&&(text+=" - "+event.description),text},$scope.getPowerEventError=function(){var i;if(angular.isObject($scope.node)&&angular.isArray($scope.node.events))for(i=0;i<$scope.node.events.length;i++){var event=$scope.node.events[i];if("warning"===event.type.level&&"Failed to query node's BMC"===event.type.description)return event;if("info"===event.type.level&&"Queried node's BMC"===event.type.description)return}},$scope.hasPowerEventError=function(){var event=$scope.getPowerEventError();return angular.isObject(event)},$scope.getPowerEventErrorText=function(){var event=$scope.getPowerEventError();return angular.isObject(event)?event.description:""},$scope.hasActionPowerError=function(actionName){if(!$scope.hasPowerError())return!1;return!!(actionName&&["commission","deploy","on","off","release"].indexOf(actionName)>-1)},$scope.hasPowerError=function(){return!!angular.isObject($scope.power.type)&&$scope.power.type.missing_packages.length>0},$scope.getPowerErrors=function(){var i,result="";if(angular.isObject($scope.power.type)){var packages=$scope.power.type.missing_packages;for(packages.sort(),i=0;i1?" packages":" package"}return result},$scope.getServiceClass=function(service){return angular.isObject(service)?"running"===service.status?"success":"dead"===service.status?"error":"degraded"===service.status?"warning":"none":"none"},$scope.hasCustomCommissioningScripts=function(){var i;for(i=0;i<$scope.scripts.length;i++)if(0===$scope.scripts[i].script_type)return!0;return!1},$scope.controllerLoaded=function(name,scope){$scope[name]=scope,angular.isObject(scope.node)&&scope.nodeLoaded()},$scope.showFailedTestWarning=function(){if(1===$scope.node.node_type||!$scope.node.status_code)return!1;switch($scope.node.status_code){case 0:case 1:case 2:case 21:case 22:return!1}switch($scope.node.testing_status){case-1:case 2:return!1}return!0},$scope.getCPUSubtext=function(){var label=$scope.node.cpu_count+" cores";return $scope.node.cpu_speed&&0!==$scope.node.cpu_speed?$scope.node.cpu_speed<1e3?label+" @ "+$scope.node.cpu_speed+" Mhz":label+" @ "+$scope.node.cpu_speed/1e3+" Ghz":label},$scope.$on("$routeUpdate",function(){GeneralManager.loadItems(["osinfo","architectures","min_hwe_kernels"])}),-1!==$location.path().indexOf("/controller")?($scope.nodesManager=ControllersManager,page_managers=[ControllersManager,ScriptsManager],$scope.isController=!0,$scope.isDevice=!1,$scope.type_name="controller",$scope.type_name_title="Controller",$rootScope.page="controllers"):-1!==$location.path().indexOf("/device")?($scope.nodesManager=DevicesManager,page_managers=[DevicesManager],$scope.isController=!1,$scope.isDevice=!0,$scope.type_name="device",$scope.type_name_title="Device",$rootScope.page="devices"):($scope.nodesManager=MachinesManager,page_managers=[MachinesManager,ScriptsManager],$scope.isController=!1,$scope.isDevice=!1,$scope.type_name="machine",$scope.type_name_title="Machine",$rootScope.page="machines"),ManagerHelperService.loadManagers($scope,[ZonesManager,GeneralManager,UsersManager,TagsManager,DomainsManager,ServicesManager,ResourcePoolsManager].concat(page_managers)).then(function(){var activeNode=$scope.nodesManager.getActiveItem();angular.isObject(activeNode)&&activeNode.system_id===$routeParams.system_id?nodeLoaded(activeNode):($scope.nodesManager.setActiveItem($routeParams.system_id).then(function(node){nodeLoaded(node)},function(error){ErrorService.raiseError(error)}),activeNode=$scope.nodesManager.getActiveItem()),$scope.isDevice&&($scope.ip_assignment=activeNode.ip_assignment)})}])},"./src/maasserver/static/js/angular/controllers/node_details_networking.js":function(module,exports){angular.module("MAAS").filter("filterByUnusedForInterface",function(){return function(vlans,nic,originalInterfaces){var filtered=[];if(!angular.isObject(nic)||!angular.isObject(originalInterfaces))return filtered;var usedVLANs=[];return angular.forEach(originalInterfaces,function(inter){"vlan"===inter.type&&(inter.parents[0]===nic.id&&usedVLANs.push(inter.vlan_id))}),angular.forEach(vlans,function(vlan){-1===usedVLANs.indexOf(vlan.id)&&filtered.push(vlan)}),filtered}}),angular.module("MAAS").filter("removeInterfaceParents",function(){return function(interfaces,childInterface,skip){if(skip||!angular.isObject(childInterface)||!angular.isArray(childInterface.parents))return interfaces;var filtered=[];return angular.forEach(interfaces,function(nic){var i,parent,found=!1;for(i=0;i1||"alias"===nicType;angular.forEach(modes,function(mode){allowLinkUp&&"link_up"===mode.mode||"alias"===nicType&&"dhcp"===mode.mode||filtered.push(mode)})}else angular.forEach(modes,function(mode){"link_up"===mode.mode&&filtered.push(mode)});return filtered}}),angular.module("MAAS").controller("NodeNetworkingController",["$scope","$filter","FabricsManager","VLANsManager","SubnetsManager","MachinesManager","ControllersManager","DevicesManager","GeneralManager","UsersManager","ManagerHelperService","ValidationService","JSONService",function($scope,$filter,FabricsManager,VLANsManager,SubnetsManager,MachinesManager,ControllersManager,DevicesManager,GeneralManager,UsersManager,ManagerHelperService,ValidationService,JSONService){var INTERFACE_TYPE={PHYSICAL:"physical",BOND:"bond",BRIDGE:"bridge",VLAN:"vlan",ALIAS:"alias"},INTERFACE_TYPE_TEXTS={physical:"Physical",bond:"Bond",bridge:"Bridge",vlan:"VLAN",alias:"Alias"},LINK_MODE={AUTO:"auto",STATIC:"static",DHCP:"dhcp",LINK_UP:"link_up"},LINK_MODE_TEXTS={auto:"Auto assign",static:"Static assign",dhcp:"DHCP",link_up:"Unconfigured"},SELECTION_MODE={NONE:null,SINGLE:"single",MULTI:"multi",DELETE:"delete",ADD:"add",CREATE_BOND:"create-bond",CREATE_BRIDGE:"create-bridge",CREATE_PHYSICAL:"create-physical",EDIT:"edit"},IP_ASSIGNMENT_DYNAMIC="dynamic",IP_ASSIGNMENT_EXTERNAL="external",IP_ASSIGNMENT_STATIC="static";function updateLoaded(){$scope.loaded=$scope.nodeHasLoaded&&$scope.managersHaveLoaded,$scope.loaded&&updateInterfaces()}function updateInterfaces(){$scope.originalInterfaces={},angular.forEach($scope.node.interfaces,function(nic){$scope.originalInterfaces[nic.id]=nic});var interfaces=[],vlanTable=[],addedVlans={};if(angular.forEach($scope.node.interfaces,function(nic){if(1===nic.children.length){var child=$scope.originalInterfaces[nic.children[0]];if(child.type===INTERFACE_TYPE.BOND||child.type===INTERFACE_TYPE.BRIDGE)return void(angular.isObject($scope.editInterface)&&$scope.editInterface.id===nic.id&&($scope.editInterface=null,$scope.selectedMode=SELECTION_MODE.NONE))}var tags,formatted;if(nic.type!==INTERFACE_TYPE.BOND&&nic.type!==INTERFACE_TYPE.BRIDGE||(nic.members=[],angular.forEach(nic.parents,function(parent){nic.members.push(angular.copy($scope.originalInterfaces[parent]))})),angular.isArray(nic.tags)&&nic.tags.length>0&&!angular.isString(nic.tags[0].text)&&(nic.tags=(tags=nic.tags,formatted=[],angular.forEach(tags,function(tag){formatted.push({text:tag})}),formatted)),nic.vlan=VLANsManager.getItemFromList(nic.vlan_id),angular.isObject(nic.vlan)&&(nic.fabric=FabricsManager.getItemFromList(nic.vlan.fabric),$scope.$parent.isController)){if(!Object.prototype.hasOwnProperty.call(addedVlans,nic.vlan.id)){addedVlans[nic.vlan.id]=!0;var vlanRecord={fabric:nic.fabric,vlan:nic.vlan,subnets:$filter("filter")($scope.subnets,{vlan:nic.vlan.id},!0),primary_rack:null,secondary_rack:null,sort_key:nic.fabric.name+"|"+$scope.getVLANText(nic.vlan)};nic.vlan.primary_rack&&(vlanRecord.primary_rack=ControllersManager.getItemFromList(nic.vlan.primary_rack)),nic.vlan.secondary_rack&&(vlanRecord.secondary_rack=ControllersManager.getItemFromList(nic.vlan.secondary_rack)),vlanTable.push(vlanRecord)}vlanTable.sort(function(a,b){return a.sort_key.localeCompare(b.sort_key)})}if(0===nic.links.length)nic.link_id=-1,nic.subnet=null,nic.mode=LINK_MODE.LINK_UP,nic.ip_address="",interfaces.push(nic);else{var idx=0;angular.forEach(nic.links,function(link){var nic_copy=angular.copy(nic);nic_copy.link_id=link.id,nic_copy.subnet=SubnetsManager.getItemFromList(link.subnet_id),nic_copy.mode=link.mode,nic_copy.ip_address=link.ip_address,angular.isUndefined(nic_copy.ip_address)&&(nic_copy.ip_address=""),nic_copy.vlan=nic.vlan,nic_copy.fabric=nic.fabric,idx>0&&(nic_copy.type=INTERFACE_TYPE.ALIAS,nic_copy.name+=":"+idx),idx++,interfaces.push(nic_copy)})}}),$scope.interfaces=interfaces,$scope.vlanTable=vlanTable,$scope.interfaceLinksMap={},angular.forEach($scope.interfaces,function(nic){var linkMaps=$scope.interfaceLinksMap[nic.id];angular.isUndefined(linkMaps)&&(linkMaps={},$scope.interfaceLinksMap[nic.id]=linkMaps),linkMaps[nic.link_id]=nic}),angular.isObject($scope.editInterface)){var links=$scope.interfaceLinksMap[$scope.editInterface.id];if(angular.isUndefined(links))$scope.editInterface=null,$scope.selectedMode=SELECTION_MODE.NONE;else{var link=links[$scope.editInterface.link_id];angular.isUndefined(link)&&($scope.editInterface=null,$scope.selectedMode=SELECTION_MODE.NONE)}}!function(){if(angular.isObject($scope.newInterface.parent)){var parentId=$scope.newInterface.parent.id,linkId=$scope.newInterface.parent.link_id,links=$scope.interfaceLinksMap[parentId];if(angular.isObject(links)){var newParent=links[linkId];if(angular.isObject(newParent)){$scope.newInterface.parent=newParent;var iType=$scope.newInterface.type,isAlias=iType===INTERFACE_TYPE.ALIAS,isVLAN=iType===INTERFACE_TYPE.VLAN,canAddAlias=$scope.canAddAlias(newParent),canAddVLAN=$scope.canAddVLAN(newParent);return void(canAddAlias||canAddVLAN?isAlias&&!canAddAlias&&canAddVLAN?($scope.newInterface.type="vlan",$scope.addTypeChanged()):isVLAN&&!canAddVLAN&&canAddAlias&&($scope.newInterface.type="alias",$scope.addTypeChanged()):($scope.newInterface={},leaveSingleSelectionMode()))}}$scope.newInterface={},leaveSingleSelectionMode()}}()}function mapNICToOriginalLink(nic_id,link_id){var originalInteface=$scope.originalInterfaces[nic_id];if(angular.isObject(originalInteface)){var i,link=null;for(i=0;i0?vlan.vid+" ("+vlan.name+")":vlan.vid:""},$scope.getSubnetText=function(subnet){return angular.isObject(subnet)?angular.isString(subnet.name)&&subnet.name.length>0&&subnet.cidr!==subnet.name?subnet.cidr+" ("+subnet.name+")":subnet.cidr:"Unconfigured"},$scope.getSubnet=function(subnetId){return SubnetsManager.getItemFromList(subnetId)},$scope.isInterfaceNameInvalid=function(nic){if(!angular.isObject(nic)||!nic.hasOwnProperty("name")||0===nic.name.length)return!0;var i;if(angular.isArray($scope.node.interfaces))for(i=0;i<$scope.node.interfaces.length;i++){var otherNic=$scope.node.interfaces[i];if(otherNic.name===nic.name&&otherNic.id!==nic.id)return!0}return!1},$scope.isLinkModeDisabled=function(nic){return angular.isFunction(nic.getValue)?!angular.isObject(nic.getValue("subnet")):!angular.isObject(nic.subnet)},$scope.getInterfaceError=function(nic){return angular.isDefined(nic.link_id)&&nic.link_id>=0?$scope.interfaceErrorsByLinkId[nic.link_id]:null},$scope.isIPAddressInvalid=function(nic){return!(!angular.isString(nic.ip_address)||"static"!==nic.mode)&&(!ValidationService.validateIP(nic.ip_address)||!ValidationService.validateIPInNetwork(nic.ip_address,nic.subnet.cidr))},$scope.getUniqueKey=function(nic){return nic.id+"/"+nic.link_id},$scope.toggleInterfaceSelect=function(nic){var key=$scope.getUniqueKey(nic),idx=$scope.selectedInterfaces.indexOf(key);idx>-1?$scope.selectedInterfaces.splice(idx,1):$scope.selectedInterfaces.push(key),$scope.selectedInterfaces.length>1?$scope.selectedMode!==SELECTION_MODE.BOND&&($scope.selectedMode=SELECTION_MODE.MULTI):1===$scope.selectedInterfaces.length?$scope.selectedMode=SELECTION_MODE.SINGLE:$scope.selectedMode=SELECTION_MODE.NONE},$scope.isInterfaceSelected=function(nic){return $scope.selectedInterfaces.indexOf($scope.getUniqueKey(nic))>-1},$scope.cannotEditInterface=function(nic){return $scope.selectedMode!==SELECTION_MODE.NONE&&($scope.selectedMode===SELECTION_MODE.MULTI||!$scope.isInterfaceSelected(nic))},$scope.isEditing=function(nic){return $scope.selectedMode===SELECTION_MODE.EDIT&&$scope.editInterface.id===nic.id},$scope.edit=function(nic){$scope.selectedInterfaces=[$scope.getUniqueKey(nic)],$scope.selectedMode=SELECTION_MODE.EDIT,$scope.$parent.isDevice?($scope.editInterface={id:nic.id,name:nic.name,mac_address:nic.mac_address,tags:nic.tags.map(function(tag){return tag.text}),subnet:nic.subnet,ip_address:nic.ip_address,ip_assignment:nic.ip_assignment,link_id:nic.link_id},void 0!==nic.subnet&&null!==nic.subnet?$scope.editInterface.defaultSubnet=nic.subnet:$scope.editInterface.defaultSubnet=$scope.subnets[0]):$scope.editInterface={id:nic.id,name:nic.name,mac_address:nic.mac_address,tags:nic.tags.map(function(tag){return tag.text}),fabric:nic.fabric,vlan:nic.vlan,subnet:nic.subnet,mode:nic.mode,ip_address:nic.ip_address,link_id:nic.link_id}},$scope.fabricChanged=function(nic){null!==nic.fabric?nic.vlan=getDefaultVLAN(nic.fabric):nic.vlan=null,$scope.vlanChanged(nic)},$scope.fabricChangedForm=function(key,value,form){var vlan;vlan=null!==value?getDefaultVLAN(value):null,form.updateValue("vlan",vlan),$scope.vlanChangedForm("vlan",vlan,form)},$scope.vlanChanged=function(nic){nic.subnet=null,$scope.subnetChanged(nic)},$scope.vlanChangedForm=function(key,value,form){form.updateValue("subnet",null),$scope.subnetChangedForm("subnet",null,form)},$scope.subnetChanged=function(nic){angular.isObject(nic.subnet)||(nic.mode=LINK_MODE.LINK_UP),$scope.$parent.isDevice&&(nic.ip_address=null),$scope.modeChanged(nic)},$scope.subnetChangedForm=function(key,value,form){angular.isObject(value)||form.updateValue("mode",LINK_MODE.LINK_UP),mode=form.getValue("mode"),form.updateValue("ip_address",null),$scope.modeChangedForm("mode",mode,form)},$scope.modeChanged=function(nic){if(nic.ip_address="","static"===nic.mode){var originalLink=mapNICToOriginalLink(nic.id,nic.link_id);angular.isObject(originalLink)&&nic.subnet.id===originalLink.subnet_id&&(nic.ip_address=originalLink.ip_address)}},$scope.modeChangedForm=function(key,value,form){if(form.updateValue("ip_address",""),"static"===value){var originalLink=mapNICToOriginalLink(form.getValue("id"),form.getValue("link_id"));angular.isObject(originalLink)&&form.getValue("subnet").id===originalLink.subnet_id&&form.updateValue("ip_address",originalLink.ip_address)}},$scope.editCancel=function(nic){$scope.selectedInterfaces=[],$scope.selectedMode=SELECTION_MODE.NONE,$scope.editInterface=null},$scope.preProcessInterface=function(nic){var params=angular.copy(nic);return delete params.id,params.system_id=$scope.node.system_id,params.interface_id=nic.id,void 0!==nic.fabric&&null!==nic.fabric?params.fabric=nic.fabric.id:params.fabric=null,void 0!==nic.vlan&&null!==nic.vlan?params.vlan=nic.vlan.id:params.vlan=null,void 0!==nic.subnet&&null!==nic.subnet?params.subnet=params.subnet.id:delete params.subnet,angular.isDefined(nic.link_id)&&nic.link_id>=0?(params.link_id=nic.link_id,delete $scope.interfaceErrorsByLinkId[nic.link_id]):delete params.link_id,(nic.mode===LINK_MODE.STATIC||nic.ip_assignment!==IP_ASSIGNMENT_DYNAMIC)&&angular.isString(nic.ip_address)&&nic.ip_address.length>0?params.ip_address=nic.ip_address:delete params.ip_address,params},$scope.saveInterface=function(nic){var params;return params=$scope.$parent.isDevice?{name:nic.name,mac_address:nic.mac_address,ip_assignment:nic.ip_assignment,ip_address:nic.ip_address}:{name:nic.name,mac_address:nic.mac_address,mode:nic.mode,tags:nic.tags.map(function(tag){return tag.text})},void 0!==nic.fabric&&null!==nic.fabric?params.fabric=nic.fabric.id:params.fabric=null,void 0!==nic.vlan&&null!==nic.vlan?params.vlan=nic.vlan.id:params.vlan=null,void 0!==nic.subnet&&null!==nic.subnet?params.subnet=nic.subnet.id:params.subnet=null,angular.isDefined(nic.link_id)&&nic.link_id>=0&&(params.link_id=nic.link_id,delete $scope.interfaceErrorsByLinkId[nic.link_id]),angular.isString(nic.ip_address)&&nic.ip_address.length>0&&(params.ip_address=nic.ip_address),$scope.$parent.nodesManager.updateInterface($scope.node,nic.id,params).then(null,function(error){console.log(error),updateInterfaces()})},$scope.saveInterfaceLink=function(nic){var params={mode:nic.mode};return $scope.$parent.isDevice&&(params.ip_assignment=nic.ip_assignment),angular.isObject(nic.subnet)&&(params.subnet=nic.subnet.id),angular.isDefined(nic.link_id)&&nic.link_id>=0&&(params.link_id=nic.link_id,delete $scope.interfaceErrorsByLinkId[nic.link_id]),nic.mode===LINK_MODE.STATIC&&angular.isString(nic.ip_address)&&nic.ip_address.length>0&&(params.ip_address=nic.ip_address),$scope.$parent.nodesManager.linkSubnet($scope.node,nic.id,params).then(null,function(error){throw console.log(error),angular.isDefined(nic.link_id)&&nic.link_id>=0&&($scope.interfaceErrorsByLinkId[nic.link_id]=error),updateInterfaces(),error})},$scope.editSave=function(editInterface){return $scope.editCancel(),editInterface},$scope.isShowingDeleteConfirm=function(){return $scope.selectedMode===SELECTION_MODE.DELETE},$scope.isShowingAdd=function(){return $scope.selectedMode===SELECTION_MODE.ADD},$scope.canAddAliasOrVLAN=function(nic){return!$scope.$parent.isController&&(!$scope.isAllNetworkingDisabled()&&($scope.canAddAlias(nic)||$scope.canAddVLAN(nic)))},$scope.canAddAlias=function(nic){return!!angular.isObject(nic)&&(nic.type!==INTERFACE_TYPE.ALIAS&&(0!==nic.links.length&&nic.links[0].mode!==LINK_MODE.LINK_UP))},$scope.canAddVLAN=function(nic){return!!angular.isObject(nic)&&(nic.type!==INTERFACE_TYPE.ALIAS&&nic.type!==INTERFACE_TYPE.VLAN&&getUnusedVLANs(nic).length>0)},$scope.canAddAnotherVLAN=function(nic){return!!$scope.canAddVLAN(nic)&&getUnusedVLANs(nic).length>1},$scope.getRemoveTypeText=function(nic){return nic.type===INTERFACE_TYPE.PHYSICAL?"interface":nic.type===INTERFACE_TYPE.VLAN?"VLAN":nic.type},$scope.canBeRemoved=function(){return!$scope.$parent.isController&&!$scope.isAllNetworkingDisabled()},$scope.remove=function(){$scope.selectedMode=SELECTION_MODE.DELETE},$scope.quickRemove=function(nic){$scope.selectedInterfaces=[$scope.getUniqueKey(nic)],$scope.remove()},$scope.cancel=function(){$scope.newInterface={},$scope.newBondInterface={},$scope.newBridgeInterface={},$scope.selectedMode===SELECTION_MODE.CREATE_BOND?$scope.selectedMode=SELECTION_MODE.MULTI:$scope.selectedMode===SELECTION_MODE.CREATE_PHYSICAL?$scope.selectedMode=SELECTION_MODE.NONE:$scope.selectedMode=SELECTION_MODE.SINGLE},$scope.confirmRemove=function(nic){$scope.selectedMode=SELECTION_MODE.NONE,$scope.selectedInterfaces=[],nic.type!==INTERFACE_TYPE.ALIAS?$scope.$parent.nodesManager.deleteInterface($scope.node,nic.id):$scope.$parent.nodesManager.unlinkSubnet($scope.node,nic.id,nic.link_id);var idx=$scope.interfaces.indexOf(nic);idx>-1&&$scope.interfaces.splice(idx,1)},$scope.add=function(type,nic){var ignoreVLANs=[];angular.isObject($scope.newInterface.vlan)&&ignoreVLANs.push($scope.newInterface.vlan);var vlans=getUnusedVLANs(nic,ignoreVLANs),defaultVLAN=null;vlans.length>0&&(defaultVLAN=vlans[0]);var defaultSubnet=null,defaultMode=LINK_MODE.LINK_UP;type===INTERFACE_TYPE.ALIAS&&(defaultVLAN=nic.vlan,defaultSubnet=$filter("filter")($scope.subnets,{vlan:defaultVLAN.id},!0)[0],defaultMode=LINK_MODE.AUTO),$scope.newInterface={type:type,vlan:defaultVLAN,subnet:defaultSubnet,mode:defaultMode,parent:nic,tags:[]},$scope.selectedMode=SELECTION_MODE.ADD},$scope.quickAdd=function(nic){$scope.selectedInterfaces=[$scope.getUniqueKey(nic)];var type="alias";$scope.canAddAlias(nic)||(type="vlan"),$scope.add(type,nic)},$scope.getAddName=function(){if($scope.newInterface.type===INTERFACE_TYPE.ALIAS){var aliasIdx=$scope.newInterface.parent.links.length;return $scope.newInterface.parent.name+":"+aliasIdx}if($scope.newInterface.type===INTERFACE_TYPE.VLAN)return $scope.newInterface.parent.name+"."+$scope.newInterface.vlan.vid},$scope.addTypeChanged=function(){if($scope.newInterface.type===INTERFACE_TYPE.ALIAS)$scope.newInterface.vlan=$scope.newInterface.parent.vlan,$scope.newInterface.subnet=$filter("filter")($scope.subnets,{vlan:$scope.newInterface.vlan.id},!0)[0],$scope.newInterface.mode=LINK_MODE.AUTO;else if($scope.newInterface.type===INTERFACE_TYPE.VLAN){var vlans=getUnusedVLANs($scope.newInterface.parent);$scope.newInterface.vlan=null,vlans.length>0&&($scope.newInterface.vlan=vlans[0]),$scope.newInterface.subnet=null,$scope.newInterface.mode=LINK_MODE.LINK_UP}},$scope.addInterface=function(type){var nic;if($scope.$parent.isDevice)nic={id:$scope.newInterface.parent.id,tags:$scope.newInterface.tags.map(function(tag){return tag.text}),ip_assignment:$scope.newInterface.ip_assignment,subnet:$scope.newInterface.subnet,ip_address:$scope.newInterface.ip_address},$scope.saveInterfaceLink(nic);else if($scope.newInterface.type===INTERFACE_TYPE.ALIAS)nic={id:$scope.newInterface.parent.id,mode:$scope.newInterface.mode,subnet:$scope.newInterface.subnet,ip_address:$scope.newInterface.ip_address},$scope.saveInterfaceLink(nic);else if($scope.newInterface.type===INTERFACE_TYPE.VLAN){var params={parent:$scope.newInterface.parent.id,vlan:$scope.newInterface.vlan.id,mode:$scope.newInterface.mode,tags:$scope.newInterface.tags.map(function(tag){return tag.text})};angular.isObject($scope.newInterface.subnet)&&(params.subnet=$scope.newInterface.subnet.id,params.ip_address=$scope.newInterface.ip_address),$scope.$parent.nodesManager.createVLANInterface($scope.node,params).then(null,function(error){console.log(error)})}angular.isString(type)?$scope.add(type,$scope.newInterface.parent):($scope.selectedMode=SELECTION_MODE.NONE,$scope.selectedInterfaces=[],$scope.newInterface={})},$scope.isDisabled=function(){return!!$scope.isAllNetworkingDisabled()||$scope.selectedMode!==SELECTION_MODE.NONE&&$scope.selectedMode!==SELECTION_MODE.SINGLE&&$scope.selectedMode!==SELECTION_MODE.MULTI},$scope.canCreateBond=function(){if($scope.selectedMode!==SELECTION_MODE.MULTI)return!1;var i,vlan,interfaces=getSelectedInterfaces();for(i=0;i-1&&$scope.interfaces.splice(idx,1)}),$scope.newBondInterface={},$scope.selectedInterfaces=[],$scope.selectedMode=SELECTION_MODE.NONE}},$scope.canCreateBridge=function(){if($scope.selectedMode!==SELECTION_MODE.SINGLE)return!1;var nic=getSelectedInterfaces()[0];return nic.type!==INTERFACE_TYPE.ALIAS&&nic.type!==INTERFACE_TYPE.BRIDGE},$scope.isShowingCreateBridge=function(){return $scope.selectedMode===SELECTION_MODE.CREATE_BRIDGE},$scope.showCreateBridge=function(){if($scope.selectedMode===SELECTION_MODE.SINGLE&&$scope.canCreateBridge()){$scope.selectedMode=SELECTION_MODE.CREATE_BRIDGE;var parents=getSelectedInterfaces();$scope.newBridgeInterface={name:getNextName("br"),tags:[],parents:parents,primary:parents[0],mac_address:"",bridge_stp:!1,bridge_fd:15}}},$scope.cannotAddBridge=function(){return $scope.isInterfaceNameInvalid($scope.newBridgeInterface)||$scope.isMACAddressInvalid($scope.newBridgeInterface.mac_address)},$scope.addBridge=function(){if(!$scope.cannotAddBridge()){var parents=[$scope.newBridgeInterface.primary.id],mac_address=$scope.newBridgeInterface.mac_address;""===mac_address&&(mac_address=$scope.newBridgeInterface.primary.mac_address);var params={name:$scope.newBridgeInterface.name,mac_address:mac_address,tags:$scope.newBridgeInterface.tags.map(function(tag){return tag.text}),parents:parents,vlan:$scope.newBridgeInterface.primary.vlan.id,bridge_stp:$scope.newBridgeInterface.bridge_stp,bridge_fd:$scope.newBridgeInterface.bridge_fd};$scope.$parent.nodesManager.createBridgeInterface($scope.node,params).then(null,function(error){console.log(error)});var idx=$scope.interfaces.indexOf($scope.newBridgeInterface.primary);idx>-1&&$scope.interfaces.splice(idx,1),$scope.newBridgeInterface={},$scope.selectedInterfaces=[],$scope.selectedMode=SELECTION_MODE.NONE}},$scope.isShowingCreatePhysical=function(){return $scope.selectedMode===SELECTION_MODE.CREATE_PHYSICAL},$scope.showCreatePhysical=function(){$scope.selectedMode===SELECTION_MODE.NONE&&($scope.selectedMode=SELECTION_MODE.CREATE_PHYSICAL,$scope.$parent.isDevice?$scope.newInterface={name:getNextName("eth"),mac_address:"",macError:!1,tags:[],errorMsg:null,subnet:null,ip_assignment:IP_ASSIGNMENT_DYNAMIC}:$scope.newInterface={name:getNextName("eth"),mac_address:"",macError:!1,tags:[],errorMsg:null,fabric:$scope.fabrics[0],vlan:getDefaultVLAN($scope.fabrics[0]),subnet:null,mode:LINK_MODE.LINK_UP})},$scope.cannotAddPhysicalInterface=function(){return $scope.isInterfaceNameInvalid($scope.newInterface)||$scope.isMACAddressInvalid($scope.newInterface.mac_address,!0)},$scope.addPhysicalInterface=function(){var params;$scope.cannotAddPhysicalInterface()||(params=$scope.$parent.isDevice?{name:$scope.newInterface.name,mac_address:$scope.newInterface.mac_address,tags:$scope.newInterface.tags.map(function(tag){return tag.text}),ip_assignment:$scope.newInterface.ip_assignment,ip_address:$scope.newInterface.ip_address}:{name:$scope.newInterface.name,tags:$scope.newInterface.tags.map(function(tag){return tag.text}),mac_address:$scope.newInterface.mac_address,vlan:$scope.newInterface.vlan.id,mode:$scope.newInterface.mode},angular.isObject($scope.newInterface.subnet)&&(params.subnet=$scope.newInterface.subnet.id),$scope.newInterface.macError=!1,$scope.newInterface.errorMsg=null,$scope.$parent.nodesManager.createPhysicalInterface($scope.node,params).then(function(){$scope.newInterface={},$scope.selectedMode=SELECTION_MODE.NONE},function(errorStr){error=JSONService.tryParse(errorStr),angular.isObject(error)?(macError=error.mac_address,angular.isArray(macError)&&($scope.newInterface.macError=!0,$scope.newInterface.errorMsg=macError[0])):console.log(errorStr)}))},ManagerHelperService.loadManagers($scope,[FabricsManager,VLANsManager,SubnetsManager,UsersManager,ControllersManager]).then(function(){GeneralManager.isDataLoaded("bond_options")||GeneralManager.loadItems(["bond_options"]),$scope.managersHaveLoaded=!0,updateLoaded()}),$scope.$parent.controllerLoaded("networkingController",$scope)}])},"./src/maasserver/static/js/angular/controllers/node_details_storage.js":function(module,exports){angular.module("MAAS").filter("removeAvailableByNew",function(){return function(disks,availableNew){if(!angular.isObject(availableNew)||!angular.isObject(availableNew.device)&&!angular.isArray(availableNew.devices))return disks;var filtered=[],single=!0;return angular.isArray(availableNew.devices)&&(single=!1),angular.forEach(disks,function(disk){if(single)disk!==availableNew.device&&filtered.push(disk);else{var i,found=!1;for(i=0;i0,filesystems=[],angular.forEach($scope.node.disks,function(disk){if(hasMountedFilesystem(disk)){var data={type:"filesystem",name:disk.name,size_human:disk.size_human,fstype:disk.filesystem.fstype,mount_point:disk.filesystem.mount_point,mount_options:disk.filesystem.mount_options,block_id:disk.id,partition_id:null,filesystem_id:disk.filesystem.id,original_type:disk.type,original:disk};"virtual"===disk.type&&(disk.parent_type=disk.parent.type),filesystems.push(data)}angular.forEach(disk.partitions,function(partition){hasMountedFilesystem(partition)&&filesystems.push({type:"filesystem",name:partition.name,size_human:partition.size_human,fstype:partition.filesystem.fstype,mount_point:partition.filesystem.mount_point,mount_options:partition.filesystem.mount_options,block_id:disk.id,partition_id:partition.id,filesystem_id:partition.filesystem.id,original_type:"partition",original:partition})})}),angular.forEach($scope.node.special_filesystems,function(filesystem){filesystems.push({type:"filesystem",name:"—",size_human:"—",fstype:filesystem.fstype,mount_point:filesystem.mount_point,mount_options:filesystem.mount_options,block_id:null,partition_id:null,original_type:"special"})}),angular.forEach(filesystems,function(filesystem){var key=getUniqueKey(filesystem),oldFilesystem=$scope.filesystemsMap[key];angular.isObject(oldFilesystem)?filesystem.$selected=oldFilesystem.$selected:filesystem.$selected=!1}),$scope.filesystems=filesystems,$scope.filesystemsMap={},angular.forEach(filesystems,function(filesystem){$scope.filesystemsMap[getUniqueKey(filesystem)]=filesystem}),$scope.updateFilesystemSelection(!1),cachesets=[],angular.forEach($scope.node.disks,function(disk){"cache-set"===disk.type&&cachesets.push({type:"cache-set",name:disk.name,size_human:disk.size_human,cache_set_id:disk.id,used_by:disk.used_for})}),angular.forEach(cachesets,function(cacheset){var key=getUniqueKey(cacheset),oldCacheSet=$scope.cachesetsMap[key];angular.isObject(oldCacheSet)?cacheset.$selected=oldCacheSet.$selected:cacheset.$selected=!1}),$scope.cachesets=cachesets,$scope.cachesetsMap={},angular.forEach(cachesets,function(cacheset){$scope.cachesetsMap[getUniqueKey(cacheset)]=cacheset}),$scope.updateCacheSetsSelection(!1),function(){var available=[];if(angular.forEach($scope.node.disks,function(disk){if(!isInUse(disk)){var has_partitions=!1;angular.isArray(disk.partitions)&&disk.partitions.length>0&&(has_partitions=!0);var data={name:disk.name,size_human:disk.size_human,available_size_human:disk.available_size_human,used_size_human:disk.used_size_human,type:disk.type,model:disk.model,serial:disk.serial,tags:getTags(disk),fstype:hasFormattedUnmountedFilesystem(disk),mount_point:null,mount_options:null,block_id:disk.id,partition_id:null,has_partitions:has_partitions,is_boot:disk.is_boot,original:disk,test_status:disk.test_status,firmware_version:disk.firmware_version};"virtual"===disk.type&&(data.parent_type=disk.parent.type),available.push(data)}angular.forEach(disk.partitions,function(partition){isInUse(partition)||available.push({name:partition.name,size_human:partition.size_human,available_size_human:partition.available_size_human,used_size_human:partition.used_size_human,type:"partition",model:"",serial:"",tags:[],fstype:hasFormattedUnmountedFilesystem(partition),mount_point:null,mount_options:null,block_id:disk.id,partition_id:partition.id,has_partitions:!1,is_boot:!1,original:partition})})}),angular.forEach(available,function(disk){var key=getUniqueKey(disk),oldDisk=$scope.availableMap[key];angular.isObject(oldDisk)?(disk.$selected=oldDisk.$selected,disk.$options=oldDisk.$options):(disk.$selected=!1,disk.$options={})}),$scope.available=available,$scope.availableMap={},angular.forEach(available,function(disk){$scope.availableMap[getUniqueKey(disk)]=disk}),angular.isObject($scope.availableNew))if(angular.isObject($scope.availableNew.device)){var key=getUniqueKey($scope.availableNew.device);$scope.availableNew.device=$scope.availableMap[key]}else if(angular.isArray($scope.availableNew.devices)){var newDevices=[];angular.forEach($scope.availableNew.devices,function(device){var key=getUniqueKey(device),newDevice=$scope.availableMap[key];angular.isObject(newDevice)&&newDevices.push(newDevice)}),$scope.availableNew.devices=newDevices}$scope.updateAvailableSelection(!1)}(),used=[],angular.forEach($scope.node.disks,function(disk){if(isInUse(disk)&&"cache-set"!==disk.type){var has_partitions=!1;angular.isArray(disk.partitions)&&disk.partitions.length>0&&(has_partitions=!0);var data={name:disk.name,type:disk.type,model:disk.model,serial:disk.serial,tags:getTags(disk),used_for:disk.used_for,is_boot:disk.is_boot,has_partitions:has_partitions,test_status:disk.test_status,firmware_version:disk.firmware_version};"virtual"===disk.type&&(data.parent_type=disk.parent.type),used.push(data)}angular.forEach(disk.partitions,function(partition){isInUse(partition)&&"cache-set"!==partition.type&&used.push({name:partition.name,type:"partition",model:"",serial:"",tags:[],used_for:partition.used_for,is_boot:!1})})}),$scope.used=used):($scope.has_disks=!1,$scope.filesystems=[],$scope.filesystemsMap={},$scope.filesystemMode=SELECTION_MODE.NONE,$scope.filesystemAllSelected=!1,$scope.cachesets=[],$scope.cachesetsMap={},$scope.cachesetsMode=SELECTION_MODE.NONE,$scope.cachesetsAllSelected=!1,$scope.available=[],$scope.availableMap={},$scope.availableMode=SELECTION_MODE.NONE,$scope.availableAllSelected=!1,$scope.availableNew={},$scope.used=[])}function deselectAll(items){angular.forEach(items,function(item){item.$selected=!1})}function capitalizeFirstLetter(string){return string.charAt(0).toUpperCase()+string.slice(1)}function getIndexFromName(prefix,name){var match=new RegExp("^"+prefix+"([0-9]+)$").exec(name);if(angular.isArray(match)&&2===match.length)return parseInt(match[1],10)}function getNextName(prefix){var idx=-1;return angular.forEach($scope.node.disks,function(disk){var dIdx=getIndexFromName(prefix,disk.name);angular.isNumber(dIdx)&&(idx=Math.max(idx,dIdx)),angular.forEach(disk.partitions,function(partition){dIdx=getIndexFromName(prefix,partition.name),angular.isNumber(dIdx)&&(idx=Math.max(idx,dIdx))})}),prefix+(idx+1)}function isNameAlreadyInUse(name,exclude_disk){if(!angular.isArray($scope.node.disks))return!1;var i,j;for(i=0;i<$scope.node.disks.length;i++){var disk=$scope.node.disks[i];if(disk.name===name&&(!angular.isObject(exclude_disk)||"partition"===exclude_disk.type||exclude_disk.block_id!==disk.id))return!0;if(angular.isArray(disk.partitions))for(j=0;j=0)))},$scope.isNameInvalid=function(disk){return""!==disk.name&&!!isNameAlreadyInUse(disk.name,disk)},$scope.nameHasChanged=function(disk){if(isLogicalVolume(disk)){var parentName=disk.original.name.split("-")[0]+"-";0!==disk.name.indexOf(parentName)&&(disk.name=parentName)}},$scope.availableCancel=function(disk){$scope.updateAvailableSelection(!0),$scope.availableNew={}},$scope.usesMountPoint=function(fstype){return angular.isString(fstype)&&"swap"!==fstype},$scope.usesStorage=function(fstype){return angular.isString(fstype)&&"tmpfs"!==fstype&&"ramfs"!==fstype},$scope.isMountPointInvalid=function(mountPoint){return!angular.isUndefined(mountPoint)&&""!==mountPoint&&("none"!==mountPoint&&"/"!==mountPoint[0])},$scope.canDelete=function(disk){return!(!$scope.isSuperUser()||$scope.isAllStorageDisabled())&&("lvm-vg"===disk.type?0===disk.original.used_size:!disk.has_partitions)},$scope.availableDelete=function(){$scope.availableMode=SELECTION_MODE.DELETE},$scope.availableQuickDelete=function(disk){deselectAll($scope.available),disk.$selected=!0,$scope.updateAvailableSelection(!0),$scope.availableDelete()},$scope.canEdit=function(disk){return!(!$scope.isSuperUser()||$scope.isAllStorageDisabled())},$scope.availableEdit=function(disk){$scope.availableMode=SELECTION_MODE.EDIT,"lvm-vg"===disk.type?disk.$options={editingTags:!1,editingFilesystem:!1}:"partition"===disk.type?disk.$options={editingTags:!1,editingFilesystem:!0,fstype:disk.fstype}:(disk.$options={editingFilesystem:!0,editingTags:!0,tags:angular.copy(disk.tags),fstype:disk.fstype},$scope.canFormatAndMount(disk)||(disk.$options.editingFilesystem=!1))},$scope.availableQuickEdit=function(disk){deselectAll($scope.available),disk.$selected=!0,$scope.updateAvailableSelection(!0),$scope.availableEdit(disk)},$scope.availableConfirmEdit=function(disk){var params={name:disk.name};if(!$scope.isNameInvalid(disk)&&!$scope.isMountPointInvalid(disk.$options.mountPoint)){if(""===disk.name&&(disk.name=disk.original.name),isLogicalVolume(disk)){var parentName=disk.original.name.split("-")[0]+"-";params.name=disk.name.slice(parentName.length)}if(angular.isDefined(disk.$options.fstype)&&(params.fstype=disk.$options.fstype,params.mount_point=disk.$options.mountPoint||"",params.mount_options=disk.$options.mountOptions||""),angular.isArray(disk.$options.tags)&&(params.tags=disk.$options.tags.map(function(tag){return tag.text})),"partition"===disk.type?MachinesManager.updateFilesystem($scope.node,disk.block_id,disk.partition_id,params.fstype,params.mount_point,params.mount_options,params.tags):MachinesManager.updateDisk($scope.node,disk.block_id,params),disk.fstype=disk.$options.fstype,disk.mount_point=disk.$options.mountPoint,disk.mount_options=disk.$options.mountOptions,disk.tags=disk.$options.tags,disk.$options={},angular.isString(disk.mount_point)&&""!==disk.mount_point){$scope.filesystems.push({name:disk.name,size_human:disk.size_human,fstype:disk.fstype,mount_point:disk.mount_point,mount_options:disk.mount_options,block_id:disk.block_id,partition_id:disk.partition_id});var idx=$scope.available.indexOf(disk);$scope.available.splice(idx,1)}disk.$selected=!1,$scope.updateAvailableSelection(!0)}},$scope.getRemoveTypeText=function(disk){if("filesystem"===disk.type){if(!angular.isObject(disk.original))return"special filesystem";disk=disk.original}return"physical"===disk.type?"physical disk":"partition"===disk.type?"partition":"lvm-vg"===disk.type?"volume group":"virtual"===disk.type?"lvm-vg"===disk.parent_type?"logical volume":0===disk.parent_type.indexOf("raid-")?"RAID "+disk.parent_type.split("-")[1]+" disk":disk.parent_type+" disk":void 0},$scope.availableConfirmDelete=function(disk){"lvm-vg"===disk.type?MachinesManager.deleteVolumeGroup($scope.node,disk.block_id):"partition"===disk.type?MachinesManager.deletePartition($scope.node,disk.partition_id):MachinesManager.deleteDisk($scope.node,disk.block_id);var idx=$scope.available.indexOf(disk);$scope.available.splice(idx,1),$scope.updateAvailableSelection(!0)},$scope.availablePartition=function(disk){$scope.availableMode=SELECTION_MODE.PARTITION;var size_and_units=disk.available_size_human.split(" ");disk.$options={size:size_and_units[0],sizeUnits:size_and_units[1],fstype:null,mountPoint:"",mountOptions:""}},$scope.availableQuickPartition=function(disk){deselectAll($scope.available),disk.$selected=!0,$scope.updateAvailableSelection(!0),$scope.availablePartition(disk)},$scope.getAddPartitionName=function(disk){var length,partitions=disk.original.partitions;return length=angular.isArray(partitions)?partitions.length:0,"mbr"===disk.original.partition_table_type&&length>2?disk.name+"-part"+(length+2):0===$scope.node.architecture.indexOf("ppc64el")&&disk.original.is_boot?disk.name+"-part"+(length+2):disk.name+"-part"+(length+1)},$scope.isAddPartitionSizeInvalid=function(disk){if(""!==disk.$options.size&&(string=disk.$options.size,/^-?\d+\.?\d*$/.test(string))){var bytes=ConverterService.unitsToBytes(disk.$options.size,disk.$options.sizeUnits);return bytesdisk.original.available_size&&ConverterService.roundUnits(disk.$options.size,disk.$options.sizeUnits)>disk.original.available_size}return!0;var string},$scope.availableConfirmPartition=function(disk){if(!$scope.isAddPartitionSizeInvalid(disk)&&!$scope.isMountPointInvalid(disk.$options.mountPoint)){var bytes=ConverterService.unitsToBytes(disk.$options.size,disk.$options.sizeUnits),size_and_units=disk.original.available_size_human.split(" ");disk.$options.size===size_and_units[0]&&disk.$options.sizeUnits===size_and_units[1]&&(bytes=disk.original.available_size);var removeDisk=!1,available_space=$scope.availablePartitionSpace(disk);bytes>=available_space&&(bytes=available_space,removeDisk=!0);var params={};if(angular.isString(disk.$options.fstype)&&""!==disk.$options.fstype&&(params.fstype=disk.$options.fstype,""!==disk.$options.mountPoint&&(params.mount_point=disk.$options.mountPoint,params.mount_options=disk.$options.mountOptions)),MachinesManager.createPartition($scope.node,disk.block_id,bytes,params),removeDisk){var idx=$scope.available.indexOf(disk);$scope.available.splice(idx,1)}$scope.updateAvailableSelection(!0)}},$scope.getSelectedCacheSets=function(){var cachesets=[];return angular.forEach($scope.cachesets,function(cacheset){cacheset.$selected&&cachesets.push(cacheset)}),cachesets},$scope.updateCacheSetsSelection=function(force){angular.isUndefined(force)&&(force=!1);var cachesets=$scope.getSelectedCacheSets();0===cachesets.length?$scope.cachesetsMode=SELECTION_MODE.NONE:1===cachesets.length&&force?$scope.cachesetsMode=SELECTION_MODE.SINGLE:force&&($scope.cachesetsMode=SELECTION_MODE.MUTLI),0===$scope.cachesets.length?$scope.cachesetsAllSelected=!1:cachesets.length===$scope.cachesets.length?$scope.cachesetsAllSelected=!0:$scope.cachesetsAllSelected=!1},$scope.toggleCacheSetSelect=function(cacheset){cacheset.$selected=!cacheset.$selected,$scope.updateCacheSetsSelection(!0)},$scope.toggleCacheSetAllSelect=function(){angular.forEach($scope.cachesets,function(cacheset){$scope.cachesetsAllSelected?cacheset.$selected=!1:cacheset.$selected=!0}),$scope.updateCacheSetsSelection(!0)},$scope.isCacheSetsDisabled=function(){return $scope.isAllStorageDisabled()&&!$scope.isSuperUser()||$scope.cachesetsMode!==SELECTION_MODE.NONE&&$scope.cachesetsMode!==SELECTION_MODE.SINGLE&&$scope.cachesetsMode!==SELECTION_MODE.MUTLI},$scope.cacheSetCancel=function(){deselectAll($scope.cachesets),$scope.updateCacheSetsSelection(!0)},$scope.canDeleteCacheSet=function(cacheset){return""===cacheset.used_by&&!$scope.isAllStorageDisabled()&&$scope.isSuperUser()},$scope.cacheSetDelete=function(){$scope.cachesetsMode=SELECTION_MODE.DELETE},$scope.quickCacheSetDelete=function(cacheset){deselectAll($scope.cachesets),cacheset.$selected=!0,$scope.updateCacheSetsSelection(!0),$scope.cacheSetDelete()},$scope.cacheSetConfirmDelete=function(cacheset){MachinesManager.deleteCacheSet($scope.node,cacheset.cache_set_id);var idx=$scope.cachesets.indexOf(cacheset);$scope.cachesets.splice(idx,1),$scope.updateCacheSetsSelection()},$scope.canCreateCacheSet=function(){if($scope.isAvailableDisabled()||!$scope.isSuperUser())return!1;var selected=$scope.getSelectedAvailable();return 1===selected.length&&(!selected[0].has_partitions&&!$scope.hasUnmountedFilesystem(selected[0])&&"lvm-vg"!==selected[0].type)},$scope.createCacheSet=function(){if($scope.canCreateCacheSet()){var disk=$scope.getSelectedAvailable()[0];MachinesManager.createCacheSet($scope.node,disk.block_id,disk.partition_id);var idx=$scope.available.indexOf(disk);$scope.available.splice(idx,1)}},$scope.getCannotCreateBcacheMsg=function(){if(0===$scope.cachesets.length)return"Create at least one cache set to create bcache";var selected=$scope.getSelectedAvailable();return 1===selected.length?$scope.hasUnmountedFilesystem(selected[0])?"Device is formatted; unformat the device to create bcache":"lvm-vg"===selected[0].type?"Cannot use a logical volume as a backing device for bcache.":selected[0].has_partitions?"Device has already been partitioned; create a new partition to use as the bcache backing device":null:"Select only one available device to create bcache"},$scope.canCreateBcache=function(){return!($scope.isAvailableDisabled()||!$scope.isSuperUser())&&null===$scope.getCannotCreateBcacheMsg()},$scope.createBcache=function(){$scope.canCreateBcache()&&($scope.availableMode=SELECTION_MODE.BCACHE,$scope.availableNew={name:getNextName("bcache"),device:$scope.getSelectedAvailable()[0],cacheset:$scope.cachesets[0],cacheMode:"writeback",fstype:null,mountPoint:"",mountOptions:"",tags:[]})},$scope.fstypeChanged=function(options){null===options.fstype?(options.mountPoint="",options.mountOptions=""):$scope.usesMountPoint(options.fstype)?"none"===options.mountPoint&&(options.mountPoint=""):options.mountPoint="none"},$scope.isNewDiskNameInvalid=function(){if(!angular.isObject($scope.node)||!angular.isArray($scope.node.disks))return!0;if(""===$scope.availableNew.name)return!0;var i,j;for(i=0;i<$scope.node.disks.length;i++){var disk=$scope.node.disks[i];if($scope.availableNew.name===disk.name)return!0;if(angular.isArray(disk.partitions))for(j=0;j0&&(params.tags=$scope.availableNew.tags.map(function(tag){return tag.text})),MachinesManager.createBcache($scope.node,params);var idx=$scope.available.indexOf($scope.availableNew.device);$scope.available.splice(idx,1),$scope.availableNew={},$scope.updateAvailableSelection(!0)}},$scope.canCreateRAID=function(){if($scope.isAvailableDisabled()||!$scope.isSuperUser())return!1;var selected=$scope.getSelectedAvailable();if(selected.length>1){var i;for(i=0;i=mode.min_disks&&modes.push(mode)}),modes},$scope.getTotalNumberOfAvailableSpares=function(){var mode=$scope.availableNew.mode;if(angular.isUndefined(mode)||!mode.allows_spares)return 0;var diff=$scope.availableNew.devices.length-mode.min_disks;return diff<0&&(diff=0),diff},$scope.getNumberOfRemainingSpares=function(){var allowed=$scope.getTotalNumberOfAvailableSpares();return allowed<=0?0:allowed-$scope.availableNew.spares.length},$scope.showSparesColumn=function(){return $scope.getTotalNumberOfAvailableSpares()>0},$scope.RAIDModeChanged=function(){$scope.availableNew.spares=[]},$scope.isActiveRAIDMember=function(disk){return!angular.isArray($scope.availableNew.spares)||-1===$scope.availableNew.spares.indexOf(getUniqueKey(disk))},$scope.isSpareRAIDMember=function(disk){return!$scope.isActiveRAIDMember(disk)},$scope.setAsActiveRAIDMember=function(disk){var idx=$scope.availableNew.spares.indexOf(getUniqueKey(disk));idx>-1&&$scope.availableNew.spares.splice(idx,1)},$scope.setAsSpareRAIDMember=function(disk){var key=getUniqueKey(disk);-1===$scope.availableNew.spares.indexOf(key)&&$scope.availableNew.spares.push(key)},$scope.getNewRAIDSize=function(){if(angular.isUndefined($scope.availableNew.mode))return"";var calculateSize=$scope.availableNew.mode.calculateSize;if(!angular.isFunction(calculateSize))return"";var numDisks=$scope.availableNew.devices.length-$scope.availableNew.spares.length,minSize=Number.MAX_VALUE;angular.forEach($scope.availableNew.devices,function(device){var deviceSize=device.original.available_size||device.original.size;minSize=Math.min(minSize,deviceSize)});var size=calculateSize(minSize,numDisks);return ConverterService.bytesToUnits(size).string},$scope.createRAIDCanSave=function(){return!$scope.isNewDiskNameInvalid()&&!$scope.isMountPointInvalid($scope.availableNew.mountPoint)},$scope.availableConfirmCreateRAID=function(){if($scope.createRAIDCanSave()){var params={name:$scope.availableNew.name,level:$scope.availableNew.mode.level,block_devices:[],partitions:[],spare_devices:[],spare_partitions:[]};angular.forEach($scope.availableNew.devices,function(device){$scope.isActiveRAIDMember(device)?"partition"===device.type?params.partitions.push(device.partition_id):params.block_devices.push(device.block_id):"partition"===device.type?params.spare_partitions.push(device.partition_id):params.spare_devices.push(device.block_id)}),angular.isString($scope.availableNew.fstype)&&""!==$scope.availableNew.fstype&&(params.fstype=$scope.availableNew.fstype,""!==$scope.availableNew.mountPoint&&(params.mount_point=$scope.availableNew.mountPoint,params.mount_options=$scope.availableNew.mountOptions)),angular.isArray($scope.availableNew.tags)&&$scope.availableNew.tags.length>0&&(params.tags=$scope.availableNew.tags.map(function(tag){return tag.text})),MachinesManager.createRAID($scope.node,params),angular.forEach($scope.availableNew.devices,function(device){var idx=$scope.available.indexOf($scope.availableNew.device);$scope.available.splice(idx,1)}),$scope.availableNew={},$scope.updateAvailableSelection(!0)}},$scope.canCreateVolumeGroup=function(){if($scope.isAvailableDisabled()||!$scope.isSuperUser())return!1;var selected=$scope.getSelectedAvailable();if(selected.length>0){var i;for(i=0;idisk.original.available_size&&(bytes=disk.original.available_size);var removeDisk=!1;bytes===disk.original.available_size&&(removeDisk=!0);var name=disk.$options.name.slice(disk.name.length+1),params={};if(angular.isString(disk.$options.fstype)&&""!==disk.$options.fstype&&(params.fstype=disk.$options.fstype,""!==disk.$options.mountPoint&&(params.mount_point=disk.$options.mountPoint,params.mount_options=disk.$options.mountOptions)),angular.isArray(disk.$options.tags)&&disk.$options.tags.length>0&&(params.tags=disk.$options.tags.map(function(tag){return tag.text})),MachinesManager.createLogicalVolume($scope.node,disk.block_id,name,bytes,params),removeDisk){var idx=$scope.available.indexOf(disk);$scope.available.splice(idx,1)}$scope.updateAvailableSelection(!0)}},$scope.isAllStorageDisabled=function(){var authUser=UsersManager.getAuthUser();return!angular.isObject(authUser)||!angular.isObject($scope.node)||!authUser.is_superuser&&authUser.username!==$scope.node.owner||!(!angular.isObject($scope.node)||-1!==["Ready","Allocated"].indexOf($scope.node.status))},$scope.hasStorageLayoutIssues=function(){return!(!angular.isObject($scope.node)||!angular.isArray($scope.node.storage_layout_issues))&&$scope.node.storage_layout_issues.length>0},$scope.$parent.controllerLoaded("storageController",$scope)}])},"./src/maasserver/static/js/angular/controllers/node_details_storage_filesystems.js":function(module,exports){!function(){function SpecialFilesystem(){this.fstype="tmpfs",this.mountPoint="",this.mountOptions=""}SpecialFilesystem.prototype.isValid=function(){return this.mountPoint.startsWith("/")},SpecialFilesystem.prototype.describe=function(){var parts=[this.fstype];switch(this.mountPoint.startsWith("/")&&parts.push("at "+this.mountPoint),this.fstype){case"tmpfs":var size=this.mountOptions.match(/\bsize=(\d+)(%?)/);null!==size&&("%"===size[2]?parts.push("limited to "+size[1]+"% of memory"):parts.push("limited to "+size[1]+" bytes"))}return parts.join(" ")},angular.module("MAAS").controller("NodeFilesystemsController",["$scope",function($scope){$scope.dropdown=null,$scope.addSpecialFilesystem=function(){$scope.dropdown="special"},$scope.addSpecialFilesystemFinished=function(){"special"===$scope.dropdown&&($scope.dropdown=null)}}]),angular.module("MAAS").controller("NodeAddSpecialFilesystemController",["$scope","MachinesManager",function($scope,MachinesManager){$scope.filesystem=new SpecialFilesystem,$scope.description=null,$scope.$watch("filesystem",function(filesystem){$scope.description=filesystem.describe()},!0),$scope.canMount=function(){return $scope.filesystem.isValid()},$scope.mount=function(){MachinesManager.mountSpecialFilesystem($scope.node,$scope.filesystem.fstype,$scope.filesystem.mountPoint,$scope.filesystem.mountOptions),$scope.addSpecialFilesystemFinished()},$scope.cancel=function(){$scope.filesystem=new SpecialFilesystem,$scope.addSpecialFilesystemFinished()}}])}()},"./src/maasserver/static/js/angular/controllers/node_events.js":function(module,exports){angular.module("MAAS").controller("NodeEventsController",["$scope","$rootScope","$routeParams","$location","MachinesManager","ControllersManager","EventsManagerFactory","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$location,MachinesManager,ControllersManager,EventsManagerFactory,ManagerHelperService,ErrorService){var eventsManager=null;function nodeLoaded(node){$scope.node=node,$scope.loaded=!0,eventsManager=EventsManagerFactory.getManager(node.id),$scope.events=eventsManager.getItems(),$scope.days=eventsManager.getMaximumDays(),eventsManager.loadItems().then(function(){$scope.eventsLoaded=!0}),$scope.$watch("node.fqdn",function(){$rootScope.title=$scope.node.fqdn+" - events"})}$rootScope.title="Loading...",$scope.loaded=!1,$scope.node=null,$scope.events=[],$scope.eventsLoaded=!1,$scope.days=1,$scope.getEventText=function(event){var text=event.type.description;return angular.isString(event.description)&&event.description.length>0&&(text+=" - "+event.description),text},$scope.loadMore=function(){$scope.days+=1,eventsManager.loadMaximumDays($scope.days)},-1!==$location.path().indexOf("/controller")?($scope.nodesManager=ControllersManager,$scope.type_name="controller",$rootScope.page="controllers"):($scope.nodesManager=MachinesManager,$scope.type_name="machine",$rootScope.page="machines"),ManagerHelperService.loadManager($scope,$scope.nodesManager).then(function(){var activeNode=$scope.nodesManager.getActiveItem();angular.isObject(activeNode)&&activeNode.system_id===$routeParams.system_id?nodeLoaded(activeNode):$scope.nodesManager.setActiveItem($routeParams.system_id).then(function(node){nodeLoaded(node)},function(error){ErrorService.raiseError(error)})}),$scope.$on("$destroy",function(){angular.isObject(eventsManager)&&eventsManager.destroy()})}])},"./src/maasserver/static/js/angular/controllers/node_result.js":function(module,exports){angular.module("MAAS").controller("NodeResultController",["$scope","$rootScope","$routeParams","$location","MachinesManager","ControllersManager","NodeResultsManagerFactory","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$location,MachinesManager,ControllersManager,NodeResultsManagerFactory,ManagerHelperService,ErrorService){function nodeLoaded(node){$scope.node=node,$scope.loaded=!0;var nodeResultsManager=NodeResultsManagerFactory.getManager($scope.node),requestedResult=parseInt($routeParams.id,10);nodeResultsManager.getItem(requestedResult).then(function(result){$scope.result=result,$scope.get_result_data($scope.output),$scope.resultLoaded=!0,$rootScope.title=$scope.node.fqdn+" - "+$scope.result.name})}$rootScope.title="Loading...",$scope.loaded=!1,$scope.resultLoaded=!1,$scope.node=null,$scope.output="combined",$scope.result=null,$scope.get_result_data=function(output){$scope.output=output,$scope.data="Loading...",NodeResultsManagerFactory.getManager($scope.node).get_result_data($scope.result.id,$scope.output).then(function(data){$scope.data=""===data?"Empty file.":data})},$scope.$watch("node.fqdn",function(){angular.isObject($scope.node)&&angular.isObject($scope.result)&&($rootScope.title=$scope.node.fqdn+" - "+$scope.result.name)}),-1!==$location.path().indexOf("/controller")?($scope.nodesManager=ControllersManager,$scope.type_name="controller",$rootScope.page="controllers"):($scope.nodesManager=MachinesManager,$scope.type_name="machine",$rootScope.page="machines"),ManagerHelperService.loadManager($scope,$scope.nodesManager).then(function(){var activeNode=$scope.nodesManager.getActiveItem();angular.isObject(activeNode)&&activeNode.system_id===$routeParams.system_id?nodeLoaded(activeNode):$scope.nodesManager.setActiveItem($routeParams.system_id).then(function(node){nodeLoaded(node)},function(error){ErrorService.raiseError(error)})}),$scope.$on("$destroy",function(){var nodeResultsManager=NodeResultsManagerFactory.getManager($scope.node);angular.isObject(nodeResultsManager)&&nodeResultsManager.destroy()})}])},"./src/maasserver/static/js/angular/controllers/node_results.js":function(module,exports){angular.module("MAAS").controller("NodeResultsController",["$scope","$routeParams","$location","MachinesManager","ControllersManager","NodeResultsManagerFactory","ManagerHelperService","ErrorService",function($scope,$routeParams,$location,MachinesManager,ControllersManager,NodeResultsManagerFactory,ManagerHelperService,ErrorService){function updateLogs(){var i,had_installation=3===$scope.logs.availableOptions.length;if($scope.logs.availableOptions.length=0,$scope.installation_results&&$scope.installation_results.length>0&&(6===$scope.node.status_code||9===$scope.node.status_code||11===$scope.node.status_code))for(i=0;i<$scope.installation_results.length;i++)if("/tmp/install.log"===$scope.installation_results[i].name){$scope.logs.availableOptions.push({title:"Installation output",id:$scope.installation_results[i].id});break}$scope.logs.availableOptions.push({title:"Machine output (YAML)",id:"summary_yaml"}),$scope.logs.availableOptions.push({title:"Machine output (XML)",id:"summary_xml"}),had_installation||3!==$scope.logs.availableOptions.length?(!$scope.selectedLog||had_installation&&2===$scope.logs.length)&&($scope.logs.option=$scope.logs.availableOptions[0]):$scope.logs.option=$scope.logs.availableOptions[0]}function nodeLoaded(node){$scope.node=node,$scope.loaded=!0,$scope.nodeResultsManager=NodeResultsManagerFactory.getManager(node,$scope.section.area),$scope.nodeResultsManager.loadItems().then(function(){$scope.commissioning_results=$scope.nodeResultsManager.commissioning_results,$scope.testing_results=$scope.nodeResultsManager.testing_results,$scope.installation_results=$scope.nodeResultsManager.installation_results,$scope.results=$scope.nodeResultsManager.results,"logs"===$scope.section.area&&(updateLogs(),$scope.$watch("installation_results",updateLogs,!0),$scope.$watch("installation_results",$scope.updateLogOutput,!0)),$scope.resultsLoaded=!0})}$scope.nodeResultsManager=null,$scope.commissioning_results=null,$scope.testing_results=null,$scope.installation_results=null,$scope.results=null,$scope.logs={option:null,availableOptions:[]},$scope.logOutput="Loading...",$scope.loaded=!1,$scope.resultsLoaded=!1,$scope.node=null,-1!==$location.path().indexOf("/controller")?$scope.nodesManager=ControllersManager:$scope.nodesManager=MachinesManager,ManagerHelperService.loadManager($scope,$scope.nodesManager).then(function(){var activeNode=$scope.nodesManager.getActiveItem();angular.isObject(activeNode)&&activeNode.system_id===$routeParams.system_id?nodeLoaded(activeNode):$scope.nodesManager.setActiveItem($routeParams.system_id).then(function(node){nodeLoaded(node)},function(error){ErrorService.raiseError(error)})}),$scope.updateLogOutput=function(){if($scope.logOutput="Loading...",$scope.node)if("summary_xml"===$scope.logs.option.id)$scope.nodesManager.getSummaryXML($scope.node).then(function(output){$scope.logOutput=output});else if("summary_yaml"===$scope.logs.option.id)$scope.nodesManager.getSummaryYAML($scope.node).then(function(output){$scope.logOutput=output});else{var i,result=null;for(i=0;i<$scope.installation_results.length;i++)if($scope.installation_results[i].id===$scope.logs.option.id){result=$scope.installation_results[i];break}switch(result.status){case 0:$scope.logOutput="System is booting...";break;case 1:$scope.logOutput="Installation has begun!";break;case 2:$scope.nodeResultsManager.get_result_data(result.id,"combined").then(function(output){$scope.logOutput=""===output?"Installation has succeeded but no output was given.":output});break;case 3:$scope.nodeResultsManager.get_result_data(result.id,"combined").then(function(output){$scope.logOutput=""===output?"Installation has failed and no output was given.":output});break;case 4:$scope.logOutput="Installation failed after 40 minutes.";break;case 5:$scope.logOutput="Installation was aborted.";break;default:$scope.logOutput="BUG: Unknown log status "+result.status}}},$scope.loadHistory=function(result){result.showing_results=!1,angular.isArray(result.history_list)?result.showing_history=!0:(result.loading_history=!0,$scope.nodeResultsManager.get_history(result.id).then(function(history){result.history_list=history,result.loading_history=!1,result.showing_history=!0}))},$scope.$on("$destroy",function(){angular.isObject($scope.nodeResultsManager)&&$scope.nodeResultsManager.destroy()})}])},"./src/maasserver/static/js/angular/controllers/nodes_list.js":function(module,exports){angular.module("MAAS").controller("NodesListController",["$q","$scope","$interval","$rootScope","$routeParams","$location","MachinesManager","DevicesManager","ControllersManager","GeneralManager","ManagerHelperService","SearchService","ZonesManager","UsersManager","ServicesManager","ScriptsManager","SwitchesManager","ResourcePoolsManager",function($q,$scope,$interval,$rootScope,$routeParams,$location,MachinesManager,DevicesManager,ControllersManager,GeneralManager,ManagerHelperService,SearchService,ZonesManager,UsersManager,ServicesManager,ScriptsManager,SwitchesManager,ResourcePoolsManager){var DEVICE_IP_ASSIGNMENT={external:"External",dynamic:"Dynamic",static:"Static"};function isViewingSelected(tab){var search=$scope.tabs[tab].search.toLowerCase();return"in:(selected)"===search||"in:selected"===search}function enterViewSelected(tab){$scope.tabs[tab].previous_search=$scope.tabs[tab].search,$scope.tabs[tab].search="in:(Selected)"}function leaveViewSelected(tab){isViewingSelected(tab)&&($scope.tabs[tab].search=$scope.tabs[tab].previous_search,$scope.updateFilters(tab))}function updateAllViewableChecked(tab){if(0!==$scope.tabs[tab].filtered_items.length){var i;for(i=0;i<$scope.tabs[tab].filtered_items.length;i++)if(!$scope.tabs[tab].filtered_items[i].$selected)return void($scope.tabs[tab].allViewableChecked=!1);$scope.tabs[tab].allViewableChecked=!0}else $scope.tabs[tab].allViewableChecked=!1}function clearAction(tab){resetActionProgress(tab),leaveViewSelected(tab),$scope.tabs[tab].actionOption=null,$scope.tabs[tab].zoneSelection=null,$scope.tabs[tab].poolSelection=null,$scope.tabs[tab].poolAction="select-pool",$scope.tabs[tab].newPool={},"machines"!==tab&&"switches"!==tab||(angular.isFunction($scope.tabs[tab].osSelection.$reset)&&$scope.tabs[tab].osSelection.$reset(),$scope.tabs[tab].commissionOptions.enableSSH=!1,$scope.tabs[tab].commissionOptions.skipNetworking=!1,$scope.tabs[tab].commissionOptions.skipStorage=!1,$scope.tabs[tab].commissionOptions.updateFirmware=!1,$scope.tabs[tab].commissionOptions.configureHBA=!1),$scope.tabs[tab].commissioningSelection=[],$scope.tabs[tab].testSelection=[]}function shouldClearAction(tab){0===$scope.tabs[tab].selectedItems.length&&clearAction(tab),$scope.tabs[tab].actionOption&&!isViewingSelected(tab)&&($scope.tabs[tab].actionOption=null)}function removeEmptyFilter(tab){0===$scope.tabs[tab].filtered_items.length&&""!==$scope.tabs[tab].search&&$scope.tabs[tab].search===$scope.tabs[tab].previous_search&&($scope.tabs[tab].search="",$scope.updateFilters(tab))}function updateActionErrorCount(tab){var i;for($scope.tabs[tab].actionErrorCount=0,i=0;i<$scope.tabs[tab].selectedItems.length;i++){$scope.supportsAction($scope.tabs[tab].selectedItems[i],tab)||($scope.tabs[tab].actionErrorCount+=1),$scope.tabs[tab].selectedItems[i].action_failed=!1}}function resetActionProgress(tab){var progress=$scope.tabs[tab].actionProgress;progress.completed=progress.total=0,progress.errors={},progress.showing_confirmation=!1,progress.affected_nodes=0}function addErrorToActionProgress(tab,error,node){var progress=$scope.tabs[tab].actionProgress;progress.completed+=1;var nodes=progress.errors[error];angular.isUndefined(nodes)?progress.errors[error]=[node]:nodes.push(node)}function updateSelectedItems(tab){$scope.hasActionsFailed(tab)?angular.forEach($scope.tabs[tab].manager.getItems(),function(node){!1===node.action_failed&&$scope.tabs[tab].manager.unselectItem(node.system_id)}):$scope.hasActionsInProgress(tab)||(clearAction(tab),enterViewSelected(tab))}$rootScope.title="Machines",$rootScope.page="machines",$scope.MAAS_config=MAAS_config,$scope.machines=MachinesManager.getItems(),$scope.zones=ZonesManager.getItems(),$scope.pools=ResourcePoolsManager.getItems(),$scope.devices=DevicesManager.getItems(),$scope.controllers=ControllersManager.getItems(),$scope.switches=SwitchesManager.getItems(),$scope.showswitches="on"===$routeParams.switches,$scope.currentpage="machines",$scope.osinfo={},$scope.scripts=ScriptsManager.getItems(),$scope.loading=!0,$scope.tabs={},$scope.pluralize=function(tab){var verb={machines:"machine",switches:"switch",devices:"device",controllers:"controller"}[tab];return $scope.tabs[tab].selectedItems.length>1&&(verb=tab),verb},$scope.tabs.machines={},$scope.tabs.machines.pagetitle="Machines",$scope.tabs.machines.currentpage="machines",$scope.tabs.machines.manager=MachinesManager,$scope.tabs.machines.previous_search="",$scope.tabs.machines.search="",$scope.tabs.machines.searchValid=!0,$scope.tabs.machines.selectedItems=MachinesManager.getSelectedItems(),$scope.tabs.machines.metadata=MachinesManager.getMetadata(),$scope.tabs.machines.filters=SearchService.getEmptyFilter(),$scope.tabs.machines.actionOption=null,$scope.tabs.machines.takeActionOptions=[],$scope.tabs.machines.actionErrorCount=0,$scope.tabs.machines.actionProgress={total:0,completed:0,errors:{},showing_confirmation:!1,affected_nodes:0},$scope.tabs.machines.osSelection={osystem:null,release:null,hwe_kernel:null},$scope.tabs.machines.zoneSelection=null,$scope.tabs.machines.poolSelection=null,$scope.tabs.machines.poolAction="select-pool",$scope.tabs.machines.newPool={},$scope.tabs.machines.commissionOptions={enableSSH:!1,skipNetworking:!1,skipStorage:!1,updateFirmware:!1,configureHBA:!1},$scope.tabs.machines.releaseOptions={},$scope.tabs.machines.commissioningSelection=[],$scope.tabs.machines.testSelection=[],$scope.tabs.pools={},$scope.tabs.pools.pagetitle="Machines",$scope.tabs.pools.currentpage="pools",$scope.tabs.pools.manager=ResourcePoolsManager,$scope.tabs.pools.actionOption=!1,$scope.tabs.pools.newPool={name:null,description:null},$scope.tabs.pools.addPool=function(){$scope.tabs.pools.actionOption=!0},$scope.tabs.pools.cancelAddPool=function(){$scope.tabs.pools.actionOption=!1,$scope.tabs.pools.newPool={}},$scope.tabs.pools.activeTarget=null,$scope.tabs.pools.activeTargetAction=null,$scope.tabs.pools.actionErrorMessage=null,$scope.tabs.pools.initiatePoolAction=function(pool,action){var tab=$scope.tabs.pools;tab.cancelPoolAction(),tab.activeTargetAction=action,tab.activeTarget=pool,tab.editingPool=pool},$scope.tabs.pools.cancelPoolAction=function(){var tab=$scope.tabs.pools;tab.activeTargetAction=null,tab.activeTarget=null,tab.actionErrorMessage=null},$scope.tabs.pools.isPoolAction=function(pool,action){var tab=$scope.tabs.pools;return(void 0===action||tab.activeTargetAction===action)&&null!==tab.activeTarget&&tab.activeTarget.id===pool.id},$scope.tabs.pools.actionConfirmEditPool=function(){$scope.tabs.pools.cancelPoolAction()},$scope.tabs.pools.actionConfirmDeletePool=function(){var tab=$scope.tabs.pools;tab.manager.deleteItem(tab.activeTarget).then(tab.cancelPoolAction,function(error){$scope.tabs.pools.actionErrorMessage=error})},$scope.tabs.pools.goToPoolMachines=function(pool){$scope.clearSearch("machines"),$scope.toggleFilter("pool",pool.name,"machines"),$scope.toggleTab("machines"),$location.path("/machines")},$scope.tabs.pools.isDefaultPool=function(pool){return 0===pool.id},$scope.tabs.devices={},$scope.tabs.devices.pagetitle="Devices",$scope.tabs.devices.currentpage="devices",$scope.tabs.devices.manager=DevicesManager,$scope.tabs.devices.previous_search="",$scope.tabs.devices.search="",$scope.tabs.devices.searchValid=!0,$scope.tabs.devices.selectedItems=DevicesManager.getSelectedItems(),$scope.tabs.devices.filtered_items=[],$scope.tabs.devices.predicate="fqdn",$scope.tabs.devices.allViewableChecked=!1,$scope.tabs.devices.metadata=DevicesManager.getMetadata(),$scope.tabs.devices.filters=SearchService.getEmptyFilter(),$scope.tabs.devices.column="fqdn",$scope.tabs.devices.actionOption=null,$scope.tabs.devices.takeActionOptions=[],$scope.tabs.devices.actionErrorCount=0,$scope.tabs.devices.actionProgress={total:0,completed:0,errors:{},showing_confirmation:!1,affected_nodes:0},$scope.tabs.devices.zoneSelection=null,$scope.tabs.devices.poolSelection=null,$scope.tabs.devices.poolAction="select-pool",$scope.tabs.devices.newPool={},$scope.tabs.controllers={},$scope.tabs.controllers.pagetitle="Controllers",$scope.tabs.controllers.currentpage="controllers",$scope.tabs.controllers.manager=ControllersManager,$scope.tabs.controllers.previous_search="",$scope.tabs.controllers.search="",$scope.tabs.controllers.searchValid=!0,$scope.tabs.controllers.selectedItems=ControllersManager.getSelectedItems(),$scope.tabs.controllers.filtered_items=[],$scope.tabs.controllers.predicate="fqdn",$scope.tabs.controllers.allViewableChecked=!1,$scope.tabs.controllers.metadata=ControllersManager.getMetadata(),$scope.tabs.controllers.filters=SearchService.getEmptyFilter(),$scope.tabs.controllers.column="fqdn",$scope.tabs.controllers.actionOption=null,$scope.tabs.controllers.takeActionOptions=[],$scope.tabs.controllers.actionErrorCount=0,$scope.tabs.controllers.actionProgress={total:0,completed:0,errors:{},showing_confirmation:!1,affected_nodes:0},$scope.tabs.controllers.zoneSelection=null,$scope.tabs.controllers.poolSelection=null,$scope.tabs.controllers.poolAction="select-pool",$scope.tabs.controllers.newPool={},$scope.tabs.controllers.syncStatuses={},$scope.tabs.controllers.addController=!1,$scope.tabs.controllers.registerUrl=MAAS_config.register_url,$scope.tabs.controllers.registerSecret=MAAS_config.register_secret,$scope.tabs.switches={},$scope.tabs.switches.pagetitle="Switches",$scope.tabs.switches.currentpage="switches",$scope.tabs.switches.manager=SwitchesManager,$scope.tabs.switches.previous_search="",$scope.tabs.switches.search="",$scope.tabs.switches.searchValid=!0,$scope.tabs.switches.selectedItems=SwitchesManager.getSelectedItems(),$scope.tabs.switches.predicate="fqdn",$scope.tabs.switches.allViewableChecked=!1,$scope.tabs.switches.metadata=SwitchesManager.getMetadata(),$scope.tabs.switches.filters=SearchService.getEmptyFilter(),$scope.tabs.switches.column="fqdn",$scope.tabs.switches.actionOption=null,$scope.tabs.switches.takeActionOptions=[],$scope.tabs.switches.actionErrorCount=0,$scope.tabs.switches.actionProgress={total:0,completed:0,errors:{},showing_confirmation:!1,affected_nodes:0},$scope.tabs.switches.osSelection={osystem:null,release:null,hwe_kernel:null},$scope.tabs.switches.zoneSelection=null,$scope.tabs.switches.poolSelection=null,$scope.tabs.switches.poolAction="select-pool",$scope.tabs.switches.newPool={},$scope.tabs.switches.commissioningSelection=[],$scope.tabs.switches.commissionOptions={enableSSH:!1,skipNetworking:!1,skipStorage:!1,updateFirmware:!1,configureHBA:!1},$scope.tabs.switches.releaseOptions={},$scope.addHardwareOption=null,$scope.addHardwareOptions=[{name:"machine",title:"Machine"},{name:"chassis",title:"Chassis"}],$scope.addHardwareScope=null,$scope.addDeviceScope=null,$scope.$on("addHardwareHidden",function(){$scope.addHardwareOption=null}),$scope.toggleTab=function(tab){switch($rootScope.title=$scope.tabs[tab].pagetitle,$rootScope.page=tab,$scope.currentpage=tab,tab){case"machines":$scope.osinfo=GeneralManager.getData("osinfo"),$scope.tabs.machines.takeActionOptions=GeneralManager.getData("machine_actions");break;case"devices":$scope.tabs.devices.takeActionOptions=GeneralManager.getData("device_actions");break;case"controllers":$scope.tabs.controllers.takeActionOptions=GeneralManager.getData("rack_controller_actions");break;case"switches":$scope.tabs.switches.takeActionOptions=GeneralManager.getData("machine_actions")}},$scope.clearSearch=function(tab){$scope.tabs[tab].search="",$scope.updateFilters(tab)},$scope.toggleChecked=function(node,tab){"machines"!==tab&&"switches"!==tab&&($scope.tabs[tab].manager.isSelected(node.system_id)?$scope.tabs[tab].manager.unselectItem(node.system_id):$scope.tabs[tab].manager.selectItem(node.system_id),updateAllViewableChecked(tab)),updateActionErrorCount(tab),shouldClearAction(tab)},$scope.toggleCheckAll=function(tab){"machines"!==tab&&"switches"!==tab&&($scope.tabs[tab].allViewableChecked?angular.forEach($scope.tabs[tab].filtered_items,function(node){$scope.tabs[tab].manager.unselectItem(node.system_id)}):angular.forEach($scope.tabs[tab].filtered_items,function(node){$scope.tabs[tab].manager.selectItem(node.system_id)}),updateAllViewableChecked(tab)),updateActionErrorCount(tab),shouldClearAction(tab)},$scope.onNodeListingChanged=function(nodes,tab){0===nodes.length&&""!==$scope.tabs[tab].search&&$scope.tabs[tab].search===$scope.tabs[tab].previous_search&&($scope.tabs[tab].search="",$scope.updateFilters(tab))},$scope.$watchCollection("tabs.devices.filtered_items",function(){updateAllViewableChecked("devices"),removeEmptyFilter("devices")}),$scope.$watchCollection("tabs.controllers.filtered_items",function(){updateAllViewableChecked("controllers"),removeEmptyFilter("controllers")}),$scope.showSelected=function(tab){enterViewSelected(tab),$scope.updateFilters(tab)},$scope.toggleFilter=function(type,value,tab){angular.isObject($scope.tabs[tab].actionOption)||($scope.tabs[tab].filters=SearchService.toggleFilter($scope.tabs[tab].filters,type,value,!0),$scope.tabs[tab].search=SearchService.filtersToString($scope.tabs[tab].filters))},$scope.isFilterActive=function(type,value,tab){return SearchService.isFilterActive($scope.tabs[tab].filters,type,value,!0)},$scope.updateFilters=function(tab){var filters=SearchService.getCurrentFilters($scope.tabs[tab].search);null===filters?($scope.tabs[tab].filters=SearchService.getEmptyFilter(),$scope.tabs[tab].searchValid=!1):($scope.tabs[tab].filters=filters,$scope.tabs[tab].searchValid=!0),shouldClearAction(tab)},$scope.sortTable=function(predicate,tab){$scope.tabs[tab].predicate=predicate,$scope.tabs[tab].reverse=!$scope.tabs[tab].reverse},$scope.selectColumnOrSort=function(predicate,tab){$scope.tabs[tab].column!==predicate?$scope.tabs[tab].column=predicate:$scope.sortTable(predicate,tab)},$scope.supportsAction=function(node,tab){return!$scope.tabs[tab].actionOption||node.actions.indexOf($scope.tabs[tab].actionOption.name)>=0},$scope.actionOptionSelected=function(tab){updateActionErrorCount(tab),enterViewSelected(tab),"machines"===tab?angular.isObject($scope.addHardwareScope)&&$scope.addHardwareScope.hide():"devices"===tab&&angular.isObject($scope.addDeviceScope)&&$scope.addDeviceScope.hide()},$scope.isActionError=function(tab){return!(!angular.isObject($scope.tabs[tab].actionOption)||"deploy"!==$scope.tabs[tab].actionOption.name||0!==$scope.tabs[tab].actionErrorCount||0!==$scope.osinfo.osystems.length&&0!==UsersManager.getSSHKeyCount())||0!==$scope.tabs[tab].actionErrorCount},$scope.isDeployError=function(tab){return 0===$scope.tabs[tab].actionErrorCount&&!(!angular.isObject($scope.tabs[tab].actionOption)||"deploy"!==$scope.tabs[tab].actionOption.name||0!==$scope.osinfo.osystems.length)},$scope.isSSHKeyError=function(tab){return 0===$scope.tabs[tab].actionErrorCount&&!(!angular.isObject($scope.tabs[tab].actionOption)||"deploy"!==$scope.tabs[tab].actionOption.name||0!==UsersManager.getSSHKeyCount())},$scope.actionCancel=function(tab){resetActionProgress(tab),leaveViewSelected(tab),$scope.tabs[tab].actionOption=null},$scope.actionGo=function(tabName){var i,tab=$scope.tabs[tabName],extra={},deferred=$q.defer(),preAction=deferred.promise;if(deferred.resolve(),"deploy"===tab.actionOption.name&&angular.isString(tab.osSelection.osystem)&&angular.isString(tab.osSelection.release)){extra.osystem=tab.osSelection.osystem;var release=tab.osSelection.release;release=(release=release.split("/"))[release.length-1],extra.distro_series=release,angular.isString(tab.osSelection.hwe_kernel)&&(tab.osSelection.hwe_kernel.indexOf("hwe-")>=0||tab.osSelection.hwe_kernel.indexOf("ga-")>=0)&&(extra.hwe_kernel=tab.osSelection.hwe_kernel)}else if("set-zone"===tab.actionOption.name&&angular.isNumber(tab.zoneSelection.id))extra.zone_id=tab.zoneSelection.id;else if("set-pool"===tab.actionOption.name)"create-pool"===tab.poolAction&&void 0!==tab.newPool.name?preAction=ResourcePoolsManager.createItem({name:tab.newPool.name}).then(function(newPool){extra.pool_id=newPool.id}):angular.isNumber(tab.poolSelection.id)&&(extra.pool_id=tab.poolSelection.id);else if("commission"===tab.actionOption.name){for(extra.enable_ssh=tab.commissionOptions.enableSSH,extra.skip_networking=tab.commissionOptions.skipNetworking,extra.skip_storage=tab.commissionOptions.skipStorage,extra.commissioning_scripts=[],i=0;i0&&progress.completed!==progress.total},$scope.hasActionsFailed=function(tab){return Object.keys($scope.tabs[tab].actionProgress.errors).length>0},$scope.addHardwareOptionChanged=function(){$scope.addHardwareOption&&$scope.addHardwareScope.show($scope.addHardwareOption.name)},$scope.addDevice=function(){$scope.addDeviceScope.show()},$scope.cancelAddDevice=function(){$scope.addDeviceScope.cancel()},$scope.getDeviceIPAssignment=function(ipAssignment){return DEVICE_IP_ASSIGNMENT[ipAssignment]},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.hasCustomCommissioningScripts=function(){var i;for(i=0;i<$scope.scripts.length;i++)if(0===$scope.scripts[i].script_type)return!0;return!1},$scope.$on("$routeUpdate",function(){GeneralManager.loadItems(["osinfo"])}),angular.forEach(["machines","pools","devices","controllers","switches"],function(node_type){-1!==$location.path().indexOf("/"+node_type)&&$scope.toggleTab(node_type)});var page_managers=[$scope.tabs[$scope.currentpage].manager];"machines"!==$scope.currentpage&&"controllers"!==$scope.currentpage||page_managers.push(ScriptsManager),ManagerHelperService.loadManagers($scope,[GeneralManager,ZonesManager,UsersManager,ResourcePoolsManager,ServicesManager].concat(page_managers)).then(function(){$scope.loading=!1}),$scope.$on("$destroy",function(){$interval.cancel($scope.statusPoll),SearchService.storeFilters("machines",$scope.tabs.machines.filters),SearchService.storeFilters("devices",$scope.tabs.devices.filters),SearchService.storeFilters("controllers",$scope.tabs.controllers.filters),SearchService.storeFilters("switches",$scope.tabs.switches.filters)});var machinesFilter=SearchService.retrieveFilters("machines");angular.isObject(machinesFilter)&&($scope.tabs.machines.search=SearchService.filtersToString(machinesFilter),$scope.updateFilters("machines"));var devicesFilter=SearchService.retrieveFilters("devices");angular.isObject(devicesFilter)&&($scope.tabs.devices.search=SearchService.filtersToString(devicesFilter),$scope.updateFilters("devices"));var controllersFilter=SearchService.retrieveFilters("controllers");angular.isObject(controllersFilter)&&($scope.tabs.controllers.search=SearchService.filtersToString(controllersFilter),$scope.updateFilters("controllers"));var switchesFilter=SearchService.retrieveFilters("switches");angular.isObject(switchesFilter)&&($scope.tabs.switches.search=SearchService.filtersToString(switchesFilter),$scope.updateFilters("switches"));var query=$routeParams.query;angular.isString(query)&&($scope.tabs[$scope.currentpage].search=query,$scope.updateFilters($scope.currentpage))}])},"./src/maasserver/static/js/angular/controllers/pod_details.js":function(module,exports){angular.module("MAAS").controller("PodDetailsController",["$scope","$rootScope","$location","$routeParams","PodsManager","GeneralManager","UsersManager","DomainsManager","ZonesManager","MachinesManager","ManagerHelperService","ErrorService","ResourcePoolsManager",function($scope,$rootScope,$location,$routeParams,PodsManager,GeneralManager,UsersManager,DomainsManager,ZonesManager,MachinesManager,ManagerHelperService,ErrorService,ResourcePoolsManager){$rootScope.title="Loading...",$rootScope.page="pods",$scope.loaded=!1,$scope.pod=null,$scope.podManager=PodsManager,$scope.action={option:null,options:[{name:"refresh",title:"Refresh",sentence:"refresh",operation:angular.bind(PodsManager,PodsManager.refresh)},{name:"delete",title:"Delete",sentence:"delete",operation:angular.bind(PodsManager,PodsManager.deleteItem)}],inProgress:!1,error:null},$scope.compose={action:{name:"compose",title:"Compose",sentence:"compose"},obj:{storage:[{type:"local",size:8,tags:[],boot:!0}]}},$scope.power_types=GeneralManager.getData("power_types"),$scope.domains=DomainsManager.getItems(),$scope.zones=ZonesManager.getItems(),$scope.pools=ResourcePoolsManager.getItems(),$scope.section={area:"summary"},$scope.machinesSearch="pod-id:=invalid",$scope.editing=!1,$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.isRackControllerConnected=function(){return $scope.power_types.length>0},$scope.canEdit=function(){return $scope.isRackControllerConnected()&&$scope.isSuperUser()},$scope.editPodConfiguration=function(){$scope.canEdit()&&($scope.editing=!0)},$scope.exitEditPodConfiguration=function(){$scope.editing=!1},$scope.isActionError=function(){return null!==$scope.action.error},$scope.actionOptionChanged=function(){$scope.action.error=null},$scope.actionCancel=function(){$scope.action.option=null,$scope.action.error=null},$scope.actionGo=function(){$scope.action.inProgress=!0,$scope.action.option.operation($scope.pod).then(function(){"delete"===$scope.action.option.name&&$location.path("/pods"),$scope.action.inProgress=!1,$scope.action.option=null,$scope.action.error=null},function(error){$scope.action.inProgress=!1,$scope.action.error=error})},$scope.getPodTypeTitle=function(){var i;for(i=0;i<$scope.power_types.length;i++){var power_type=$scope.power_types[i];if(power_type.name===$scope.pod.type)return power_type.description}return $scope.pod.type},$scope.canCompose=function(){return!!angular.isObject($scope.pod)&&($scope.isSuperUser()&&$scope.pod.capabilities.indexOf("composable")>=0)},$scope.composeMachine=function(){$scope.action.option=$scope.compose.action},$scope.composePreProcess=function(params){(params=angular.copy(params)).id=$scope.pod.id;var sorted=$scope.compose.obj.storage.sort(function(a,b){return a.boot===b.boot?0:a.boot&&!b.boot?-1:1}),storage=[];return angular.forEach(sorted,function(disk,idx){var constraint=idx+":"+disk.size,tags=disk.tags.map(function(tag){return tag.text});tags.splice(0,0,disk.type),constraint+="("+tags.join(",")+")",storage.push(constraint)}),params.storage=storage.join(","),params},$scope.cancelCompose=function(){$scope.compose.obj={storage:[{type:"local",size:8,tags:[],boot:!0}]},$scope.action.option=null},$scope.composeAddStorage=function(){var storage={type:"local",size:8,tags:[],boot:!1};$scope.pod.capabilities.indexOf("iscsi_storage")>=0&&(storage.type="iscsi"),$scope.compose.obj.storage.push(storage)},$scope.composeSetBootDisk=function(storage){angular.forEach($scope.compose.obj.storage,function(disk){disk.boot=!1}),storage.boot=!0},$scope.composeRemoveDisk=function(storage){var idx=$scope.compose.obj.storage.indexOf(storage);idx>=0&&$scope.compose.obj.storage.splice(idx,1)},$scope.startWatching=function(){$scope.$watch("pod.name",function(){$rootScope.title="Pod "+$scope.pod.name}),$scope.$watch("pod.capabilities",function(){var idx=$scope.action.options.indexOf($scope.compose.action);$scope.canCompose()?-1===idx&&$scope.action.options.splice(0,0,$scope.compose.action):idx>=0&&$scope.action.options.splice(idx,1)}),$scope.$watch("action.option",function(now,then){now&&"compose"===now.name&&(then&&"compose"===then.name||($scope.compose.obj.domain=DomainsManager.getDefaultDomain().id,$scope.compose.obj.zone=ZonesManager.getDefaultZone().id,$scope.compose.obj.pool=$scope.pod.default_pool))})},ManagerHelperService.loadManagers($scope,[PodsManager,GeneralManager,UsersManager,DomainsManager,ZonesManager,MachinesManager,ResourcePoolsManager]).then(function(){var activePod=PodsManager.getActiveItem();angular.isObject(activePod)&&activePod.id===parseInt($routeParams.id,10)?($scope.pod=activePod,$scope.loaded=!0,$scope.machinesSearch="pod-id:="+$scope.pod.id,$scope.startWatching()):PodsManager.setActiveItem(parseInt($routeParams.id,10)).then(function(pod){$scope.pod=pod,$scope.loaded=!0,$scope.machinesSearch="pod-id:="+$scope.pod.id,$scope.startWatching()},function(error){ErrorService.raiseError(error)})})}])},"./src/maasserver/static/js/angular/controllers/pods_list.js":function(module,exports){angular.module("MAAS").controller("PodsListController",["$scope","$rootScope","PodsManager","UsersManager","GeneralManager","ZonesManager","ManagerHelperService","ResourcePoolsManager",function($scope,$rootScope,PodsManager,UsersManager,GeneralManager,ZonesManager,ManagerHelperService,ResourcePoolsManager){function updateAllViewableChecked(){if(0!==$scope.pods.length){var i;for(i=0;i<$scope.pods.length;i++)if(!$scope.pods[i].$selected)return void($scope.allViewableChecked=!1);$scope.allViewableChecked=!0}else $scope.allViewableChecked=!1}function clearAction(){resetActionProgress(),$scope.action.option=null}function shouldClearAction(){0===$scope.selectedItems.length&&(clearAction(),$scope.action.option&&($scope.action.option=null))}function resetActionProgress(){var progress=$scope.action.progress;progress.completed=progress.total=progress.errors=0,angular.forEach($scope.pods,function(pod){delete pod.action_failed})}function updateSelectedItems(){$scope.hasActionsFailed()?(angular.forEach($scope.pods,function(pod){!1===pod.action_failed&&PodsManager.unselectItem(pod.id)}),shouldClearAction()):$scope.hasActionsInProgress()||clearAction()}$rootScope.title="Pods",$rootScope.page="pods",$scope.podManager=PodsManager,$scope.pods=PodsManager.getItems(),$scope.loading=!0,$scope.filteredItems=[],$scope.selectedItems=PodsManager.getSelectedItems(),$scope.predicate="name",$scope.allViewableChecked=!1,$scope.action={option:null,options:[{name:"refresh",title:"Refresh",sentence:"refresh",operation:angular.bind(PodsManager,PodsManager.refresh)},{name:"delete",title:"Delete",sentence:"delete",operation:angular.bind(PodsManager,PodsManager.deleteItem)}],progress:{total:0,completed:0,errors:0}},$scope.add={open:!1,obj:{cpu_over_commit_ratio:1,memory_over_commit_ratio:1}},$scope.powerTypes=GeneralManager.getData("power_types"),$scope.zones=ZonesManager.getItems(),$scope.pools=ResourcePoolsManager.getItems(),$scope.toggleChecked=function(pod){PodsManager.isSelected(pod.id)?PodsManager.unselectItem(pod.id):PodsManager.selectItem(pod.id),updateAllViewableChecked(),shouldClearAction()},$scope.toggleCheckAll=function(){$scope.allViewableChecked?angular.forEach($scope.pods,function(pod){PodsManager.unselectItem(pod.id)}):angular.forEach($scope.pods,function(pod){PodsManager.selectItem(pod.id)}),updateAllViewableChecked(),shouldClearAction()},$scope.$watchCollection("pods",function(){updateAllViewableChecked()}),$scope.sortTable=function(predicate){$scope.predicate=predicate,$scope.reverse=!$scope.reverse},$scope.actionCancel=function(){resetActionProgress(),$scope.action.option=null},$scope.actionGo=function(){resetActionProgress(),$scope.action.progress.total=$scope.selectedItems.length;var operation=$scope.action.option.operation;angular.forEach($scope.selectedItems,function(pod){operation(pod).then(function(){$scope.action.progress.completed+=1,pod.action_failed=!1,updateSelectedItems()},function(error){$scope.action.progress.errors+=1,pod.action_error=error,pod.action_failed=!0,updateSelectedItems()})})},$scope.hasActionsInProgress=function(){var progress=$scope.action.progress;return progress.total>0&&progress.completed+progress.errors!==progress.total},$scope.hasActionsFailed=function(){return $scope.action.progress.errors>0},$scope.addPod=function(){$scope.add.open=!0,$scope.add.obj.zone=ZonesManager.getDefaultZone().id,$scope.add.obj.default_pool=ResourcePoolsManager.getDefaultPool().id,$scope.add.obj.cpu_over_commit_ratio=1,$scope.add.obj.memory_over_commit_ratio=1},$scope.cancelAddPod=function(){$scope.add.open=!1,$scope.add.obj={}},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.isRackControllerConnected=function(){return $scope.powerTypes.length>0},$scope.canAddPod=function(){return $scope.isRackControllerConnected()&&$scope.isSuperUser()},$scope.getPowerTypeTitle=function(power_type){var i;for(i=0;i<$scope.powerTypes.length;i++){var powerType=$scope.powerTypes[i];if(powerType.name===power_type)return powerType.description}return power_type},ManagerHelperService.loadManagers($scope,[PodsManager,UsersManager,GeneralManager,ZonesManager,ResourcePoolsManager]).then(function(){$scope.loading=!1})}])},"./src/maasserver/static/js/angular/controllers/prefs.js":function(module,exports){angular.module("MAAS").controller("PreferencesController",["$scope","UsersManager","ManagerHelperService",function($scope,UsersManager,ManagerHelperService){$scope.loading=!0,ManagerHelperService.loadManager($scope,UsersManager).then(function(){$scope.loading=!1})}])},"./src/maasserver/static/js/angular/controllers/settings.js":function(module,exports){angular.module("MAAS").controller("SettingsController",["$scope","$rootScope","$routeParams","PackageRepositoriesManager","DHCPSnippetsManager","MachinesManager","ControllersManager","DevicesManager","SubnetsManager","GeneralManager","ManagerHelperService",function($scope,$rootScope,$routeParams,PackageRepositoriesManager,DHCPSnippetsManager,MachinesManager,ControllersManager,DevicesManager,SubnetsManager,GeneralManager,ManagerHelperService){$rootScope.title="Loading...",$rootScope.page="settings",$scope.loading=!0,$scope.snippetsManager=DHCPSnippetsManager,$scope.snippets=DHCPSnippetsManager.getItems(),$scope.subnets=SubnetsManager.getItems(),$scope.machines=MachinesManager.getItems(),$scope.devices=DevicesManager.getItems(),$scope.controllers=ControllersManager.getItems(),$scope.known_architectures=GeneralManager.getData("known_architectures"),$scope.pockets_to_disable=GeneralManager.getData("pockets_to_disable"),$scope.components_to_disable=GeneralManager.getData("components_to_disable"),$scope.packageRepositoriesManager=PackageRepositoriesManager,$scope.repositories=PackageRepositoriesManager.getItems(),$scope.newSnippet=null,$scope.editSnippet=null,$scope.deleteSnippet=null,$scope.snippetTypes=["Global","Subnet","Node"],$scope.newRepository=null,$scope.editRepository=null,$scope.deleteRepository=null,$scope.repositoryEnabledToggle=function(repository){PackageRepositoriesManager.updateItem(repository)},$scope.repositoryEnterRemove=function(repository){$scope.newRepository=null,$scope.editRepository=null,$scope.deleteRepository=repository},$scope.repositoryExitRemove=function(){$scope.deleteRepository=null},$scope.repositoryConfirmRemove=function(){PackageRepositoriesManager.deleteItem($scope.deleteRepository).then(function(){$scope.repositoryExitRemove()})},$scope.isPPA=function(data){return!!angular.isObject(data)&&(!!angular.isString(data.url)&&(0===data.url.indexOf("ppa:")||data.url.indexOf("ppa.launchpad.net")>-1))},$scope.isMirror=function(data){return!!angular.isObject(data)&&(!!angular.isString(data.name)&&("main_archive"===data.name||"ports_archive"===data.name))},$scope.repositoryEnterEdit=function(repository){$scope.newRepository=null,$scope.deleteRepository=null,$scope.editRepository=repository},$scope.repositoryExitEdit=function(){$scope.editRepository=null},$scope.repositoryAdd=function(isPPA){var repo={name:"",enabled:!0,url:"",key:"",arches:["i386","amd64"],distributions:[],components:[]};isPPA&&(repo.url="ppa:"),$scope.newRepository=repo},$scope.repositoryAddCancel=function(){$scope.newRepository=null},$scope.getSubnetName=function(subnet){return SubnetsManager.getName(subnet)},$scope.getSnippetTypeText=function(snippet){return angular.isString(snippet.node)?"Node":angular.isNumber(snippet.subnet)?"Subnet":"Global"},$scope.getSnippetAppliesToObject=function(snippet){return angular.isString(snippet.node)?(system_id=snippet.node,node=MachinesManager.getItemFromList(system_id),angular.isObject(node)?node:(node=DevicesManager.getItemFromList(system_id),angular.isObject(node)?node:(node=ControllersManager.getItemFromList(system_id),angular.isObject(node)?node:void 0))):angular.isNumber(snippet.subnet)?SubnetsManager.getItemFromList(snippet.subnet):void 0;var system_id,node},$scope.getSnippetAppliesToText=function(snippet){var obj=$scope.getSnippetAppliesToObject(snippet);return angular.isString(snippet.node)&&angular.isObject(obj)?obj.fqdn:angular.isNumber(snippet.subnet)&&angular.isObject(obj)?SubnetsManager.getName(obj):""},$scope.snippetEnterRemove=function(snippet){$scope.newSnippet=null,$scope.editSnippet=null,$scope.deleteSnippet=snippet},$scope.snippetExitRemove=function(){$scope.deleteSnippet=null},$scope.snippetConfirmRemove=function(){DHCPSnippetsManager.deleteItem($scope.deleteSnippet).then(function(){$scope.snippetExitRemove()})},$scope.snippetEnterEdit=function(snippet){$scope.newSnippet=null,$scope.deleteSnippet=null,$scope.editSnippet=snippet,$scope.editSnippet.type=$scope.getSnippetTypeText(snippet)},$scope.snippetExitEdit=function(){$scope.editSnippet=null},$scope.snippetToggle=function(snippet){DHCPSnippetsManager.updateItem(snippet).then(null,function(error){snippet.enabled=!snippet.enabled,console.log(error)})},$scope.snippetAdd=function(){$scope.editSnippet=null,$scope.deleteSnippet=null,$scope.newSnippet={name:"",type:"Global",enabled:!0}},$scope.snippetAddCancel=function(){$scope.newSnippet=null},"dhcp"===$routeParams.section?($rootScope.title="DHCP snippets",$scope.currentpage="dhcp"):"repositories"===$routeParams.section&&($rootScope.title="Package repositories",$scope.currentpage="repositories"),ManagerHelperService.loadManagers($scope,[PackageRepositoriesManager,DHCPSnippetsManager,MachinesManager,DevicesManager,ControllersManager,SubnetsManager,GeneralManager]).then(function(){$scope.loading=!1})}])},"./src/maasserver/static/js/angular/controllers/space_details.js":function(module,exports){angular.module("MAAS").controller("SpaceDetailsController",["$scope","$rootScope","$routeParams","$filter","$location","SpacesManager","VLANsManager","SubnetsManager","FabricsManager","UsersManager","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$filter,$location,SpacesManager,VLANsManager,SubnetsManager,FabricsManager,UsersManager,ManagerHelperService,ErrorService){function spaceLoaded(space){$scope.space=space,$rootScope.title=$scope.space.name,$scope.predicate="[subnet_name, vlan_name]",$scope.$watch("subnets",updateSubnets,!0),updateSubnets(),$scope.loaded=!0}function updateSubnets(){var rows=[];angular.forEach($filter("filter")($scope.subnets,{space:$scope.space.id},!0),function(subnet){var vlan=VLANsManager.getItemFromList(subnet.vlan),fabric=FabricsManager.getItemFromList(vlan.fabric),row={vlan:vlan,vlan_name:VLANsManager.getName(vlan),subnet:subnet,subnet_name:SubnetsManager.getName(subnet),fabric:fabric,fabric_name:fabric.name};rows.push(row)}),$scope.rows=rows}$rootScope.title="Loading...",$rootScope.page="networks",$scope.space=null,$scope.spaceManager=SpacesManager,$scope.subnets=SubnetsManager.getItems(),$scope.loaded=!1,$scope.editSummary=!1,$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.enterEditSummary=function(){$scope.editSummary=!0},$scope.exitEditSummary=function(){$scope.editSummary=!1},$scope.isDefaultSpace=function(){return!!angular.isObject($scope.space)&&0===$scope.space.id},$scope.canBeDeleted=function(){return!!angular.isObject($scope.space)&&0===$scope.space.subnet_ids.length},$scope.deleteButton=function(){$scope.error=null,$scope.confirmingDelete=!0},$scope.cancelDeleteButton=function(){$scope.confirmingDelete=!1},$scope.deleteConfirmButton=function(){SpacesManager.deleteSpace($scope.space).then(function(){$scope.confirmingDelete=!1,$location.path("/networks"),$location.search("by","space")},function(error){$scope.error=ManagerHelperService.parseValidationError(error)})},ManagerHelperService.loadManagers($scope,[SpacesManager,SubnetsManager,VLANsManager,FabricsManager,UsersManager]).then(function(){var activeSpace=SpacesManager.getActiveItem(),requestedSpace=parseInt($routeParams.space_id,10);isNaN(requestedSpace)?ErrorService.raiseError("Invalid space identifier."):angular.isObject(activeSpace)&&activeSpace.id===requestedSpace?spaceLoaded(activeSpace):SpacesManager.setActiveItem(requestedSpace).then(function(space){spaceLoaded(space)},function(error){ErrorService.raiseError(error)})})}])},"./src/maasserver/static/js/angular/controllers/subnet_details.js":function(module,exports){angular.module("MAAS").filter("filterSource",["ValidationService",function(){return function(subnets,source){var filtered=[];return angular.forEach(subnets,function(subnet){subnet.id!==source.id&&subnet.version===source.version&&filtered.push(subnet)}),filtered}}]),angular.module("MAAS").controller("SubnetDetailsController",["$scope","$rootScope","$routeParams","$filter","$location","ConfigsManager","SubnetsManager","SpacesManager","VLANsManager","UsersManager","FabricsManager","StaticRoutesManager","ManagerHelperService","ErrorService","ConverterService",function($scope,$rootScope,$routeParams,$filter,$location,ConfigsManager,SubnetsManager,SpacesManager,VLANsManager,UsersManager,FabricsManager,StaticRoutesManager,ManagerHelperService,ErrorService,ConverterService){$rootScope.title="Loading...",$rootScope.page="networks",$scope.loaded=!1,$scope.subnet=null,$scope.editSummary=!1,$scope.active_discovery_data=null,$scope.active_discovery_interval=null,$scope.subnets=SubnetsManager.getItems(),$scope.subnetManager=SubnetsManager,$scope.staticRoutes=StaticRoutesManager.getItems(),$scope.staticRoutesManager=StaticRoutesManager,$scope.space=null,$scope.vlans=VLANsManager.getItems(),$scope.fabrics=FabricsManager.getItems(),$scope.actionError=null,$scope.actionOption=null,$scope.actionOptions=[],$scope.reverse=!1,$scope.newStaticRoute=null,$scope.editStaticRoute=null,$scope.deleteStaticRoute=null,$scope.MAP_SUBNET_ACTION={name:"map_subnet",title:"Map subnet"},$scope.DELETE_ACTION={name:"delete",title:"Delete"};var ALLOC_TYPES={0:"Automatic",1:"Static",4:"User reserved",5:"DHCP",6:"Observed"},NODE_TYPES={0:"Machine",1:"Device",2:"Rack controller",3:"Region controller",4:"Rack and region controller",5:"Chassis",6:"Storage"};function updateTitle(){subnet=$scope.subnet,subnet&&subnet.cidr&&($rootScope.title=subnet.cidr,subnet.name&&subnet.cidr!==subnet.name&&($rootScope.title+=" ("+subnet.name+")"))}function updateIPVersion(){-1===$scope.subnet.cidr.split("/")[0].indexOf(":")?$scope.ipVersion=4:$scope.ipVersion=6}function subnetLoaded(subnet){$scope.subnet=subnet,$scope.loaded=!0,updateTitle();var updateFabric=function(){$scope.subnet.fabric=VLANsManager.getItemFromList($scope.subnet.vlan).fabric,$scope.subnet.fabric_name=FabricsManager.getItemFromList(subnet.fabric).name};$scope.$watch("subnet.fabric",updateFabric),$scope.$watch("subnet.fabric_name",updateFabric),$scope.$watch("subnet.vlan",updateFabric),$scope.$watch("subnet.vlan_name",function(){var vlan=VLANsManager.getItemFromList($scope.subnet.vlan);$scope.subnet.vlan_name=VLANsManager.getName(vlan)}),$scope.$watch("subnet.space",function(){$scope.space=SpacesManager.getItemFromList($scope.subnet.space)}),$scope.$watch("subnet.cidr",updateIPVersion)}$scope.ipSort=function(ipAddress){return 4===$scope.ipVersion?ConverterService.ipv4ToInteger(ipAddress.ip):ConverterService.ipv6Expand(ipAddress.ip)},$scope.predicate=$scope.ipSort,$scope.getAllocType=function(allocType){var str=ALLOC_TYPES[allocType];return angular.isString(str)?str:"Unknown"},$scope.getSubnetCIDR=function(destId){return SubnetsManager.getItemFromList(destId).cidr},$scope.allocTypeSort=function(ipAddress){return $scope.getAllocType(ipAddress.alloc_type)},$scope.getUsageForIP=function(ip){if(angular.isObject(ip.node_summary)){var isContainer=ip.node_summary.is_container,nodeType=ip.node_summary.node_type;if(1===nodeType&&!0===isContainer)return"Container";var str=NODE_TYPES[nodeType];return angular.isString(str)?str:"Unknown"}return angular.isObject(ip.bmcs)?"BMC":angular.isObject(ip.dns_records)?"DNS":"Unknown"},$scope.nodeTypeSort=function(ipAddress){return $scope.getUsageForIP(ipAddress)},$scope.ownerSort=function(ipAddress){var owner=ipAddress.user;return angular.isString(owner)&&owner.length>0?owner:"MAAS"},$scope.sortIPTable=function(predicate){$scope.predicate=predicate,$scope.reverse=!$scope.reverse},$scope.getVLANName=function(vlan){return VLANsManager.getName(vlan)},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.actionRetry=function(){$scope.actionError=null},$scope.actionGo=function(){"map_subnet"===$scope.actionOption.name?SubnetsManager.scanSubnet($scope.subnet).then(function(result){result&&0===result.scan_started_on.length?$scope.actionError=ManagerHelperService.parseValidationError(result.result):($scope.actionOption=null,$scope.actionError=null)},function(error){$scope.actionError=ManagerHelperService.parseValidationError(error)}):"delete"===$scope.actionOption.name&&SubnetsManager.deleteSubnet($scope.subnet).then(function(result){$scope.actionOption=null,$scope.actionError=null,$location.path("/networks")},function(error){$scope.actionError=ManagerHelperService.parseValidationError(error)})},$scope.actionChanged=function(){$scope.actionError=null},$scope.cancelAction=function(){$scope.actionOption=null,$scope.actionError=null},$scope.updateActions=function(){UsersManager.isSuperUser()?$scope.actionOptions=[$scope.MAP_SUBNET_ACTION,$scope.DELETE_ACTION]:$scope.actionOptions=[]},$scope.enterEditSummary=function(){$scope.editSummary=!0},$scope.exitEditSummary=function(){$scope.editSummary=!1},$scope.subnetPreSave=function(subnet,changedFields){return-1!==changedFields.indexOf("fabric")&&(subnet.vlan=FabricsManager.getItemFromList(subnet.fabric).default_vlan_id),subnet},$scope.addStaticRoute=function(){$scope.editStaticRoute=null,$scope.deleteStaticRoute=null,$scope.newStaticRoute={source:$scope.subnet.id,gateway_ip:"",destination:null,metric:0}},$scope.cancelAddStaticRoute=function(){$scope.newStaticRoute=null},$scope.isStaticRouteInEditMode=function(route){return $scope.editStaticRoute===route},$scope.staticRouteToggleEditMode=function(route){$scope.newStaticRoute=null,$scope.deleteStaticRoute=null,$scope.isStaticRouteInEditMode(route)?$scope.editStaticRoute=null:$scope.editStaticRoute=route},$scope.isStaticRouteInDeleteMode=function(route){return $scope.deleteStaticRoute===route},$scope.staticRouteEnterDeleteMode=function(route){$scope.newStaticRoute=null,$scope.editStaticRoute=null,$scope.deleteStaticRoute=route},$scope.staticRouteCancelDelete=function(){$scope.deleteStaticRoute=null},$scope.staticRouteConfirmDelete=function(){StaticRoutesManager.deleteItem($scope.deleteStaticRoute).then(function(){$scope.deleteStaticRoute=null})},ManagerHelperService.loadManagers($scope,[ConfigsManager,SubnetsManager,SpacesManager,VLANsManager,UsersManager,FabricsManager,StaticRoutesManager]).then(function(){$scope.updateActions(),$scope.active_discovery_data=ConfigsManager.getItemFromList("active_discovery_interval"),angular.forEach($scope.active_discovery_data.choices,function(choice){choice[0]===$scope.active_discovery_data.value&&($scope.active_discovery_interval=choice[1])});var activeSubnet=SubnetsManager.getActiveItem(),requestedSubnet=parseInt($routeParams.subnet_id,10);isNaN(requestedSubnet)?ErrorService.raiseError("Invalid subnet identifier."):angular.isObject(activeSubnet)&&activeSubnet.id===requestedSubnet?subnetLoaded(activeSubnet):SubnetsManager.setActiveItem(requestedSubnet).then(function(subnet){subnetLoaded(subnet)},function(error){ErrorService.raiseError(error)})})}])},"./src/maasserver/static/js/angular/controllers/vlan_details.js":function(module,exports){angular.module("MAAS").filter("ignoreSelf",function(){return function(objects,self){var filtered=[];return angular.forEach(objects,function(obj){obj!==self&&filtered.push(obj)}),filtered}}),angular.module("MAAS").controller("VLANDetailsController",["$scope","$rootScope","$routeParams","$filter","$location","VLANsManager","SubnetsManager","SpacesManager","FabricsManager","ControllersManager","UsersManager","ManagerHelperService","ErrorService","ValidationService",function($scope,$rootScope,$routeParams,$filter,$location,VLANsManager,SubnetsManager,SpacesManager,FabricsManager,ControllersManager,UsersManager,ManagerHelperService,ErrorService,ValidationService){var vm=this,filterByVLAN=$filter("filterByVLAN");$filter("filterControllersByVLAN");function updateTitle(){var vlan=vm.vlan,fabric=vm.fabric;angular.isObject(vlan)&&angular.isObject(fabric)&&(vlan.name?vm.title=vlan.name:0===vlan.vid?vm.title="Default VLAN":vm.title="VLAN "+vlan.vid,vm.title+=" in "+fabric.name,$rootScope.title=vm.title)}function updateManagementRacks(){var vlan=vm.vlan;angular.isObject(vlan)&&(vlan.primary_rack?vm.primaryRack=ControllersManager.getItemFromList(vlan.primary_rack):vm.primaryRack=null,vlan.secondary_rack?vm.secondaryRack=ControllersManager.getItemFromList(vlan.secondary_rack):vm.secondaryRack=null)}function updateRelatedControllers(){var vlan=vm.vlan;if(angular.isObject(vlan)){var racks=[];angular.forEach(vlan.rack_sids,function(rack_sid){var rack=ControllersManager.getItemFromList(rack_sid);angular.isObject(rack)&&racks.push(rack)}),vm.relatedControllers=racks}}function updateRelatedSubnets(){var vlan=vm.vlan;if(angular.isObject(vlan)){var subnets=[];angular.forEach(filterByVLAN(vm.subnets,vlan),function(subnet){var space=SpacesManager.getItemFromList(subnet.space);angular.isObject(space)||(space={name:"(undefined)"});var row={subnet:subnet,space:space};subnets.push(row)}),vm.relatedSubnets=subnets}}function updatePossibleActions(){var vlan=vm.vlan;angular.isObject(vlan)&&(vm.actionOptions.length=0,UsersManager.isSuperUser()&&(vlan.relay_vlan?(vm.actionOptions.push(vm.RELAY_DHCP_ACTION),vm.actionOptions.push(vm.DISABLE_DHCP_ACTION),vm.RELAY_DHCP_ACTION.title="Reconfigure DHCP relay"):!0===vlan.dhcp_on?(vm.PROVIDE_DHCP_ACTION.title="Reconfigure DHCP",vm.actionOptions.push(vm.PROVIDE_DHCP_ACTION),vm.actionOptions.push(vm.DISABLE_DHCP_ACTION)):(vm.PROVIDE_DHCP_ACTION.title="Provide DHCP",vm.RELAY_DHCP_ACTION.title="Relay DHCP",vm.actionOptions.push(vm.PROVIDE_DHCP_ACTION),vm.actionOptions.push(vm.RELAY_DHCP_ACTION)),vm.isFabricDefault||vm.actionOptions.push(vm.DELETE_ACTION)))}function vlanLoaded(vlan){vm.vlan=vlan,vm.fabric=FabricsManager.getItemFromList(vlan.fabric),vm.isFabricDefault=vm.fabric.default_vlan_id===vm.vlan.id,vm.loaded=!0,updateTitle(),updateManagementRacks(),updateRelatedControllers(),updateRelatedSubnets(),updatePossibleActions()}$rootScope.title="Loading...",$rootScope.page="networks",vm.PROVIDE_DHCP_ACTION={name:"enable_dhcp"},vm.RELAY_DHCP_ACTION={name:"relay_dhcp"},vm.DISABLE_DHCP_ACTION={name:"disable_dhcp",title:"Disable DHCP"},vm.DELETE_ACTION={name:"delete",title:"Delete"},vm.loaded=!1,vm.vlan=null,vm.title=null,vm.actionOption=null,vm.actionOptions=[],vm.vlanManager=VLANsManager,vm.vlans=VLANsManager.getItems(),vm.subnets=SubnetsManager.getItems(),vm.spaces=SpacesManager.getItems(),vm.fabrics=FabricsManager.getItems(),vm.controllers=ControllersManager.getItems(),vm.actionError=null,vm.relatedSubnets=[],vm.relatedControllers=[],vm.provideDHCPAction={},vm.primaryRack=null,vm.secondaryRack=null,vm.editSummary=!1,vm.isSuperUser=function(){return UsersManager.isSuperUser()},vm.enterEditSummary=function(){vm.editSummary=!0},vm.exitEditSummary=function(){vm.editSummary=!1},vm.getSpaceName=function(){var space=SpacesManager.getItemFromList(vm.vlan.space);return space?space.name:"(undefined)"},vm.getActionByName=function(name){var i;for(i=0;i0&&(dhcp.primaryRack=vm.relatedControllers[0].system_id),angular.isObject(vm.secondaryRack)?dhcp.secondaryRack=vm.secondaryRack.system_id:vm.relatedControllers.length>1&&(dhcp.secondaryRack=vm.relatedControllers[1].system_id),dhcp.maxIPs=0,dhcp.startIP=null,dhcp.endIP=null,dhcp.gatewayIP="",angular.isObject(vm.relatedSubnets)){var i,subnet;for(vm.relatedSubnets.length>0&&angular.isObject(vm.relatedSubnets[0].subnet)&&(dhcp.subnet=vm.relatedSubnets[0].subnet.id),dhcp.needsDynamicRange=!0,i=0;i0?(dhcp.maxIPs=iprange.num_addresses,forRelay?(dhcp.startIP="",dhcp.endIP="",dhcp.startPlaceholder=iprange.start+"(optional)",dhcp.endPlaceholder=iprange.end+" (optional)"):(dhcp.startIP=iprange.start,dhcp.endIP=iprange.end,dhcp.startPlaceholder=iprange.start,dhcp.endPlaceholder=iprange.end)):(dhcp.maxIPs=0,dhcp.startIP="",dhcp.endIP="",dhcp.startPlaceholder="(no available IPs)",dhcp.endPlaceholder="(no available IPs)"),angular.isString(suggested_gateway)?forRelay?(dhcp.gatewayIP="",dhcp.gatewayPlaceholder=suggested_gateway+" (optional)"):(dhcp.gatewayIP=suggested_gateway,dhcp.gatewayPlaceholder=suggested_gateway):(dhcp.gatewayIP="",dhcp.gatewayPlaceholder="")}else dhcp.maxIPs=0,dhcp.startIP=null,dhcp.endIP=null,dhcp.gatewayIP="";angular.isObject(subnet)?dhcp.subnetMissingGatewayIP=!angular.isString(subnet.gateway_ip):dhcp.subnetMissingGatewayIP=!1,!1===dhcp.subnetMissingGatewayIP&&(dhcp.gatewayIP=null)},vm.actionRetry=function(){vm.actionError=null},vm.canPerformAction=function(){return"enable_dhcp"===vm.actionOption.name?vm.relatedSubnets.length>0:"relay_dhcp"!==vm.actionOption.name||angular.isObject(vm.provideDHCPAction.relayVLAN)},vm.actionGo=function(){if(vm.canPerformAction())if("enable_dhcp"===vm.actionOption.name){var dhcp=vm.provideDHCPAction,controllers=[],extra={};if(extra.subnet=dhcp.subnet,extra.start=dhcp.startIP,extra.end=dhcp.endIP,extra.gateway=dhcp.gatewayIP,angular.isString(dhcp.primaryRack)&&controllers.push(dhcp.primaryRack),angular.isString(dhcp.secondaryRack)&&controllers.push(dhcp.secondaryRack),0===controllers.length)return void(vm.actionError="A primary rack controller must be specified.");VLANsManager.configureDHCP(vm.vlan,controllers,extra).then(function(){vm.actionOption=null,vm.actionError=null},function(result){vm.actionError=result.error,vm.actionOption=vm.PROVIDE_DHCP_ACTION})}else if("relay_dhcp"===vm.actionOption.name){var extraDHCP={};extraDHCP.subnet=vm.provideDHCPAction.subnet,extraDHCP.start=vm.provideDHCPAction.startIP,extraDHCP.end=vm.provideDHCPAction.endIP,extraDHCP.gateway=vm.provideDHCPAction.gatewayIP;var relay=vm.provideDHCPAction.relayVLAN.id;VLANsManager.configureDHCP(vm.vlan,[],extraDHCP,relay).then(function(){vm.actionOption=null,vm.actionError=null},function(result){vm.actionError=result.error,vm.actionOption=vm.RELAY_DHCP_ACTION})}else"disable_dhcp"===vm.actionOption.name?VLANsManager.disableDHCP(vm.vlan).then(function(){vm.actionOption=null,vm.actionError=null},function(result){vm.actionError=result.error,vm.actionOption=vm.DISABLE_DHCP_ACTION}):"delete"===vm.actionOption.name&&VLANsManager.deleteVLAN(vm.vlan).then(function(){$location.path("/networks"),vm.actionOption=null,vm.actionError=null},function(result){vm.actionError=result.error,vm.actionOption=vm.DELETE_ACTION})},vm.isActionError=function(){return null!==vm.actionError},vm.getFullVLANName=function(vlan_id){var vlan=VLANsManager.getItemFromList(vlan_id),fabric=FabricsManager.getItemFromList(vlan.fabric);return FabricsManager.getName(fabric)+"."+VLANsManager.getName(vlan)},vm.getDHCPStatus=function(){return vm.vlan?vm.vlan.dhcp_on?"Enabled":vm.vlan.relay_vlan?"Relayed via "+vm.getFullVLANName(vm.vlan.relay_vlan):"Disabled":""},ManagerHelperService.loadManagers($scope,[VLANsManager,SubnetsManager,SpacesManager,FabricsManager,ControllersManager,UsersManager]).then(function(){var activeVLAN=VLANsManager.getActiveItem(),requestedVLAN=parseInt($routeParams.vlan_id,10);isNaN(requestedVLAN)?ErrorService.raiseError("Invalid VLAN identifier."):angular.isObject(activeVLAN)&&activeVLAN.id===requestedVLAN?vlanLoaded(activeVLAN):VLANsManager.setActiveItem(requestedVLAN).then(function(vlan){vlanLoaded(vlan)},function(error){ErrorService.raiseError(error)}),$scope.$watch("vlanDetails.vlan.name",updateTitle),$scope.$watch("vlanDetails.vlan.vid",updateTitle),$scope.$watch("vlanDetails.vlan.dhcp_on",updatePossibleActions),$scope.$watch("vlanDetails.vlan.relay_vlan",updatePossibleActions),$scope.$watch("vlanDetails.fabric.name",updateTitle),$scope.$watch("vlanDetails.vlan.primary_rack",updateManagementRacks),$scope.$watch("vlanDetails.vlan.secondary_rack",updateManagementRacks),$scope.$watchCollection("vlanDetails.subnets",updateRelatedSubnets),$scope.$watchCollection("vlanDetails.spaces",updateRelatedSubnets),$scope.$watchCollection("vlanDetails.controllers",updateRelatedControllers)})}])},"./src/maasserver/static/js/angular/controllers/zone_details.js":function(module,exports){angular.module("MAAS").controller("ZoneDetailsController",["$scope","$rootScope","$routeParams","$location","MachinesManager","DevicesManager","ZonesManager","UsersManager","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$location,MachinesManager,DevicesManager,ZonesManager,UsersManager,ManagerHelperService,ErrorService){function zoneLoaded(zone){$scope.zone=zone,$scope.loaded=!0,$rootScope.title=$scope.zone.name}$rootScope.title="Loading...",$rootScope.page="zones",$scope.loaded=!1,$scope.zone=null,$scope.zoneManager=ZonesManager,$scope.editSummary=!1,$scope.predicate="name",$scope.reverse=!1,$scope.enterEditSummary=function(){$scope.editSummary=!0},$scope.exitEditSummary=function(){$scope.editSummary=!1},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.isDefaultZone=function(){return!!angular.isObject($scope.zone)&&1===$scope.zone.id},$scope.canBeDeleted=function(){return!!angular.isObject($scope.zone)&&0!==$scope.zone.id},$scope.deleteButton=function(){$scope.error=null,$scope.confirmingDelete=!0},$scope.cancelDeleteButton=function(){$scope.confirmingDelete=!1},$scope.deleteConfirmButton=function(){ZonesManager.deleteItem($scope.zone).then(function(){$scope.confirmingDelete=!1,$location.path("/zones")},function(error){$scope.error=ManagerHelperService.parseValidationError(error)})},ManagerHelperService.loadManagers($scope,[ZonesManager,UsersManager]).then(function(){var activeZone=ZonesManager.getActiveItem(),requestedZone=parseInt($routeParams.zone_id,10);isNaN(requestedZone)?ErrorService.raiseError("Invalid zone identifier."):angular.isObject(activeZone)&&activeZone.id===requestedZone?zoneLoaded(activeZone):ZonesManager.setActiveItem(requestedZone).then(function(zone){zoneLoaded(zone)},function(error){ErrorService.raiseError(error)})})}])},"./src/maasserver/static/js/angular/controllers/zones_list.js":function(module,exports){angular.module("MAAS").controller("ZonesListController",["$scope","$rootScope","$routeParams","$filter","MachinesManager","DevicesManager","ControllersManager","ZonesManager","ServicesManager","UsersManager","ManagerHelperService","ErrorService",function($scope,$rootScope,$routeParams,$filter,MachinesManager,DevicesManager,ControllersManager,ZonesManager,ServicesManager,UsersManager,ManagerHelperService,ErrorService){$rootScope.title="Zones",$rootScope.page="zones",$scope.zoneManager=ZonesManager,$scope.zones=ZonesManager.getItems(),$scope.currentpage="zones",$scope.predicate="name",$scope.reverse=!1,$scope.loading=!0,$scope.action={open:!1,obj:{}},$scope.addZone=function(){$scope.action.open=!0},$scope.closeZone=function(){$scope.action.open=!1,$scope.action.obj={}},$scope.isSuperUser=function(){return UsersManager.isSuperUser()},ManagerHelperService.loadManagers($scope,[ZonesManager,UsersManager]).then(function(){$scope.loading=!1})}])},"./src/maasserver/static/js/angular/directives/accordion.js":function(module,exports){angular.module("MAAS").directive("maasAccordion",function(){return{restrict:"C",link:function(scope,element,attrs){var clickHandler=function(evt){var tab=evt.data.tab;angular.element(tab).toggleClass("is-selected")},tabs=element.find(".maas-accordion-tab");angular.forEach(tabs,function(tab){(tab=angular.element(tab)).on("click",{tab:tab},clickHandler)}),scope.$on("$destroy",function(){angular.forEach(tabs,function(tab){angular.element(tab).off("click",clickHandler)})})}}})},"./src/maasserver/static/js/angular/directives/boot_images.js":function(module,exports){angular.module("MAAS").directive("maasBootImagesStatus",["BootResourcesManager",function(BootResourcesManager){return{restrict:"E",scope:{},template:['

','','',"Step 1/2: Region controller importing","","

",'

','','',"Step 2/2: Rack controller(s) importing","","

"].join(""),controller:function($scope,$rootScope,$element,$document){$scope.data=BootResourcesManager.getData()}}}]),angular.module("MAAS").directive("maasBootImages",["$filter","BootResourcesManager","UsersManager","ManagerHelperService",function($filter,BootResourcesManager,UsersManager,ManagerHelperService){return{restrict:"E",scope:{design:"=?"},templateUrl:"static/partials/boot-images.html?v="+MAAS_config.files_version,controller:function($scope,$rootScope,$element,$document){$scope.loading=!0,$scope.saving=!1,$scope.stopping=!1,$scope.design=$scope.design||"page",$scope.bootResources=BootResourcesManager.getData(),$scope.ubuntuImages=[],$scope.source={isNew:!1,tooMany:!1,showingAdvanced:!1,connecting:!1,errorMessage:"",source_type:"maas.io",url:"",keyring_filename:"",keyring_data:"",releases:[],arches:[],selections:{changed:!1,releases:[],arches:[]}},$scope.ubuntuCoreImages=[],$scope.ubuntu_core={changed:!1,images:[]},$scope.otherImages=[],$scope.other={changed:!1,images:[]},$scope.generatedImages=[],$scope.customImages=[],$scope.ubuntuDeleteId=null,$scope.removingImage=null,$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.getTitleIcon=function(){return 0===$scope.bootResources.resources.length?"p-icon--success-muted":"p-icon--success"},$scope.showMirrorPath=function(){return"custom"===$scope.source.source_type},$scope.isShowingAdvancedOptions=function(){return $scope.source.showingAdvanced},$scope.toggleAdvancedOptions=function(){$scope.source.showingAdvanced=!$scope.source.showingAdvanced},$scope.bothKeyringOptionsSet=function(){return $scope.source.keyring_filename.length>0&&$scope.source.keyring_data.length>0},$scope.showConnectButton=function(){return $scope.source.isNew},$scope.sourceChanged=function(){var currentSources=$scope.bootResources.ubuntu.sources;if(0===currentSources.length)$scope.source.isNew=!0;else{var prevNew=$scope.source.isNew;$scope.source.isNew=$scope.source.source_type!==currentSources[0].source_type,"custom"===$scope.source.source_type&&($scope.source.isNew=$scope.source.isNew||$scope.source.url!==currentSources[0].url),prevNew&&!$scope.source.isNew&&($scope.source.url=currentSources[0].url,$scope.source.keyring_filename=currentSources[0].keyring_filename,$scope.source.keyring_data=currentSources[0].keyring_data),$scope.source.releases=[],$scope.source.arches=[],$scope.source.selections={changed:!1,releases:[],arches:[]}}$scope.updateSource(),$scope.regenerateUbuntuImages(),$scope.source.isNew&&"maas.io"===$scope.source.source_type&&$scope.connect()},$scope.isConnectButtonDisabled=function(){return"maas.io"!==$scope.source.source_type&&($scope.bothKeyringOptionsSet()||0===$scope.source.url.length||$scope.source.connecting)},$scope.getSourceParams=function(){return"maas.io"===$scope.source.source_type?{source_type:"maas.io"}:{source_type:$scope.source.source_type,url:$scope.source.url,keyring_filename:$scope.source.keyring_filename,keyring_data:$scope.source.keyring_data}},$scope.selectDefaults=function(){angular.forEach($scope.source.releases,function(release){"bionic"===release.name&&$scope.source.selections.releases.push(release)}),angular.forEach($scope.source.arches,function(arch){"amd64"===arch.name&&$scope.source.selections.arches.push(arch)})},$scope.connect=function(){if(!$scope.isConnectButtonDisabled()){var source=$scope.getSourceParams();$scope.source.connecting=!0,$scope.source.releases=[],$scope.source.arches=[],$scope.source.selections.changed=!0,$scope.source.selections.releases=[],$scope.source.selections.arches=[],$scope.regenerateUbuntuImages(),BootResourcesManager.fetch(source).then(function(data){$scope.source.connecting=!1,data=angular.fromJson(data),$scope.source.releases=data.releases,$scope.source.arches=data.arches,$scope.selectDefaults(),$scope.regenerateUbuntuImages()},function(error){$scope.source.connecting=!1,$scope.source.errorMessage=error})}},$scope.showConnectBlock=function(){return $scope.source.tooMany||$scope.source.isNew&&!$scope.showSelections()},$scope.showSelections=function(){return $scope.source.releases.length>0&&$scope.source.arches.length>0},$scope.getUbuntuLTSReleases=function(){var releases=$scope.bootResources.ubuntu.releases;$scope.source.isNew&&(releases=$scope.source.releases);var filtered=[];return angular.forEach(releases,function(release){release.deleted||-1===release.title.indexOf("LTS")||filtered.push(release)}),filtered},$scope.getUbuntuNonLTSReleases=function(){var releases=$scope.bootResources.ubuntu.releases;$scope.source.isNew&&(releases=$scope.source.releases);var filtered=[];return angular.forEach(releases,function(release){release.deleted||-1!==release.title.indexOf("LTS")||filtered.push(release)}),filtered},$scope.getArchitectures=function(){var arches=$scope.bootResources.ubuntu.arches;$scope.source.isNew&&(arches=$scope.source.arches);var filtered=[];return angular.forEach(arches,function(arch){arch.deleted||filtered.push(arch)}),filtered},$scope.isSelected=function(type,obj){return $scope.source.selections[type].indexOf(obj)>=0},$scope.toggleSelection=function(type,obj){var idx=$scope.source.selections[type].indexOf(obj);-1===idx?$scope.source.selections[type].push(obj):$scope.source.selections[type].splice(idx,1),$scope.source.selections.changed=!0,$scope.regenerateUbuntuImages()},$scope.showImagesTable=function(){return $scope.ubuntuImages.length>0||$scope.source.arches.length>0&&$scope.source.releases.length>0},$scope.regenerateUbuntuImages=function(){var getResource=function(){return null},resources=$scope.bootResources.resources.filter(function(resource){var resource_os=resource.name.split("/")[0];return 0===resource.rtype&&"ubuntu"===resource_os});$scope.source.isNew||(getResource=function(release,arch){var i;for(i=0;i=0)if(parseInt($scope.ubuntuImages[i].title.split(".")[0],10)>=14)return!0}return!1},$scope.commissioningSeriesSelected=function(){var i;for(i=0;i<$scope.ubuntuImages.length;i++)if(!$scope.ubuntuImages[i].beingDeleted&&$scope.ubuntuImages[i].name===$scope.bootResources.ubuntu.commissioning_series)return!0;return!1},$scope.isShowingDeleteConfirm=function(image){return image===$scope.removingImage},$scope.quickRemove=function(image){$scope.removingImage=image},$scope.cancelRemove=function(){$scope.removingImage=null},$scope.confirmRemove=function(image){image===$scope.removingImage&&BootResourcesManager.deleteImage({id:image.image_id}),$scope.cancelRemove()},$scope.showStopImportButton=function(){return $scope.bootResources.region_import_running},$scope.showSaveSelection=function(){return $scope.showImagesTable()},$scope.canSaveSelection=function(){var i,commissioning_series_being_deleted=!1,commissioning_series_arches=0;for(i=0;i<$scope.ubuntuImages.length;i++)$scope.ubuntuImages[i].name===$scope.bootResources.ubuntu.commissioning_series&&commissioning_series_arches++;for(i=0;i<$scope.ubuntuImages.length;i++)if($scope.ubuntuImages[i].beingDeleted&&$scope.ubuntuImages[i].name===$scope.bootResources.ubuntu.commissioning_series&&1===commissioning_series_arches){commissioning_series_being_deleted=!0;break}return!commissioning_series_being_deleted&&!$scope.saving&&!$scope.stopping&&$scope.ltsIsSelected()},$scope.getSaveSelectionText=function(){return $scope.saving?"Saving...":"Update selection"},$scope.canStopImport=function(){return!$scope.saving&&!$scope.stopping},$scope.getStopImportText=function(){return $scope.stopping?"Stopping...":"Stop import"},$scope.stopImport=function(){$scope.canStopImport()&&($scope.stopping=!0,BootResourcesManager.stopImport().then(function(){$scope.stopping=!1}))},$scope.saveSelection=function(){if($scope.canSaveSelection()){var params=$scope.getSourceParams();params.releases=$scope.source.selections.releases.map(function(obj){return obj.name}),params.arches=$scope.source.selections.arches.map(function(obj){return obj.name}),$scope.saving=!0,BootResourcesManager.saveUbuntu(params).then(function(){$scope.saving=!1,$scope.source.isNew=!1,$scope.source.selections.changed=!1,$scope.updateSource()})}},$scope.getNewSelections=function(newObjs,oldSelections){var newSelections=[];return angular.forEach(newObjs,function(obj){angular.forEach(oldSelections,function(selection){obj.name===selection.name&&newSelections.push(obj)})}),newSelections},$scope.updateSource=function(){if(!$scope.source.isNew){var source_len=$scope.bootResources.ubuntu.sources.length;if(0===source_len)$scope.source.isNew=!0,$scope.source.source_type="custom",$scope.source.errorMessage="Currently no source exists.";else if(1===source_len){var source=$scope.bootResources.ubuntu.sources[0];$scope.source.source_type=source.source_type,"maas.io"===source.source_type?($scope.source.url="",$scope.source.keyring_filename="",$scope.source.keyring_data=""):($scope.source.url=source.url,$scope.source.keyring_filename=source.keyring_filename,$scope.source.keyring_data=source.keyring_data),$scope.source.releases=$scope.bootResources.ubuntu.releases,$scope.source.arches=$scope.bootResources.ubuntu.arches,$scope.source.selections.changed?($scope.source.selections.releases=$scope.getNewSelections($scope.source.releases,$scope.source.selections.releases),$scope.source.selections.arches=$scope.getNewSelections($scope.source.arches,$scope.source.selections.arches)):($scope.source.selections.releases=$scope.source.releases.filter(function(obj){return obj.checked}),$scope.source.selections.arches=$scope.source.arches.filter(function(obj){return obj.checked})),$scope.regenerateUbuntuImages()}else $scope.source.tooMany=!0,$scope.source.releases=$scope.bootResources.ubuntu.releases,$scope.source.arches=$scope.bootResources.ubuntu.arches,$scope.source.selections.releases=$scope.source.releases.filter(function(obj){return obj.checked}),$scope.source.selections.arches=$scope.source.arches.filter(function(obj){return obj.checked}),$scope.source.errorMessage="More than one image source exists. UI does not support modification of sources when more than one has been defined. Used the API to adjust your sources.",$scope.regenerateUbuntuImages()}},$scope.toggleUbuntuCoreSelection=function(image){$scope.ubuntu_core.changed=!0,image.checked=!image.checked,$scope.regenerateUbuntuCoreImages()},$scope.saveUbuntuCoreSelection=function(){var params={images:$scope.ubuntu_core.images.filter(function(image){return image.checked}).map(function(image){return image.name})};$scope.saving=!0,BootResourcesManager.saveUbuntuCore(params).then(function(){$scope.saving=!1})},$scope.toggleOtherSelection=function(image){$scope.other.changed=!0,image.checked=!image.checked,$scope.regenerateOtherImages()},$scope.saveOtherSelection=function(){var params={images:$scope.other.images.filter(function(image){return image.checked}).map(function(image){return image.name})};$scope.saving=!0,BootResourcesManager.saveOther(params).then(function(){$scope.saving=!1})},$scope.canBeRemoved=function(image){if(!angular.isNumber(image.resourceId))return!1;var i,releases=$scope.bootResources.ubuntu.releases,arches=$scope.bootResources.ubuntu.arches;for(i=0;i',"",'
","","
",""].join(""))}]),angular.module("MAAS").directive("maasCta",function(){return{restrict:"A",replace:!0,require:"ngModel",scope:{maasCta:"=",ngModel:"="},templateUrl:"directive/templates/cta.html",link:function(scope,element,attrs,ngModelCtrl){var defaultTitle="Take action";angular.isString(attrs.defaultTitle)&&""!==attrs.defaultTitle&&(defaultTitle=attrs.defaultTitle),scope.selectItem=function(select){scope.shown=!1,ngModelCtrl.$setViewValue(select)},scope.getTitle=function(){return angular.isObject(ngModelCtrl.$modelValue)?(option=ngModelCtrl.$modelValue,scope.secondary=!0,angular.isString(option.selectedTitle)?option.selectedTitle:option.title):(scope.secondary=!1,defaultTitle)},scope.getOptionTitle=function(option){return scope.secondary&&angular.isString(option.selectedTitle)?option.selectedTitle:option.title},ngModelCtrl.$viewChangeListeners.push(function(){scope.$eval(attrs.ngChange)})},controller:function($scope,$rootScope,$element,$document){$scope.shown=!1,$scope.secondary=!1,$element.bind("click",function(evt){evt.stopPropagation()}),$document.find("body").bind("click",function(){$rootScope.$apply($scope.shown=!1)})}}})},"./src/maasserver/static/js/angular/directives/card_loader.js":function(module,exports){angular.module("MAAS").directive("maasCardLoader",["$compile",function($compile){return{restrict:"A",link:function(scope,element,attrs){var include="";element.html(include),$compile(element.contents())(scope)}}}])},"./src/maasserver/static/js/angular/directives/code_lines.js":function(module,exports){angular.module("MAAS").directive("maasCodeLines",function(){return{restrict:"A",scope:{maasCodeLines:"&"},link:function(scope,element,attributes){scope.$watch(scope.maasCodeLines,function(){element.empty(),element.text(scope.maasCodeLines);var lines=element.html().split("\n"),newLine="",insert="";$.each(lines,function(){insert+=newLine+''+this+"",newLine="\n"}),insert+="",element.html(insert)})}}})},"./src/maasserver/static/js/angular/directives/contenteditable.js":function(module,exports){angular.module("MAAS").directive("contenteditable",function(){return{restrict:"A",require:"ngModel",scope:{ngDisabled:"&",maasEditing:"&"},link:function(scope,element,attrs,ngModel){var focusHandler=function(){scope.ngDisabled()?element.blur():scope.$apply(scope.maasEditing())};element.bind("focus",focusHandler);var changeHandler=function(){scope.$apply(ngModel.$setViewValue(element.text()))};element.bind("blur keyup change",changeHandler),ngModel.$render=function(){element.html(ngModel.$viewValue||"")},ngModel.$viewChangeListeners.push(function(){scope.$eval(attrs.ngChange)}),scope.$on("$destroy",function(){element.unbind("blur keyup change",changeHandler),element.unbind("focus",focusHandler)})}}})},"./src/maasserver/static/js/angular/directives/controller_image_status.js":function(module,exports){angular.module("MAAS").service("ControllerImageStatusService",["$timeout","$interval","ControllersManager",function($timeout,$interval,ControllersManager){var self=this;this.controllers=[],this.statuses={},this.updateStatuses=function(){var controllerIds=[];angular.forEach(self.controllers,function(system_id){controllerIds.push({system_id:system_id})}),ControllersManager.checkImageStates(controllerIds).then(function(results){angular.forEach(controllerIds,function(controller){var status=results[controller.system_id];self.statuses[controller.system_id]=status||"Unknown"})})},this.register=function(system_id){var known=self.controllers.indexOf(system_id)>=0;if(known||self.controllers.push(system_id),angular.isDefined(self.runningInterval)){if(known)return;$interval.cancel(self.runningInterval),self.runningInterval=void 0}angular.isDefined(self.startTimeout)&&$timeout.cancel(self.startTimeout),self.startTimeout=$timeout(function(){self.startTimeout=void 0,self.runningInterval=$interval(function(){self.updateStatuses()},3e4),self.updateStatuses()},100)},this.unregister=function(system_id){var idx=self.controllers.indexOf(system_id);idx>-1&&self.controllers.splice(idx,1),0===self.controllers.length&&(angular.isDefined(self.startTimeout)&&($timeout.cancel(self.startTimeout),self.startTimeout=void 0),angular.isDefined(self.runningInterval)&&($interval.cancel(self.runningInterval),self.runningInterval=void 0))},this.showSpinner=function(system_id){var status=self.statuses[system_id];return!angular.isString(status)||"Syncing"===status},this.getImageStatus=function(system_id){var status=self.statuses[system_id];return angular.isString(status)?status:"Asking for status..."}}]),angular.module("MAAS").directive("maasControllerImageStatus",["ControllerImageStatusService",function(ControllerImageStatusService){return{restrict:"E",scope:{systemId:"="},template:[' ',"{$ getImageStatus() $}"].join(""),link:function(scope,element,attrs){var unwatch,registered=!1;unwatch=scope.$watch("systemId",function(){angular.isDefined(scope.systemId)&&!registered&&(ControllerImageStatusService.register(scope.systemId),registered=!0,unwatch())}),scope.showSpinner=function(){return ControllerImageStatusService.showSpinner(scope.systemId)},scope.getImageStatus=function(){return ControllerImageStatusService.getImageStatus(scope.systemId)},scope.$on("$destroy",function(){registered&&ControllerImageStatusService.unregister(scope.systemId)})}}}])},"./src/maasserver/static/js/angular/directives/controller_status.js":function(module,exports){angular.module("MAAS").run(["$templateCache",function($templateCache){$templateCache.put("directive/templates/controller-status.html",["",'',"",'',""].join(""))}]),angular.module("MAAS").directive("maasControllerStatus",["ControllersManager","ServicesManager",function(ControllersManager,ServicesManager){return{restrict:"A",scope:{controller:"=maasControllerStatus",textOnly:"=?maasTextOnly"},templateUrl:"directive/templates/controller-status.html",controller:function($scope){function getClass(service){return"running"===service.status?"success":"degraded"===service.status?"warning":"dead"===service.status?"error":"unknown"}function countClass(classes,class_name){var counter=0;return angular.forEach(classes,function(name){name===class_name&&counter++}),counter}function updateStatusClass(){if($scope.serviceClass="unknown",angular.isObject($scope.controller)){var services=ControllersManager.getServices($scope.controller);if(services.length>0){var classes=services.map(getClass);-1!==classes.indexOf("error")?($scope.serviceClass="power-error",$scope.serviceText=countClass(classes,"error")+" dead"):-1!==classes.indexOf("warning")?($scope.serviceClass="warning",$scope.serviceText=countClass(classes,"warning")+" degraded"):($scope.serviceClass="success",$scope.serviceText=countClass(classes,"success")+" running")}}}$scope.serviceClass="unknown",$scope.services=ServicesManager.getItems(),$scope.serviceText="",$scope.textOnly?$scope.textOnly=!0:$scope.textOnly=!1,$scope.$watch("controller.service_ids",updateStatusClass),$scope.$watch("services",updateStatusClass,!0),updateStatusClass()}}}])},"./src/maasserver/static/js/angular/directives/dbl_click_overlay.js":function(module,exports){angular.module("MAAS").run(["$templateCache",function($templateCache){var styleElement=document.createElement("style");styleElement.innerHTML=[".maas-dbl-overlay {","display: inline-block;","position: relative;","}",".maas-dbl-overlay--overlay {","position: absolute;","left: 0;","right: 0;","top: 0;","bottom: 0;","-webkit-touch-callout: none;","-webkit-user-select: none;","-khtml-user-select: none;","-moz-user-select: none;","-ms-user-select: none;","user-select: none;","}"].join(""),document.getElementsByTagName("head")[0].appendChild(styleElement),$templateCache.put("directive/templates/double_click_overlay.html",['
',"",'
',"
"].join(""))}]),angular.module("MAAS").directive("maasDblClickOverlay",["BrowserService",function(BrowserService){return{restrict:"A",transclude:!0,replace:!0,scope:{maasDblClickOverlay:"&"},templateUrl:"directive/templates/double_click_overlay.html",link:function(scope,element,attrs){var clickElement,overlay=element.find(".maas-dbl-overlay--overlay"),transclude=element.find("span[data-ng-transclude]").children()[0];"SELECT"===transclude.tagName?(clickElement=function(){var evt=document.createEvent("MouseEvents");evt.initMouseEvent("mousedown",!0,!0,window,0,0,0,0,0,!1,!1,!1,!1,0,null),transclude.dispatchEvent(evt)},overlay.css({cursor:"pointer"})):"INPUT"===transclude.tagName?(clickElement=function(){angular.element(transclude).triggerHandler("focus")},overlay.css({cursor:"text"})):clickElement=function(){angular.element(transclude).triggerHandler("click")};var overlayClick=function(evt){clickElement(),evt.preventDefault(),evt.stopPropagation()},overlayDblClick=function(evt){scope.$apply(scope.maasDblClickOverlay),evt.preventDefault(),evt.stopPropagation()};"firefox"!==BrowserService.browser?(overlay.on("click",overlayClick),overlay.on("dblclick",overlayDblClick)):overlay.addClass("ng-hide"),scope.$on("$destroy",function(){overlay.off("click",overlayClick),overlay.off("dblclick",overlayDblClick)})}}}])},"./src/maasserver/static/js/angular/directives/default_os_select.js":function(module,exports){angular.module("MAAS").directive("maasDefaultOsSelect",function(){return{restrict:"A",scope:{osInput:"@maasDefaultOsSelect",seriesInput:"@maasDefaultSeriesSelect"},link:function($scope,$element,$attrs){var osElement=angular.element($element.find($scope.osInput)),seriesElement=angular.element($element.find($scope.seriesInput));if(!osElement||!seriesElement)throw new Error("Unable to find os or series elements");var switchTo=function(newOSValue,initialSkip){var options=seriesElement.find("option"),selected=!1;angular.forEach(options,function(option){var sel=function(option,newOSValue,initialSkip){var selected=!1,value=option.val(),split_value=value.split("/");return""===newOSValue?""===value?(option.removeClass("u-hide"),option.attr("selected","selected")):option.addClass("u-hide"):split_value[0]===newOSValue?(option.removeClass("u-hide"),""!==split_value[1]||initialSkip||(selected=!0,option.attr("selected","selected"))):option.addClass("u-hide"),selected}(angular.element(option),newOSValue,initialSkip);!1===selected&&(selected=sel)}),initialSkip||selected||function(options){var first_option=null;angular.forEach(options,function(option){(option=angular.element(option)).hasClass("u-hide")||null===first_option&&(first_option=option)}),null!==first_option&&seriesElement.val(first_option.val())}(options)};osElement.on("change",function(evt){switchTo(osElement.val(),!1)}),switchTo(osElement.val(),!0)}}})},"./src/maasserver/static/js/angular/directives/enter.js":function(module,exports){angular.module("MAAS").directive("maasEnter",function(){return{restrict:"A",link:function(scope,element,attrs){element.bind("keydown keypress",function(evt){13===evt.which&&(scope.$apply(function(){scope.$eval(attrs.maasEnter)}),evt.preventDefault())})}}})},"./src/maasserver/static/js/angular/directives/enter_blur.js":function(module,exports){angular.module("MAAS").directive("maasEnterBlur",function(){return{restrict:"A",link:function(scope,element,attrs){element.bind("keydown keypress",function(evt){13===evt.which&&(element.blur(),evt.preventDefault())})}}})},"./src/maasserver/static/js/angular/directives/error_overlay.js":function(module,exports){angular.module("MAAS").run(["$templateCache",function($templateCache){if($templateCache.put("directive/templates/error_overlay.html",['",'
',"
","
"].join("")),!angular.isDefined(window.jasmine)){var image=new Image;image.src="static/assets/images/icons/error.svg",(image=new Image).src="static/assets/images/icons/error.png"}}]),angular.module("MAAS").directive("maasErrorOverlay",["$window","$timeout","RegionConnection","ErrorService",function($window,$timeout,RegionConnection,ErrorService){return{restrict:"A",transclude:!0,scope:!0,templateUrl:"directive/templates/error_overlay.html",link:function(scope,element,attrs){var markDisconnected;scope.connected=!1,scope.showDisconnected=!1,scope.clientError=!1,scope.wasConnected=!1,scope.show=function(){return!!scope.clientError||!scope.connected&&(!scope.wasConnected||scope.showDisconnected)},scope.getTitle=function(){return scope.clientError?"Error occurred":scope.wasConnected?"Connection lost, reconnecting...":"Connecting..."},scope.reload=function(){$window.location.reload()};var watchConnection=function(){if(!scope.clientError){var connected=RegionConnection.isConnected();connected!==scope.connected&&(scope.connected=connected,connected||(scope.showDisconnected=!1,markDisconnected=$timeout(function(){scope.showDisconnected=!0,markDisconnected=void 0},500))),scope.error=RegionConnection.error,!scope.wasConnected&&connected&&(scope.wasConnected=!0)}};scope.$watch(function(){return RegionConnection.isConnected()},watchConnection),scope.$watch(function(){return RegionConnection.error},watchConnection);scope.$watch(function(){return ErrorService._error},function(){var error=ErrorService._error;angular.isString(error)&&(scope.clientError=!0,scope.error=ErrorService._error)}),scope.$on("$destroy",function(){angular.isDefined(markDisconnected)&&$timeout.cancel(markDisconnected)})}}}])},"./src/maasserver/static/js/angular/directives/error_toggle.js":function(module,exports){angular.module("MAAS").directive("maasErrorToggle",["$timeout","RegionConnection","ErrorService",function($timeout,RegionConnection,ErrorService){return{restrict:"A",link:function(scope,element,attrs){var disconnectedPromise,cancelTimeout=function(){angular.isDefined(disconnectedPromise)&&($timeout.cancel(disconnectedPromise),disconnectedPromise=void 0)},watchConnectionAndError=function(){var connected=RegionConnection.isConnected(),error=ErrorService._error;connected&&!angular.isString(error)?(cancelTimeout(),element.removeClass("ng-hide")):angular.isString(error)?(cancelTimeout(),element.addClass("ng-hide")):connected||(cancelTimeout(),disconnectedPromise=$timeout(function(){element.addClass("ng-hide")},500))};scope.$watch(function(){return RegionConnection.isConnected()},watchConnectionAndError),scope.$watch(function(){return ErrorService._error},watchConnectionAndError),scope.$on("$destroy",function(){cancelTimeout()})}}}])},"./src/maasserver/static/js/angular/directives/ipranges.js":function(module,exports){angular.module("MAAS").directive("maasIpRanges",["$filter","IPRangesManager","UsersManager","ManagerHelperService","ConverterService",function($filter,IPRangesManager,UsersManager,ManagerHelperService,ConverterService){return{restrict:"E",scope:{subnet:"=",vlan:"="},templateUrl:"static/partials/ipranges.html?v="+MAAS_config.files_version,controller:function($scope,$rootScope,$element,$document){$scope.loading=!0,$scope.ipranges=IPRangesManager.getItems(),$scope.iprangeManager=IPRangesManager,$scope.newRange=null,$scope.editIPRange=null,$scope.deleteIPRange=null,$scope.isSuperUser=function(){return UsersManager.isSuperUser()},$scope.addRange=function(type){$scope.newRange={type:type,start_ip:"",end_ip:"",comment:""},angular.isObject($scope.subnet)&&($scope.newRange.subnet=$scope.subnet.id),angular.isObject($scope.vlan)&&($scope.newRange.vlan=$scope.vlan.id),"dynamic"===type&&($scope.newRange.comment="Dynamic")},$scope.cancelAddRange=function(){$scope.newRange=null},$scope.ipRangeCanBeModified=function(range){return!!$scope.isSuperUser()||"reserved"===range.type&&range.user===UsersManager.getAuthUser().id},$scope.isIPRangeInEditMode=function(range){return $scope.editIPRange===range},$scope.ipRangeToggleEditMode=function(range){$scope.deleteIPRange=null,$scope.isIPRangeInEditMode(range)?$scope.editIPRange=null:$scope.editIPRange=range},$scope.ipRangeClearEditMode=function(){$scope.editIPRange=null},$scope.isIPRangeInDeleteMode=function(range){return $scope.deleteIPRange===range},$scope.ipRangeEnterDeleteMode=function(range){$scope.editIPRange=null,$scope.deleteIPRange=range},$scope.ipRangeCancelDelete=function(){$scope.deleteIPRange=null},$scope.ipRangeConfirmDelete=function(){IPRangesManager.deleteItem($scope.deleteIPRange).then(function(){$scope.deleteIPRange=null})},$scope.ipRangeSort=function(range){return-1!==range.start_ip.indexOf(":")?ConverterService.ipv6Expand(range.start_ip):ConverterService.ipv4ToInteger(range.start_ip)},ManagerHelperService.loadManagers($scope,[IPRangesManager,UsersManager]).then(function(){$scope.loading=!1})}}}])},"./src/maasserver/static/js/angular/directives/login.js":function(module,exports){angular.module("MAAS").factory("getBakery",function(){return function(visitPage){return new Bakery(new WebHandler,new BakeryStorage(localStorage,{}),{visitPage:visitPage})}}).directive("externalLogin",["$window","getBakery",function($window,getBakery){return{restrict:"E",scope:{},template:[''," Go to login page","",'
'," Error getting login link:
"," {{ errorMessage }}","
"].join(""),controller:function($scope,$rootScope,$element,$document){$scope.errorMessage="",$scope.loginURL="#",$scope.externalAuthURL=$element.attr("auth-url");var bakery=getBakery(function(error){$scope.$apply(function(){$scope.loginURL=error.Info.VisitURL,$scope.errorMessage=""})}),nextPath=$element.attr("next-path");bakery.get("/MAAS/accounts/discharge-request/",{Accept:"application/json","Content-Type":"application/json"},function(error,response){200!=response.currentTarget.status?$scope.$apply(function(){$scope.errorMessage=response.currentTarget.responseText}):$window.location.replace(nextPath)})}}}])},"./src/maasserver/static/js/angular/directives/maas_obj_form.js":function(module,exports){angular.module("MAAS").directive("maasObjForm",["JSONService",function(JSONService){function MAASFormController(scope){this.obj=scope.obj,this.manager=scope.manager,this.fields={},this.scope=scope,this.scope.saving=!1,this.scope.savingKeys=[],angular.isObject(this.scope.obj)&&(this.scope.obj.$maasForm=this),this.managerMethod=scope.managerMethod,angular.isUndefined(this.managerMethod)&&(this.managerMethod="updateItem");var self=this;scope.$watch("obj",function(){self.obj=scope.obj,angular.isObject(self.obj)&&(self.obj.$maasForm=self)}),scope.$on("$destroy",function(){angular.isObject(self.obj)&&delete self.obj.$maasForm})}return MAASFormController.prototype.getValue=function(key){var field=this.fields[key];if(angular.isObject(field)&&angular.isObject(field.scope))return field.scope.getValue()},MAASFormController.prototype.updateValue=function(key,value){var field=this.fields[key];if(angular.isObject(field)&&angular.isObject(field.scope))return field.scope.updateValue(value)},MAASFormController.prototype.cloneObject=function(){if(angular.isObject(this.obj)){delete this.obj.$maasForm;var clonedObj=angular.copy(this.obj);return this.obj.$maasForm=this,clonedObj}return this.obj},MAASFormController.prototype.isTableForm=function(){return!angular.isUndefined(this.scope.tableForm)&&this.scope.tableForm},MAASFormController.prototype.saveOnBlur=function(){return!!angular.isUndefined(this.scope.saveOnBlur)||this.scope.saveOnBlur},MAASFormController.prototype.isSaving=function(key){return this.scope.saving&&this.scope.savingKeys.indexOf(key)>=0},MAASFormController.prototype.showInputSaving=function(key){return this.saveOnBlur()&&this.isSaving(key)},MAASFormController.prototype.hasErrors=function(){var hasErrors=!1;return angular.forEach(this.fields,function(field){field.scope.hasErrors()&&(hasErrors=!0)}),angular.isDefined(this.errorScope)&&this.errorScope.hasErrors()&&(hasErrors=!0),hasErrors},MAASFormController.prototype.registerField=function(key,scope){this.fields[key]={editing:!1,scope:scope};var self=this;return this.scope.$watch("obj."+key,function(){angular.isObject(self.obj)&&!self.fields[key].editing&&self.fields[key].scope.updateValue(self.obj[key])}),angular.isObject(this.obj)?this.obj[key]:null},MAASFormController.prototype.unregisterField=function(key){delete this.fields[key]},MAASFormController.prototype.startEditingField=function(key){this.fields[key].editing=!0},MAASFormController.prototype.stopEditingField=function(key,value){var field=this.fields[key];if(this.saveOnBlur()){field.scope.clearErrors();var updatedObj=this.cloneObject();updatedObj[key]=value,updatedObj[key]!==this.obj[key]?(this.scope.saving=!0,this.scope.savingKeys=[key],this.updateItem(updatedObj,[key])):field.editing=!1}else field.editing=!1},MAASFormController.prototype.updateItem=function(updatedObj,keys){var key=keys[0],field=this.fields[key],self=this;return angular.isFunction(this.scope.preProcess)&&(updatedObj=this.scope.preProcess(updatedObj,keys)),this.manager[this.managerMethod](updatedObj).then(function(newObj){return field.editing=!1,field.scope.updateValue(newObj[key]),self.scope.saving=!1,self.scope.savingKeys=[],angular.isFunction(self.scope.afterSave)&&self.scope.afterSave(newObj),newObj},function(error){var errorJson=JSONService.tryParse(error);return angular.isObject(errorJson)?angular.forEach(errorJson,function(value,key){var errorField=self.fields[key];angular.isArray(value)||(value=[value]),angular.isObject(errorField)?errorField.scope.setErrors(value):("__all__"!==key&&(value=value.map(function(v){return key+": "+v})),field.scope.setErrors(value))}):field.scope.setErrors([error]),self.scope.saving=!1,self.scope.savingKeys=[],error})},MAASFormController.prototype.saveForm=function(){var keys=[],updatedObj=this.cloneObject();angular.forEach(this.fields,function(value,key){value.scope.clearErrors();var newValue=value.scope.getValue();angular.isDefined(newValue)&&updatedObj[key]!==newValue&&(updatedObj[key]=newValue,keys.push(key))}),angular.isFunction(this.scope.preProcess)&&(updatedObj=this.scope.preProcess(updatedObj,keys)),angular.isDefined(this.errorScope)&&this.errorScope.clearErrors();var self=this;return this.scope.saving=!0,this.scope.savingKeys=keys,this.manager[this.managerMethod](updatedObj).then(function(newObj){return self.scope.saving=!1,self.scope.savingKeys=[],angular.isFunction(self.scope.afterSave)&&self.scope.afterSave(newObj),newObj},function(error){var errorJson=JSONService.tryParse(error);return angular.isObject(errorJson)?angular.forEach(errorJson,function(value,key){var errorField=self.fields[key];angular.isArray(value)||(value=[value]),angular.isObject(errorField)?errorField.scope.setErrors(value):("__all__"!==key&&(value=value.map(function(v){return key+": "+v})),angular.isDefined(self.errorScope)?self.errorScope.setErrors(value):console.log(value))}):angular.isDefined(self.errorScope)?self.errorScope.setErrors([error]):console.log(error),self.scope.saving=!1,self.scope.savingKeys=[],error})},{restrict:"E",scope:{obj:"=",manager:"=",managerMethod:"@",preProcess:"=",afterSave:"=",tableForm:"=",saveOnBlur:"=",inline:"=",ngDisabled:"&"},transclude:!0,template:"
",controller:["$scope",MAASFormController]}}]),angular.module("MAAS").directive("maasObjFieldGroup",["JSONService",function(JSONService){function MAASGroupController(scope,timeout){this.fields={},this.scope=scope,this.scope.saving=!1,this.scope.savingKeys=[],this.timeout=timeout;var self=this;this.scope.isEditing=function(){var editing=!1;return angular.forEach(self.fields,function(value){editing||(editing=value.editing)}),editing}}return MAASGroupController.prototype.isTableForm=function(){return this.formController.isTableForm()},MAASGroupController.prototype.saveOnBlur=function(){return this.formController.saveOnBlur()},MAASGroupController.prototype.isSaving=function(key){return this.scope.saving&&this.scope.savingKeys.indexOf(key)>=0},MAASGroupController.prototype.showInputSaving=function(key){return this.saveOnBlur()&&this.scope.saving},MAASGroupController.prototype.registerField=function(key,scope){return this.fields[key]={editing:!1,scope:scope},this.formController.registerField(key,scope)},MAASGroupController.prototype.unregisterField=function(key){delete this.fields[key],this.formController.unregisterField(key)},MAASGroupController.prototype.startEditingField=function(key){this.fields[key].editing=!0;var self=this;angular.forEach(this.fields,function(value,key){self.formController.startEditingField(key)})},MAASGroupController.prototype.stopEditingField=function(key,value){if(this.fields[key].editing=!1,this.saveOnBlur()){var self=this;this.timeout(function(){var editing=!1;if(angular.forEach(self.fields,function(value){editing||(editing=value.editing)}),!editing){var keys=[],changed=!1,updatedObj=self.formController.cloneObject();if(angular.forEach(self.fields,function(value,key){value.scope.clearErrors();var newValue=value.scope.getValue();angular.isDefined(newValue)&&updatedObj[key]!==newValue&&(keys.push(key),updatedObj[key]=newValue,changed=!0)}),changed){var keyIdx=keys.indexOf(key);-1!==keyIdx&&(keys.splice(keyIdx,1),keys.splice(0,0,key)),self.scope.saving=!0,self.scope.savingKeys=keys,self.formController.updateItem(updatedObj,keys).then(function(obj){return self.scope.saving=!1,self.scope.savingKeys=[],obj},function(error){return self.scope.saving=!1,self.scope.savingKeys=[],error})}}},10)}},{restrict:"E",require:["^^maasObjForm","maasObjFieldGroup"],scope:{},transclude:!0,template:'
',controller:["$scope","$timeout",MAASGroupController],link:{pre:function(scope,element,attrs,controllers){controllers[1].formController=controllers[0],scope.ngDisabled=controllers[0].scope.ngDisabled,controllers[0].scope.$watch("obj",function(obj){scope.obj=obj})}}}}]),angular.module("MAAS").directive("maasObjField",["$compile",function($compile){return{restrict:"E",require:["^^maasObjForm","?^^maasObjFieldGroup"],scope:{onChange:"=",subtleText:"@"},transclude:!0,template:"
",link:function(scope,element,attrs,controllers){var controller=controllers[1];angular.isObject(controller)||(controller=controllers[0]),scope.ngDisabled=controller.scope.ngDisabled,element.addClass("p-form__group"),"false"!==attrs.subtle&&element.addClass("form__group--subtle");var missingAttrs=[];if(angular.isString(attrs.type)&&0!==attrs.type.length||missingAttrs.push("type"),angular.isString(attrs.key)&&0!==attrs.key.length||missingAttrs.push("key"),missingAttrs.length>0)throw new Error(missingAttrs.join(", ")+" are required on maas-obj-field.");angular.isString(attrs.disabled)&&(scope.ngDisabled=function(){return!0}),element.find("div").remove();var label=attrs.label||attrs.key;if("true"!==attrs.disableLabel&&"hidden"!==attrs.type){var labelElement=angular.element("