horizon-13.0.3/0000775000175000017500000000000013553661043013316 5ustar zuulzuul00000000000000horizon-13.0.3/test-shim.js0000664000175000017500000000617613553660754015613 0ustar zuulzuul00000000000000/** * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations * under the License. */ /* * Shim for Javascript unit tests; supplying expected global features. * This should be removed from the codebase once i18n services are provided. * Taken from default i18n file provided by Django. */ var horizonPlugInModules = []; (function (globals) { var django = globals.django || (globals.django = {}); django.pluralidx = function (count) { return (count == 1) ? 0 : 1; }; /* gettext identity library */ django.gettext = function (msgid) { return msgid; }; django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; }; django.gettext_noop = function (msgid) { return msgid; }; django.pgettext = function (context, msgid) { return msgid; }; django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; }; django.interpolate = function (fmt, obj, named) { if (named) { return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])}); } else { return fmt.replace(/%s/g, function(match){return String(obj.shift())}); } }; /* formatting library */ django.formats = { "DATETIME_FORMAT": "N j, Y, P", "DATETIME_INPUT_FORMATS": [ "%Y-%m-%d %H:%M:%S", "%Y-%m-%d %H:%M:%S.%f", "%Y-%m-%d %H:%M", "%Y-%m-%d", "%m/%d/%Y %H:%M:%S", "%m/%d/%Y %H:%M:%S.%f", "%m/%d/%Y %H:%M", "%m/%d/%Y", "%m/%d/%y %H:%M:%S", "%m/%d/%y %H:%M:%S.%f", "%m/%d/%y %H:%M", "%m/%d/%y" ], "DATE_FORMAT": "N j, Y", "DATE_INPUT_FORMATS": [ "%Y-%m-%d", "%m/%d/%Y", "%m/%d/%y" ], "DECIMAL_SEPARATOR": ".", "FIRST_DAY_OF_WEEK": "0", "MONTH_DAY_FORMAT": "F j", "NUMBER_GROUPING": "3", "SHORT_DATETIME_FORMAT": "m/d/Y P", "SHORT_DATE_FORMAT": "m/d/Y", "THOUSAND_SEPARATOR": ",", "TIME_FORMAT": "P", "TIME_INPUT_FORMATS": [ "%H:%M:%S", "%H:%M:%S.%f", "%H:%M" ], "YEAR_MONTH_FORMAT": "F Y" }; django.get_format = function (format_type) { var value = django.formats[format_type]; if (typeof(value) == 'undefined') { return format_type; } else { return value; } }; /* add to global namespace */ globals.pluralidx = django.pluralidx; globals.gettext = django.gettext; globals.ngettext = django.ngettext; globals.gettext_noop = django.gettext_noop; globals.pgettext = django.pgettext; globals.npgettext = django.npgettext; globals.interpolate = django.interpolate; globals.get_format = django.get_format; globals.STATIC_URL = '/static/'; globals.WEBROOT = '/'; }(this)); horizon-13.0.3/PKG-INFO0000664000175000017500000000612413553661043014416 0ustar zuulzuul00000000000000Metadata-Version: 2.1 Name: horizon Version: 13.0.3 Summary: OpenStack Dashboard Home-page: https://docs.openstack.org/horizon/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: ============================= Horizon (OpenStack Dashboard) ============================= Horizon is a Django-based project aimed at providing a complete OpenStack Dashboard along with an extensible framework for building new dashboards from reusable components. The ``openstack_dashboard`` module is a reference implementation of a Django site that uses the ``horizon`` app to provide web-based interactions with the various OpenStack projects. * Release management: https://launchpad.net/horizon * Blueprints and feature specifications: https://blueprints.launchpad.net/horizon * Issue tracking: https://bugs.launchpad.net/horizon .. image:: https://governance.openstack.org/tc/badges/horizon.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Using Horizon ============= See ``doc/source/install/index.rst`` about how to install Horizon in your OpenStack setup. It describes the example steps and has pointers for more detailed settings and configurations. It is also available at `Installation Guide `_. Getting Started for Developers ============================== ``doc/source/quickstart.rst`` or `Quickstart Guide `_ describes how to setup Horizon development environment and start development. Building Contributor Documentation ================================== This documentation is written by contributors, for contributors. The source is maintained in the ``doc/source`` directory using `reStructuredText`_ and built by `Sphinx`_ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Sphinx: http://sphinx-doc.org/ To build the docs, use:: $ tox -e docs Results are in the ``doc/build/html`` directory Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: OpenStack Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Internet :: WWW/HTTP Provides-Extra: test horizon-13.0.3/README.rst0000664000175000017500000000341613553660754015021 0ustar zuulzuul00000000000000============================= Horizon (OpenStack Dashboard) ============================= Horizon is a Django-based project aimed at providing a complete OpenStack Dashboard along with an extensible framework for building new dashboards from reusable components. The ``openstack_dashboard`` module is a reference implementation of a Django site that uses the ``horizon`` app to provide web-based interactions with the various OpenStack projects. * Release management: https://launchpad.net/horizon * Blueprints and feature specifications: https://blueprints.launchpad.net/horizon * Issue tracking: https://bugs.launchpad.net/horizon .. image:: https://governance.openstack.org/tc/badges/horizon.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Using Horizon ============= See ``doc/source/install/index.rst`` about how to install Horizon in your OpenStack setup. It describes the example steps and has pointers for more detailed settings and configurations. It is also available at `Installation Guide `_. Getting Started for Developers ============================== ``doc/source/quickstart.rst`` or `Quickstart Guide `_ describes how to setup Horizon development environment and start development. Building Contributor Documentation ================================== This documentation is written by contributors, for contributors. The source is maintained in the ``doc/source`` directory using `reStructuredText`_ and built by `Sphinx`_ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Sphinx: http://sphinx-doc.org/ To build the docs, use:: $ tox -e docs Results are in the ``doc/build/html`` directory horizon-13.0.3/openstack_auth/0000775000175000017500000000000013553661042016325 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/urls.py0000664000175000017500000000232613553660755017701 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from django.conf.urls import url from openstack_auth import utils from openstack_auth import views urlpatterns = [ url(r"^login/$", views.login, name='login'), url(r"^logout/$", views.logout, name='logout'), url(r'^switch/(?P[^/]+)/$', views.switch, name='switch_tenants'), url(r'^switch_services_region/(?P[^/]+)/$', views.switch_region, name='switch_services_region'), url(r'^switch_keystone_provider/(?P[^/]+)/$', views.switch_keystone_provider, name='switch_keystone_provider') ] if utils.is_websso_enabled(): urlpatterns.append(url(r"^websso/$", views.websso, name='websso')) horizon-13.0.3/openstack_auth/exceptions.py0000664000175000017500000000123013553660755021066 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. class KeystoneAuthException(Exception): """Generic error class to identify and catch our own errors.""" pass horizon-13.0.3/openstack_auth/backend.py0000664000175000017500000002754013553660755020310 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. """ Module defining the Django auth backend class for the Keystone API. """ import datetime import logging import pytz from django.conf import settings from django.utils.module_loading import import_string from django.utils.translation import ugettext_lazy as _ from openstack_auth import exceptions from openstack_auth import user as auth_user from openstack_auth import utils LOG = logging.getLogger(__name__) KEYSTONE_CLIENT_ATTR = "_keystoneclient" class KeystoneBackend(object): """Django authentication backend for use with ``django.contrib.auth``.""" def __init__(self): self._auth_plugins = None @property def auth_plugins(self): if self._auth_plugins is None: plugins = getattr( settings, 'AUTHENTICATION_PLUGINS', ['openstack_auth.plugin.password.PasswordPlugin', 'openstack_auth.plugin.token.TokenPlugin']) self._auth_plugins = [import_string(p)() for p in plugins] return self._auth_plugins def check_auth_expiry(self, auth_ref, margin=None): if not utils.is_token_valid(auth_ref, margin): msg = _("The authentication token issued by the Identity service " "has expired.") LOG.warning("The authentication token issued by the Identity " "service appears to have expired before it was " "issued. This may indicate a problem with either your " "server or client configuration.") raise exceptions.KeystoneAuthException(msg) return True def get_user(self, user_id): """Returns the current user from the session data. If authenticated, this return the user object based on the user ID and session data. .. note:: This required monkey-patching the ``contrib.auth`` middleware to make the ``request`` object available to the auth backend class. """ if (hasattr(self, 'request') and user_id == self.request.session["user_id"]): token = self.request.session['token'] endpoint = self.request.session['region_endpoint'] services_region = self.request.session['services_region'] user = auth_user.create_user_from_token(self.request, token, endpoint, services_region) return user else: return None def authenticate(self, auth_url=None, **kwargs): """Authenticates a user via the Keystone Identity API.""" LOG.debug('Beginning user authentication') if not auth_url: auth_url = settings.OPENSTACK_KEYSTONE_URL auth_url, url_fixed = utils.fix_auth_url_version_prefix(auth_url) if url_fixed: LOG.warning("The OPENSTACK_KEYSTONE_URL setting points to a v2.0 " "Keystone endpoint, but v3 is specified as the API " "version to use by Horizon. Using v3 endpoint for " "authentication.") for plugin in self.auth_plugins: unscoped_auth = plugin.get_plugin(auth_url=auth_url, **kwargs) if unscoped_auth: break else: msg = _('No authentication backend could be determined to ' 'handle the provided credentials.') LOG.warning('No authentication backend could be determined to ' 'handle the provided credentials. This is likely a ' 'configuration error that should be addressed.') raise exceptions.KeystoneAuthException(msg) # the recent project id a user might have set in a cookie recent_project = None request = kwargs.get('request') if request: # Grab recent_project found in the cookie, try to scope # to the last project used. recent_project = request.COOKIES.get('recent_project') unscoped_auth_ref = plugin.get_access_info(unscoped_auth) # Check expiry for our unscoped auth ref. self.check_auth_expiry(unscoped_auth_ref) domain_name = kwargs.get('user_domain_name', None) domain_auth, domain_auth_ref = plugin.get_domain_scoped_auth( unscoped_auth, unscoped_auth_ref, domain_name) scoped_auth, scoped_auth_ref = plugin.get_project_scoped_auth( unscoped_auth, unscoped_auth_ref, recent_project=recent_project) # Abort if there are no projects for this user and a valid domain # token has not been obtained # # The valid use cases for a user login are: # Keystone v2: user must have a role on a project and be able # to obtain a project scoped token # Keystone v3: 1) user can obtain a domain scoped token (user # has a role on the domain they authenticated to), # only, no roles on a project # 2) user can obtain a domain scoped token and has # a role on a project in the domain they # authenticated to (and can obtain a project scoped # token) # 3) user cannot obtain a domain scoped token, but can # obtain a project scoped token if not scoped_auth_ref and domain_auth_ref: # if the user can't obtain a project scoped token, set the scoped # token to be the domain token, if valid scoped_auth = domain_auth scoped_auth_ref = domain_auth_ref elif not scoped_auth_ref and not domain_auth_ref: msg = _('You are not authorized for any projects.') if utils.get_keystone_version() >= 3: msg = _('You are not authorized for any projects or domains.') raise exceptions.KeystoneAuthException(msg) # Check expiry for our new scoped token. self.check_auth_expiry(scoped_auth_ref) # We want to try to use the same region we just logged into # which may or may not be the default depending upon the order # keystone uses region_name = None id_endpoints = scoped_auth_ref.service_catalog.\ get_endpoints(service_type='identity') for id_endpoint in [cat for cat in id_endpoints['identity']]: if auth_url in id_endpoint.values(): region_name = id_endpoint['region'] break interface = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'public') endpoint, url_fixed = utils.fix_auth_url_version_prefix( scoped_auth_ref.service_catalog.url_for( service_type='identity', interface=interface, region_name=region_name)) if url_fixed: LOG.warning("The Keystone URL in service catalog points to a v2.0 " "Keystone endpoint, but v3 is specified as the API " "version to use by Horizon. Using v3 endpoint for " "authentication.") # If we made it here we succeeded. Create our User! unscoped_token = unscoped_auth_ref.auth_token user = auth_user.create_user_from_token( request, auth_user.Token(scoped_auth_ref, unscoped_token=unscoped_token), endpoint, services_region=region_name) if request is not None: # if no k2k providers exist then the function returns quickly utils.store_initial_k2k_session(auth_url, request, scoped_auth_ref, unscoped_auth_ref) request.session['unscoped_token'] = unscoped_token if domain_auth_ref: # check django session engine, if using cookies, this will not # work, as it will overflow the cookie so don't add domain # scoped token to the session and put error in the log if utils.using_cookie_backed_sessions(): LOG.error('Using signed cookies as SESSION_ENGINE with ' 'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT is ' 'enabled. This disables the ability to ' 'perform identity operations due to cookie size ' 'constraints.') else: request.session['domain_token'] = domain_auth_ref request.user = user timeout = getattr(settings, "SESSION_TIMEOUT", 3600) token_life = user.token.expires - datetime.datetime.now(pytz.utc) session_time = min(timeout, int(token_life.total_seconds())) request.session.set_expiry(session_time) keystone_client_class = utils.get_keystone_client().Client session = utils.get_session() scoped_client = keystone_client_class(session=session, auth=scoped_auth) # Support client caching to save on auth calls. setattr(request, KEYSTONE_CLIENT_ATTR, scoped_client) LOG.debug('Authentication completed.') return user def get_group_permissions(self, user, obj=None): """Returns an empty set since Keystone doesn't support "groups".""" # Keystone V3 added "groups". The Auth token response includes the # roles from the user's Group assignment. It should be fine just # returning an empty set here. return set() def get_all_permissions(self, user, obj=None): """Returns a set of permission strings that the user has. This permission available to the user is derived from the user's Keystone "roles". The permissions are returned as ``"openstack.{{ role.name }}"``. """ if user.is_anonymous() or obj is not None: return set() # TODO(gabrielhurley): Integrate policy-driven RBAC # when supported by Keystone. role_perms = {utils.get_role_permission(role['name']) for role in user.roles} services = [] for service in user.service_catalog: try: service_type = service['type'] except KeyError: continue service_regions = [utils.get_endpoint_region(endpoint) for endpoint in service.get('endpoints', [])] if user.services_region in service_regions: services.append(service_type.lower()) service_perms = {"openstack.services.%s" % service for service in services} return role_perms | service_perms def has_perm(self, user, perm, obj=None): """Returns True if the given user has the specified permission.""" if not user.is_active: return False return perm in self.get_all_permissions(user, obj) def has_module_perms(self, user, app_label): """Returns True if user has any permissions in the given app_label. Currently this matches for the app_label ``"openstack"``. """ if not user.is_active: return False for perm in self.get_all_permissions(user): if perm[:perm.index('.')] == app_label: return True return False horizon-13.0.3/openstack_auth/utils.py0000664000175000017500000005001313553660755020050 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import logging import re from django.conf import settings from django.contrib import auth from django.contrib.auth import models from django.utils import timezone from keystoneauth1.identity import v2 as v2_auth from keystoneauth1.identity import v3 as v3_auth from keystoneauth1 import session from keystoneauth1 import token_endpoint from keystoneclient.v2_0 import client as client_v2 from keystoneclient.v3 import client as client_v3 from six.moves.urllib import parse as urlparse LOG = logging.getLogger(__name__) _TOKEN_TIMEOUT_MARGIN = getattr(settings, 'TOKEN_TIMEOUT_MARGIN', 0) """ We need the request object to get the user, so we'll slightly modify the existing django.contrib.auth.get_user method. To do so we update the auth middleware to point to our overridden method. Calling "patch_middleware_get_user" is done in our custom middleware at "openstack_auth.middleware" to monkeypatch the code in before it is needed. """ def middleware_get_user(request): if not hasattr(request, '_cached_user'): request._cached_user = get_user(request) return request._cached_user def get_user(request): try: user_id = request.session[auth.SESSION_KEY] backend_path = request.session[auth.BACKEND_SESSION_KEY] backend = auth.load_backend(backend_path) backend.request = request user = backend.get_user(user_id) or models.AnonymousUser() except KeyError: user = models.AnonymousUser() return user def patch_middleware_get_user(): # NOTE(adriant): We can't import middleware until our customer user model # is actually registered, otherwise a call to get_user_model within the # middleware module will fail. from django.contrib.auth import middleware middleware.get_user = middleware_get_user auth.get_user = get_user """ End Monkey-Patching. """ def is_token_valid(token, margin=None): """Timezone-aware checking of the auth token's expiration timestamp. Returns ``True`` if the token has not yet expired, otherwise ``False``. :param token: The openstack_auth.user.Token instance to check :param margin: A time margin in seconds to subtract from the real token's validity. An example usage is that the token can be valid once the middleware passed, and invalid (timed-out) during a view rendering and this generates authorization errors during the view rendering. A default margin can be set by the TOKEN_TIMEOUT_MARGIN in the django settings. """ expiration = token.expires # In case we get an unparseable expiration timestamp, return False # so you can't have a "forever" token just by breaking the expires param. if expiration is None: return False if margin is None: margin = getattr(settings, 'TOKEN_TIMEOUT_MARGIN', 0) expiration = expiration - datetime.timedelta(seconds=margin) if settings.USE_TZ and timezone.is_naive(expiration): # Presumes that the Keystone is using UTC. expiration = timezone.make_aware(expiration, timezone.utc) return expiration > timezone.now() # From django.contrib.auth.views # Added in Django 1.4.3, 1.5b2 # Vendored here for compatibility with old Django versions. def is_safe_url(url, host=None): """Return ``True`` if the url is a safe redirection. The safe redirection means that it doesn't point to a different host. Always returns ``False`` on an empty url. """ if not url: return False netloc = urlparse.urlparse(url)[1] return not netloc or netloc == host # DEPRECATED -- Mitaka # This method definition is included to prevent breaking backward compatibility # The original functionality was problematic and has been removed. def remove_project_cache(token): pass # Helper for figuring out keystone version # Implementation will change when API version discovery is available def get_keystone_version(): return getattr(settings, 'OPENSTACK_API_VERSIONS', {}).get('identity', 3) def get_session(): insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False) verify = getattr(settings, 'OPENSTACK_SSL_CACERT', True) if insecure: verify = False return session.Session(verify=verify) def get_keystone_client(): if get_keystone_version() < 3: return client_v2 else: return client_v3 def is_token_deletion_disabled(): LOG.warning("Deprecated TOKEN_DELETION_DISABLED setting is no longer used") return getattr(settings, 'TOKEN_DELETION_DISABLED', False) def is_websso_enabled(): """Websso is supported in Keystone version 3.""" websso_enabled = getattr(settings, 'WEBSSO_ENABLED', False) keystonev3_plus = (get_keystone_version() >= 3) return websso_enabled and keystonev3_plus def build_absolute_uri(request, relative_url): """Ensure absolute_uri are relative to WEBROOT.""" webroot = getattr(settings, 'WEBROOT', '') if webroot.endswith("/") and relative_url.startswith("/"): webroot = webroot[:-1] return request.build_absolute_uri(webroot + relative_url) def get_websso_url(request, auth_url, websso_auth): """Return the keystone endpoint for initiating WebSSO. Generate the keystone WebSSO endpoint that will redirect the user to the login page of the federated identity provider. Based on the authentication type selected by the user in the login form, it will construct the keystone WebSSO endpoint. :param request: Django http request object. :type request: django.http.HttpRequest :param auth_url: Keystone endpoint configured in the horizon setting. The value is derived from: - OPENSTACK_KEYSTONE_URL - AVAILABLE_REGIONS :type auth_url: string :param websso_auth: Authentication type selected by the user from the login form. The value is derived from the horizon setting WEBSSO_CHOICES. :type websso_auth: string Example of horizon WebSSO setting:: WEBSSO_CHOICES = ( ("credentials", "Keystone Credentials"), ("oidc", "OpenID Connect"), ("saml2", "Security Assertion Markup Language"), ("acme_oidc", "ACME - OpenID Connect"), ("acme_saml2", "ACME - SAML2") ) WEBSSO_IDP_MAPPING = { "acme_oidc": ("acme", "oidc"), "acme_saml2": ("acme", "saml2") } } The value of websso_auth will be looked up in the WEBSSO_IDP_MAPPING dictionary, if a match is found it will return a IdP specific WebSSO endpoint using the values found in the mapping. The value in WEBSSO_IDP_MAPPING is expected to be a tuple formatted as (, ). Using the values found, a IdP/protocol specific URL will be constructed:: /auth/OS-FEDERATION/identity_providers/ /protocols//websso If no value is found from the WEBSSO_IDP_MAPPING dictionary, it will treat the value as the global WebSSO protocol and construct the WebSSO URL by:: /auth/OS-FEDERATION/websso/ :returns: Keystone WebSSO endpoint. :rtype: string """ origin = build_absolute_uri(request, '/auth/websso/') idp_mapping = getattr(settings, 'WEBSSO_IDP_MAPPING', {}) idp_id, protocol_id = idp_mapping.get(websso_auth, (None, websso_auth)) if idp_id: # Use the IDP specific WebSSO endpoint url = ('%s/auth/OS-FEDERATION/identity_providers/%s' '/protocols/%s/websso?origin=%s' % (auth_url, idp_id, protocol_id, origin)) else: # If no IDP mapping found for the identifier, # perform WebSSO by protocol. url = ('%s/auth/OS-FEDERATION/websso/%s?origin=%s' % (auth_url, protocol_id, origin)) return url def has_in_url_path(url, subs): """Test if any of `subs` strings is present in the `url` path.""" scheme, netloc, path, query, fragment = urlparse.urlsplit(url) return any([sub in path for sub in subs]) def url_path_replace(url, old, new, count=None): """Return a copy of url with replaced path. Return a copy of url with all occurrences of old replaced by new in the url path. If the optional argument count is given, only the first count occurrences are replaced. """ args = [] scheme, netloc, path, query, fragment = urlparse.urlsplit(url) if count is not None: args.append(count) return urlparse.urlunsplit(( scheme, netloc, path.replace(old, new, *args), query, fragment)) def url_path_append(url, suffix): scheme, netloc, path, query, fragment = urlparse.urlsplit(url) path = (path + suffix).replace('//', '/') return urlparse.urlunsplit((scheme, netloc, path, query, fragment)) def _augment_url_with_version(auth_url): """Optionally augment auth_url path with version suffix. Check if path component already contains version suffix and if it does not, append version suffix to the end of path, not erasing the previous path contents, since keystone web endpoint (like /identity) could be there. Keystone version needs to be added to endpoint because as of Kilo, the identity URLs returned by Keystone might no longer contain API versions, leaving the version choice up to the user. """ if has_in_url_path(auth_url, ["/v2.0", "/v3"]): return auth_url if get_keystone_version() >= 3: return url_path_append(auth_url, "/v3") else: return url_path_append(auth_url, "/v2.0") # TODO(tsufiev): remove this legacy version as soon as Horizon switches to # the new fix_auth_url_version_prefix() call def fix_auth_url_version(auth_url): """Fix up the auth url if an invalid or no version prefix was given. People still give a v2 auth_url even when they specify that they want v3 authentication. Fix the URL to say v3 in this case and add version if it is missing entirely. This should be smarter and use discovery. """ auth_url = _augment_url_with_version(auth_url) if get_keystone_version() >= 3 and has_in_url_path(auth_url, ["/v2.0"]): LOG.warning("The Keystone URL (either in Horizon settings or in " "service catalog) points to a v2.0 Keystone endpoint, " "but v3 is specified as the API version to use by " "Horizon. Using v3 endpoint for authentication.") auth_url = url_path_replace(auth_url, "/v2.0", "/v3", 1) return auth_url def fix_auth_url_version_prefix(auth_url): """Fix up the auth url if an invalid or no version prefix was given. People still give a v2 auth_url even when they specify that they want v3 authentication. Fix the URL to say v3 in this case and add version if it is missing entirely. This should be smarter and use discovery. """ auth_url = _augment_url_with_version(auth_url) url_fixed = False if get_keystone_version() >= 3 and has_in_url_path(auth_url, ["/v2.0"]): url_fixed = True auth_url = url_path_replace(auth_url, "/v2.0", "/v3", 1) return auth_url, url_fixed def clean_up_auth_url(auth_url): """Clean up the auth url to extract the exact Keystone URL""" # NOTE(mnaser): This drops the query and fragment because we're only # trying to extract the Keystone URL. scheme, netloc, path, query, fragment = urlparse.urlsplit(auth_url) return urlparse.urlunsplit(( scheme, netloc, re.sub(r'/auth.*', '', path), '', '')) def get_token_auth_plugin(auth_url, token, project_id=None, domain_name=None): if get_keystone_version() >= 3: if domain_name: return v3_auth.Token(auth_url=auth_url, token=token, domain_name=domain_name, reauthenticate=False) else: return v3_auth.Token(auth_url=auth_url, token=token, project_id=project_id, reauthenticate=False) else: return v2_auth.Token(auth_url=auth_url, token=token, tenant_id=project_id, reauthenticate=False) def get_project_list(*args, **kwargs): is_federated = kwargs.get('is_federated', False) sess = kwargs.get('session') or get_session() auth_url, _ = fix_auth_url_version_prefix(kwargs['auth_url']) auth = token_endpoint.Token(auth_url, kwargs['token']) client = get_keystone_client().Client(session=sess, auth=auth) if get_keystone_version() < 3: projects = client.tenants.list() elif is_federated: projects = client.federation.projects.list() else: projects = client.projects.list(user=kwargs.get('user_id')) projects.sort(key=lambda project: project.name.lower()) return projects def default_services_region(service_catalog, request=None, selected_region=None): """Returns the first endpoint region for first non-identity service. Extracted from the service catalog. """ if service_catalog: available_regions = [get_endpoint_region(endpoint) for service in service_catalog for endpoint in service.get('endpoints', []) if (service.get('type') is not None and service.get('type') != 'identity')] if not available_regions: # this is very likely an incomplete keystone setup LOG.warning('No regions could be found excluding identity.') available_regions = [get_endpoint_region(endpoint) for service in service_catalog for endpoint in service.get('endpoints', [])] if not available_regions: # if there are no region setup for any service endpoint, # this is a critical problem and it's not clear how this occurs LOG.error('No regions can be found in the service catalog.') return None if request and selected_region is None: selected_region = request.COOKIES.get('services_region', available_regions[0]) if selected_region not in available_regions: selected_region = available_regions[0] return selected_region return None def set_response_cookie(response, cookie_name, cookie_value): """Common function for setting the cookie in the response. Provides a common policy of setting cookies for last used project and region, can be reused in other locations. This method will set the cookie to expire in 365 days. """ now = timezone.now() expire_date = now + datetime.timedelta(days=365) response.set_cookie(cookie_name, cookie_value, expires=expire_date) def get_endpoint_region(endpoint): """Common function for getting the region from endpoint. In Keystone V3, region has been deprecated in favor of region_id. This method provides a way to get region that works for both Keystone V2 and V3. """ return endpoint.get('region_id') or endpoint.get('region') def using_cookie_backed_sessions(): engine = getattr(settings, 'SESSION_ENGINE', '') return "signed_cookies" in engine def get_admin_roles(): """Common function for getting the admin roles from settings :return: Set object including all admin roles. If there is no role, this will return empty:: { "foo", "bar", "admin" } """ admin_roles = {role.lower() for role in getattr(settings, 'OPENSTACK_KEYSTONE_ADMIN_ROLES', ['admin'])} return admin_roles def get_role_permission(role): """Common function for getting the permission froms arg This format is 'openstack.roles.xxx' and 'xxx' is a real role name. :returns: String like "openstack.roles.admin" If role is None, this will return None. """ return "openstack.roles.%s" % role.lower() def get_admin_permissions(): """Common function for getting the admin permissions from settings This format is 'openstack.roles.xxx' and 'xxx' is a real role name. :returns: Set object including all admin permission. If there is no permission, this will return empty:: { "openstack.roles.foo", "openstack.roles.bar", "openstack.roles.admin" } """ return {get_role_permission(role) for role in get_admin_roles()} def get_client_ip(request): """Return client ip address using SECURE_PROXY_ADDR_HEADER variable. If not present or not defined on settings then REMOTE_ADDR is used. :param request: Django http request object. :type request: django.http.HttpRequest :returns: Possible client ip address :rtype: string """ _SECURE_PROXY_ADDR_HEADER = getattr( settings, 'SECURE_PROXY_ADDR_HEADER', False ) if _SECURE_PROXY_ADDR_HEADER: return request.META.get( _SECURE_PROXY_ADDR_HEADER, request.META.get('REMOTE_ADDR') ) return request.META.get('REMOTE_ADDR') def store_initial_k2k_session(auth_url, request, scoped_auth_ref, unscoped_auth_ref): """Stores session variables if there are k2k service providers This stores variables related to Keystone2Keystone federation. This function gets skipped if there are no Keystone service providers. An unscoped token to the identity provider keystone gets stored so that it can be used to do federated login into the service providers when switching keystone providers. The settings file can be configured to set the display name of the local (identity provider) keystone by setting KEYSTONE_PROVIDER_IDP_NAME. The KEYSTONE_PROVIDER_IDP_ID settings variable is used for comparison against the service providers. It should not conflict with any of the service provider ids. :param auth_url: base token auth url :param request: Django http request object :param scoped_auth_ref: Scoped Keystone access info object :param unscoped_auth_ref: Unscoped Keystone access info object """ keystone_provider_id = request.session.get('keystone_provider_id', None) if keystone_provider_id: return None providers = getattr(scoped_auth_ref, 'service_providers', None) if providers: providers = getattr(providers, '_service_providers', None) if providers: keystone_idp_name = getattr(settings, 'KEYSTONE_PROVIDER_IDP_NAME', 'Local Keystone') keystone_idp_id = getattr( settings, 'KEYSTONE_PROVIDER_IDP_ID', 'localkeystone') keystone_identity_provider = {'name': keystone_idp_name, 'id': keystone_idp_id} # (edtubill) We will use the IDs as the display names # We may want to be able to set display names in the future. keystone_providers = [ {'name': provider_id, 'id': provider_id} for provider_id in providers] keystone_providers.append(keystone_identity_provider) # We treat the Keystone idp ID as None request.session['keystone_provider_id'] = keystone_idp_id request.session['keystone_providers'] = keystone_providers request.session['k2k_base_unscoped_token'] =\ unscoped_auth_ref.auth_token request.session['k2k_auth_url'] = auth_url horizon-13.0.3/openstack_auth/locale/0000775000175000017500000000000013553661042017564 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/eo/0000775000175000017500000000000013553661042020167 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/eo/LC_MESSAGES/0000775000175000017500000000000013553661042021754 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/eo/LC_MESSAGES/django.po0000664000175000017500000000562513553660755023600 0ustar zuulzuul00000000000000# Georg Hennemann , 2017. #zanata # Georg Hennemann , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-19 11:05+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-19 07:15+0000\n" "Last-Translator: Georg Hennemann \n" "Language-Team: Esperanto\n" "Language: eo\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "Eraro okazis dum aŭtentigado. Bonvolu provu denove poste." msgid "Authenticate using" msgstr "Aŭtentigu uzante" msgid "Could not find service provider ID on keystone." msgstr "Neeblas trovi servo provizanto ID en keystone." msgid "Domain" msgstr "Domajno" msgid "Identity provider authentication failed." msgstr "Identeco provizanto autentikado malsukcesis. " msgid "Invalid credentials." msgstr "Maljusta legitimaĵo." msgid "K2K Federation not setup for this session" msgstr "K2K Federacio ne establita por tiu seanco" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "Nebla difini aŭtentiga servo por trakti la provizitajn legitimaĵojn." msgid "Password" msgstr "Pasvorto" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Bonvole konsideru ŝanĝi vian pasvorton, ĝi malvalidas en %s minutoj" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Projekto ŝanĝo malsukcesis por uzanto \"%(username)s\"." msgid "Region" msgstr "Regiono" #, python-format msgid "Service provider authentication failed. %s" msgstr "Servo provizanto aŭtentigado malsukcesis. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "Ŝanĝo al Keystone Provizanto \"%(keystone_provider)s\"sukcesis." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Ŝanĝi al projekto \"%(project_name)s\" sukcesis." msgid "The authentication token issued by the Identity service has expired." msgstr "" "La aŭtentiga ĵetono eldonita de la Identeco servo estas senvalidiĝita. " msgid "Unable to establish connection to keystone endpoint." msgstr "Neeblas konekti al keystone finpunkto." msgid "Unable to retrieve authorized domains." msgstr "Neeblas trovi rajtigitaj domajnoj." msgid "Unable to retrieve authorized projects." msgstr "Neeblas trovi rajtigitaj projektoj." msgid "User Name" msgstr "Uzanto Nomo" msgid "You are not authorized for any projects or domains." msgstr "Vi ne estas rajtigita pri iujn projektojn au domajnojn." msgid "You are not authorized for any projects." msgstr "Vi ne estas rajtigita pri iujn projektojn." horizon-13.0.3/openstack_auth/locale/ja/0000775000175000017500000000000013553661042020156 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/ja/LC_MESSAGES/0000775000175000017500000000000013553661042021743 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/ja/LC_MESSAGES/django.po0000664000175000017500000000647213553660755023570 0ustar zuulzuul00000000000000# Yuko Katabami , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-07 19:44+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-07 09:55+0000\n" "Last-Translator: Akihiro Motoki \n" "Language-Team: Japanese\n" "Language: ja\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "An error occurred authenticating. Please try again later." msgstr "認証中にエラーが発生しました。後ほどもう一度お試しください。" msgid "Authenticate using" msgstr "使用する認証方法" msgid "Could not find service provider ID on keystone." msgstr "keystone でサービスプロバイダー ID が見つかりません。" msgid "Domain" msgstr "ドメイン" msgid "Identity provider authentication failed." msgstr "認証プロバイダーの認証に失敗しました。" msgid "Invalid credentials." msgstr "認証情報が無効です。" msgid "K2K Federation not setup for this session" msgstr "このセッションには K2K フェデレーションは設定されていません。" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "指定された認証情報を処理する認証バックエンドが決定できませんでした。" msgid "Password" msgstr "パスワード" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "" "パスワードの変更をお勧めします。今のパスワードはあと %s 分で無効になります。" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "ユーザー \"%(username)s\" のプロジェクト切り替えに失敗しました。" msgid "Region" msgstr "リージョン" #, python-format msgid "Service provider authentication failed. %s" msgstr "サービスプロバイダーの認証に失敗しました。%s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "" "Keystone プロバイダー \"%(keystone_provider)s\" への切り替えが正常に完了しま" "した。" #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "プロジェクト \"%(project_name)s\" へ正常に切り替えました。" msgid "The authentication token issued by the Identity service has expired." msgstr "Identity サービスにより発行された認証トークンの期限が切れました。" msgid "Unable to establish connection to keystone endpoint." msgstr "Keystone エンドポイントへの接続を確立できません。" msgid "Unable to retrieve authorized domains." msgstr "認証されたドメインを取得できません。" msgid "Unable to retrieve authorized projects." msgstr "権限を持っているプロジェクトの情報を取得できません。" msgid "User Name" msgstr "ユーザー名" msgid "You are not authorized for any projects or domains." msgstr "どのプロジェクトやドメインに対しても権限がありません。" msgid "You are not authorized for any projects." msgstr "どのプロジェクトに対しても権限がありません。" horizon-13.0.3/openstack_auth/locale/es/0000775000175000017500000000000013553661042020173 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/es/LC_MESSAGES/0000775000175000017500000000000013553661042021760 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/es/LC_MESSAGES/django.po0000664000175000017500000000601613553660755023577 0ustar zuulzuul00000000000000# Alberto Molina Coballes , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-01 07:31+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-01-31 06:49+0000\n" "Last-Translator: Alberto Molina Coballes \n" "Language-Team: Spanish\n" "Language: es\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "" "Ha ocurrido un error en la autenticación. Por favor, inténtelo más tarde." msgid "Authenticate using" msgstr "Autenticar mediante" msgid "Could not find service provider ID on keystone." msgstr "No se puede encontrar el ID del proveedor en keystone." msgid "Domain" msgstr "Dominio" msgid "Identity provider authentication failed." msgstr "Ha fallado la autenticación del proveedor de identidad." msgid "Invalid credentials." msgstr "Credenciales no válidas." msgid "K2K Federation not setup for this session" msgstr "Se ha configurado una federación K2K en esta sesión" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "No se puede determinar el sistema de autenticación a utilizar para manejar " "las credenciales proporcionadas." msgid "Password" msgstr "Contraseña" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Por favor, considere cambiar su contraseña, expirará en %s minutos" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Ha fallado el cambio de proyecto para el usuario \"%(username)s\"." msgid "Region" msgstr "Región" #, python-format msgid "Service provider authentication failed. %s" msgstr "Ha fallado la autenticación del proveedor del servicio. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "" "Se ha cambiado al proveedor de keystone \"%(keystone_provider)s\" " "correctamente." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Se ha cambiado al proyecto \"%(project_name)s\" correcamente." msgid "The authentication token issued by the Identity service has expired." msgstr "Ha caducado el token de autenticación del servicio de identidad." msgid "Unable to establish connection to keystone endpoint." msgstr "No es posible establecer una conexión con el endpoint de keystone." msgid "Unable to retrieve authorized domains." msgstr "No ha sido posible obtener dominios autorizados." msgid "Unable to retrieve authorized projects." msgstr "No ha sido posible obtener proyectos autorizados." msgid "User Name" msgstr "Usuario" msgid "You are not authorized for any projects or domains." msgstr "No está autorizado en ningún proyecto o dominio." msgid "You are not authorized for any projects." msgstr "No está autorizado en ningún proyecto." horizon-13.0.3/openstack_auth/locale/en_GB/0000775000175000017500000000000013553661042020536 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/en_GB/LC_MESSAGES/0000775000175000017500000000000013553661042022323 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/en_GB/LC_MESSAGES/django.po0000664000175000017500000000557013553660755024146 0ustar zuulzuul00000000000000# Andi Chandler , 2017. #zanata # Andi Chandler , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-26 12:13+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-01 01:04+0000\n" "Last-Translator: Andi Chandler \n" "Language-Team: English (United Kingdom)\n" "Language: en_GB\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "An error occurred authenticating. Please try again later." msgid "Authenticate using" msgstr "Authenticate using" msgid "Could not find service provider ID on keystone." msgstr "Could not find service provider ID on Keystone." msgid "Domain" msgstr "Domain" msgid "Identity provider authentication failed." msgstr "Identity provider authentication failed." msgid "Invalid credentials." msgstr "Invalid credentials." msgid "K2K Federation not setup for this session" msgstr "K2K Federation not setup for this session" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "No authentication backend could be determined to handle the provided " "credentials." msgid "Password" msgstr "Password" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Please consider changing your password, it will expire in %s minutes" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Project switch failed for user \"%(username)s\"." msgid "Region" msgstr "Region" #, python-format msgid "Service provider authentication failed. %s" msgstr "Service provider authentication failed. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "Switch to Keystone Provider \"%(keystone_provider)s\"successful." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Switch to project \"%(project_name)s\" successful." msgid "The authentication token issued by the Identity service has expired." msgstr "The authentication token issued by the Identity service has expired." msgid "Unable to establish connection to keystone endpoint." msgstr "Unable to establish connection to Keystone endpoint." msgid "Unable to retrieve authorized domains." msgstr "Unable to retrieve authorised domains." msgid "Unable to retrieve authorized projects." msgstr "Unable to retrieve authorised projects." msgid "User Name" msgstr "User Name" msgid "You are not authorized for any projects or domains." msgstr "You are not authorised for any projects or domains." msgid "You are not authorized for any projects." msgstr "You are not authorised for any projects." horizon-13.0.3/openstack_auth/locale/pt_BR/0000775000175000017500000000000013553661042020572 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/pt_BR/LC_MESSAGES/0000775000175000017500000000000013553661042022357 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/pt_BR/LC_MESSAGES/django.po0000664000175000017500000000611613553660755024177 0ustar zuulzuul00000000000000# Fernando Pimenta , 2018. #zanata # Marcelo Dieder , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-26 12:13+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-01 01:54+0000\n" "Last-Translator: Marcelo Dieder \n" "Language-Team: Portuguese (Brazil)\n" "Language: pt_BR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "" "Um erro ocorreu na autenticação. Por favor, tente novamente mais tarde." msgid "Authenticate using" msgstr "Autenticar utilizando" msgid "Could not find service provider ID on keystone." msgstr "Não foi possível localizar o ID do provedor do serviço no keystone." msgid "Domain" msgstr "Domínio" msgid "Identity provider authentication failed." msgstr "A autenticação do provedor de identidade falhou." msgid "Invalid credentials." msgstr "Credenciais inválidas" msgid "K2K Federation not setup for this session" msgstr "Federação K2K não configurada para esta sessão" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "Nenhum backend de autenticação pode ser determinado para lidar com as " "credenciais fornecidas." msgid "Password" msgstr "Senha" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Por favor, considere alterar sua senha, ela irá expirar em %s minutos." #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Troca de projeto falhou para o usuário \"%(username)s\"." msgid "Region" msgstr "Região" #, python-format msgid "Service provider authentication failed. %s" msgstr "Autenticação do provedor de serviços falhou. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "" "Troca para o provedor Keystone \"%(keystone_provider)s\" realizada com " "sucesso." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Troca para o projeto \"%(project_name)s\" com sucesso." msgid "The authentication token issued by the Identity service has expired." msgstr "O token de autenticação emitido pelo serviço de Identidade expirou." msgid "Unable to establish connection to keystone endpoint." msgstr "Não é possível estabelecer conexão com o endpoint do Keystone." msgid "Unable to retrieve authorized domains." msgstr "Não é possível recuperar domínios autorizados." msgid "Unable to retrieve authorized projects." msgstr "Não é possível recuperar projetos autorizados." msgid "User Name" msgstr "Nome de Usuário" msgid "You are not authorized for any projects or domains." msgstr "Você não está autorizado para nenhum projeto ou domínio." msgid "You are not authorized for any projects." msgstr "Você não está autorizado para nenhum projeto." horizon-13.0.3/openstack_auth/locale/ko_KR/0000775000175000017500000000000013553661042020571 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/ko_KR/LC_MESSAGES/0000775000175000017500000000000013553661042022356 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/ko_KR/LC_MESSAGES/django.po0000664000175000017500000000615413553660755024200 0ustar zuulzuul00000000000000# ByungYeol Woo , 2017. #zanata # Ian Y. Choi , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-26 12:13+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-07 02:14+0000\n" "Last-Translator: Ian Y. Choi \n" "Language-Team: Korean (South Korea)\n" "Language: ko_KR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "An error occurred authenticating. Please try again later." msgstr "인증 도중 오류가 발생했습니다. 잠시 후 다시 시도하세요." msgid "Authenticate using" msgstr "인증 사용" msgid "Could not find service provider ID on keystone." msgstr "keystone 에서 서비스 프로바이더 ID 를 찾지 못하였습니다." msgid "Domain" msgstr "도메인" msgid "Identity provider authentication failed." msgstr "Identity 프로바이더 인증에 실패했습니다." msgid "Invalid credentials." msgstr "올바르지 않은 자격증명." msgid "K2K Federation not setup for this session" msgstr "이 세션에는 K2K 페더레이션이 설정되지 않았습니다." msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "제공된 자격증명을 처리할 수 있는 인증 백엔드를 정할 수 없습니다." msgid "Password" msgstr "비밀번호" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "암호는 %s 분 후에 만료되므로 변경해 주시기 바랍니다." #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "사용자 \"%(username)s\"의 프로젝트 전환이 실패했습니다." msgid "Region" msgstr "지역" #, python-format msgid "Service provider authentication failed. %s" msgstr "서비스 제공자 인증이 실패됨. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "키스톤 제공자 \"%(keystone_provider)s\" 로의 변경이 성공하였습니다." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "프로젝트 \"%(project_name)s\" 로 전환에 성공하였습니다." msgid "The authentication token issued by the Identity service has expired." msgstr "Identity 서비스에서 발급한 인증 토큰이 만료되었습니다." msgid "Unable to establish connection to keystone endpoint." msgstr "키스톤 엔드포인트에 연결할 수 없습니다." msgid "Unable to retrieve authorized domains." msgstr "인가된 도메인을 가져올 수 없습니다." msgid "Unable to retrieve authorized projects." msgstr "인가된 프로젝트를 가져올 수 없습니다." msgid "User Name" msgstr "사용자 이름" msgid "You are not authorized for any projects or domains." msgstr "모든 프로젝트나 도메인에 대한 권한이 없습니다." msgid "You are not authorized for any projects." msgstr "모든 프로젝트에 대한 권한이 없습니다." horizon-13.0.3/openstack_auth/locale/zh_CN/0000775000175000017500000000000013553661042020565 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/zh_CN/LC_MESSAGES/0000775000175000017500000000000013553661042022352 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/zh_CN/LC_MESSAGES/django.po0000664000175000017500000000527313553660755024175 0ustar zuulzuul00000000000000# TigerFang , 2017. #zanata # Tony , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-26 12:13+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-06 12:19+0000\n" "Last-Translator: Tony \n" "Language-Team: Chinese (China)\n" "Language: zh_CN\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "An error occurred authenticating. Please try again later." msgstr "验证过程中发生错误。请稍后重试。" msgid "Authenticate using" msgstr "用于认证" msgid "Could not find service provider ID on keystone." msgstr "在 keystone 中无法找到服务提供者。" msgid "Domain" msgstr "域" msgid "Identity provider authentication failed." msgstr "身份提供者身份验证失败。" msgid "Invalid credentials." msgstr "证书不可用。" msgid "K2K Federation not setup for this session" msgstr "当前会话中没有设置K2K联盟。" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "对于提供的证书,无法找到对应的认证后端来进行处理。" msgid "Password" msgstr "密码" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "您的密码将于%s后过期,请及时修改" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "用户\"%(username)s\"切换项目失败。" msgid "Region" msgstr "区域" #, python-format msgid "Service provider authentication failed. %s" msgstr "服务提供者验证失败。 %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "成功切换至Keystone提供者\"%(keystone_provider)s\"。" #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "成功切换到\"%(project_name)s\"项目。" msgid "The authentication token issued by the Identity service has expired." msgstr "认证服务中使用的验证令牌已经过期。" msgid "Unable to establish connection to keystone endpoint." msgstr "无法与keystone服务终端建立连接。" msgid "Unable to retrieve authorized domains." msgstr "无法获取授权的域。" msgid "Unable to retrieve authorized projects." msgstr "无法获取授权的项目。" msgid "User Name" msgstr "用户名" msgid "You are not authorized for any projects or domains." msgstr "您没有任何授权的项目或者域。" msgid "You are not authorized for any projects." msgstr "您没有任何授权的项目。" horizon-13.0.3/openstack_auth/locale/es_MX/0000775000175000017500000000000013553661042020577 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/es_MX/LC_MESSAGES/0000775000175000017500000000000013553661042022364 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/es_MX/LC_MESSAGES/django.po0000664000175000017500000000576713553660755024217 0ustar zuulzuul00000000000000# Jesus Montoya , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-04-12 01:35+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-04-16 11:04+0000\n" "Last-Translator: Jesus Montoya \n" "Language-Team: Spanish (Mexico)\n" "Language: es_MX\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "" "Se ha producido un error de autenticación. Por favor inténtelo de nuevo más " "tarde." msgid "Authenticate using" msgstr "Autentificar usando" msgid "Could not find service provider ID on keystone." msgstr "No es posible encontrar la identidad de proveedor keystone." msgid "Domain" msgstr "Dominio" msgid "Identity provider authentication failed." msgstr "Autenticación fallida para el prveedor de identidad." msgid "Invalid credentials." msgstr "Credenciales inválidas." msgid "K2K Federation not setup for this session" msgstr "K2K Federation no configurado para esta sesión" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "Autentificación de soporte no puede ser determinada para manejar las " "credenciales proveidas." msgid "Password" msgstr "Contraseña" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Por favor considere cambiar su contraseña, ésta expirará en %s minutos" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Cambio de proyecto fallido para el usuario \"%(username)s\"." msgid "Region" msgstr "Región." #, python-format msgid "Service provider authentication failed. %s" msgstr "Autenticación fallida para el prveedor de servicio. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "Cambio al proveedor Keystone \"%(keystone_provider)s\" satisfactorio." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Cambio al proyecto \"%(project_name)s\" satisfactorio." msgid "The authentication token issued by the Identity service has expired." msgstr "" "El token de autenticación otorgado por el servicio de identidad ha expirado." msgid "Unable to establish connection to keystone endpoint." msgstr "No es posible establecer conexión a punto final de keystone." msgid "Unable to retrieve authorized domains." msgstr "No se pueden recuperar los dominios autorizados." msgid "Unable to retrieve authorized projects." msgstr "No se pueden recuperar los proyectos autorizados." msgid "User Name" msgstr "Nombre de Usuario" msgid "You are not authorized for any projects or domains." msgstr "No está autorizado para ningún proyecto o dominio." msgid "You are not authorized for any projects." msgstr "No está autorizado para ningún proyecto." horizon-13.0.3/openstack_auth/locale/fr/0000775000175000017500000000000013553661042020173 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/fr/LC_MESSAGES/0000775000175000017500000000000013553661042021760 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/fr/LC_MESSAGES/django.po0000664000175000017500000000614413553660755023601 0ustar zuulzuul00000000000000# Loic Nicolle , 2017. #zanata # JF Taltavull , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-22 09:28+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-25 06:04+0000\n" "Last-Translator: JF Taltavull \n" "Language-Team: French\n" "Language: fr\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n > 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "" "Une erreur s'est produite pendant l'authentification. Veuillez réessayer " "ultérieurement." msgid "Authenticate using" msgstr "Mode d'authentification" msgid "Could not find service provider ID on keystone." msgstr "Impossible de trouver l'Id du fournisseur de service sur keystone." msgid "Domain" msgstr "Domaine" msgid "Identity provider authentication failed." msgstr "L'authentification auprès du fournisseur d'identité a échoué." msgid "Invalid credentials." msgstr "Données d'identification non valides." msgid "K2K Federation not setup for this session" msgstr "La fédération K2K n'a pas été configurée pour cette session" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "Aucun backend d'authentification pour gérer les informations " "d'authentification fournies." msgid "Password" msgstr "Mot de passe" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Merci de changer votre mot de passe, il va expirer dans %s minutes" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "La bascule de projet a échoué pour l'utilisateur \"%(username)s\"." msgid "Region" msgstr "Région" #, python-format msgid "Service provider authentication failed. %s" msgstr "L'authentification auprès du fournisseur de services a échoué. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "" "Bascule vers le fournisseur Keystone \"%(keystone_provider)s\" réussie." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Bascule vers le projet \"%(project_name)s\" réussie. " msgid "The authentication token issued by the Identity service has expired." msgstr "" "Le jeton d'authentification délivré par le service d'Identité a expiré." msgid "Unable to establish connection to keystone endpoint." msgstr "Impossible d'établir la connexion avec le endpoint keystone." msgid "Unable to retrieve authorized domains." msgstr "Impossible de récupérer les domaines autorisés." msgid "Unable to retrieve authorized projects." msgstr "Impossible de récupérer les projets autorisés." msgid "User Name" msgstr "Nom d'utilisateur" msgid "You are not authorized for any projects or domains." msgstr "Vous n'êtes autorisé sur aucun projet ou domaine." msgid "You are not authorized for any projects." msgstr "Vous n'êtes autorisé sur aucun projet." horizon-13.0.3/openstack_auth/locale/de/0000775000175000017500000000000013553661042020154 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/de/LC_MESSAGES/0000775000175000017500000000000013553661042021741 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/de/LC_MESSAGES/django.po0000664000175000017500000000602513553660755023560 0ustar zuulzuul00000000000000# Robert Simai , 2017. #zanata # Reik Keutterling , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-14 03:55+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-13 01:11+0000\n" "Last-Translator: Reik Keutterling \n" "Language-Team: German\n" "Language: de\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=2; plural=(n != 1)\n" msgid "An error occurred authenticating. Please try again later." msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal." msgid "Authenticate using" msgstr "Authentifizieren mit" msgid "Could not find service provider ID on keystone." msgstr "Dienstanbieter ID in Keystone nicht gefunden." msgid "Domain" msgstr "Domäne" msgid "Identity provider authentication failed." msgstr "Identitätsanbieter Authentifizierung fehlgeschlagen." msgid "Invalid credentials." msgstr "Unzureichende Berechtigung." msgid "K2K Federation not setup for this session" msgstr "K2K Verbund ist für diese Sitzung nicht eingerichtet" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "Es konnte kein Authentifizierungsbackend für die angegebene Legitimierung " "gefunden werden." msgid "Password" msgstr "Passwort" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "Bitte ändern Sie Ihr Passwort. Es läuft in %s Minuten ab." #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Projektumschaltung für Benutzer \"%(username)s\" fehlgeschlagen." msgid "Region" msgstr "Region" #, python-format msgid "Service provider authentication failed. %s" msgstr "Dienstanbieter Authentifizierung fehlgeschlagen. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "" "Umschalten zum Keystone Anbieter \"%(keystone_provider)s\" erfolgreich." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Umschalten zum Projekt \"%(project_name)s\" erfolgreich." msgid "The authentication token issued by the Identity service has expired." msgstr "" "Das vom Identitätsdienst ausgegebene Authentifizierungs-Token ist abgelaufen." msgid "Unable to establish connection to keystone endpoint." msgstr "Es kann keine Verbindung zum Keystone Endpunkt aufgebaut werden." msgid "Unable to retrieve authorized domains." msgstr "Autorisierte Domänen können nicht abgerufen werden." msgid "Unable to retrieve authorized projects." msgstr "Autorisierte Projekte können nicht abgerufen werden." msgid "User Name" msgstr "Benutzername" msgid "You are not authorized for any projects or domains." msgstr "Sie sind nicht autorisiert für irgendein Projekt oder eine Domäne." msgid "You are not authorized for any projects." msgstr "Sie sind für kein Projekt berechtigt." horizon-13.0.3/openstack_auth/locale/id/0000775000175000017500000000000013553661042020160 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/id/LC_MESSAGES/0000775000175000017500000000000013553661042021745 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/locale/id/LC_MESSAGES/django.po0000664000175000017500000000566013553660755023570 0ustar zuulzuul00000000000000# suhartono , 2017. #zanata # suhartono , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon VERSION\n" "Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n" "POT-Creation-Date: 2018-02-08 14:02+0000\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-02-08 04:05+0000\n" "Last-Translator: suhartono \n" "Language-Team: Indonesian\n" "Language: id\n" "X-Generator: Zanata 3.9.6\n" "Plural-Forms: nplurals=1; plural=0\n" msgid "An error occurred authenticating. Please try again later." msgstr "Terjadi kesalahan saat melakukan otentikasi. Silakan coba lagi nanti." msgid "Authenticate using" msgstr "Otentikasi menggunakan" msgid "Could not find service provider ID on keystone." msgstr "Tidak dapat menemukan ID penyedia layanan pada keystone." msgid "Domain" msgstr "Domain" msgid "Identity provider authentication failed." msgstr "Otentikasi penyedia identitas gagal" msgid "Invalid credentials." msgstr "Kredensial tidak valid." msgid "K2K Federation not setup for this session" msgstr "K2K Federation tidak disiapkan untuk sesi ini" msgid "" "No authentication backend could be determined to handle the provided " "credentials." msgstr "" "Tidak ada backend otentikasi yang bisa ditentukan untuk menangani kredensial " "yang diberikan." msgid "Password" msgstr "Password" #, python-format msgid "Please consider changing your password, it will expire in %s minutes" msgstr "" "Mohon pertimbangkan untuk mengubah kata sandi Anda, maka akan kedaluwarsa " "dalam %s minutes" #, python-format msgid "Project switch failed for user \"%(username)s\"." msgstr "Peralihan proyek gagal bagi pengguna \"%(username)s\"." msgid "Region" msgstr "Region" #, python-format msgid "Service provider authentication failed. %s" msgstr "Otentikasi penyedia layanan gagal. %s" #, python-format msgid "Switch to Keystone Provider \"%(keystone_provider)s\"successful." msgstr "Beralih ke Keystone Provider \"%(keystone_provider)s\" berhasil." #, python-format msgid "Switch to project \"%(project_name)s\" successful." msgstr "Beralih ke proyek \"%(project_name)s\" berhasil." msgid "The authentication token issued by the Identity service has expired." msgstr "" "Token otentikasi yang dikeluarkan oleh layanan Identity telah kadaluarsa." msgid "Unable to establish connection to keystone endpoint." msgstr "Tidak dapat menjalin koneksi ke keystone endpoint." msgid "Unable to retrieve authorized domains." msgstr "Tidak dapat mengambil domain resmi." msgid "Unable to retrieve authorized projects." msgstr "Tidak dapat mengambil proyek resmi" msgid "User Name" msgstr "User Name" msgid "You are not authorized for any projects or domains." msgstr "Anda tidak berwenang untuk proyek atau domain apa pun." msgid "You are not authorized for any projects." msgstr "Anda tidak berwenang untuk proyek apa pun." horizon-13.0.3/openstack_auth/middleware.py0000664000175000017500000000133513553660755021030 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from openstack_auth import utils # NOTE: The main role of this middleware is to call this. utils.patch_middleware_get_user() class OpenstackAuthMonkeyPatchMiddleware(object): pass horizon-13.0.3/openstack_auth/plugin/0000775000175000017500000000000013553661042017623 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/plugin/base.py0000664000175000017500000002350713553660755021130 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import abc import logging from django.utils.translation import ugettext_lazy as _ from keystoneauth1 import exceptions as keystone_exceptions from keystoneclient.v2_0 import client as v2_client from keystoneclient.v3 import client as v3_client import six from openstack_auth import exceptions from openstack_auth import utils LOG = logging.getLogger(__name__) __all__ = ['BasePlugin'] @six.add_metaclass(abc.ABCMeta) class BasePlugin(object): """Base plugin to provide ways to log in to dashboard. Provides a framework for keystoneclient plugins that can be used with the information provided to return an unscoped token. """ @abc.abstractmethod def get_plugin(self, auth_url=None, **kwargs): """Create a new plugin to attempt to authenticate. Given the information provided by the login providers attempt to create an authentication plugin that can be used to authenticate the user. If the provided login information does not contain enough information for this plugin to proceed then it should return None. :param str auth_url: The URL to authenticate against. :returns: A plugin that will be used to authenticate or None if the plugin cannot authenticate with the data provided. :rtype: keystoneclient.auth.BaseAuthPlugin """ return None @property def keystone_version(self): """The Identity API version as specified in the settings file.""" return utils.get_keystone_version() def list_projects(self, session, auth_plugin, auth_ref=None): """List the projects that are accessible to this plugin. Query the keystone server for all projects that this authentication token can be rescoped to. This function is overrideable by plugins if they use a non-standard mechanism to determine projects. :param session: A session object for communication: :type session: keystoneclient.session.Session :param auth_plugin: The auth plugin returned by :py:meth:`get_plugin`. :type auth_plugin: keystoneclient.auth.BaseAuthPlugin :param auth_ref: The current authentication data. This is optional as future auth plugins may not have auth_ref data and all the required information should be available via the auth_plugin. :type auth_ref: keystoneclient.access.AccessInfo` or None. :raises: exceptions.KeystoneAuthException on lookup failure. :returns: A list of projects. This currently accepts returning both v2 or v3 keystoneclient projects objects. """ try: if self.keystone_version >= 3: client = v3_client.Client(session=session, auth=auth_plugin) if auth_ref.is_federated: return client.federation.projects.list() else: return client.projects.list(user=auth_ref.user_id) else: client = v2_client.Client(session=session, auth=auth_plugin) return client.tenants.list() except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): msg = _('Unable to retrieve authorized projects.') raise exceptions.KeystoneAuthException(msg) def list_domains(self, session, auth_plugin, auth_ref=None): try: if self.keystone_version >= 3: client = v3_client.Client(session=session, auth=auth_plugin) return client.auth.domains() else: return [] except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): msg = _('Unable to retrieve authorized domains.') raise exceptions.KeystoneAuthException(msg) def get_access_info(self, keystone_auth): """Get the access info from an unscoped auth This function provides the base functionality that the plugins will use to authenticate and get the access info object. :param keystone_auth: keystoneauth1 identity plugin :raises: exceptions.KeystoneAuthException on auth failure :returns: keystoneclient.access.AccessInfo """ session = utils.get_session() try: unscoped_auth_ref = keystone_auth.get_access(session) except keystone_exceptions.ConnectFailure as exc: LOG.error(str(exc)) msg = _('Unable to establish connection to keystone endpoint.') raise exceptions.KeystoneAuthException(msg) except (keystone_exceptions.Unauthorized, keystone_exceptions.Forbidden, keystone_exceptions.NotFound) as exc: LOG.debug(str(exc)) raise exceptions.KeystoneAuthException(_('Invalid credentials.')) except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure) as exc: msg = _("An error occurred authenticating. " "Please try again later.") LOG.debug(str(exc)) raise exceptions.KeystoneAuthException(msg) return unscoped_auth_ref def get_project_scoped_auth(self, unscoped_auth, unscoped_auth_ref, recent_project=None): """Get the project scoped keystone auth and access info This function returns a project scoped keystone token plugin and AccessInfo object. :param unscoped_auth: keystone auth plugin :param unscoped_auth_ref: keystoneclient.access.AccessInfo` or None. :param recent_project: project that we should try to scope to :return: keystone token auth plugin, AccessInfo object """ auth_url = unscoped_auth.auth_url session = utils.get_session() projects = self.list_projects( session, unscoped_auth, unscoped_auth_ref) # Attempt to scope only to enabled projects projects = [project for project in projects if project.enabled] # if a most recent project was found, try using it first if recent_project: for pos, project in enumerate(projects): if project.id == recent_project: # move recent project to the beginning projects.pop(pos) projects.insert(0, project) break scoped_auth = None scoped_auth_ref = None for project in projects: token = unscoped_auth_ref.auth_token scoped_auth = utils.get_token_auth_plugin(auth_url, token=token, project_id=project.id) try: scoped_auth_ref = scoped_auth.get_access(session) except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): LOG.info('Attempted scope to project %s failed, will attempt' 'to scope to another project.', project.name) pass else: break return scoped_auth, scoped_auth_ref def get_domain_scoped_auth(self, unscoped_auth, unscoped_auth_ref, domain_name=None): """Get the domain scoped keystone auth and access info This function returns a domain scoped keystone token plugin and AccessInfo object. :param unscoped_auth: keystone auth plugin :param unscoped_auth_ref: keystoneclient.access.AccessInfo` or None. :param domain_name: domain that we should try to scope to :return: keystone token auth plugin, AccessInfo object """ session = utils.get_session() auth_url = unscoped_auth.auth_url if utils.get_keystone_version() < 3: return None, None if domain_name: domains = [domain_name] else: domains = self.list_domains(session, unscoped_auth, unscoped_auth_ref) domains = [domain.name for domain in domains if domain.enabled] # domain support can require domain scoped tokens to perform # identity operations depending on the policy files being used # for keystone. domain_auth = None domain_auth_ref = None for domain_name in domains: token = unscoped_auth_ref.auth_token domain_auth = utils.get_token_auth_plugin( auth_url, token, domain_name=domain_name) try: domain_auth_ref = domain_auth.get_access(session) except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): LOG.info('Attempted scope to domain %s failed, will attempt' 'to scope to another domain.', domain_name) pass else: if len(domains) > 1: LOG.info("More than one valid domain found for user %s," " scoping to %s", (unscoped_auth_ref.user_id, domain_name)) break return domain_auth, domain_auth_ref horizon-13.0.3/openstack_auth/plugin/__init__.py0000664000175000017500000000154413553660754021751 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from openstack_auth.plugin.base import BasePlugin from openstack_auth.plugin.k2k import K2KAuthPlugin from openstack_auth.plugin.password import PasswordPlugin from openstack_auth.plugin.token import TokenPlugin __all__ = ['BasePlugin', 'PasswordPlugin', 'TokenPlugin', 'K2KAuthPlugin'] horizon-13.0.3/openstack_auth/plugin/password.py0000664000175000017500000000351213553660755022052 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging from keystoneauth1.identity import v2 as v2_auth from keystoneauth1.identity import v3 as v3_auth from openstack_auth.plugin import base from openstack_auth import utils LOG = logging.getLogger(__name__) __all__ = ['PasswordPlugin'] class PasswordPlugin(base.BasePlugin): """Authenticate against keystone given a username and password. This is the default login mechanism. Given a username and password inputted from a login form returns a v2 or v3 keystone Password plugin for authentication. """ def get_plugin(self, auth_url=None, username=None, password=None, user_domain_name=None, **kwargs): if not all((auth_url, username, password)): return None LOG.debug('Attempting to authenticate for %s', username) if utils.get_keystone_version() >= 3: return v3_auth.Password(auth_url=auth_url, username=username, password=password, user_domain_name=user_domain_name, unscoped=True) else: return v2_auth.Password(auth_url=auth_url, username=username, password=password) horizon-13.0.3/openstack_auth/plugin/token.py0000664000175000017500000000272113553660755021331 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from keystoneauth1.identity import v2 as v2_auth from keystoneauth1.identity import v3 as v3_auth from openstack_auth.plugin import base from openstack_auth import utils __all__ = ['TokenPlugin'] class TokenPlugin(base.BasePlugin): """Authenticate against keystone with an existing token.""" def get_plugin(self, auth_url=None, token=None, project_id=None, **kwargs): if not all((auth_url, token)): return None if utils.get_keystone_version() >= 3: return v3_auth.Token(auth_url=auth_url, token=token, project_id=project_id, reauthenticate=False) else: return v2_auth.Token(auth_url=auth_url, token=token, tenant_id=project_id, reauthenticate=False) horizon-13.0.3/openstack_auth/plugin/k2k.py0000664000175000017500000000773313553660755020710 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from django.conf import settings from django.utils.translation import ugettext_lazy as _ from keystoneauth1.identity import v3 as v3_auth from openstack_auth import exceptions from openstack_auth.plugin import base from openstack_auth import utils LOG = logging.getLogger(__name__) __all__ = ['K2KAuthPlugin'] class K2KAuthPlugin(base.BasePlugin): def get_plugin(self, service_provider=None, auth_url=None, plugins=None, **kwargs): """Authenticate using keystone to keystone federation. This plugin uses other v3 plugins to authenticate a user to a identity provider in order to authenticate the user to a service provider :param service_provider: service provider ID :param auth_url: Keystone auth url :param plugins: list of openstack_auth plugins to check :returns Keystone2Keystone keystone auth plugin """ # Avoid mutable default arg for plugins plugins = plugins or [] # service_provider being None prevents infinite recursion if utils.get_keystone_version() < 3 or not service_provider: return None keystone_idp_id = getattr(settings, 'KEYSTONE_PROVIDER_IDP_ID', 'localkeystone') if service_provider == keystone_idp_id: return None for plugin in plugins: unscoped_idp_auth = plugin.get_plugin(plugins=plugins, auth_url=auth_url, **kwargs) if unscoped_idp_auth: break else: LOG.debug('Could not find base authentication backend for ' 'K2K plugin with the provided credentials.') return None idp_exception = None scoped_idp_auth = None unscoped_auth_ref = base.BasePlugin.get_access_info( self, unscoped_idp_auth) try: scoped_idp_auth, __ = self.get_project_scoped_auth( unscoped_idp_auth, unscoped_auth_ref) except exceptions.KeystoneAuthException as idp_excp: idp_exception = idp_excp if not scoped_idp_auth or idp_exception: msg = _('Identity provider authentication failed.') raise exceptions.KeystoneAuthException(msg) session = utils.get_session() if scoped_idp_auth.get_sp_auth_url(session, service_provider) is None: msg = _('Could not find service provider ID on keystone.') raise exceptions.KeystoneAuthException(msg) unscoped_auth = v3_auth.Keystone2Keystone( base_plugin=scoped_idp_auth, service_provider=service_provider) return unscoped_auth def get_access_info(self, unscoped_auth): """Get the access info object We attempt to get the auth ref. If it fails and if the K2K auth plugin was being used then we will prepend a message saying that the error was on the service provider side. :param: unscoped_auth: Keystone auth plugin for unscoped user :returns: keystoneclient.access.AccessInfo object """ try: unscoped_auth_ref = base.BasePlugin.get_access_info( self, unscoped_auth) except exceptions.KeystoneAuthException as excp: msg = _('Service provider authentication failed. %s') raise exceptions.KeystoneAuthException(msg % str(excp)) return unscoped_auth_ref horizon-13.0.3/openstack_auth/policy.py0000664000175000017500000002126013553660755020211 0ustar zuulzuul00000000000000# # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Policy engine for openstack_auth""" import logging import os.path from django.conf import settings from oslo_config import cfg from oslo_policy import opts as policy_opts from oslo_policy import policy from openstack_auth import user as auth_user from openstack_auth import utils as auth_utils LOG = logging.getLogger(__name__) _ENFORCER = None _BASE_PATH = getattr(settings, 'POLICY_FILES_PATH', '') def _get_policy_conf(policy_file, policy_dirs=None): conf = cfg.ConfigOpts() # Passing [] is required. Otherwise oslo.config looks up sys.argv. conf([]) policy_opts.set_defaults(conf) policy_file = os.path.join(_BASE_PATH, policy_file) conf.set_default('policy_file', policy_file, 'oslo_policy') # Policy Enforcer has been updated to take in a policy directory # as a config option. However, the default value in is set to # ['policy.d'] which causes the code to break. Set the default # value to empty list for now. if policy_dirs is None: policy_dirs = [] policy_dirs = [os.path.join(_BASE_PATH, policy_dir) for policy_dir in policy_dirs] conf.set_default('policy_dirs', policy_dirs, 'oslo_policy') return conf def _get_enforcer(): global _ENFORCER if not _ENFORCER: _ENFORCER = {} policy_files = getattr(settings, 'POLICY_FILES', {}) policy_dirs = getattr(settings, 'POLICY_DIRS', {}) for service in policy_files.keys(): conf = _get_policy_conf(policy_file=policy_files[service], policy_dirs=policy_dirs.get(service, [])) enforcer = policy.Enforcer(conf) # Ensure enforcer.policy_path is populated. enforcer.load_rules() if os.path.isfile(enforcer.policy_path): LOG.debug("adding enforcer for service: %s", service) _ENFORCER[service] = enforcer else: LOG.warning("policy file for service: %s not found at %s", (service, enforcer.policy_path)) return _ENFORCER def reset(): global _ENFORCER _ENFORCER = None def check(actions, request, target=None): """Check user permission. Check if the user has permission to the action according to policy setting. :param actions: list of scope and action to do policy checks on, the composition of which is (scope, action). Multiple actions are treated as a logical AND. * scope: service type managing the policy for action * action: string representing the action to be checked this should be colon separated for clarity. i.e. | compute:create_instance | compute:attach_volume | volume:attach_volume for a policy action that requires a single action, actions should look like | "(("compute", "compute:create_instance"),)" for a multiple action check, actions should look like | "(("identity", "identity:list_users"), | ("identity", "identity:list_roles"))" :param request: django http request object. If not specified, credentials must be passed. :param target: dictionary representing the object of the action for object creation this should be a dictionary representing the location of the object e.g. {'project_id': object.project_id} :returns: boolean if the user has permission or not for the actions. """ if target is None: target = {} user = auth_utils.get_user(request) # Several service policy engines default to a project id check for # ownership. Since the user is already scoped to a project, if a # different project id has not been specified use the currently scoped # project's id. # # The reason is the operator can edit the local copies of the service # policy file. If a rule is removed, then the default rule is used. We # don't want to block all actions because the operator did not fully # understand the implication of editing the policy file. Additionally, # the service APIs will correct us if we are too permissive. if target.get('project_id') is None: target['project_id'] = user.project_id if target.get('tenant_id') is None: target['tenant_id'] = target['project_id'] # same for user_id if target.get('user_id') is None: target['user_id'] = user.id domain_id_keys = [ 'domain_id', 'project.domain_id', 'user.domain_id', 'group.domain_id' ] # populates domain id keys with user's current domain id for key in domain_id_keys: if target.get(key) is None: target[key] = user.user_domain_id credentials = _user_to_credentials(user) domain_credentials = _domain_to_credentials(request, user) # if there is a domain token use the domain_id instead of the user's domain if domain_credentials: credentials['domain_id'] = domain_credentials.get('domain_id') enforcer = _get_enforcer() for action in actions: scope, action = action[0], action[1] if scope in enforcer: # this is for handling the v3 policy file and will only be # needed when a domain scoped token is present if scope == 'identity' and domain_credentials: # use domain credentials if not _check_credentials(enforcer[scope], action, target, domain_credentials): return False # use project credentials if not _check_credentials(enforcer[scope], action, target, credentials): return False # if no policy for scope, allow action, underlying API will # ultimately block the action if not permitted, treat as though # allowed return True def _check_credentials(enforcer_scope, action, target, credentials): is_valid = True if not enforcer_scope.enforce(action, target, credentials): # to match service implementations, if a rule is not found, # use the default rule for that service policy # # waiting to make the check because the first call to # enforce loads the rules if action not in enforcer_scope.rules: if not enforcer_scope.enforce('default', target, credentials): if 'default' in enforcer_scope.rules: is_valid = False else: is_valid = False return is_valid def _user_to_credentials(user): if not hasattr(user, "_credentials"): roles = [role['name'] for role in user.roles] user._credentials = {'user_id': user.id, 'token': user.token, 'username': user.username, 'project_id': user.project_id, 'tenant_id': user.project_id, 'project_name': user.project_name, 'domain_id': user.user_domain_id, 'is_admin': user.is_superuser, 'roles': roles} return user._credentials def _domain_to_credentials(request, user): if not hasattr(user, "_domain_credentials"): try: domain_auth_ref = request.session.get('domain_token') # no domain role or not running on V3 if not domain_auth_ref: return None domain_user = auth_user.create_user_from_token( request, auth_user.Token(domain_auth_ref), domain_auth_ref.service_catalog.url_for(interface=None)) user._domain_credentials = _user_to_credentials(domain_user) # uses the domain_id associated with the domain_user user._domain_credentials['domain_id'] = domain_user.domain_id except Exception: LOG.warning("Failed to create user from domain scoped token.") return None return user._domain_credentials horizon-13.0.3/openstack_auth/models.py0000664000175000017500000000122213553660754020170 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. # import the User model in here so Django can find it from openstack_auth.user import User __all__ = ['User'] horizon-13.0.3/openstack_auth/views.py0000664000175000017500000003256013553660755020054 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import logging from django.conf import settings from django.contrib import auth from django.contrib.auth.decorators import login_required from django.contrib.auth import views as django_auth_views from django.contrib import messages from django import http as django_http from django import shortcuts from django.utils import functional from django.utils import http from django.utils.translation import ugettext_lazy as _ from django.views.decorators.cache import never_cache from django.views.decorators.csrf import csrf_exempt from django.views.decorators.csrf import csrf_protect from django.views.decorators.debug import sensitive_post_parameters from keystoneauth1 import exceptions as keystone_exceptions import six from openstack_auth import exceptions from openstack_auth import forms from openstack_auth import plugin # This is historic and is added back in to not break older versions of # Horizon, fix to Horizon to remove this requirement was committed in # Juno from openstack_auth.forms import Login # noqa:F401 from openstack_auth import user as auth_user from openstack_auth import utils try: is_safe_url = http.is_safe_url except AttributeError: is_safe_url = utils.is_safe_url LOG = logging.getLogger(__name__) @sensitive_post_parameters() @csrf_protect @never_cache def login(request, template_name=None, extra_context=None, **kwargs): """Logs a user in using the :class:`~openstack_auth.forms.Login` form.""" # If the user enabled websso and selects default protocol # from the dropdown, We need to redirect user to the websso url if request.method == 'POST': auth_type = request.POST.get('auth_type', 'credentials') if utils.is_websso_enabled() and auth_type != 'credentials': auth_url = request.POST.get('region') url = utils.get_websso_url(request, auth_url, auth_type) return shortcuts.redirect(url) if not request.is_ajax(): # If the user is already authenticated, redirect them to the # dashboard straight away, unless the 'next' parameter is set as it # usually indicates requesting access to a page that requires different # permissions. if (request.user.is_authenticated() and auth.REDIRECT_FIELD_NAME not in request.GET and auth.REDIRECT_FIELD_NAME not in request.POST): return shortcuts.redirect(settings.LOGIN_REDIRECT_URL) # Get our initial region for the form. initial = {} current_region = request.session.get('region_endpoint', None) requested_region = request.GET.get('region', None) regions = dict(getattr(settings, "AVAILABLE_REGIONS", [])) if requested_region in regions and requested_region != current_region: initial.update({'region': requested_region}) if request.method == "POST": form = functional.curry(forms.Login) else: form = functional.curry(forms.Login, initial=initial) if extra_context is None: extra_context = {'redirect_field_name': auth.REDIRECT_FIELD_NAME} extra_context['csrf_failure'] = request.GET.get('csrf_failure') if not template_name: if request.is_ajax(): template_name = 'auth/_login.html' extra_context['hide'] = True else: template_name = 'auth/login.html' res = django_auth_views.login(request, template_name=template_name, authentication_form=form, extra_context=extra_context, **kwargs) # Save the region in the cookie, this is used as the default # selected region next time the Login form loads. if request.method == "POST": utils.set_response_cookie(res, 'login_region', request.POST.get('region', '')) utils.set_response_cookie(res, 'login_domain', request.POST.get('domain', '')) # Set the session data here because django's session key rotation # will erase it if we set it earlier. if request.user.is_authenticated(): auth_user.set_session_from_user(request, request.user) regions = dict(forms.Login.get_region_choices()) region = request.user.endpoint login_region = request.POST.get('region') region_name = regions.get(login_region) request.session['region_endpoint'] = region request.session['region_name'] = region_name expiration_time = request.user.time_until_expiration() threshold_days = getattr( settings, 'PASSWORD_EXPIRES_WARNING_THRESHOLD_DAYS', -1) if expiration_time is not None and \ expiration_time.days <= threshold_days: expiration_time = str(expiration_time).rsplit(':', 1)[0] msg = (_('Please consider changing your password, it will expire' ' in %s minutes') % expiration_time).replace(':', ' Hours and ') messages.warning(request, msg) return res @sensitive_post_parameters() @csrf_exempt @never_cache def websso(request): """Logs a user in using a token from Keystone's POST.""" referer = request.META.get('HTTP_REFERER', settings.OPENSTACK_KEYSTONE_URL) auth_url = utils.clean_up_auth_url(referer) token = request.POST.get('token') try: request.user = auth.authenticate(request=request, auth_url=auth_url, token=token) except exceptions.KeystoneAuthException as exc: msg = 'Login failed: %s' % six.text_type(exc) res = django_http.HttpResponseRedirect(settings.LOGIN_URL) res.set_cookie('logout_reason', msg, max_age=10) return res auth_user.set_session_from_user(request, request.user) auth.login(request, request.user) if request.session.test_cookie_worked(): request.session.delete_test_cookie() return django_http.HttpResponseRedirect(settings.LOGIN_REDIRECT_URL) def logout(request, login_url=None, **kwargs): """Logs out the user if he is logged in. Then redirects to the log-in page. :param login_url: Once logged out, defines the URL where to redirect after login :param kwargs: see django.contrib.auth.views.logout_then_login extra parameters. """ msg = 'Logging out user "%(username)s".' % \ {'username': request.user.username} LOG.info(msg) """ Securely logs a user out. """ return django_auth_views.logout_then_login(request, login_url=login_url, **kwargs) def delete_token(endpoint, token_id): """Delete a token.""" LOG.warning("The delete_token method is deprecated and now does nothing") @login_required def switch(request, tenant_id, redirect_field_name=auth.REDIRECT_FIELD_NAME): """Switches an authenticated user from one project to another.""" LOG.debug('Switching to tenant %s for user "%s".', (tenant_id, request.user.username)) endpoint, __ = utils.fix_auth_url_version_prefix(request.user.endpoint) session = utils.get_session() # Keystone can be configured to prevent exchanging a scoped token for # another token. Always use the unscoped token for requesting a # scoped token. unscoped_token = request.user.unscoped_token auth = utils.get_token_auth_plugin(auth_url=endpoint, token=unscoped_token, project_id=tenant_id) try: auth_ref = auth.get_access(session) msg = 'Project switch successful for user "%(username)s".' % \ {'username': request.user.username} LOG.info(msg) except keystone_exceptions.ClientException: msg = ( _('Project switch failed for user "%(username)s".') % {'username': request.user.username}) messages.error(request, msg) auth_ref = None LOG.exception('An error occurred while switching sessions.') # Ensure the user-originating redirection url is safe. # Taken from django.contrib.auth.views.login() redirect_to = request.GET.get(redirect_field_name, '') if not is_safe_url(url=redirect_to, host=request.get_host()): redirect_to = settings.LOGIN_REDIRECT_URL if auth_ref: user = auth_user.create_user_from_token( request, auth_user.Token(auth_ref, unscoped_token=unscoped_token), endpoint) auth_user.set_session_from_user(request, user) message = ( _('Switch to project "%(project_name)s" successful.') % {'project_name': request.user.project_name}) messages.success(request, message) response = shortcuts.redirect(redirect_to) utils.set_response_cookie(response, 'recent_project', request.user.project_id) return response @login_required def switch_region(request, region_name, redirect_field_name=auth.REDIRECT_FIELD_NAME): """Switches the user's region for all services except Identity service. The region will be switched if the given region is one of the regions available for the scoped project. Otherwise the region is not switched. """ if region_name in request.user.available_services_regions: request.session['services_region'] = region_name LOG.debug('Switching services region to %s for user "%s".', (region_name, request.user.username)) redirect_to = request.GET.get(redirect_field_name, '') if not is_safe_url(url=redirect_to, host=request.get_host()): redirect_to = settings.LOGIN_REDIRECT_URL response = shortcuts.redirect(redirect_to) utils.set_response_cookie(response, 'services_region', request.session['services_region']) return response @login_required def switch_keystone_provider(request, keystone_provider=None, redirect_field_name=auth.REDIRECT_FIELD_NAME): """Switches the user's keystone provider using K2K Federation If keystone_provider is given then we switch the user to the keystone provider using K2K federation. Otherwise if keystone_provider is None then we switch the user back to the Identity Provider Keystone which a non federated token auth will be used. """ base_token = request.session.get('k2k_base_unscoped_token', None) k2k_auth_url = request.session.get('k2k_auth_url', None) keystone_providers = request.session.get('keystone_providers', None) if not base_token or not k2k_auth_url: msg = _('K2K Federation not setup for this session') raise exceptions.KeystoneAuthException(msg) redirect_to = request.GET.get(redirect_field_name, '') if not is_safe_url(url=redirect_to, host=request.get_host()): redirect_to = settings.LOGIN_REDIRECT_URL unscoped_auth_ref = None keystone_idp_id = getattr( settings, 'KEYSTONE_PROVIDER_IDP_ID', 'localkeystone') if keystone_provider == keystone_idp_id: current_plugin = plugin.TokenPlugin() unscoped_auth = current_plugin.get_plugin(auth_url=k2k_auth_url, token=base_token) else: # Switch to service provider using K2K federation plugins = [plugin.TokenPlugin()] current_plugin = plugin.K2KAuthPlugin() unscoped_auth = current_plugin.get_plugin( auth_url=k2k_auth_url, service_provider=keystone_provider, plugins=plugins, token=base_token) try: # Switch to identity provider using token auth unscoped_auth_ref = current_plugin.get_access_info(unscoped_auth) except exceptions.KeystoneAuthException as exc: msg = 'Switching to Keystone Provider %s has failed. %s' \ % (keystone_provider, (six.text_type(exc))) messages.error(request, msg) if unscoped_auth_ref: try: request.user = auth.authenticate( request=request, auth_url=unscoped_auth.auth_url, token=unscoped_auth_ref.auth_token) except exceptions.KeystoneAuthException as exc: msg = 'Keystone provider switch failed: %s' % six.text_type(exc) res = django_http.HttpResponseRedirect(settings.LOGIN_URL) res.set_cookie('logout_reason', msg, max_age=10) return res auth.login(request, request.user) auth_user.set_session_from_user(request, request.user) request.session['keystone_provider_id'] = keystone_provider request.session['keystone_providers'] = keystone_providers request.session['k2k_base_unscoped_token'] = base_token request.session['k2k_auth_url'] = k2k_auth_url message = ( _('Switch to Keystone Provider "%(keystone_provider)s"' 'successful.') % {'keystone_provider': keystone_provider}) messages.success(request, message) response = shortcuts.redirect(redirect_to) return response horizon-13.0.3/openstack_auth/__init__.py0000664000175000017500000000000013553660754020435 0ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/user.py0000664000175000017500000004070513553660755017675 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import hashlib import logging import django from django.conf import settings from django.contrib.auth import models from django.db import models as db_models from django.utils import deprecation from keystoneauth1 import exceptions as keystone_exceptions from keystoneclient.common import cms as keystone_cms import six from openstack_auth import utils LOG = logging.getLogger(__name__) _TOKEN_HASH_ENABLED = getattr(settings, 'OPENSTACK_TOKEN_HASH_ENABLED', True) def set_session_from_user(request, user): request.session['token'] = user.token request.session['user_id'] = user.id request.session['region_endpoint'] = user.endpoint request.session['services_region'] = user.services_region # Update the user object cached in the request request._cached_user = user request.user = user def create_user_from_token(request, token, endpoint, services_region=None): # if the region is provided, use that, otherwise use the preferred region default_service_regions = getattr(settings, 'DEFAULT_SERVICE_REGIONS', {}) default_service_region = default_service_regions.get(endpoint) svc_region = services_region or \ utils.default_services_region(token.serviceCatalog, request, selected_region=default_service_region) return User(id=token.user['id'], token=token, user=token.user['name'], password_expires_at=token.user['password_expires_at'], user_domain_id=token.user_domain_id, # We need to consider already logged-in users with an old # version of Token without user_domain_name. user_domain_name=getattr(token, 'user_domain_name', None), project_id=token.project['id'], project_name=token.project['name'], domain_id=token.domain['id'], domain_name=token.domain['name'], enabled=True, service_catalog=token.serviceCatalog, roles=token.roles, endpoint=endpoint, services_region=svc_region, is_federated=getattr(token, 'is_federated', False), unscoped_token=getattr(token, 'unscoped_token', request.session.get('unscoped_token'))) class Token(object): """Encapsulates the AccessInfo object from keystoneclient. Token object provides a consistent interface for accessing the keystone token information and service catalog. Added for maintaining backward compatibility with horizon that expects Token object in the user object. """ def __init__(self, auth_ref, unscoped_token=None): # User-related attributes user = {'id': auth_ref.user_id, 'name': auth_ref.username} data = getattr(auth_ref, '_data', {}) expiration_date = data.get('token', {}).get('user', {})\ .get('password_expires_at') user['password_expires_at'] = expiration_date self.user = user self.user_domain_id = auth_ref.user_domain_id self.user_domain_name = auth_ref.user_domain_name # Token-related attributes self.id = auth_ref.auth_token self.unscoped_token = unscoped_token if _TOKEN_HASH_ENABLED and self._is_pki_token(self.id): algorithm = getattr(settings, 'OPENSTACK_TOKEN_HASH_ALGORITHM', 'md5') hasher = hashlib.new(algorithm) hasher.update(self.id.encode('utf-8')) self.id = hasher.hexdigest() # Only hash unscoped token if needed if self._is_pki_token(self.unscoped_token): hasher = hashlib.new(algorithm) hasher.update(self.unscoped_token.encode('utf-8')) self.unscoped_token = hasher.hexdigest() self.expires = auth_ref.expires # Project-related attributes project = {} project['id'] = auth_ref.project_id project['name'] = auth_ref.project_name project['is_admin_project'] = getattr(auth_ref, 'is_admin_project', False) project['domain_id'] = getattr(auth_ref, 'project_domain_id', None) self.project = project self.tenant = self.project # Domain-related attributes domain = {} domain['id'] = auth_ref.domain_id domain['name'] = auth_ref.domain_name self.domain = domain # Federation-related attributes self.is_federated = auth_ref.is_federated self.roles = [{'name': role} for role in auth_ref.role_names] self.serviceCatalog = auth_ref.service_catalog.catalog def _is_pki_token(self, token): """Determines if this is a pki-based token (pki or pkiz)""" if token is None: return False return (keystone_cms.is_ans1_token(token) or keystone_cms.is_pkiz(token)) class User(models.AbstractBaseUser, models.AnonymousUser): """A User class with some extra special sauce for Keystone. In addition to the standard Django user attributes, this class also has the following: .. attribute:: token The Keystone token object associated with the current user/tenant. The token object is deprecated, user auth_ref instead. .. attribute:: tenant_id The id of the Keystone tenant for the current user/token. The tenant_id keyword argument is deprecated, use project_id instead. .. attribute:: tenant_name The name of the Keystone tenant for the current user/token. The tenant_name keyword argument is deprecated, use project_name instead. .. attribute:: project_id The id of the Keystone project for the current user/token. .. attribute:: project_name The name of the Keystone project for the current user/token. .. attribute:: service_catalog The ``ServiceCatalog`` data returned by Keystone. .. attribute:: roles A list of dictionaries containing role names and ids as returned by Keystone. .. attribute:: services_region A list of non-identity service endpoint regions extracted from the service catalog. .. attribute:: user_domain_id The domain id of the current user. .. attribute:: user_domain_name The domain name of the current user. .. attribute:: domain_id The id of the Keystone domain scoped for the current user/token. .. attribute:: is_federated Whether user is federated Keystone user. (Boolean) .. attribute:: unscoped_token Unscoped Keystone token. .. attribute:: password_expires_at Password expiration date. This attribute could be None when using keystone version < 3.0 or if the feature is not enabled in keystone. """ keystone_user_id = db_models.CharField(primary_key=True, max_length=255) USERNAME_FIELD = 'keystone_user_id' def __init__(self, id=None, token=None, user=None, tenant_id=None, service_catalog=None, tenant_name=None, roles=None, authorized_tenants=None, endpoint=None, enabled=False, services_region=None, user_domain_id=None, user_domain_name=None, domain_id=None, domain_name=None, project_id=None, project_name=None, is_federated=False, unscoped_token=None, password=None, password_expires_at=None): self.id = id self.pk = id self.token = token self.keystone_user_id = id self.username = user self.user_domain_id = user_domain_id self.user_domain_name = user_domain_name self.domain_id = domain_id self.domain_name = domain_name self.project_id = project_id or tenant_id self.project_name = project_name or tenant_name self.service_catalog = service_catalog self._services_region = ( services_region or utils.default_services_region(service_catalog) ) self.roles = roles or [] self.endpoint = endpoint self.enabled = enabled self._authorized_tenants = authorized_tenants self.is_federated = is_federated self.password_expires_at = password_expires_at # Unscoped token is used for listing user's project that works # for both federated and keystone user. self.unscoped_token = unscoped_token # List of variables to be deprecated. self.tenant_id = self.project_id self.tenant_name = self.project_name # Required by AbstractBaseUser self.password = None def __unicode__(self): return self.username def __repr__(self): return "<%s: %s>" % (self.__class__.__name__, self.username) def is_token_expired(self, margin=None): """Determine if the token is expired. :returns: ``True`` if the token is expired, ``False`` if not, and ``None`` if there is no token set. :param margin: A security time margin in seconds before real expiration. Will return ``True`` if the token expires in less than ``margin`` seconds of time. A default margin can be set by the TOKEN_TIMEOUT_MARGIN in the django settings. """ if self.token is None: return None return not utils.is_token_valid(self.token, margin) if django.VERSION >= (1, 10): @property def is_authenticated(self): """Checks for a valid authentication.""" if (self.token is not None and utils.is_token_valid(self.token)): return deprecation.CallableTrue else: return deprecation.CallableFalse @property def is_anonymous(self): """Return if the user is not authenticated. :returns: ``True`` if not authenticated,``False`` otherwise. """ return deprecation.CallableBool(not self.is_authenticated) else: def is_authenticated(self, margin=None): """Checks for a valid authentication. :param margin: A security time margin in seconds before end of authentication. Will return ``False`` if authentication ends in less than ``margin`` seconds of time. A default margin can be set by the TOKEN_TIMEOUT_MARGIN in the django settings. """ return (self.token is not None and utils.is_token_valid(self.token, margin)) def is_anonymous(self, margin=None): """Return if the user is not authenticated. :returns: ``True`` if not authenticated,``False`` otherwise. :param margin: A security time margin in seconds before end of an eventual authentication. Will return ``True`` even if authenticated but that authentication ends in less than ``margin`` seconds of time. A default margin can be set by the TOKEN_TIMEOUT_MARGIN in the django settings. """ return not self.is_authenticated(margin) @property def is_active(self): return self.enabled @property def is_superuser(self): """Evaluates whether this user has admin privileges. :returns: ``True`` or ``False``. """ admin_roles = utils.get_admin_roles() user_roles = {role['name'].lower() for role in self.roles} return not admin_roles.isdisjoint(user_roles) @property def authorized_tenants(self): """Returns a memoized list of tenants this user may access.""" if self.is_authenticated() and self._authorized_tenants is None: endpoint = self.endpoint try: self._authorized_tenants = utils.get_project_list( user_id=self.id, auth_url=endpoint, token=self.unscoped_token, is_federated=self.is_federated) except (keystone_exceptions.ClientException, keystone_exceptions.AuthorizationFailure): LOG.exception('Unable to retrieve project list.') return self._authorized_tenants or [] @authorized_tenants.setter def authorized_tenants(self, tenant_list): self._authorized_tenants = tenant_list @property def services_region(self): return self._services_region @services_region.setter def services_region(self, region): self._services_region = region @property def available_services_regions(self): """Returns list of unique region name values in service catalog.""" regions = [] if self.service_catalog: for service in self.service_catalog: service_type = service.get('type') if service_type is None or service_type == 'identity': continue for endpoint in service.get('endpoints', []): region = utils.get_endpoint_region(endpoint) if region not in regions: regions.append(region) return regions def save(*args, **kwargs): # Presume we can't write to Keystone. pass def delete(*args, **kwargs): # Presume we can't write to Keystone. pass # Check for OR'd permission rules, check that user has one of the # required permission. def has_a_matching_perm(self, perm_list, obj=None): """Returns True if the user has one of the specified permissions. If object is passed, it checks if the user has any of the required perms for this object. """ # If there are no permissions to check, just return true if not perm_list: return True # Check that user has at least one of the required permissions. for perm in perm_list: if self.has_perm(perm, obj): return True return False # Override the default has_perms method. Allowing for more # complex combinations of permissions. Will check for logical AND of # all top level permissions. Will use logical OR for all first level # tuples (check that use has one permissions in the tuple) # # Examples: # Checks for all required permissions # ('openstack.roles.admin', 'openstack.roles.L3-support') # # Checks for admin AND (L2 or L3) # ('openstack.roles.admin', ('openstack.roles.L3-support', # 'openstack.roles.L2-support'),) def has_perms(self, perm_list, obj=None): """Returns True if the user has all of the specified permissions. Tuples in the list will possess the required permissions if the user has a permissions matching one of the elements of that tuple """ # If there are no permissions to check, just return true if not perm_list: return True for perm in perm_list: if isinstance(perm, six.string_types): # check that the permission matches if not self.has_perm(perm, obj): return False else: # check that a permission in the tuple matches if not self.has_a_matching_perm(perm, obj): return False return True def time_until_expiration(self): """Returns the number of remaining days until user's password expires. Calculates the number days until the user must change their password, once the password expires the user will not able to log in until an admin changes its password. """ if self.password_expires_at is not None: expiration_date = datetime.datetime.strptime( self.password_expires_at, "%Y-%m-%dT%H:%M:%S.%f") return expiration_date - datetime.datetime.now() class Meta(object): app_label = 'openstack_auth' horizon-13.0.3/openstack_auth/tests/0000775000175000017500000000000013553661042017467 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/urls.py0000664000175000017500000000155713553660754021047 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from django.conf.urls import include from django.conf.urls import url from django.views import generic from openstack_auth import views urlpatterns = [ url(r"", include('openstack_auth.urls')), url(r"^websso/$", views.websso, name='websso'), url(r"^$", generic.TemplateView.as_view(template_name="auth/blank.html")) ] horizon-13.0.3/openstack_auth/tests/unit/0000775000175000017500000000000013553661042020446 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/unit/test_policy.py0000664000175000017500000001671013553660755023375 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from django import http from django import test import mock from openstack_auth import policy from openstack_auth import user from openstack_auth import utils class PolicyLoaderTestCase(test.TestCase): def test_policy_file_load(self): policy.reset() enforcer = policy._get_enforcer() self.assertEqual(2, len(enforcer)) self.assertIn('identity', enforcer) self.assertIn('compute', enforcer) def test_policy_reset(self): policy._get_enforcer() self.assertEqual(2, len(policy._ENFORCER)) policy.reset() self.assertIsNone(policy._ENFORCER) class PolicyTestCase(test.TestCase): _roles = [] def setUp(self): mock_user = user.User(id=1, roles=self._roles) patcher = mock.patch('openstack_auth.utils.get_user', return_value=mock_user) self.MockClass = patcher.start() self.addCleanup(patcher.stop) self.request = http.HttpRequest() class PolicyTestCaseNonAdmin(PolicyTestCase): _roles = [{'id': '1', 'name': 'member'}] def test_check_admin_required_false(self): policy.reset() value = policy.check((("identity", "admin_required"),), request=self.request) self.assertFalse(value) def test_check_identity_rule_not_found_false(self): policy.reset() value = policy.check((("identity", "i_dont_exist"),), request=self.request) # this should fail because the default check for # identity is admin_required self.assertFalse(value) def test_check_nova_context_is_admin_false(self): policy.reset() value = policy.check((("compute", "context_is_admin"),), request=self.request) self.assertFalse(value) def test_compound_check_false(self): policy.reset() value = policy.check((("identity", "admin_required"), ("identity", "identity:default"),), request=self.request) self.assertFalse(value) def test_scope_not_found(self): policy.reset() value = policy.check((("dummy", "default"),), request=self.request) self.assertTrue(value) class PolicyTestCheckCredentials(PolicyTestCase): _roles = [{'id': '1', 'name': 'member'}] def setUp(self): policy_files = { 'no_default': 'no_default_policy.json', 'with_default': 'with_default_policy.json', } override = self.settings(POLICY_FILES=policy_files) override.enable() self.addCleanup(override.disable) mock_user = user.User(id=1, roles=self._roles, user_domain_id='admin_domain_id') patcher = mock.patch('openstack_auth.utils.get_user', return_value=mock_user) self.MockClass = patcher.start() self.addCleanup(patcher.stop) self.request = http.HttpRequest() def test_check_credentials(self): policy.reset() enforcer = policy._get_enforcer() scope = enforcer['no_default'] user = utils.get_user() credentials = policy._user_to_credentials(user) target = { 'project_id': user.project_id, 'tenant_id': user.project_id, 'user_id': user.id, 'domain_id': user.user_domain_id, 'user.domain_id': user.user_domain_id, 'group.domain_id': user.user_domain_id, 'project.domain_id': user.user_domain_id, } is_valid = policy._check_credentials(scope, 'action', target, credentials) self.assertTrue(is_valid) def test_check_credentials_default(self): policy.reset() enforcer = policy._get_enforcer() scope = enforcer['with_default'] user = utils.get_user() credentials = policy._user_to_credentials(user) target = { 'project_id': user.project_id, 'tenant_id': user.project_id, 'user_id': user.id, 'domain_id': user.user_domain_id, 'user.domain_id': user.user_domain_id, 'group.domain_id': user.user_domain_id, 'project.domain_id': user.user_domain_id, } is_valid = policy._check_credentials(scope, 'action', target, credentials) self.assertFalse(is_valid) class PolicyTestCaseAdmin(PolicyTestCase): _roles = [{'id': '1', 'name': 'admin'}] def test_check_admin_required_true(self): policy.reset() value = policy.check((("identity", "admin_required"),), request=self.request) self.assertTrue(value) def test_check_identity_rule_not_found_true(self): policy.reset() value = policy.check((("identity", "i_dont_exist"),), request=self.request) # this should succeed because the default check for # identity is admin_required self.assertTrue(value) def test_compound_check_true(self): policy.reset() value = policy.check((("identity", "admin_required"), ("identity", "identity:default"),), request=self.request) self.assertTrue(value) def test_check_nova_context_is_admin_true(self): policy.reset() value = policy.check((("compute", "context_is_admin"),), request=self.request) self.assertTrue(value) class PolicyTestCaseV3Admin(PolicyTestCase): _roles = [{'id': '1', 'name': 'admin'}] def setUp(self): policy_files = { 'identity': 'policy.v3cloudsample.json', 'compute': 'nova_policy.json'} override = self.settings(POLICY_FILES=policy_files) override.enable() self.addCleanup(override.disable) mock_user = user.User(id=1, roles=self._roles, user_domain_id='admin_domain_id') patcher = mock.patch('openstack_auth.utils.get_user', return_value=mock_user) self.MockClass = patcher.start() self.addCleanup(patcher.stop) self.request = http.HttpRequest() def test_check_cloud_admin_required_true(self): policy.reset() value = policy.check((("identity", "cloud_admin"),), request=self.request) self.assertTrue(value) def test_check_domain_admin_required_true(self): policy.reset() value = policy.check(( ("identity", "admin_and_matching_domain_id"),), request=self.request) self.assertTrue(value) def test_check_any_admin_required_true(self): policy.reset() value = policy.check((("identity", "admin_or_cloud_admin"),), request=self.request) self.assertTrue(value) horizon-13.0.3/openstack_auth/tests/unit/test_auth.py0000664000175000017500000013707113553660755023043 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import uuid import django from django.conf import settings from django.contrib import auth from django.core.urlresolvers import reverse from django import test from keystoneauth1 import exceptions as keystone_exceptions from keystoneauth1.identity import v2 as v2_auth from keystoneauth1.identity import v3 as v3_auth from keystoneauth1 import session from keystoneauth1 import token_endpoint from keystoneclient.v2_0 import client as client_v2 from keystoneclient.v3 import client as client_v3 from mox3 import mox from testscenarios import load_tests_apply_scenarios from openstack_auth.tests import data_v2 from openstack_auth.tests import data_v3 from openstack_auth import utils DEFAULT_DOMAIN = settings.OPENSTACK_KEYSTONE_DEFAULT_DOMAIN class OpenStackAuthTestsMixin(object): '''Common functions for version specific tests.''' scenarios = [ ('pure', {'interface': None}), ('public', {'interface': 'publicURL'}), ('internal', {'interface': 'internalURL'}), ('admin', {'interface': 'adminURL'}) ] def _mock_unscoped_client(self, user): plugin = self._create_password_auth() plugin.get_access(mox.IsA(session.Session)). \ AndReturn(self.data.unscoped_access_info) plugin.auth_url = settings.OPENSTACK_KEYSTONE_URL return self.ks_client_module.Client(session=mox.IsA(session.Session), auth=plugin) def _mock_unscoped_client_with_token(self, user, unscoped): plugin = token_endpoint.Token(settings.OPENSTACK_KEYSTONE_URL, unscoped.auth_token) return self.ks_client_module.Client(session=mox.IsA(session.Session), auth=plugin) def _mock_client_token_auth_failure(self, unscoped, tenant_id): plugin = self._create_token_auth(tenant_id, unscoped.auth_token) plugin.get_access(mox.IsA(session.Session)). \ AndRaise(keystone_exceptions.AuthorizationFailure) def _mock_client_password_auth_failure(self, username, password, exc): plugin = self._create_password_auth(username=username, password=password) plugin.get_access(mox.IsA(session.Session)).AndRaise(exc) def _mock_scoped_client_for_tenant(self, auth_ref, tenant_id, url=None, client=True, token=None): if url is None: url = settings.OPENSTACK_KEYSTONE_URL if not token: token = self.data.unscoped_access_info.auth_token plugin = self._create_token_auth( tenant_id, token=token, url=url) self.scoped_token_auth = plugin plugin.get_access(mox.IsA(session.Session)).AndReturn(auth_ref) if client: return self.ks_client_module.Client( session=mox.IsA(session.Session), auth=plugin) def get_form_data(self, user): return {'region': settings.OPENSTACK_KEYSTONE_URL, 'domain': DEFAULT_DOMAIN, 'password': user.password, 'username': user.name} class OpenStackAuthFederatedTestsMixin(object): """Common functions for federation""" def _mock_unscoped_federated_list_projects(self, client, projects): client.federation = self.mox.CreateMockAnything() client.federation.projects = self.mox.CreateMockAnything() client.federation.projects.list().AndReturn(projects) def _mock_unscoped_list_domains(self, client, domains): client.auth = self.mox.CreateMockAnything() client.auth.domains().AndReturn(domains) def _mock_unscoped_token_client(self, unscoped, auth_url=None, client=True, plugin=None): if not auth_url: auth_url = settings.OPENSTACK_KEYSTONE_URL if unscoped and not plugin: plugin = self._create_token_auth( None, token=unscoped.auth_token, url=auth_url) plugin.get_access(mox.IsA(session.Session)).AndReturn(unscoped) plugin.auth_url = auth_url if client: return self.ks_client_module.Client( session=mox.IsA(session.Session), auth=plugin) def _mock_plugin(self, unscoped, auth_url=None): if not auth_url: auth_url = settings.OPENSTACK_KEYSTONE_URL plugin = self._create_token_auth( None, token=unscoped.auth_token, url=auth_url) plugin.get_access(mox.IsA(session.Session)).AndReturn(unscoped) plugin.auth_url = settings.OPENSTACK_KEYSTONE_URL return plugin def _mock_federated_client_list_projects(self, unscoped_auth, projects): client = self._mock_unscoped_token_client(None, plugin=unscoped_auth) self._mock_unscoped_federated_list_projects(client, projects) def _mock_federated_client_list_domains(self, unscoped_auth, domains): client = self._mock_unscoped_token_client(None, plugin=unscoped_auth) self._mock_unscoped_list_domains(client, domains) class OpenStackAuthTestsV2(OpenStackAuthTestsMixin, test.TestCase): def setUp(self): super(OpenStackAuthTestsV2, self).setUp() if getattr(self, 'interface', None): override = self.settings(OPENSTACK_ENDPOINT_TYPE=self.interface) override.enable() self.addCleanup(override.disable) self.mox = mox.Mox() self.addCleanup(self.mox.VerifyAll) self.addCleanup(self.mox.UnsetStubs) self.data = data_v2.generate_test_data() self.ks_client_module = client_v2 settings.OPENSTACK_API_VERSIONS['identity'] = 2.0 settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0" self.mox.StubOutClassWithMocks(token_endpoint, 'Token') self.mox.StubOutClassWithMocks(v2_auth, 'Token') self.mox.StubOutClassWithMocks(v2_auth, 'Password') self.mox.StubOutClassWithMocks(client_v2, 'Client') def _mock_unscoped_list_tenants(self, client, tenants): client.tenants = self.mox.CreateMockAnything() client.tenants.list().AndReturn(tenants) def _mock_unscoped_client_list_tenants(self, user, tenants): client = self._mock_unscoped_client(user) self._mock_unscoped_list_tenants(client, tenants) def _create_password_auth(self, username=None, password=None, url=None): if not username: username = self.data.user.name if not password: password = self.data.user.password if not url: url = settings.OPENSTACK_KEYSTONE_URL return v2_auth.Password(auth_url=url, password=password, username=username) def _create_token_auth(self, project_id, token=None, url=None): if not token: token = self.data.unscoped_access_info.auth_token if not url: url = settings.OPENSTACK_KEYSTONE_URL return v2_auth.Token(auth_url=url, token=token, tenant_id=project_id, reauthenticate=False) def _login(self): tenants = [self.data.tenant_one, self.data.tenant_two] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_login(self): self._login() def test_login_with_disabled_tenant(self): # Test to validate that authentication will not try to get # scoped token for disabled project. tenants = [self.data.tenant_two, self.data.tenant_one] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_login_w_bad_region_cookie(self): self.client.cookies['services_region'] = "bad_region" self._login() self.assertNotEqual("bad_region", self.client.session['services_region']) self.assertEqual("RegionOne", self.client.session['services_region']) def test_no_enabled_tenants(self): tenants = [self.data.tenant_two] user = self.data.user form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, 'You are not authorized for any projects.') def test_no_tenants(self): user = self.data.user form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, []) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, 'You are not authorized for any projects.') def test_invalid_credentials(self): user = self.data.user form_data = self.get_form_data(user) form_data['password'] = "invalid" exc = keystone_exceptions.Unauthorized(401) self._mock_client_password_auth_failure(user.name, "invalid", exc) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, "Invalid credentials.") def test_exception(self): user = self.data.user form_data = self.get_form_data(user) exc = keystone_exceptions.ClientException(500) self._mock_client_password_auth_failure(user.name, user.password, exc) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, ("An error occurred authenticating. Please try " "again later.")) def test_redirect_when_already_logged_in(self): self._login() response = self.client.get(reverse('login')) self.assertEqual(response.status_code, 302) self.assertNotIn(reverse('login'), response['location']) def test_dont_redirect_when_already_logged_in_if_next_is_set(self): self._login() expected_url = "%s?%s=/%s/" % (reverse('login'), auth.REDIRECT_FIELD_NAME, 'special') response = self.client.get(expected_url) self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, 'auth/login.html') def test_switch(self, next=None): tenant = self.data.tenant_two tenants = [self.data.tenant_one, self.data.tenant_two] user = self.data.user unscoped = self.data.unscoped_access_info scoped = self.data.scoped_access_info sc = self.data.service_catalog et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') endpoint = sc.url_for(service_type='identity', interface=et) form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) self._mock_scoped_client_for_tenant(unscoped, self.data.tenant_one.id) self._mock_scoped_client_for_tenant(scoped, tenant.id, url=endpoint, client=False) self.mox.ReplayAll() url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) url = reverse('switch_tenants', args=[tenant.id]) scoped._token['tenant']['id'] = self.data.tenant_two.id if next: form_data.update({auth.REDIRECT_FIELD_NAME: next}) response = self.client.get(url, form_data) if next: if django.VERSION >= (1, 9): expected_url = next else: expected_url = 'http://testserver%s' % next self.assertEqual(response['location'], expected_url) else: self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) self.assertEqual(self.client.session['token'].tenant['id'], scoped.tenant_id) def test_switch_with_next(self): self.test_switch(next='/next_url') def test_switch_region(self, next=None): tenants = [self.data.tenant_one, self.data.tenant_two] user = self.data.user scoped = self.data.scoped_access_info sc = self.data.service_catalog form_data = self.get_form_data(user) self._mock_unscoped_client_list_tenants(user, tenants) self._mock_scoped_client_for_tenant(scoped, self.data.tenant_one.id) self.mox.ReplayAll() url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) old_region = sc.get_endpoints()['compute'][0]['region'] self.assertEqual(self.client.session['services_region'], old_region) region = sc.get_endpoints()['compute'][1]['region'] url = reverse('switch_services_region', args=[region]) form_data['region_name'] = region if next: form_data.update({auth.REDIRECT_FIELD_NAME: next}) response = self.client.get(url, form_data) if next: if django.VERSION >= (1, 9): expected_url = next else: expected_url = 'http://testserver%s' % next self.assertEqual(response['location'], expected_url) else: self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) self.assertEqual(self.client.session['services_region'], region) self.assertEqual(self.client.cookies['services_region'].value, region) def test_switch_region_with_next(self, next=None): self.test_switch_region(next='/next_url') def test_tenant_sorting(self): tenants = [self.data.tenant_two, self.data.tenant_one] expected_tenants = [self.data.tenant_one, self.data.tenant_two] user = self.data.user unscoped = self.data.unscoped_access_info client = self._mock_unscoped_client_with_token(user, unscoped) self._mock_unscoped_list_tenants(client, tenants) self.mox.ReplayAll() tenant_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, token=unscoped.auth_token) self.assertEqual(tenant_list, expected_tenants) class OpenStackAuthTestsV3(OpenStackAuthTestsMixin, OpenStackAuthFederatedTestsMixin, test.TestCase): def _mock_unscoped_client_list_projects(self, user, projects): client = self._mock_unscoped_client(user) self._mock_unscoped_list_projects(client, user, projects) def _mock_unscoped_list_projects(self, client, user, projects): client.projects = self.mox.CreateMockAnything() client.projects.list(user=user.id).AndReturn(projects) def _mock_unscoped_client_list_projects_fail(self, user): client = self._mock_unscoped_client(user) self._mock_unscoped_list_projects_fail(client, user) def _mock_unscoped_list_projects_fail(self, client, user): plugin = self._create_token_auth( project_id=None, domain_name=DEFAULT_DOMAIN, token=self.data.unscoped_access_info.auth_token, url=settings.OPENSTACK_KEYSTONE_URL) plugin.get_access(mox.IsA(session.Session)).AndReturn( self.data.domain_scoped_access_info) client.projects = self.mox.CreateMockAnything() client.projects.list(user=user.id).AndRaise( keystone_exceptions.AuthorizationFailure) def _mock_unscoped_and_domain_list_projects(self, user, projects): client = self._mock_unscoped_client(user) self._mock_scoped_for_domain(projects) self._mock_unscoped_list_projects(client, user, projects) def _mock_scoped_for_domain(self, projects): url = settings.OPENSTACK_KEYSTONE_URL plugin = self._create_token_auth( project_id=None, domain_name=DEFAULT_DOMAIN, token=self.data.unscoped_access_info.auth_token, url=url) plugin.get_access(mox.IsA(session.Session)).AndReturn( self.data.domain_scoped_access_info) # if no projects or no enabled projects for user, but domain scoped # token client auth gets set to domain scoped auth otherwise it's set # to the project scoped auth and that happens in a different mock enabled_projects = [project for project in projects if project.enabled] if not projects or not enabled_projects: return self.ks_client_module.Client( session=mox.IsA(session.Session), auth=plugin) def _create_password_auth(self, username=None, password=None, url=None): if not username: username = self.data.user.name if not password: password = self.data.user.password if not url: url = settings.OPENSTACK_KEYSTONE_URL return v3_auth.Password(auth_url=url, password=password, username=username, user_domain_name=DEFAULT_DOMAIN, unscoped=True) def _create_token_auth(self, project_id, token=None, url=None, domain_name=None): if not token: token = self.data.unscoped_access_info.auth_token if not url: url = settings.OPENSTACK_KEYSTONE_URL if domain_name: return v3_auth.Token(auth_url=url, token=token, domain_name=domain_name, reauthenticate=False) else: return v3_auth.Token(auth_url=url, token=token, project_id=project_id, reauthenticate=False) def setUp(self): super(OpenStackAuthTestsV3, self).setUp() if getattr(self, 'interface', None): override = self.settings(OPENSTACK_ENDPOINT_TYPE=self.interface) override.enable() self.addCleanup(override.disable) self.mox = mox.Mox() self.addCleanup(self.mox.VerifyAll) self.addCleanup(self.mox.UnsetStubs) self.data = data_v3.generate_test_data() self.ks_client_module = client_v3 settings.OPENSTACK_API_VERSIONS['identity'] = 3 settings.OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v3" self.mox.StubOutClassWithMocks(token_endpoint, 'Token') self.mox.StubOutClassWithMocks(v3_auth, 'Token') self.mox.StubOutClassWithMocks(v3_auth, 'Password') self.mox.StubOutClassWithMocks(client_v3, 'Client') self.mox.StubOutClassWithMocks(v3_auth, 'Keystone2Keystone') def test_login(self): projects = [self.data.project_one, self.data.project_two] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_login_with_disabled_project(self): # Test to validate that authentication will not try to get # scoped token for disabled project. projects = [self.data.project_two, self.data.project_one] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_no_enabled_projects(self): projects = [self.data.project_two] user = self.data.user form_data = self.get_form_data(user) self._mock_unscoped_and_domain_list_projects(user, projects) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_no_projects(self): user = self.data.user form_data = self.get_form_data(user) self._mock_unscoped_and_domain_list_projects(user, []) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_fail_projects(self): user = self.data.user form_data = self.get_form_data(user) self._mock_unscoped_client_list_projects_fail(user) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, 'Unable to retrieve authorized projects.') def test_invalid_credentials(self): user = self.data.user form_data = self.get_form_data(user) form_data['password'] = "invalid" exc = keystone_exceptions.Unauthorized(401) self._mock_client_password_auth_failure(user.name, "invalid", exc) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, "Invalid credentials.") def test_exception(self): user = self.data.user form_data = self.get_form_data(user) exc = keystone_exceptions.ClientException(500) self._mock_client_password_auth_failure(user.name, user.password, exc) self.mox.ReplayAll() url = reverse('login') # GET the page to set the test cookie. response = self.client.get(url, form_data) self.assertEqual(response.status_code, 200) # POST to the page to log in. response = self.client.post(url, form_data) self.assertTemplateUsed(response, 'auth/login.html') self.assertContains(response, ("An error occurred authenticating. Please try " "again later.")) def test_switch(self, next=None): project = self.data.project_two projects = [self.data.project_one, self.data.project_two] user = self.data.user scoped = self.data.scoped_access_info sc = self.data.service_catalog et = getattr(settings, 'OPENSTACK_ENDPOINT_TYPE', 'publicURL') form_data = self.get_form_data(user) self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(scoped, self.data.project_one.id) self._mock_scoped_client_for_tenant( scoped, project.id, url=sc.url_for(service_type='identity', interface=et), client=False) self.mox.ReplayAll() url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) url = reverse('switch_tenants', args=[project.id]) scoped._project['id'] = self.data.project_two.id if next: form_data.update({auth.REDIRECT_FIELD_NAME: next}) response = self.client.get(url, form_data) if next: if django.VERSION >= (1, 9): expected_url = next else: expected_url = 'http://testserver%s' % next self.assertEqual(response['location'], expected_url) else: self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) self.assertEqual(self.client.session['token'].project['id'], scoped.project_id) def test_switch_with_next(self): self.test_switch(next='/next_url') def test_switch_region(self, next=None): projects = [self.data.project_one, self.data.project_two] user = self.data.user scoped = self.data.unscoped_access_info sc = self.data.service_catalog form_data = self.get_form_data(user) self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(scoped, self.data.project_one.id) self.mox.ReplayAll() url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) old_region = sc.get_endpoints()['compute'][0]['region'] self.assertEqual(self.client.session['services_region'], old_region) region = sc.get_endpoints()['compute'][1]['region'] url = reverse('switch_services_region', args=[region]) form_data['region_name'] = region if next: form_data.update({auth.REDIRECT_FIELD_NAME: next}) response = self.client.get(url, form_data) if next: if django.VERSION >= (1, 9): expected_url = next else: expected_url = 'http://testserver%s' % next self.assertEqual(response['location'], expected_url) else: self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) self.assertEqual(self.client.session['services_region'], region) def test_switch_region_with_next(self, next=None): self.test_switch_region(next='/next_url') def test_switch_keystone_provider_remote_fail(self): auth_url = settings.OPENSTACK_KEYSTONE_URL target_provider = 'k2kserviceprovider' self.data = data_v3.generate_test_data(service_providers=True) self.sp_data = data_v3.generate_test_data(endpoint='http://sp2') projects = [self.data.project_one, self.data.project_two] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) # mock authenticate self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) # mock switch plugin = v3_auth.Token(auth_url=auth_url, token=unscoped.auth_token, project_id=None, reauthenticate=False) plugin.get_access(mox.IsA(session.Session) ).AndReturn(self.data.unscoped_access_info) plugin.auth_url = auth_url client = self.ks_client_module.Client(session=mox.IsA(session.Session), auth=plugin) self._mock_unscoped_list_projects(client, user, projects) plugin = self._create_token_auth( self.data.project_one.id, token=self.data.unscoped_access_info.auth_token, url=settings.OPENSTACK_KEYSTONE_URL) plugin.get_access(mox.IsA(session.Session)).AndReturn( settings.OPENSTACK_KEYSTONE_URL) plugin.get_sp_auth_url( mox.IsA(session.Session), target_provider ).AndReturn('https://k2kserviceprovider/sp_url') # let the K2K plugin fail when logging in plugin = v3_auth.Keystone2Keystone( base_plugin=plugin, service_provider=target_provider) plugin.get_access(mox.IsA(session.Session)).AndRaise( keystone_exceptions.AuthorizationFailure) self.mox.ReplayAll() # Log in url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Switch url = reverse('switch_keystone_provider', args=[target_provider]) form_data['keystone_provider'] = target_provider response = self.client.get(url, form_data, follow=True) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Assert that provider has not changed because of failure self.assertEqual(self.client.session['keystone_provider_id'], 'localkeystone') # These should never change self.assertEqual(self.client.session['k2k_base_unscoped_token'], unscoped.auth_token) self.assertEqual(self.client.session['k2k_auth_url'], auth_url) def test_switch_keystone_provider_remote(self): auth_url = settings.OPENSTACK_KEYSTONE_URL target_provider = 'k2kserviceprovider' self.data = data_v3.generate_test_data(service_providers=True) self.sp_data = data_v3.generate_test_data(endpoint='http://sp2') projects = [self.data.project_one, self.data.project_two] domains = [] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) # mock authenticate self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) # mock switch plugin = v3_auth.Token(auth_url=auth_url, token=unscoped.auth_token, project_id=None, reauthenticate=False) plugin.get_access(mox.IsA(session.Session)).AndReturn( self.data.unscoped_access_info) plugin.auth_url = auth_url client = self.ks_client_module.Client(session=mox.IsA(session.Session), auth=plugin) self._mock_unscoped_list_projects(client, user, projects) plugin = self._create_token_auth( self.data.project_one.id, token=self.data.unscoped_access_info.auth_token, url=settings.OPENSTACK_KEYSTONE_URL) plugin.get_access(mox.IsA(session.Session)).AndReturn( settings.OPENSTACK_KEYSTONE_URL) plugin.get_sp_auth_url( mox.IsA(session.Session), target_provider ).AndReturn('https://k2kserviceprovider/sp_url') plugin = v3_auth.Keystone2Keystone(base_plugin=plugin, service_provider=target_provider) plugin.get_access(mox.IsA(session.Session)). \ AndReturn(self.sp_data.unscoped_access_info) plugin.auth_url = 'http://service_provider_endp:5000/v3' # mock authenticate for service provider sp_projects = [self.sp_data.project_one, self.sp_data.project_two] sp_unscoped = self.sp_data.federated_unscoped_access_info sp_unscoped_auth = self._mock_plugin(sp_unscoped, auth_url=plugin.auth_url) client = self._mock_unscoped_token_client(None, plugin.auth_url, plugin=sp_unscoped_auth) self._mock_unscoped_list_domains(client, domains) client = self._mock_unscoped_token_client(None, plugin.auth_url, plugin=sp_unscoped_auth) self._mock_unscoped_federated_list_projects(client, sp_projects) self._mock_scoped_client_for_tenant(sp_unscoped, self.sp_data.project_one.id, url=plugin.auth_url, token=sp_unscoped.auth_token) self.mox.ReplayAll() # Log in url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Switch url = reverse('switch_keystone_provider', args=[target_provider]) form_data['keystone_provider'] = target_provider response = self.client.get(url, form_data, follow=True) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Assert keystone provider has changed self.assertEqual(self.client.session['keystone_provider_id'], target_provider) # These should not change self.assertEqual(self.client.session['k2k_base_unscoped_token'], unscoped.auth_token) self.assertEqual(self.client.session['k2k_auth_url'], auth_url) def test_switch_keystone_provider_local(self): auth_url = settings.OPENSTACK_KEYSTONE_URL self.data = data_v3.generate_test_data(service_providers=True) keystone_provider = 'localkeystone' projects = [self.data.project_one, self.data.project_two] domains = [] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) # mock authenticate self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) self._mock_unscoped_token_client(unscoped, auth_url=auth_url, client=False) unscoped_auth = self._mock_plugin(unscoped) client = self._mock_unscoped_token_client(None, auth_url=auth_url, plugin=unscoped_auth) self._mock_unscoped_list_domains(client, domains) client = self._mock_unscoped_token_client(None, auth_url=auth_url, plugin=unscoped_auth) self._mock_unscoped_list_projects(client, user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) self.mox.ReplayAll() # Log in url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Switch url = reverse('switch_keystone_provider', args=[keystone_provider]) form_data['keystone_provider'] = keystone_provider response = self.client.get(url, form_data, follow=True) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Assert nothing has changed since we are going from local to local self.assertEqual(self.client.session['keystone_provider_id'], keystone_provider) self.assertEqual(self.client.session['k2k_base_unscoped_token'], unscoped.auth_token) self.assertEqual(self.client.session['k2k_auth_url'], auth_url) def test_switch_keystone_provider_local_fail(self): auth_url = settings.OPENSTACK_KEYSTONE_URL self.data = data_v3.generate_test_data(service_providers=True) keystone_provider = 'localkeystone' projects = [self.data.project_one, self.data.project_two] user = self.data.user unscoped = self.data.unscoped_access_info form_data = self.get_form_data(user) # mock authenticate self._mock_unscoped_and_domain_list_projects(user, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) # Let using the base token for logging in fail plugin = v3_auth.Token(auth_url=auth_url, token=unscoped.auth_token, project_id=None, reauthenticate=False) plugin.get_access(mox.IsA(session.Session)). \ AndRaise(keystone_exceptions.AuthorizationFailure) plugin.auth_url = auth_url self.mox.ReplayAll() # Log in url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Switch url = reverse('switch_keystone_provider', args=[keystone_provider]) form_data['keystone_provider'] = keystone_provider response = self.client.get(url, form_data, follow=True) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) # Assert self.assertEqual(self.client.session['keystone_provider_id'], keystone_provider) self.assertEqual(self.client.session['k2k_base_unscoped_token'], unscoped.auth_token) self.assertEqual(self.client.session['k2k_auth_url'], auth_url) def test_tenant_sorting(self): projects = [self.data.project_two, self.data.project_one] expected_projects = [self.data.project_one, self.data.project_two] user = self.data.user unscoped = self.data.unscoped_access_info client = self._mock_unscoped_client_with_token(user, unscoped) self._mock_unscoped_list_projects(client, user, projects) self.mox.ReplayAll() project_list = utils.get_project_list( user_id=user.id, auth_url=settings.OPENSTACK_KEYSTONE_URL, token=unscoped.auth_token) self.assertEqual(project_list, expected_projects) def test_login_form_multidomain(self): override = self.settings(OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT=True) override.enable() self.addCleanup(override.disable) url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'id="id_domain"') self.assertContains(response, 'name="domain"') def test_login_form_multidomain_dropdown(self): override = self.settings(OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT=True, OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN=True, OPENSTACK_KEYSTONE_DOMAIN_CHOICES=( ('Default', 'Default'),) ) override.enable() self.addCleanup(override.disable) url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'id="id_domain"') self.assertContains(response, 'name="domain"') self.assertContains(response, 'option value="Default"') settings.OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN = False class OpenStackAuthTestsWebSSO(OpenStackAuthTestsMixin, OpenStackAuthFederatedTestsMixin, test.TestCase): def _create_token_auth(self, project_id=None, token=None, url=None): if not token: token = self.data.federated_unscoped_access_info.auth_token if not url: url = settings.OPENSTACK_KEYSTONE_URL return v3_auth.Token(auth_url=url, token=token, project_id=project_id, reauthenticate=False) def setUp(self): super(OpenStackAuthTestsWebSSO, self).setUp() self.mox = mox.Mox() self.addCleanup(self.mox.VerifyAll) self.addCleanup(self.mox.UnsetStubs) self.data = data_v3.generate_test_data() self.ks_client_module = client_v3 self.idp_id = uuid.uuid4().hex self.idp_oidc_id = uuid.uuid4().hex self.idp_saml2_id = uuid.uuid4().hex settings.OPENSTACK_API_VERSIONS['identity'] = 3 settings.OPENSTACK_KEYSTONE_URL = 'http://localhost:5000/v3' settings.WEBSSO_ENABLED = True settings.WEBSSO_CHOICES = ( ('credentials', 'Keystone Credentials'), ('oidc', 'OpenID Connect'), ('saml2', 'Security Assertion Markup Language'), (self.idp_oidc_id, 'IDP OIDC'), (self.idp_saml2_id, 'IDP SAML2') ) settings.WEBSSO_IDP_MAPPING = { self.idp_oidc_id: (self.idp_id, 'oidc'), self.idp_saml2_id: (self.idp_id, 'saml2') } self.mox.StubOutClassWithMocks(token_endpoint, 'Token') self.mox.StubOutClassWithMocks(v3_auth, 'Token') self.mox.StubOutClassWithMocks(v3_auth, 'Password') self.mox.StubOutClassWithMocks(client_v3, 'Client') def test_login_form(self): url = reverse('login') response = self.client.get(url) self.assertEqual(response.status_code, 200) self.assertContains(response, 'credentials') self.assertContains(response, 'oidc') self.assertContains(response, 'saml2') self.assertContains(response, self.idp_oidc_id) self.assertContains(response, self.idp_saml2_id) def test_websso_redirect_by_protocol(self): origin = 'http://testserver/auth/websso/' protocol = 'oidc' redirect_url = ('%s/auth/OS-FEDERATION/websso/%s?origin=%s' % (settings.OPENSTACK_KEYSTONE_URL, protocol, origin)) form_data = {'auth_type': protocol, 'region': settings.OPENSTACK_KEYSTONE_URL} url = reverse('login') # POST to the page and redirect to keystone. response = self.client.post(url, form_data) self.assertRedirects(response, redirect_url, status_code=302, target_status_code=404) def test_websso_redirect_by_idp(self): origin = 'http://testserver/auth/websso/' protocol = 'oidc' redirect_url = ('%s/auth/OS-FEDERATION/identity_providers/%s' '/protocols/%s/websso?origin=%s' % (settings.OPENSTACK_KEYSTONE_URL, self.idp_id, protocol, origin)) form_data = {'auth_type': self.idp_oidc_id, 'region': settings.OPENSTACK_KEYSTONE_URL} url = reverse('login') # POST to the page and redirect to keystone. response = self.client.post(url, form_data) self.assertRedirects(response, redirect_url, status_code=302, target_status_code=404) def test_websso_login(self): projects = [self.data.project_one, self.data.project_two] domains = [] unscoped = self.data.federated_unscoped_access_info token = unscoped.auth_token unscoped_auth = self._mock_plugin(unscoped) form_data = {'token': token} self._mock_federated_client_list_domains(unscoped_auth, domains) self._mock_federated_client_list_projects(unscoped_auth, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) self.mox.ReplayAll() url = reverse('websso') # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) def test_websso_login_with_auth_in_url(self): settings.OPENSTACK_KEYSTONE_URL = 'http://auth.openstack.org:5000/v3' projects = [self.data.project_one, self.data.project_two] domains = [] unscoped = self.data.federated_unscoped_access_info token = unscoped.auth_token unscoped_auth = self._mock_plugin(unscoped) form_data = {'token': token} self._mock_federated_client_list_domains(unscoped_auth, domains) self._mock_federated_client_list_projects(unscoped_auth, projects) self._mock_scoped_client_for_tenant(unscoped, self.data.project_one.id) self.mox.ReplayAll() url = reverse('websso') # POST to the page to log in. response = self.client.post(url, form_data) self.assertRedirects(response, settings.LOGIN_REDIRECT_URL) load_tests = load_tests_apply_scenarios horizon-13.0.3/openstack_auth/tests/unit/test_utils.py0000664000175000017500000001172013553660755023232 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from django.conf import settings from django import http from django import test from django.test.utils import override_settings from openstack_auth import utils class RoleTestCaseAdmin(test.TestCase): def test_get_admin_roles_with_default_value(self): admin_roles = utils.get_admin_roles() self.assertSetEqual({'admin'}, admin_roles) @override_settings(OPENSTACK_KEYSTONE_ADMIN_ROLES=['foO', 'BAR', 'admin']) def test_get_admin_roles(self): admin_roles = utils.get_admin_roles() self.assertSetEqual({'foo', 'bar', 'admin'}, admin_roles) @override_settings(OPENSTACK_KEYSTONE_ADMIN_ROLES=['foO', 'BAR', 'admin']) def test_get_admin_permissions(self): admin_permissions = utils.get_admin_permissions() self.assertSetEqual({'openstack.roles.foo', 'openstack.roles.bar', 'openstack.roles.admin'}, admin_permissions) class UtilsTestCase(test.TestCase): def test_fix_auth_url_version_v20(self): settings.OPENSTACK_API_VERSIONS['identity'] = 2.0 test_urls = [ ("http://a/", ("http://a/v2.0", False)), ("http://a", ("http://a/v2.0", False)), ("http://a:8080/", ("http://a:8080/v2.0", False)), ("http://a/v2.0", ("http://a/v2.0", False)), ("http://a/v2.0/", ("http://a/v2.0/", False)), ("http://a/identity", ("http://a/identity/v2.0", False)), ("http://a/identity/", ("http://a/identity/v2.0", False)), ("http://a:5000/identity/v2.0", ("http://a:5000/identity/v2.0", False)), ("http://a/identity/v2.0/", ("http://a/identity/v2.0/", False)) ] for src, expected in test_urls: self.assertEqual(expected, utils.fix_auth_url_version_prefix(src)) def test_fix_auth_url_version_v3(self): settings.OPENSTACK_API_VERSIONS['identity'] = 3 test_urls = [ ("http://a/", ("http://a/v3", False)), ("http://a", ("http://a/v3", False)), ("http://a:8080/", ("http://a:8080/v3", False)), ("http://a/v3", ("http://a/v3", False)), ("http://a/v3/", ("http://a/v3/", False)), ("http://a/v2.0/", ("http://a/v3/", True)), ("http://a/v2.0", ("http://a/v3", True)), ("http://a/identity", ("http://a/identity/v3", False)), ("http://a:5000/identity/", ("http://a:5000/identity/v3", False)), ("http://a/identity/v3", ("http://a/identity/v3", False)), ("http://a/identity/v3/", ("http://a/identity/v3/", False)) ] for src, expected in test_urls: self.assertEqual(expected, utils.fix_auth_url_version_prefix(src)) class BehindProxyTestCase(test.TestCase): def setUp(self): super(BehindProxyTestCase, self).setUp() self.request = http.HttpRequest() def test_without_proxy(self): self.request.META['REMOTE_ADDR'] = '10.111.111.2' from openstack_auth.utils import get_client_ip self.assertEqual('10.111.111.2', get_client_ip(self.request)) def test_with_proxy_no_settings(self): from openstack_auth.utils import get_client_ip self.request.META['REMOTE_ADDR'] = '10.111.111.2' self.request.META['HTTP_X_REAL_IP'] = '192.168.15.33' self.request.META['HTTP_X_FORWARDED_FOR'] = '172.18.0.2' self.assertEqual('10.111.111.2', get_client_ip(self.request)) def test_with_settings_without_proxy(self): from openstack_auth.utils import get_client_ip self.request.META['REMOTE_ADDR'] = '10.111.111.2' self.assertEqual('10.111.111.2', get_client_ip(self.request)) @override_settings(SECURE_PROXY_ADDR_HEADER='HTTP_X_FORWARDED_FOR') def test_with_settings_with_proxy_forwardfor(self): from openstack_auth.utils import get_client_ip self.request.META['REMOTE_ADDR'] = '10.111.111.2' self.request.META['HTTP_X_FORWARDED_FOR'] = '172.18.0.2' self.assertEqual('172.18.0.2', get_client_ip(self.request)) @override_settings(SECURE_PROXY_ADDR_HEADER='HTTP_X_REAL_IP') def test_with_settings_with_proxy_real_ip(self): from openstack_auth.utils import get_client_ip self.request.META['REMOTE_ADDR'] = '10.111.111.2' self.request.META['HTTP_X_REAL_IP'] = '192.168.15.33' self.request.META['HTTP_X_FORWARDED_FOR'] = '172.18.0.2' self.assertEqual('192.168.15.33', get_client_ip(self.request)) horizon-13.0.3/openstack_auth/tests/unit/test_user.py0000664000175000017500000000350413553660755023051 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. from django import test import mock from openstack_auth.tests import data_v3 from openstack_auth import user class PermTestCase(test.TestCase): def test_has_perms(self): testuser = user.User(id=1, roles=[]) def has_perm(perm, obj=None): return perm in ('perm1', 'perm3') with mock.patch.object(testuser, 'has_perm', side_effect=has_perm): self.assertFalse(testuser.has_perms(['perm2'])) # perm1 AND perm3 self.assertFalse(testuser.has_perms(['perm1', 'perm2'])) # perm1 AND perm3 self.assertTrue(testuser.has_perms(['perm1', 'perm3'])) # perm1 AND (perm2 OR perm3) perm_list = ['perm1', ('perm2', 'perm3')] self.assertTrue(testuser.has_perms(perm_list)) class UserTestCase(test.TestCase): def setUp(self): super(UserTestCase, self).setUp() self.data = data_v3.generate_test_data(pki=True) def test_unscoped_token_is_none(self): created_token = user.Token(self.data.domain_scoped_access_info, unscoped_token=None) self.assertTrue(created_token._is_pki_token( self.data.domain_scoped_access_info.auth_token)) self.assertFalse(created_token._is_pki_token(None)) horizon-13.0.3/openstack_auth/tests/unit/__init__.py0000664000175000017500000000000013553660754022556 0ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/templates/0000775000175000017500000000000013553661042021465 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/templates/auth/0000775000175000017500000000000013553661042022426 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/templates/auth/login.html0000664000175000017500000000031513553660754024434 0ustar zuulzuul00000000000000 Login
{{ csrf_token }} {{ form.as_p }}
horizon-13.0.3/openstack_auth/tests/templates/auth/blank.html0000664000175000017500000000000013553660754024402 0ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/data_v3.py0000664000175000017500000002675013553660755021406 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import uuid from django.utils import datetime_safe from keystoneauth1.access import access from keystoneauth1.access import service_catalog from keystoneclient.common import cms from keystoneclient.v3 import domains from keystoneclient.v3 import projects from keystoneclient.v3 import roles from keystoneclient.v3 import users import requests class TestDataContainer(object): """Arbitrary holder for test data in an object-oriented fashion.""" pass class TestResponse(requests.Response): """Class used to wrap requests.Response. It also provides some convenience to initialize with a dict. """ def __init__(self, data): self._text = None super(TestResponse, self).__init__() if isinstance(data, dict): self.status_code = data.get('status_code', 200) self.headers = data.get('headers', None) # Fake the text attribute to streamline Response creation self._text = data.get('text', None) else: self.status_code = data def __eq__(self, other): return self.__dict__ == other.__dict__ @property def text(self): return self._text def generate_test_data(pki=False, service_providers=False, endpoint='localhost'): '''Builds a set of test_data data as returned by Keystone V2.''' test_data = TestDataContainer() keystone_service = { 'type': 'identity', 'id': uuid.uuid4().hex, 'endpoints': [ { 'url': 'http://admin.%s:35357/v3' % endpoint, 'region': 'RegionOne', 'interface': 'admin', 'id': uuid.uuid4().hex, }, { 'url': 'http://internal.%s:5000/v3' % endpoint, 'region': 'RegionOne', 'interface': 'internal', 'id': uuid.uuid4().hex }, { 'url': 'http://public.%s:5000/v3' % endpoint, 'region': 'RegionOne', 'interface': 'public', 'id': uuid.uuid4().hex } ] } # Domains domain_dict = {'id': uuid.uuid4().hex, 'name': 'domain', 'description': '', 'enabled': True} test_data.domain = domains.Domain(domains.DomainManager(None), domain_dict, loaded=True) # Users user_dict = {'id': uuid.uuid4().hex, 'name': 'gabriel', 'email': 'gabriel@example.com', 'password': 'swordfish', 'domain_id': domain_dict['id'], 'token': '', 'enabled': True} test_data.user = users.User(users.UserManager(None), user_dict, loaded=True) # Projects project_dict_1 = {'id': uuid.uuid4().hex, 'name': 'tenant_one', 'description': '', 'domain_id': domain_dict['id'], 'enabled': True} project_dict_2 = {'id': uuid.uuid4().hex, 'name': 'tenant_two', 'description': '', 'domain_id': domain_dict['id'], 'enabled': False} test_data.project_one = projects.Project(projects.ProjectManager(None), project_dict_1, loaded=True) test_data.project_two = projects.Project(projects.ProjectManager(None), project_dict_2, loaded=True) # Roles role_dict = {'id': uuid.uuid4().hex, 'name': 'Member'} test_data.role = roles.Role(roles.RoleManager, role_dict) nova_service = { 'type': 'compute', 'id': uuid.uuid4().hex, 'endpoints': [ { 'url': ('http://nova-admin.%s:8774/v2.0/%s' % (endpoint, project_dict_1['id'])), 'region': 'RegionOne', 'interface': 'admin', 'id': uuid.uuid4().hex, }, { 'url': ('http://nova-internal.%s:8774/v2.0/%s' % (endpoint, project_dict_1['id'])), 'region': 'RegionOne', 'interface': 'internal', 'id': uuid.uuid4().hex }, { 'url': ('http://nova-public.%s:8774/v2.0/%s' % (endpoint, project_dict_1['id'])), 'region': 'RegionOne', 'interface': 'public', 'id': uuid.uuid4().hex }, { 'url': ('http://nova2-admin.%s:8774/v2.0/%s' % (endpoint, project_dict_1['id'])), 'region': 'RegionTwo', 'interface': 'admin', 'id': uuid.uuid4().hex, }, { 'url': ('http://nova2-internal.%s:8774/v2.0/%s' % (endpoint, project_dict_1['id'])), 'region': 'RegionTwo', 'interface': 'internal', 'id': uuid.uuid4().hex }, { 'url': ('http://nova2-public.%s:8774/v2.0/%s' % (endpoint, project_dict_1['id'])), 'region': 'RegionTwo', 'interface': 'public', 'id': uuid.uuid4().hex } ] } # Tokens tomorrow = datetime_safe.datetime.now() + datetime.timedelta(days=1) expiration = datetime_safe.datetime.isoformat(tomorrow) if pki: # We don't need a real PKI token, but just the prefix to make the # keystone client treat it as a PKI token auth_token = cms.PKI_ASN1_PREFIX + uuid.uuid4().hex else: auth_token = uuid.uuid4().hex auth_response_headers = { 'X-Subject-Token': auth_token } auth_response = TestResponse({ "headers": auth_response_headers }) scoped_token_dict = { 'token': { 'methods': ['password'], 'expires_at': expiration, 'project': { 'id': project_dict_1['id'], 'name': project_dict_1['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] } }, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] } }, 'roles': [role_dict], 'catalog': [keystone_service, nova_service] } } sp_list = None if service_providers: test_data.sp_auth_url = 'http://service_provider_endp:5000/v3' test_data.service_provider_id = 'k2kserviceprovider' # The access info for the identity provider # should return a list of service providers sp_list = [ {'auth_url': test_data.sp_auth_url, 'id': test_data.service_provider_id, 'sp_url': 'https://k2kserviceprovider/sp_url'} ] scoped_token_dict['token']['service_providers'] = sp_list test_data.scoped_access_info = access.create( resp=auth_response, body=scoped_token_dict ) domain_token_dict = { 'token': { 'methods': ['password'], 'expires_at': expiration, 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'], }, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] } }, 'roles': [role_dict], 'catalog': [keystone_service, nova_service] } } test_data.domain_scoped_access_info = access.create( resp=auth_response, body=domain_token_dict ) unscoped_token_dict = { 'token': { 'methods': ['password'], 'expires_at': expiration, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] } }, 'catalog': [keystone_service] } } if service_providers: unscoped_token_dict['token']['service_providers'] = sp_list test_data.unscoped_access_info = access.create( resp=auth_response, body=unscoped_token_dict ) # Service Catalog test_data.service_catalog = service_catalog.ServiceCatalogV3( [keystone_service, nova_service]) # federated user federated_scoped_token_dict = { 'token': { 'methods': ['password'], 'expires_at': expiration, 'project': { 'id': project_dict_1['id'], 'name': project_dict_1['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] } }, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] }, 'OS-FEDERATION': { 'identity_provider': 'ACME', 'protocol': 'OIDC', 'groups': [ {'id': uuid.uuid4().hex}, {'id': uuid.uuid4().hex} ] } }, 'roles': [role_dict], 'catalog': [keystone_service, nova_service] } } test_data.federated_scoped_access_info = access.create( resp=auth_response, body=federated_scoped_token_dict ) federated_unscoped_token_dict = { 'token': { 'methods': ['password'], 'expires_at': expiration, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'domain': { 'id': domain_dict['id'], 'name': domain_dict['name'] }, 'OS-FEDERATION': { 'identity_provider': 'ACME', 'protocol': 'OIDC', 'groups': [ {'id': uuid.uuid4().hex}, {'id': uuid.uuid4().hex} ] } }, 'catalog': [keystone_service] } } test_data.federated_unscoped_access_info = access.create( resp=auth_response, body=federated_unscoped_token_dict ) return test_data horizon-13.0.3/openstack_auth/tests/conf/0000775000175000017500000000000013553661042020414 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/conf/nova_policy.json0000664000175000017500000003206513553660754023650 0ustar zuulzuul00000000000000{ "context_is_admin": "role:admin", "admin_or_owner": "is_admin:True or project_id:%(project_id)s", "default": "rule:admin_or_owner", "cells_scheduler_filter:TargetCellFilter": "is_admin:True", "compute:create": "", "compute:create:attach_network": "", "compute:create:attach_volume": "", "compute:create:forced_host": "is_admin:True", "compute:delete": "rule:default", "compute:get_all": "", "compute:get_all_tenants": "", "compute:reboot": "rule:default", "compute:rebuild": "rule:default", "compute:snapshot": "rule:default", "compute:start": "rule:default", "compute:stop": "rule:default", "compute:unlock_override": "rule:admin_api", "compute:attach_volume" : "rule:default", "compute:detach_volume" : "rule:default", "compute:update": "rule:default", "compute:resize": "rule:default", "compute:confirm_resize": "rule:default", "compute:revert_resize": "rule:default", "compute:shelve": "", "compute:shelve_offload": "", "compute:unshelve": "", "admin_api": "is_admin:True", "compute_extension:accounts": "rule:admin_api", "compute_extension:admin_actions": "rule:admin_api", "compute_extension:admin_actions:pause": "rule:admin_or_owner", "compute_extension:admin_actions:unpause": "rule:admin_or_owner", "compute_extension:admin_actions:suspend": "rule:admin_or_owner", "compute_extension:admin_actions:resume": "rule:admin_or_owner", "compute_extension:admin_actions:lock": "rule:admin_or_owner", "compute_extension:admin_actions:unlock": "rule:admin_or_owner", "compute_extension:admin_actions:resetNetwork": "rule:admin_api", "compute_extension:admin_actions:injectNetworkInfo": "rule:admin_api", "compute_extension:admin_actions:createBackup": "rule:admin_or_owner", "compute_extension:admin_actions:migrateLive": "rule:admin_api", "compute_extension:admin_actions:resetState": "rule:admin_api", "compute_extension:admin_actions:migrate": "rule:admin_api", "compute_extension:v3:os-admin-actions": "rule:admin_api", "compute_extension:v3:os-admin-actions:pause": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:unpause": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:suspend": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:resume": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:lock": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:unlock": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:reset_network": "rule:admin_api", "compute_extension:v3:os-admin-actions:inject_network_info": "rule:admin_api", "compute_extension:v3:os-admin-actions:create_backup": "rule:admin_or_owner", "compute_extension:v3:os-admin-actions:migrate_live": "rule:admin_api", "compute_extension:v3:os-admin-actions:reset_state": "rule:admin_api", "compute_extension:v3:os-admin-actions:migrate": "rule:admin_api", "compute_extension:v3:os-admin-password": "", "compute_extension:aggregates": "rule:admin_api", "compute_extension:v3:os-aggregates": "rule:admin_api", "compute_extension:agents": "rule:admin_api", "compute_extension:v3:os-agents": "rule:admin_api", "compute_extension:attach_interfaces": "", "compute_extension:v3:os-attach-interfaces": "", "compute_extension:baremetal_nodes": "rule:admin_api", "compute_extension:v3:os-baremetal-nodes": "rule:admin_api", "compute_extension:cells": "rule:admin_api", "compute_extension:v3:os-cells": "rule:admin_api", "compute_extension:certificates": "", "compute_extension:v3:os-certificates": "", "compute_extension:cloudpipe": "rule:admin_api", "compute_extension:cloudpipe_update": "rule:admin_api", "compute_extension:console_output": "", "compute_extension:v3:consoles:discoverable": "", "compute_extension:v3:os-console-output": "", "compute_extension:consoles": "", "compute_extension:v3:os-remote-consoles": "", "compute_extension:coverage_ext": "rule:admin_api", "compute_extension:v3:os-coverage": "rule:admin_api", "compute_extension:createserverext": "", "compute_extension:deferred_delete": "", "compute_extension:v3:os-deferred-delete": "", "compute_extension:disk_config": "", "compute_extension:evacuate": "rule:admin_api", "compute_extension:v3:os-evacuate": "rule:admin_api", "compute_extension:extended_server_attributes": "rule:admin_api", "compute_extension:v3:os-extended-server-attributes": "rule:admin_api", "compute_extension:extended_status": "", "compute_extension:v3:os-extended-status": "", "compute_extension:extended_availability_zone": "", "compute_extension:v3:os-extended-availability-zone": "", "compute_extension:extended_ips": "", "compute_extension:extended_ips_mac": "", "compute_extension:extended_vif_net": "", "compute_extension:v3:extension_info:discoverable": "", "compute_extension:extended_volumes": "", "compute_extension:v3:os-extended-volumes": "", "compute_extension:v3:os-extended-volumes:attach": "", "compute_extension:v3:os-extended-volumes:detach": "", "compute_extension:fixed_ips": "rule:admin_api", "compute_extension:v3:os-fixed-ips:discoverable": "", "compute_extension:v3:os-fixed-ips": "rule:admin_api", "compute_extension:flavor_access": "", "compute_extension:v3:os-flavor-access": "", "compute_extension:flavor_disabled": "", "compute_extension:v3:os-flavor-disabled": "", "compute_extension:flavor_rxtx": "", "compute_extension:v3:os-flavor-rxtx": "", "compute_extension:flavor_swap": "", "compute_extension:flavorextradata": "", "compute_extension:flavorextraspecs:index": "", "compute_extension:flavorextraspecs:show": "", "compute_extension:flavorextraspecs:create": "rule:admin_api", "compute_extension:flavorextraspecs:update": "rule:admin_api", "compute_extension:flavorextraspecs:delete": "rule:admin_api", "compute_extension:v3:flavor-extra-specs:index": "", "compute_extension:v3:flavor-extra-specs:show": "", "compute_extension:v3:flavor-extra-specs:create": "rule:admin_api", "compute_extension:v3:flavor-extra-specs:update": "rule:admin_api", "compute_extension:v3:flavor-extra-specs:delete": "rule:admin_api", "compute_extension:flavormanage": "rule:admin_api", "compute_extension:floating_ip_dns": "", "compute_extension:floating_ip_pools": "", "compute_extension:floating_ips": "", "compute_extension:floating_ips_bulk": "rule:admin_api", "compute_extension:fping": "", "compute_extension:fping:all_tenants": "rule:admin_api", "compute_extension:hide_server_addresses": "is_admin:False", "compute_extension:v3:os-hide-server-addresses": "is_admin:False", "compute_extension:hosts": "rule:admin_api", "compute_extension:v3:os-hosts": "rule:admin_api", "compute_extension:hypervisors": "rule:admin_api", "compute_extension:v3:os-hypervisors": "rule:admin_api", "compute_extension:image_size": "", "compute_extension:v3:os-image-metadata": "", "compute_extension:v3:os-images": "", "compute_extension:instance_actions": "", "compute_extension:v3:os-instance-actions": "", "compute_extension:instance_actions:events": "rule:admin_api", "compute_extension:v3:os-instance-actions:events": "rule:admin_api", "compute_extension:instance_usage_audit_log": "rule:admin_api", "compute_extension:v3:os-instance-usage-audit-log": "rule:admin_api", "compute_extension:v3:ips:discoverable": "", "compute_extension:keypairs": "", "compute_extension:keypairs:index": "", "compute_extension:keypairs:show": "", "compute_extension:keypairs:create": "", "compute_extension:keypairs:delete": "", "compute_extension:v3:os-keypairs:discoverable": "", "compute_extension:v3:os-keypairs": "", "compute_extension:v3:os-keypairs:index": "", "compute_extension:v3:os-keypairs:show": "", "compute_extension:v3:os-keypairs:create": "", "compute_extension:v3:os-keypairs:delete": "", "compute_extension:multinic": "", "compute_extension:v3:os-multinic": "", "compute_extension:networks": "rule:admin_api", "compute_extension:networks:view": "", "compute_extension:networks_associate": "rule:admin_api", "compute_extension:quotas:show": "", "compute_extension:quotas:update": "rule:admin_api", "compute_extension:quotas:delete": "rule:admin_api", "compute_extension:v3:os-quota-sets:show": "", "compute_extension:v3:os-quota-sets:update": "rule:admin_api", "compute_extension:v3:os-quota-sets:delete": "rule:admin_api", "compute_extension:quota_classes": "", "compute_extension:v3:os-quota-class-sets": "", "compute_extension:rescue": "", "compute_extension:v3:os-rescue": "", "compute_extension:security_group_default_rules": "rule:admin_api", "compute_extension:security_groups": "", "compute_extension:v3:os-security-groups": "", "compute_extension:server_diagnostics": "rule:admin_api", "compute_extension:v3:os-server-diagnostics": "rule:admin_api", "compute_extension:server_password": "", "compute_extension:v3:os-server-password": "", "compute_extension:server_usage": "", "compute_extension:v3:os-server-usage": "", "compute_extension:services": "rule:admin_api", "compute_extension:v3:os-services": "rule:admin_api", "compute_extension:v3:servers:discoverable": "", "compute_extension:shelve": "", "compute_extension:shelveOffload": "rule:admin_api", "compute_extension:v3:os-shelve:shelve": "", "compute_extension:v3:os-shelve:shelve_offload": "rule:admin_api", "compute_extension:simple_tenant_usage:show": "rule:admin_or_owner", "compute_extension:v3:os-simple-tenant-usage:show": "rule:admin_or_owner", "compute_extension:simple_tenant_usage:list": "rule:admin_api", "compute_extension:v3:os-simple-tenant-usage:list": "rule:admin_api", "compute_extension:unshelve": "", "compute_extension:v3:os-shelve:unshelve": "", "compute_extension:users": "rule:admin_api", "compute_extension:virtual_interfaces": "", "compute_extension:virtual_storage_arrays": "", "compute_extension:volumes": "", "compute_extension:volume_attachments:index": "", "compute_extension:volume_attachments:show": "", "compute_extension:volume_attachments:create": "", "compute_extension:volume_attachments:update": "", "compute_extension:volume_attachments:delete": "", "compute_extension:volumetypes": "", "compute_extension:availability_zone:list": "", "compute_extension:v3:os-availability-zone:list": "", "compute_extension:availability_zone:detail": "rule:admin_api", "compute_extension:v3:os-availability-zone:detail": "rule:admin_api", "compute_extension:used_limits_for_admin": "rule:admin_api", "compute_extension:v3:os-used-limits": "", "compute_extension:v3:os-used-limits:tenant": "rule:admin_api", "compute_extension:migrations:index": "rule:admin_api", "compute_extension:v3:os-migrations:index": "rule:admin_api", "volume:create": "", "volume:get_all": "", "volume:get_volume_metadata": "", "volume:get_snapshot": "", "volume:get_all_snapshots": "", "volume_extension:types_manage": "rule:admin_api", "volume_extension:types_extra_specs": "rule:admin_api", "volume_extension:volume_admin_actions:reset_status": "rule:admin_api", "volume_extension:snapshot_admin_actions:reset_status": "rule:admin_api", "volume_extension:volume_admin_actions:force_delete": "rule:admin_api", "network:get_all": "", "network:get": "", "network:create": "", "network:delete": "", "network:associate": "", "network:disassociate": "", "network:get_vifs_by_instance": "", "network:allocate_for_instance": "", "network:deallocate_for_instance": "", "network:validate_networks": "", "network:get_instance_uuids_by_ip_filter": "", "network:get_instance_id_by_floating_address": "", "network:setup_networks_on_host": "", "network:get_backdoor_port": "", "network:get_floating_ip": "", "network:get_floating_ip_pools": "", "network:get_floating_ip_by_address": "", "network:get_floating_ips_by_project": "", "network:get_floating_ips_by_fixed_address": "", "network:allocate_floating_ip": "", "network:deallocate_floating_ip": "", "network:associate_floating_ip": "", "network:disassociate_floating_ip": "", "network:release_floating_ip": "", "network:migrate_instance_start": "", "network:migrate_instance_finish": "", "network:get_fixed_ip": "", "network:get_fixed_ip_by_address": "", "network:add_fixed_ip_to_instance": "", "network:remove_fixed_ip_from_instance": "", "network:add_network_to_project": "", "network:get_instance_nw_info": "", "network:get_dns_domains": "", "network:add_dns_entry": "", "network:modify_dns_entry": "", "network:delete_dns_entry": "", "network:get_dns_entries_by_address": "", "network:get_dns_entries_by_name": "", "network:create_private_dns_domain": "", "network:create_public_dns_domain": "", "network:delete_dns_domain": "" } horizon-13.0.3/openstack_auth/tests/conf/with_default_policy.json0000664000175000017500000000007713553660754025362 0ustar zuulzuul00000000000000{ "with_default:action": "", "default": "role:admin" } horizon-13.0.3/openstack_auth/tests/conf/no_default_policy.json0000664000175000017500000000004013553660754025011 0ustar zuulzuul00000000000000{ "no_default:action": "" } horizon-13.0.3/openstack_auth/tests/conf/keystone_policy.json0000664000175000017500000001443513553660754024547 0ustar zuulzuul00000000000000{ "admin_required": "role:admin or is_admin:1", "service_role": "role:service", "service_or_admin": "rule:admin_required or rule:service_role", "owner" : "user_id:%(user_id)s", "admin_or_owner": "rule:admin_required or rule:owner", "default": "rule:admin_required", "identity:get_region": "", "identity:list_regions": "", "identity:create_region": "rule:admin_required", "identity:update_region": "rule:admin_required", "identity:delete_region": "rule:admin_required", "identity:get_service": "rule:admin_required", "identity:list_services": "rule:admin_required", "identity:create_service": "rule:admin_required", "identity:update_service": "rule:admin_required", "identity:delete_service": "rule:admin_required", "identity:get_endpoint": "rule:admin_required", "identity:list_endpoints": "rule:admin_required", "identity:create_endpoint": "rule:admin_required", "identity:update_endpoint": "rule:admin_required", "identity:delete_endpoint": "rule:admin_required", "identity:get_catalog": "", "identity:get_domain": "rule:admin_required", "identity:list_domains": "rule:admin_required", "identity:create_domain": "rule:admin_required", "identity:update_domain": "rule:admin_required", "identity:delete_domain": "rule:admin_required", "identity:get_project": "rule:admin_required", "identity:list_projects": "rule:admin_required", "identity:list_user_projects": "rule:admin_or_owner", "identity:create_project": "rule:admin_required", "identity:update_project": "rule:admin_required", "identity:delete_project": "rule:admin_required", "identity:get_user": "rule:admin_required", "identity:list_users": "rule:admin_required", "identity:create_user": "rule:admin_required", "identity:update_user": "rule:admin_required", "identity:delete_user": "rule:admin_required", "identity:change_password": "rule:admin_or_owner", "identity:get_group": "rule:admin_required", "identity:list_groups": "rule:admin_required", "identity:list_groups_for_user": "rule:admin_or_owner", "identity:create_group": "rule:admin_required", "identity:update_group": "rule:admin_required", "identity:delete_group": "rule:admin_required", "identity:list_users_in_group": "rule:admin_required", "identity:remove_user_from_group": "rule:admin_required", "identity:check_user_in_group": "rule:admin_required", "identity:add_user_to_group": "rule:admin_required", "identity:get_credential": "rule:admin_required", "identity:list_credentials": "rule:admin_required", "identity:create_credential": "rule:admin_required", "identity:update_credential": "rule:admin_required", "identity:delete_credential": "rule:admin_required", "identity:ec2_get_credential": "rule:admin_or_owner", "identity:ec2_list_credentials": "rule:admin_or_owner", "identity:ec2_create_credential": "rule:admin_or_owner", "identity:ec2_delete_credential": "rule:admin_required or (rule:owner and user_id:%(target.credential.user_id)s)", "identity:get_role": "rule:admin_required", "identity:list_roles": "rule:admin_required", "identity:create_role": "rule:admin_required", "identity:update_role": "rule:admin_required", "identity:delete_role": "rule:admin_required", "identity:check_grant": "rule:admin_required", "identity:list_grants": "rule:admin_required", "identity:create_grant": "rule:admin_required", "identity:revoke_grant": "rule:admin_required", "identity:list_role_assignments": "rule:admin_required", "identity:get_policy": "rule:admin_required", "identity:list_policies": "rule:admin_required", "identity:create_policy": "rule:admin_required", "identity:update_policy": "rule:admin_required", "identity:delete_policy": "rule:admin_required", "identity:check_token": "rule:admin_required", "identity:validate_token": "rule:service_or_admin", "identity:validate_token_head": "rule:service_or_admin", "identity:revocation_list": "rule:service_or_admin", "identity:revoke_token": "rule:admin_or_owner", "identity:create_trust": "user_id:%(trust.trustor_user_id)s", "identity:get_trust": "rule:admin_or_owner", "identity:list_trusts": "", "identity:list_roles_for_trust": "", "identity:check_role_for_trust": "", "identity:get_role_for_trust": "", "identity:delete_trust": "", "identity:create_consumer": "rule:admin_required", "identity:get_consumer": "rule:admin_required", "identity:list_consumers": "rule:admin_required", "identity:delete_consumer": "rule:admin_required", "identity:update_consumer": "rule:admin_required", "identity:authorize_request_token": "rule:admin_required", "identity:list_access_token_roles": "rule:admin_required", "identity:get_access_token_role": "rule:admin_required", "identity:list_access_tokens": "rule:admin_required", "identity:get_access_token": "rule:admin_required", "identity:delete_access_token": "rule:admin_required", "identity:list_projects_for_endpoint": "rule:admin_required", "identity:add_endpoint_to_project": "rule:admin_required", "identity:check_endpoint_in_project": "rule:admin_required", "identity:list_endpoints_for_project": "rule:admin_required", "identity:remove_endpoint_from_project": "rule:admin_required", "identity:create_identity_provider": "rule:admin_required", "identity:list_identity_providers": "rule:admin_required", "identity:get_identity_provider": "rule:admin_required", "identity:update_identity_provider": "rule:admin_required", "identity:delete_identity_provider": "rule:admin_required", "identity:create_protocol": "rule:admin_required", "identity:update_protocol": "rule:admin_required", "identity:get_protocol": "rule:admin_required", "identity:list_protocols": "rule:admin_required", "identity:delete_protocol": "rule:admin_required", "identity:create_mapping": "rule:admin_required", "identity:get_mapping": "rule:admin_required", "identity:list_mappings": "rule:admin_required", "identity:delete_mapping": "rule:admin_required", "identity:update_mapping": "rule:admin_required", "identity:list_projects_for_groups": "", "identity:list_domains_for_groups": "", "identity:list_revoke_events": "" } horizon-13.0.3/openstack_auth/tests/conf/policy.v3cloudsample.json0000664000175000017500000002571313553660754025407 0ustar zuulzuul00000000000000{ "admin_required": "role:admin", "cloud_admin": "rule:admin_required and domain_id:admin_domain_id", "service_role": "role:service", "service_or_admin": "rule:admin_required or rule:service_role", "owner" : "user_id:%(user_id)s or user_id:%(target.token.user_id)s", "admin_or_owner": "(rule:admin_required and domain_id:%(target.token.user.domain.id)s) or rule:owner", "admin_or_cloud_admin": "rule:admin_required or rule:cloud_admin", "admin_and_matching_domain_id": "rule:admin_required and domain_id:%(domain_id)s", "service_admin_or_owner": "rule:service_or_admin or rule:owner", "default": "rule:admin_required", "identity:get_region": "", "identity:list_regions": "", "identity:create_region": "rule:cloud_admin", "identity:update_region": "rule:cloud_admin", "identity:delete_region": "rule:cloud_admin", "identity:get_service": "rule:admin_or_cloud_admin", "identity:list_services": "rule:admin_or_cloud_admin", "identity:create_service": "rule:cloud_admin", "identity:update_service": "rule:cloud_admin", "identity:delete_service": "rule:cloud_admin", "identity:get_endpoint": "rule:admin_or_cloud_admin", "identity:list_endpoints": "rule:admin_or_cloud_admin", "identity:create_endpoint": "rule:cloud_admin", "identity:update_endpoint": "rule:cloud_admin", "identity:delete_endpoint": "rule:cloud_admin", "identity:get_domain": "rule:cloud_admin or rule:admin_and_matching_domain_id", "identity:list_domains": "rule:cloud_admin", "identity:create_domain": "rule:cloud_admin", "identity:update_domain": "rule:cloud_admin", "identity:delete_domain": "rule:cloud_admin", "admin_and_matching_target_project_domain_id": "rule:admin_required and domain_id:%(target.project.domain_id)s", "admin_and_matching_project_domain_id": "rule:admin_required and domain_id:%(project.domain_id)s", "identity:get_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id", "identity:list_projects": "rule:cloud_admin or rule:admin_and_matching_domain_id", "identity:list_user_projects": "rule:owner or rule:admin_and_matching_domain_id", "identity:create_project": "rule:cloud_admin or rule:admin_and_matching_project_domain_id", "identity:update_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id", "identity:delete_project": "rule:cloud_admin or rule:admin_and_matching_target_project_domain_id", "admin_and_matching_target_user_domain_id": "rule:admin_required and domain_id:%(target.user.domain_id)s", "admin_and_matching_user_domain_id": "rule:admin_required and domain_id:%(user.domain_id)s", "identity:get_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id", "identity:list_users": "rule:cloud_admin or rule:admin_and_matching_domain_id", "identity:create_user": "rule:cloud_admin or rule:admin_and_matching_user_domain_id", "identity:update_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id", "identity:delete_user": "rule:cloud_admin or rule:admin_and_matching_target_user_domain_id", "admin_and_matching_target_group_domain_id": "rule:admin_required and domain_id:%(target.group.domain_id)s", "admin_and_matching_group_domain_id": "rule:admin_required and domain_id:%(group.domain_id)s", "identity:get_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:list_groups": "rule:cloud_admin or rule:admin_and_matching_domain_id", "identity:list_groups_for_user": "rule:owner or rule:admin_and_matching_domain_id", "identity:create_group": "rule:cloud_admin or rule:admin_and_matching_group_domain_id", "identity:update_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:delete_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:list_users_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:remove_user_from_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:check_user_in_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:add_user_to_group": "rule:cloud_admin or rule:admin_and_matching_target_group_domain_id", "identity:get_credential": "rule:admin_required", "identity:list_credentials": "rule:admin_required or user_id:%(user_id)s", "identity:create_credential": "rule:admin_required", "identity:update_credential": "rule:admin_required", "identity:delete_credential": "rule:admin_required", "identity:ec2_get_credential": "rule:admin_or_cloud_admin or (rule:owner and user_id:%(target.credential.user_id)s)", "identity:ec2_list_credentials": "rule:admin_or_cloud_admin or rule:owner", "identity:ec2_create_credential": "rule:admin_or_cloud_admin or rule:owner", "identity:ec2_delete_credential": "rule:admin_or_cloud_admin or (rule:owner and user_id:%(target.credential.user_id)s)", "identity:get_role": "rule:admin_or_cloud_admin", "identity:list_roles": "rule:admin_or_cloud_admin", "identity:create_role": "rule:cloud_admin", "identity:update_role": "rule:cloud_admin", "identity:delete_role": "rule:cloud_admin", "domain_admin_for_grants": "rule:admin_required and (domain_id:%(domain_id)s or domain_id:%(target.project.domain_id)s)", "project_admin_for_grants": "rule:admin_required and project_id:%(project_id)s", "identity:check_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", "identity:list_grants": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", "identity:create_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", "identity:revoke_grant": "rule:cloud_admin or rule:domain_admin_for_grants or rule:project_admin_for_grants", "admin_on_domain_filter" : "rule:admin_required and domain_id:%(scope.domain.id)s", "admin_on_project_filter" : "rule:admin_required and project_id:%(scope.project.id)s", "identity:list_role_assignments": "rule:cloud_admin or rule:admin_on_domain_filter or rule:admin_on_project_filter", "identity:get_policy": "rule:cloud_admin", "identity:list_policies": "rule:cloud_admin", "identity:create_policy": "rule:cloud_admin", "identity:update_policy": "rule:cloud_admin", "identity:delete_policy": "rule:cloud_admin", "identity:change_password": "rule:owner", "identity:check_token": "rule:admin_or_owner", "identity:validate_token": "rule:service_admin_or_owner", "identity:validate_token_head": "rule:service_or_admin", "identity:revocation_list": "rule:service_or_admin", "identity:revoke_token": "rule:admin_or_owner", "identity:create_trust": "user_id:%(trust.trustor_user_id)s", "identity:list_trusts": "", "identity:list_roles_for_trust": "", "identity:get_role_for_trust": "", "identity:delete_trust": "", "identity:create_consumer": "rule:admin_required", "identity:get_consumer": "rule:admin_required", "identity:list_consumers": "rule:admin_required", "identity:delete_consumer": "rule:admin_required", "identity:update_consumer": "rule:admin_required", "identity:authorize_request_token": "rule:admin_required", "identity:list_access_token_roles": "rule:admin_required", "identity:get_access_token_role": "rule:admin_required", "identity:list_access_tokens": "rule:admin_required", "identity:get_access_token": "rule:admin_required", "identity:delete_access_token": "rule:admin_required", "identity:list_projects_for_endpoint": "rule:admin_required", "identity:add_endpoint_to_project": "rule:admin_required", "identity:check_endpoint_in_project": "rule:admin_required", "identity:list_endpoints_for_project": "rule:admin_required", "identity:remove_endpoint_from_project": "rule:admin_required", "identity:create_endpoint_group": "rule:admin_required", "identity:list_endpoint_groups": "rule:admin_required", "identity:get_endpoint_group": "rule:admin_required", "identity:update_endpoint_group": "rule:admin_required", "identity:delete_endpoint_group": "rule:admin_required", "identity:list_projects_associated_with_endpoint_group": "rule:admin_required", "identity:list_endpoints_associated_with_endpoint_group": "rule:admin_required", "identity:get_endpoint_group_in_project": "rule:admin_required", "identity:list_endpoint_groups_for_project": "rule:admin_required", "identity:add_endpoint_group_to_project": "rule:admin_required", "identity:remove_endpoint_group_from_project": "rule:admin_required", "identity:create_identity_provider": "rule:cloud_admin", "identity:list_identity_providers": "rule:cloud_admin", "identity:get_identity_provider": "rule:cloud_admin", "identity:update_identity_provider": "rule:cloud_admin", "identity:delete_identity_provider": "rule:cloud_admin", "identity:create_protocol": "rule:cloud_admin", "identity:update_protocol": "rule:cloud_admin", "identity:get_protocol": "rule:cloud_admin", "identity:list_protocols": "rule:cloud_admin", "identity:delete_protocol": "rule:cloud_admin", "identity:create_mapping": "rule:cloud_admin", "identity:get_mapping": "rule:cloud_admin", "identity:list_mappings": "rule:cloud_admin", "identity:delete_mapping": "rule:cloud_admin", "identity:update_mapping": "rule:cloud_admin", "identity:create_service_provider": "rule:cloud_admin", "identity:list_service_providers": "rule:cloud_admin", "identity:get_service_provider": "rule:cloud_admin", "identity:update_service_provider": "rule:cloud_admin", "identity:delete_service_provider": "rule:cloud_admin", "identity:get_auth_catalog": "", "identity:get_auth_projects": "", "identity:get_auth_domains": "", "identity:list_projects_for_groups": "", "identity:list_domains_for_groups": "", "identity:list_revoke_events": "", "identity:create_policy_association_for_endpoint": "rule:cloud_admin", "identity:check_policy_association_for_endpoint": "rule:cloud_admin", "identity:delete_policy_association_for_endpoint": "rule:cloud_admin", "identity:create_policy_association_for_service": "rule:cloud_admin", "identity:check_policy_association_for_service": "rule:cloud_admin", "identity:delete_policy_association_for_service": "rule:cloud_admin", "identity:create_policy_association_for_region_and_service": "rule:cloud_admin", "identity:check_policy_association_for_region_and_service": "rule:cloud_admin", "identity:delete_policy_association_for_region_and_service": "rule:cloud_admin", "identity:get_policy_for_endpoint": "rule:cloud_admin", "identity:list_endpoints_for_policy": "rule:cloud_admin", "identity:create_domain_config": "rule:cloud_admin", "identity:get_domain_config": "rule:cloud_admin", "identity:update_domain_config": "rule:cloud_admin", "identity:delete_domain_config": "rule:cloud_admin" } horizon-13.0.3/openstack_auth/tests/settings.py0000664000175000017500000000463313553660755021721 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import os ALLOWED_HOSTS = ['*'] DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3'}} INSTALLED_APPS = [ 'django', 'django.contrib.contenttypes', 'django.contrib.auth', 'django.contrib.sessions', 'django.contrib.messages', 'openstack_auth', 'openstack_auth.tests' ] MIDDLEWARE_CLASSES = [ 'openstack_auth.middleware.OpenstackAuthMonkeyPatchMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware' ] AUTHENTICATION_BACKENDS = ['openstack_auth.backend.KeystoneBackend'] OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v3" ROOT_URLCONF = 'openstack_auth.tests.urls' LOGIN_REDIRECT_URL = '/' SECRET_KEY = 'badcafe' OPENSTACK_API_VERSIONS = { "identity": 3 } USE_TZ = True OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'domain' # NOTE(saschpe): The openstack_auth.user.Token object isn't # JSON-serializable ATM SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer' TEST_DIR = os.path.dirname(os.path.abspath(__file__)) POLICY_FILES_PATH = os.path.join(TEST_DIR, "conf") POLICY_FILES = { 'identity': 'keystone_policy.json', 'compute': 'nova_policy.json' } TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'APP_DIRS': True, }, ] AUTH_USER_MODEL = 'openstack_auth.User' LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'handlers': { 'test': { 'level': 'ERROR', 'class': 'logging.StreamHandler', } }, 'loggers': { 'openstack_auth': { 'handlers': ['test'], 'propagate': False, }, } } horizon-13.0.3/openstack_auth/tests/__init__.py0000664000175000017500000000000013553660754021577 0ustar zuulzuul00000000000000horizon-13.0.3/openstack_auth/tests/data_v2.py0000664000175000017500000001136013553660755021374 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import datetime import uuid from django.utils import datetime_safe from keystoneauth1.access import access from keystoneauth1.access import service_catalog from keystoneclient.v2_0 import roles from keystoneclient.v2_0 import tenants from keystoneclient.v2_0 import users class TestDataContainer(object): """Arbitrary holder for test data in an object-oriented fashion.""" pass def generate_test_data(): '''Builds a set of test_data data as returned by Keystone V2.''' test_data = TestDataContainer() keystone_service = { 'type': 'identity', 'name': 'keystone', 'endpoints_links': [], 'endpoints': [ { 'region': 'RegionOne', 'adminURL': 'http://admin.localhost:35357/v2.0', 'internalURL': 'http://internal.localhost:5000/v2.0', 'publicURL': 'http://public.localhost:5000/v2.0' } ] } # Users user_dict = {'id': uuid.uuid4().hex, 'name': 'gabriel', 'email': 'gabriel@example.com', 'password': 'swordfish', 'token': '', 'enabled': True} test_data.user = users.User(None, user_dict, loaded=True) # Tenants tenant_dict_1 = {'id': uuid.uuid4().hex, 'name': 'tenant_one', 'description': '', 'enabled': True} tenant_dict_2 = {'id': uuid.uuid4().hex, 'name': 'tenant_two', 'description': '', 'enabled': False} test_data.tenant_one = tenants.Tenant(None, tenant_dict_1, loaded=True) test_data.tenant_two = tenants.Tenant(None, tenant_dict_2, loaded=True) nova_service = { 'type': 'compute', 'name': 'nova', 'endpoint_links': [], 'endpoints': [ { 'region': 'RegionOne', 'adminURL': ('http://nova-admin.localhost:8774/v2.0/%s' % (tenant_dict_1['id'])), 'internalURL': ('http://nova-internal.localhost:8774/v2.0/%s' % (tenant_dict_1['id'])), 'publicURL': ('http://nova-public.localhost:8774/v2.0/%s' % (tenant_dict_1['id'])) }, { 'region': 'RegionTwo', 'adminURL': ('http://nova2-admin.localhost:8774/v2.0/%s' % (tenant_dict_1['id'])), 'internalURL': ('http://nova2-internal.localhost:8774/v2.0/%s' % (tenant_dict_1['id'])), 'publicURL': ('http://nova2-public.localhost:8774/v2.0/%s' % (tenant_dict_1['id'])) } ] } # Roles role_dict = {'id': uuid.uuid4().hex, 'name': 'Member'} test_data.role = roles.Role(roles.RoleManager, role_dict) # Tokens tomorrow = datetime_safe.datetime.now() + datetime.timedelta(days=1) expiration = datetime_safe.datetime.isoformat(tomorrow) scoped_token_dict = { 'access': { 'token': { 'id': uuid.uuid4().hex, 'expires': expiration, 'tenant': tenant_dict_1, 'tenants': [tenant_dict_1, tenant_dict_2]}, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'roles': [role_dict]}, 'serviceCatalog': [keystone_service, nova_service] } } test_data.scoped_access_info = access.create( resp=None, body=scoped_token_dict) unscoped_token_dict = { 'access': { 'token': { 'id': uuid.uuid4().hex, 'expires': expiration}, 'user': { 'id': user_dict['id'], 'name': user_dict['name'], 'roles': [role_dict]}, 'serviceCatalog': [keystone_service] } } test_data.unscoped_access_info = access.create( resp=None, body=unscoped_token_dict) # Service Catalog test_data.service_catalog = service_catalog.ServiceCatalogV2( [keystone_service, nova_service]) return test_data horizon-13.0.3/openstack_auth/forms.py0000664000175000017500000001516713553660755020051 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or # implied. # See the License for the specific language governing permissions and # limitations under the License. import collections import logging from django.conf import settings from django.contrib.auth import authenticate from django.contrib.auth import forms as django_auth_forms from django import forms from django.utils.translation import ugettext_lazy as _ from django.views.decorators.debug import sensitive_variables from openstack_auth import exceptions from openstack_auth import utils LOG = logging.getLogger(__name__) class Login(django_auth_forms.AuthenticationForm): """Form used for logging in a user. Handles authentication with Keystone by providing the domain name, username and password. A scoped token is fetched after successful authentication. A domain name is required if authenticating with Keystone V3 running multi-domain configuration. If the user authenticated has a default project set, the token will be automatically scoped to their default project. If the user authenticated has no default project set, the authentication backend will try to scope to the projects returned from the user's assigned projects. The first successful project scoped will be returned. Inherits from the base ``django.contrib.auth.forms.AuthenticationForm`` class for added security features. """ use_required_attribute = False region = forms.ChoiceField(label=_("Region"), required=False) username = forms.CharField( label=_("User Name"), widget=forms.TextInput(attrs={"autofocus": "autofocus"})) password = forms.CharField(label=_("Password"), widget=forms.PasswordInput(render_value=False)) def __init__(self, *args, **kwargs): super(Login, self).__init__(*args, **kwargs) fields_ordering = ['username', 'password', 'region'] if getattr(settings, 'OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT', False): last_domain = self.request.COOKIES.get('login_domain', None) if getattr(settings, 'OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN', False): self.fields['domain'] = forms.ChoiceField( label=_("Domain"), initial=last_domain, required=True, choices=getattr(settings, 'OPENSTACK_KEYSTONE_DOMAIN_CHOICES', ())) else: self.fields['domain'] = forms.CharField( initial=last_domain, label=_("Domain"), required=True, widget=forms.TextInput(attrs={"autofocus": "autofocus"})) self.fields['username'].widget = forms.widgets.TextInput() fields_ordering = ['domain', 'username', 'password', 'region'] self.fields['region'].choices = self.get_region_choices() if len(self.fields['region'].choices) == 1: self.fields['region'].initial = self.fields['region'].choices[0][0] self.fields['region'].widget = forms.widgets.HiddenInput() elif len(self.fields['region'].choices) > 1: self.fields['region'].initial = self.request.COOKIES.get( 'login_region') # if websso is enabled and keystone version supported # prepend the websso_choices select input to the form if utils.is_websso_enabled(): initial = getattr(settings, 'WEBSSO_INITIAL_CHOICE', 'credentials') self.fields['auth_type'] = forms.ChoiceField( label=_("Authenticate using"), choices=getattr(settings, 'WEBSSO_CHOICES', ()), required=False, initial=initial) # add auth_type to the top of the list fields_ordering.insert(0, 'auth_type') # websso is enabled, but keystone version is not supported elif getattr(settings, 'WEBSSO_ENABLED', False): msg = ("Websso is enabled but horizon is not configured to work " + "with keystone version 3 or above.") LOG.warning(msg) self.fields = collections.OrderedDict( (key, self.fields[key]) for key in fields_ordering) @staticmethod def get_region_choices(): default_region = (settings.OPENSTACK_KEYSTONE_URL, "Default Region") regions = getattr(settings, 'AVAILABLE_REGIONS', []) if not regions: regions = [default_region] return regions @sensitive_variables() def clean(self): default_domain = getattr(settings, 'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN', 'Default') username = self.cleaned_data.get('username') password = self.cleaned_data.get('password') region = self.cleaned_data.get('region') domain = self.cleaned_data.get('domain', default_domain) if not (username and password): # Don't authenticate, just let the other validators handle it. return self.cleaned_data try: self.user_cache = authenticate(request=self.request, username=username, password=password, user_domain_name=domain, auth_url=region) LOG.info('Login successful for user "%(username)s" using domain ' '"%(domain)s", remote address %(remote_ip)s.', {'username': username, 'domain': domain, 'remote_ip': utils.get_client_ip(self.request)}) except exceptions.KeystoneAuthException as exc: LOG.info('Login failed for user "%(username)s" using domain ' '"%(domain)s", remote address %(remote_ip)s.', {'username': username, 'domain': domain, 'remote_ip': utils.get_client_ip(self.request)}) raise forms.ValidationError(exc) if hasattr(self, 'check_for_test_cookie'): # Dropped in django 1.7 self.check_for_test_cookie() return self.cleaned_data horizon-13.0.3/tools/0000775000175000017500000000000013553661043014456 5ustar zuulzuul00000000000000horizon-13.0.3/tools/abandon_old_reviews.sh0000775000175000017500000000535513553660755021042 0ustar zuulzuul00000000000000#!/bin/bash # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # # # before you run this modify your .ssh/config to create a # review.openstack.org entry: # # Host review.openstack.org # User # Port 29418 # # Note: due to gerrit bug somewhere, this double posts messages. :( # first purge the all reviews that are more than 4w old and blocked by a core -2 set -o errexit function abandon_review { local gitid=$1 shift local msg=$@ echo "Abandoning $gitid" # echo ssh review.openstack.org gerrit review $gitid --abandon --message \"$msg\" ssh review.openstack.org gerrit review $gitid --abandon --message \"$msg\" } PROJECTS="(project:openstack/horizon OR project:openstack/django_openstack_auth)" blocked_reviews=$(ssh review.openstack.org "gerrit query --current-patch-set --format json $PROJECTS status:open age:4w label:Code-Review<=-2" | jq .currentPatchSet.revision | grep -v null | sed 's/"//g') blocked_msg=$(cat < 4 weeks without comment and currently blocked by a core reviewer with a -2. We are abandoning this for now. Feel free to reactivate the review by pressing the restore button and contacting the reviewer with the -2 on this review to ensure you address their concerns. EOF ) # For testing, put in a git rev of something you own and uncomment # blocked_reviews="b6c4218ae4d75b86c33fa3d37c27bc23b46b6f0f" for review in $blocked_reviews; do # echo ssh review.openstack.org gerrit review $review --abandon --message \"$msg\" echo "Blocked review $review" abandon_review $review $blocked_msg done # then purge all the reviews that are > 4w with no changes and Jenkins has -1ed failing_reviews=$(ssh review.openstack.org "gerrit query --current-patch-set --format json $PROJECTS status:open age:4w NOT label:Verified>=1,jenkins" | jq .currentPatchSet.revision | grep -v null | sed 's/"//g') failing_msg=$(cat < 4 weeks without comment, and failed Jenkins the last time it was checked. We are abandoning this for now. Feel free to reactivate the review by pressing the restore button and leaving a 'recheck' comment to get fresh test results. EOF ) for review in $failing_reviews; do echo "Failing review $review" abandon_review $review $failing_msg done horizon-13.0.3/tools/policy-diff.py0000664000175000017500000000315613553660754017252 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """Tool to check policy file differeneces.""" from __future__ import print_function import argparse import yaml def main(): parser = argparse.ArgumentParser() parser.add_argument('--old', required=True, help='Current policy file') parser.add_argument('--new', required=True, help='New policy file') parser.add_argument('--mode', choices=['add', 'remove'], default='remove', help='Diffs to be shown') parsed_args = parser.parse_args() with open(parsed_args.old) as f: old_data = yaml.safe_load(f) with open(parsed_args.new) as f: new_data = yaml.safe_load(f) added = set(new_data.keys()) - set(old_data.keys()) removed = set(old_data.keys()) - set(new_data.keys()) if parsed_args.mode == 'remove': for key in sorted(removed): print(key) if parsed_args.mode == 'add': for key in sorted(added): print(key) if __name__ == '__main__': main() horizon-13.0.3/tools/executable_files.txt0000664000175000017500000000024513553660755020534 0ustar zuulzuul00000000000000./manage.py ./node_modules ./tools/abandon_old_reviews.sh ./tools/gate/integration/post_test_hook.sh ./tools/gate/integration/pre_test_hook.sh ./tools/unit_tests.sh horizon-13.0.3/tools/find_executables.sh0000664000175000017500000000057513553660754020335 0ustar zuulzuul00000000000000OUTPUT=`find . \( -name .tox -o -name .git \) -prune -o -type f -perm /a=x -print \ | grep -v -F -f ./tools/executable_files.txt` if [ -n "$OUTPUT" ]; then echo "Unexpected executable files are found:" for f in $OUTPUT; do echo $f done echo echo "If you really need to add an executable file, add it to tools/executable_files.txt" exit 1 fi horizon-13.0.3/tools/gate/0000775000175000017500000000000013553661042015375 5ustar zuulzuul00000000000000horizon-13.0.3/tools/gate/integration/0000775000175000017500000000000013553661043017721 5ustar zuulzuul00000000000000horizon-13.0.3/tools/gate/integration/pre_test_hook.sh0000775000175000017500000000107113553660754023134 0ustar zuulzuul00000000000000#!/bin/bash # This script will be executed inside pre_test_hook function in devstack gate set -x HORIZON_CODE_DIR=/opt/stack/new/horizon cd ${HORIZON_CODE_DIR}/openstack_dashboard/local/local_settings.d mv _20_integration_tests_scaffolds.py.example _20_integration_tests_scaffolds.py if [ "$1" == "deprecated" ] ; then mv _2010_integration_tests_deprecated.py.example _2010_integration_tests_deprecated.py cat > ${HORIZON_CODE_DIR}/openstack_dashboard/test/integration_tests/local-horizon.conf <=0.12.0 # Apache-2.0 # coverage!=4.4,>=4.0 # Apache-2.0 django-nose>=1.4.4 # BSD doc8>=0.6.0 # Apache-2.0 flake8-import-order==0.12 # LGPLv3 mock>=2.0.0 # BSD mox3>=0.20.0 # Apache-2.0 nodeenv>=0.9.4 # BSD nose>=1.3.7 # LGPL nose-exclude>=0.3.0 # LGPL nosexcover>=1.0.10 # BSD openstack.nose-plugin>=0.7 # Apache-2.0 openstackdocstheme>=1.18.1 # Apache-2.0 reno>=2.5.0 # Apache-2.0 requests>=2.14.2 # Apache-2.0 selenium>=2.50.1 # Apache-2.0 sphinx!=1.6.6,>=1.6.2 # BSD testscenarios>=0.4 # Apache-2.0/BSD testtools>=2.2.0 # MIT # This also needs xvfb library installed on your OS xvfbwrapper>=0.1.3 #license: MIT horizon-13.0.3/doc/0000775000175000017500000000000013553661042014062 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/0000775000175000017500000000000013553661042015362 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/contributor/0000775000175000017500000000000013553661042017734 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/contributor/intro.rst0000664000175000017500000001045213553660754021634 0ustar zuulzuul00000000000000.. _contributor-intro: ============== Horizon Basics ============== Values ====== "Think simple" as my old master used to say - meaning reduce the whole of its parts into the simplest terms, getting back to first principles. -- Frank Lloyd Wright Horizon holds several key values at the core of its design and architecture: * Core Support: Out-of-the-box support for all core OpenStack projects. * Extensible: Anyone can add a new component as a "first-class citizen". * Manageable: The core codebase should be simple and easy-to-navigate. * Consistent: Visual and interaction paradigms are maintained throughout. * Stable: A reliable API with an emphasis on backwards-compatibility. * Usable: Providing an *awesome* interface that people *want* to use. The only way to attain and uphold those ideals is to make it *easy* for developers to implement those values. History ======= Horizon started life as a single app to manage OpenStack's compute project. As such, all it needed was a set of views, templates, and API calls. From there it grew to support multiple OpenStack projects and APIs gradually, arranged rigidly into "dash" and "syspanel" groupings. During the "Diablo" release cycle an initial plugin system was added using signals to hook in additional URL patterns and add links into the "dash" and "syspanel" navigation. This incremental growth served the goal of "Core Support" phenomenally, but left "Extensible" and "Manageable" behind. And while the other key values took shape of their own accord, it was time to re-architect for an extensible, modular future. The Current Architecture & How It Meets Our Values ================================================== At its core, **Horizon should be a registration pattern for applications to hook into**. Here's what that means and how it is implemented in terms of our values: Core Support ------------ Horizon ships with three central dashboards, a "User Dashboard", a "System Dashboard", and a "Settings" dashboard. Between these three they cover the core OpenStack applications and deliver on Core Support. The Horizon application also ships with a set of API abstractions for the core OpenStack projects in order to provide a consistent, stable set of reusable methods for developers. Using these abstractions, developers working on Horizon don't need to be intimately familiar with the APIs of each OpenStack project. Extensible ---------- A Horizon dashboard application is based around the :class:`~horizon.Dashboard` class that provides a consistent API and set of capabilities for both core OpenStack dashboard apps shipped with Horizon and equally for third-party apps. The :class:`~horizon.Dashboard` class is treated as a top-level navigation item. Should a developer wish to provide functionality within an existing dashboard (e.g. adding a monitoring panel to the user dashboard) the simple registration pattern makes it possible to write an app which hooks into other dashboards just as easily as creating a new dashboard. All you have to do is import the dashboard you wish to modify. Manageable ---------- Within the application, there is a simple method for registering a :class:`~horizon.Panel` (sub-navigation items). Each panel contains the necessary logic (views, forms, tests, etc.) for that interface. This granular breakdown prevents files (such as ``api.py``) from becoming thousands of lines long and makes code easy to find by correlating it directly to the navigation. Consistent ---------- By providing the necessary core classes to build from, as well as a solid set of reusable templates and additional tools (base form classes, base widget classes, template tags, and perhaps even class-based views) we can maintain consistency across applications. Stable ------ By architecting around these core classes and reusable components we create an implicit contract that changes to these components will be made in the most backwards-compatible ways whenever possible. Usable ------ Ultimately that's up to each and every developer that touches the code, but if we get all the other goals out of the way then we are free to focus on the best possible experience. .. seealso:: * :ref:`quickstart` A short guide to getting started with using Horizon. * :ref:`faq` Common questions and answers. * :ref:`glossary` Common terms and their definitions. horizon-13.0.3/doc/source/contributor/topics/0000775000175000017500000000000013553661042021235 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/contributor/topics/workflows.rst0000664000175000017500000001306113553660754024036 0ustar zuulzuul00000000000000.. _topics-workflows: ====================== Workflows Topic Guide ====================== One of the most challenging aspects of building a compelling user experience is crafting complex multi-part workflows. Horizon's ``workflows`` module aims to bring that capability within everyday reach. .. seealso:: For detailed API information refer to the :ref:`ref-workflows`. Workflows ========= Workflows are complex forms with tabs, each workflow must consist of classes extending the :class:`~horizon.workflows.Workflow`, :class:`~horizon.workflows.Step` and :class:`~horizon.workflows.Action` Complex example of a workflow ------------------------------ The following is a complex example of how data is exchanged between urls, views, workflows and templates: #. In ``urls.py``, we have the named parameter. E.g. ``resource_class_id``. :: RESOURCE_CLASS = r'^(?P[^/]+)/%s$' urlpatterns = [ url(RESOURCE_CLASS % 'update', UpdateView.as_view(), name='update') ] #. In ``views.py``, we pass data to the template and to the action(form) (action can also pass data to the ``get_context_data`` method and to the template). :: class UpdateView(workflows.WorkflowView): workflow_class = UpdateResourceClass def get_context_data(self, **kwargs): context = super(UpdateView, self).get_context_data(**kwargs) # Data from URL are always in self.kwargs, here we pass the data # to the template. context["resource_class_id"] = self.kwargs['resource_class_id'] # Data contributed by Workflow's Steps are in the # context['workflow'].context list. We can use that in the # template too. return context def _get_object(self, *args, **kwargs): # Data from URL are always in self.kwargs, we can use them here # to load our object of interest. resource_class_id = self.kwargs['resource_class_id'] # Code omitted, this method should return some object obtained # from API. def get_initial(self): resource_class = self._get_object() # This data will be available in the Action's methods and # Workflow's handle method. # But only if the steps will depend on them. return {'resource_class_id': resource_class.id, 'name': resource_class.name, 'service_type': resource_class.service_type} #. In ``workflows.py`` we process the data, it is just more complex django form. :: class ResourcesAction(workflows.Action): # The name field will be automatically available in all action's # methods. # If we want this field to be used in the another Step or Workflow, # it has to be contributed by this step, then depend on in another # step. name = forms.CharField(max_length=255, label=_("Testing Name"), help_text="", required=True) def handle(self, request, data): pass # If we want to use some data from the URL, the Action's step # has to depend on them. It's then available in # self.initial['resource_class_id'] or data['resource_class_id']. # In other words, resource_class_id has to be passed by view's # get_initial and listed in step's depends_on list. # We can also use here the data from the other steps. If we want # the data from the other step, the step needs to contribute the # data and the steps needs to be ordered properly. class UpdateResources(workflows.Step): action_class = ResourcesAction # This passes data from Workflow context to action methods # (handle, clean). Workflow context consists of URL data and data # contributed by other steps. depends_on = ("resource_class_id",) # By contributing, the data on these indexes will become available to # Workflow and to other Steps (if they will depend on them). Notice, # that the resources_object_ids key has to be manually added in # contribute method first. contributes = ("resources_object_ids", "name") def contribute(self, data, context): # We can obtain the http request from workflow. request = self.workflow.request if data: # Only fields defined in Action are automatically # available for contribution. If we want to contribute # something else, We need to override the contribute method # and manually add it to the dictionary. context["resources_object_ids"] =\ request.POST.getlist("resources_object_ids") # We have to merge new context with the passed data or let # the superclass do this. context.update(data) return context class UpdateResourceClass(workflows.Workflow): default_steps = (UpdateResources,) def handle(self, request, data): pass # This method is called as last (after all Action's handle # methods). All data that are listed in step's 'contributes=' # and 'depends_on=' are available here. # It can be easier to have the saving logic only here if steps # are heavily connected or complex. # data["resources_object_ids"], data["name"] and # data["resources_class_id"] are available here. horizon-13.0.3/doc/source/contributor/topics/policy.rst0000664000175000017500000001677313553660754023315 0ustar zuulzuul00000000000000.. _topics-policy: ============================================================ Horizon Policy Enforcement (RBAC: Role Based Access Control) ============================================================ Introduction ============ Horizon's policy enforcement builds on the oslo_policy engine. The basis of which is ``openstack_auth/policy.py``. Services in OpenStack use the oslo policy engine to define policy rules to limit access to APIs based primarily on role grants and resource ownership. The implementation in Horizon is based on copies of policy files found in the service's source code. The service rules files are loaded into the policy engine to determine access rights to actions and service APIs. Horizon Settings ================ There are a few settings that must be in place for the Horizon policy engine to work. * ``POLICY_CHECK_FUNCTION`` * ``POLICY_DIRS`` * ``POLICY_FILES_PATH`` * ``POLICY_FILES`` For more detail, see :doc:`/configuration/settings`. How user's roles are determined =============================== Each policy check uses information about the user stored on the request to determine the user's roles. This information was extracted from the scoped token received from Keystone when authenticating. Entity ownership is also a valid role. To verify access to specific entities like a project, the target must be specified. See the section :ref:`rule targets ` later in this document. How to Utilize RBAC =================== Django: Table action -------------------- The primary way to add role based access control checks to panels is in the definition of table actions. When implementing a derived action class, setting the :attr:`~horizon.tables.Action.policy_rules` attribute to valid policy rules will force a policy check before the :meth:`horizon.tables.Action.allowed` method is called on the action. These rules are defined in the policy files pointed to by ``POLICY_PATH`` and ``POLICY_FILES``. The rules are role based, where entity owner is also a role. The format for the ``policy_rules`` is a list of two item tuples. The first component of the tuple is the scope of the policy rule, this is the service type. This informs the policy engine which policy file to reference. The second component is the rule to enforce from the policy file specified by the scope. An example tuple is:: ("identity", "identity:get_user") x tuples can be added to enforce x rules. .. note:: If a rule specified is not found in the policy file, the policy check will return False and the action will not be allowed. Django: policy check function ----------------------------- The secondary way to add a role based check is to directly use the :meth:`~openstack_dashboard.policy.check` method. The method takes a list of actions, same format as the :attr:`~horizon.tables.Action.policy_rules` attribute detailed above; the current request object; and a dictionary of action targets. This is the method that :class:`horizon.tables.Action` class utilizes. Examples look like:: from openstack_dashboard import policy allowed = policy.check((("identity", "identity:get_user"), ("identity", "identity:get_project"),), request) can_see = policy.check((("identity", "identity:get_user"),), request, target={"domain_id": domainId}) .. note:: Any time multiple rules are specified in a single `policy.check` method call, the result is the logical `and` of each rule check. So, if any rule fails verification, the result is `False`. Angular: ifAllowed method ------------------------- The third way to add a role based check is in javascript files. Use the method 'ifAllowed()' in file 'openstack_dashboard.static.app.core.policy.service.js'. The method takes a list of actions, similar format with the :attr:`~horizon.tables.Action.policy_rules` attribute detailed above. An Example looks like:: angular .module('horizon.dashboard.identity.users') .controller('identityUsersTableController', identityUsersTableController); identityUsersTableController.$inject = [ 'horizon.app.core.openstack-service-api.policy', ]; function identityUsersTableController(toast, gettext, policy, keystone) { var rules = [['identity', 'identity:list_users']]; policy.ifAllowed({ rules: rules }).then(policySuccess, policyFailed); } Angular: hz-if-policies ----------------------- The fourth way to add a role based check is in html files. Use angular directive 'hz-if-policies' in file 'openstack_dashboard/static/app/core/cloud-services/hz-if-policies.directive.js'. Assume you have the following policy defined in your angular controller:: ctrl.policy = { rules: [["identity", "identity:update_user"]] } Then in your HTML, use it like so::
I am visible if the policy is allowed!
.. _rule_targets: Rule Targets ============ Some rules allow access if the user owns the entity. Policy check targets specify particular entities to check for user ownership. The target parameter to the :meth:`~openstack_dashboard.policy.check` method is a simple dictionary. For instance, the target for checking access a project looks like:: {"project_id": "0905760626534a74979afd3f4a9d67f1"} If the value matches the ``project_id`` to which the user's token is scoped, then access is allowed. When deriving the :class:`horizon.tables.Action` class for use in a table, if a policy check is desired for a particular target, the implementer should override the :meth:`horizon.tables.Action.get_policy_target` method. This allows a programmatic way to specify the target based on the current datum. The value returned should be the target dictionary. Policy file maintenance ======================= The policy implementation uses the copies of policies defined in back-end services. As of Queens, the OpenStack community are in the process of `policy-in-code `__. Some projects already define their policies in the code, and some still have their policies in ``policy.json`` files. For project with the legacy ``policy.json`` files, what we need to do is just to copy ``policy.json`` into the horizon tree. For projects with "policy-in-code", all policies are defined as python codes, so we first need to generate policy files with its default rules. To do this, run the following command after install a corresponding project. .. code-block:: console oslopolicy-sample-generator --namespace $PROJECT --format json \ --output-file $HORIZON_REPO/openstack_dashboard/conf/$PROJECT_policy.json After syncing policies from back-end services, you need to check what are changed. If a policy referred by horizon has been changed, you need to check and modify the horizon code base accordingly. To summarize which policies are removed or added, a convenient tool is provided: .. code-block:: console $ cd openstack_dashboard/conf/ $ python ../../tools/policy-diff.py --help usage: policy-diff.py [-h] --old OLD --new NEW [--mode {add,remove}] optional arguments: -h, --help show this help message and exit --old OLD Current policy file --new NEW New policy file --mode {add,remove} Diffs to be shown # Show removed policies # The default is "--mode remove". You can omit --mode option. $ python ../../tools/policy-diff.py \ --old keystone_policy.json --new keystone_policy.json.new --mode remove default identity:change_password identity:get_identity_provider horizon-13.0.3/doc/source/contributor/topics/microversion_support.rst0000664000175000017500000000335013553660754026314 0ustar zuulzuul00000000000000============================ Horizon Microversion Support ============================ Introduction ============ Several services use API microversions, which allows consumers of that API to specify an exact version when making a request. This can be useful in ensuring a feature continues to work as expected across many service releases. Adding a feature that was introduced in a microversion ====================================================== 1. Add the feature to the ``MICROVERSION_FEATURES`` dict in ``openstack_dashboard/api/microversions.py`` under the appropriate service name. The feature should have at least two versions listed; the minimum version (i.e. the version that introduced the feature) and the current working version. Providing multiple versions reduces project maintenance overheads and helps Horizon work with older service deployments. 2. Use the ``is_feature_available`` function for your service to show or hide the function.:: from openstack_dashboard.api import service ... def allowed(self, request): return service.is_feature_available('feature') 3. Send the correct microversion with ``get_microversion`` function in the API layer.:: def resource_list(request): try: microversion = get_microversion(request, 'feature') client = serviceclient(request, microversion) return client.resource_list() Microversion references ======================= :Nova: https://docs.openstack.org/nova/latest/reference/api-microversion-history.html :Cinder: https://docs.openstack.org/cinder/latest/contributor/api_microversion_history.html :API-WG: https://specs.openstack.org/openstack/api-wg/guidelines/microversion_specification.html horizon-13.0.3/doc/source/contributor/topics/styling.rst0000664000175000017500000001113213553660754023467 0ustar zuulzuul00000000000000========================= Styling in Horizon (SCSS) ========================= Horizon uses `SCSS`_ (not to be confused with Sass) to style its HTML. This guide is targeted at developers adding code to upstream Horizon. For information on creating your own branding/theming, see :ref:`install-customizing`. .. _SCSS: http://sass-lang.com/guide Code Layout =========== The base SCSS can be found at ``openstack_dashboard/static/dashboard/scss/``. This directory should **only** contain the minimal styling for functionality code that isn't configurable by themes. ``horizon.scss`` is a top level file that imports from the ``components/`` directory, as well as other base styling files; potentially some basic page layout rules that Horizon relies on to function. .. Note:: Currently, a great deal of theming is also kept in the ``horizon.scss`` file in this directory, but that will be reduced as we proceed with the new code design. Horizon's ``default`` theme stylesheets can be found at ``openstack_dashboard/themes/default/``. :: ├── _styles.scss ├── _variables.scss ├── bootstrap/ └── ... └── horizon/ └── ... The top level ``_styles.scss`` and ``_variables.scss`` contain imports from the ``bootstrap`` and ``horizon`` directories. The "bootstrap" directory ------------------------- This directory contains overrides and customization of Bootstrap variables, and markup used by Bootstrap components. This should **only** override existing Bootstrap content. For examples of these components, see the `Theme Preview Panel`_. :: bootstrap/ ├── _styles.scss ├── _variables.scss └── components/ ├── _component_0.scss ├── _component_1.scss └── ... - ``_styles.scss`` imports the SCSS defined for each component. - ``_variables.scss`` contains the definitions for every Bootstrap variable. These variables can be altered to affect the look and feel of Horizon's default theme. - The ``components`` directory contains overrides for Bootstrap components, such as tables or navbars. The "horizon" directory ----------------------- This directory contains SCSS that is absolutely specific to Horizon; code here should **not** override existing Bootstrap content, such as variables and rules. :: horizon/ ├── _styles.scss ├── _variables.scss └── components/ ├── _component_0.scss ├── _component_1.scss └── ... - ``_styles.scss`` imports the SCSS defined for each component. It may also contain some minor styling overrides. - ``_variables.scss`` contains variable definitions that are specific to the horizon theme. This should **not** override any bootstrap variables, only define new ones. You can however, inherit bootstrap variables for reuse (and are encouraged to do so where possible). - The ``components`` directory contains styling for each individual component defined by Horizon, such as the sidebar or pie charts. Adding new SCSS =============== To keep Horizon easily themable, there are several code design guidelines that should be adhered to: - Reuse Bootstrap variables where possible. This allows themes to influence styling by simply overriding a few existing variables, instead of rewriting large chunks of the SCSS files. - If you are unable to use existing variables - such as for very specific functionality - keep the new rules as specific as possible to your component so they do not cause issues in unexpected places. - Check if existing components suit your use case. There may be existing components defined by Bootstrap or Horizon that can be reused, rather than writing new ones. Theme Preview Panel =================== The Bootstrap Theme Preview panel contains examples of all stock Bootstrap markup with the currently applied theme, as well as source code for replicating them; click the ```` symbol when hovering over a component. To enable the Developer dashboard with the Theme Preview panel: #. Set :ref:`DEBUG ` setting to ``True``. #. Copy ``_9001_developer.py`` and ``_9010_preview.py`` from ``openstack_dashboard/contrib/developer/enabled/`` to ``openstack_dashboard/local/enabled/``. #. Restart the web server. Alternate Theme =============== A second theme is provided by default at ``openstack_dashboard/themes/material/``. When adding new SCSS to horizon, you should check that it does not interfere with the Material theme. Images of how the Material theme should look can be found at https://bootswatch.com/paper/. This theme is now configured to run as the alternate theme within Horizon. horizon-13.0.3/doc/source/contributor/topics/packaging.rst0000664000175000017500000002462313553660754023733 0ustar zuulzuul00000000000000================== Packaging Software ================== Software packages ----------------- This section describes some general things that a developer should know about packaging software. This content is mostly derived from best practices. A developer building a package is comparable to an engineer building a car with only a manual and very few tools. If the engineer needs a specific tool to build the car, he must create the tool, too. As a developer, if you are going to add a library named “foo”, the package must adhere to the following standards: - Be a free package created with free software. - Include all tools that are required to build the package. - Have an active and responsive upstream to maintain the package. - Adhere to Filesystem Hierarchy Standards (FHS). A specific file system layout is not required. Embedded copies not allowed --------------------------- Imagine if all packages had a local copy of jQuery. If a security hole is discovered in jQuery, we must write more than 90 patches in Debian, one for each package that includes a copy. This is simply not practical. Therefore, it is unacceptable for Horizon to copy code from other repositories when creating a package. Copying code from another repository tends to create a fork, diverging from the upstream code. The fork includes code that is not being maintained, so if a bug is discovered in the original upstream, it cannot easily be fixed by updating a single package. Another reason to avoid copying a library into Horizon source code is that it might create conflicting licenses. Distributing sources with conflicting licenses in one tarball revokes rights in best case. In the worst case, you could be held legally responsible. Free software ------------- Red Hat, Debian, and SUSE distributions are made only of free software (free as in Libre, or free speech). The software that we include in our repository is free. The tools are also free, and available in the distribution. Because package maintainers care about the quality of the packages we upload, we run tests that are available from upstream repositories. This also qualifies test requirements as build requirements. The same rules apply for building the software as for the software itself. Special build requirements that are not included in the overall distribution are not allowed. An example of historically limiting, non-free software is Selenium. For a long time, Selenium was only available from the non-free repositories of Debian. The reason was that upstream included some .xpi binaries. These .xpi included some Windows .dll and Linux .so files. Because they could not be rebuilt from the source, all of python-selenium was declared non-free. If we made Horizon build-depends on python-selenium, this would mean Horizon wouldn't be in Debian main anymore (contrib and non-free are *not* considered part of Debian). Recently, the package maintainer of python-selenium decided to remove the .xpi files from python-selenium, and upload it to Debian Experimental (this time, in main, not in non-free). If at some point it is possible for Horizon to use python-selenium (without the non-free .xpi files), then we could run Selenium tests at package build time. Running unit tests at build time -------------------------------- The build environment inside a distribution is not exactly the same as the one in the OpenStack gate. For example, versions of a given library can be slightly different from the one in the gate. We want to detect when problematic differences exist so that we can fix them. Whenever possible, try to make the lives of the package maintainer easier, and allow them (or help them) to run unit tests. Minified JavaScript policy -------------------------- In free software distributions that actively maintain OpenStack packages (such as RDO, Debian, and Ubuntu), minified JavaScript is considered non-free. This means that minified JavaScript should *not* be present in upstream source code. At the very least, a non-minified version should be present next to the minified version. Also, be aware of potential security issues with minifiers. This `blog post`_ explains it very well. .. _`blog post`: https://zyan.scripts.mit.edu/blog/backdooring-js/ Component version ----------------- Be careful about the version of all the components you use in your application. Since it is not acceptable to embed a given component within Horizon, we must use what is in the distribution, including all fonts, JavaScript, etc. This is where it becomes a bit tricky. In most distributions, it is not acceptable to have multiple versions of the same piece of software. In Red Hat systems, it is technically possible to install 2 versions of one library at the same time, but a few restrictions apply, especially for usage. However, package maintainers try to avoid multiple versions as much as possible. For package dependency resolution, it might be necessary to provide packages for depending packages as well. For example, if you had Django-1.4 and Django-1.8 in the same release, you must provide Horizon built for Django-1.4 and another package providing Horizon built for Django-1.8. This is a large effort and needs to be evaluated carefully. In Debian, it is generally forbidden to have multiple versions of the same library in the same Debian release. Very few exceptions exist. Component versioning has consequences for an upstream author willing to integrate their software in a downstream distribution. The best situation is when it is possible to support whatever version is currently available in the target distributions, up to the latest version upstream. Declaring lower and upper bounds within your requirements.txt does not solve the issue. It allows all the tests to pass on gate because they are run against a narrow set of versions in requirements.txt. The downstream distribution might still have some dependencies with versions outside of the range that is specified in requirements.txt. These dependencies may lead to failures that are not caught in the OpenStack gate. At times it might not be possible to support all versions of a library. It might be too much work, or it might be very hard to test in the gate. In this case, it is best to use whatever is available inside the target distributions. For example, Horizon currently supports jQuery >= 1.7.2, as this is what is currently available in Debian Jessie and Ubuntu Trusty (the last LTS). You can search in a distribution for a piece of software foo using a command like ``dnf search foo``, or ``zypper se -s foo``. ``dnf info foo`` returns more detailed information about the package. Filesystem Hierarchy Standards ------------------------------ Every distribution must comply with the Filesystem Hierarchy Standards (FHS). The FHS defines a set of rules that we *must* follow as package maintainers. Some of the most important ones are: - /usr is considered read only. Software must not write in /usr at runtime. However, it is fine for a package post-installation script to write in /usr. When this rule was not followed, distributions had to write many tricks to convince Horizon to write in ``/var/lib`` only. For example, distributions wrote symlinks to ``/var/lib/openstack-dashboard``, or patched the default ``local_settings.py`` to write the ``SECRET_KEY`` in /var. - Configuration must always be in /etc, no matter what. When this rule was not followed, package maintainers had to place symlinks to ``/etc/openstack-dashboard/local_settings`` in Red Hat based distributions instead of using directly ``/usr/share/openstack-dashboard/openstack_dashboard/local/local_settings.py`` which Horizon expects. In Debian,the configuration file is named ``/etc/openstack-dashboard/local_settings.py.`` Packaging Horizon ----------------- Why we use XStatic ~~~~~~~~~~~~~~~~~~ XStatic provides the following features that are not currently available by default with systems like NPM and Grunt: - Dependency checks: XStatic checks that dependencies, such as fonts and JavaScript libs, are available in downstream distributions. - Reusable components across projects: The XStatic system ensures components are reusable by other packages, like Fuel. - System-wide registry of static content: XStatic brings a system-wide registry of components, so that it is easy to check if one is missing. For example, it can detect if there is no egg-info, or a broken package dependency exists. - No embedded content: The XStatic system helps us avoid embedding files that are already available in the distribution, for example, libjs-* or fonts-* packages. It even provides a compatibility layer for distributions. Not every distribution places static files in the same position in the file system. If you are packaging an XStatic package for your distribution, make sure that you are using the static files provided by that specific distribution. Having put together an XStatic package is *no* guarantee to get it into a distribution. XStatic provides only the abstraction layer to use distribution provided static files. - Package build systems are disconnected from the outside network (for several reasons). Other packaging systems download dependencies directly from the internet without verifying that the downloaded file is intact, matches a provided checksum, etc. With these other systems, there is no way to provide a mirror, a proxy or a cache, making builds even more unstable when minor networking issues are encountered. The previous features are critical requirements of the Horizon packaging system. Any new system *must* keep these features. Although XStatic may mean a few additional steps from individual developers, those steps help maintain consistency and prevent errors across the project. Packaging Horizon for distributions ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Horizon is a Python module. Preferably, it is installed at the default location for python. In Fedora and openSUSE, this is ``/usr/lib/python2.7/site-packages/horizon``, and in Debian/Ubuntu it is ``/usr/lib/python2.7/dist-packages/horizon``. Configuration files should reside under ``/etc/openstack-dashboard``. Policy files should be created and modified there as well. It is expected that ``manage.py collectstatic`` will be run during package build. This is the `recommended way`_ for Django applications. Depending on configuration, it might be required to ``manage.py compress`` during package build, too. .. _`recommended way`: https://docs.djangoproject.com/en/dev/howto/static-files/deployment/ horizon-13.0.3/doc/source/contributor/topics/testing.rst0000664000175000017500000003201513553660754023456 0ustar zuulzuul00000000000000.. _topics-testing: ================ Testing Overview ================ Having good tests in place is absolutely critical for ensuring a stable, maintainable codebase. Hopefully that doesn't need any more explanation. However, what defines a "good" test is not always obvious, and there are a lot of common pitfalls that can easily shoot your test suite in the foot. If you already know everything about testing but are fed up with trying to debug why a specific test failed, you can skip the intro and jump straight to :ref:`debugging_unit_tests`. .. toctree:: :maxdepth: 1 Angular specific testing An overview of testing ====================== There are three main types of tests, each with their associated pros and cons: Unit tests ---------- These are isolated, stand-alone tests with no external dependencies. They are written from the perspective of "knowing the code", and test the assumptions of the codebase and the developer. Pros: * Generally lightweight and fast. * Can be run anywhere, anytime since they have no external dependencies. Cons: * Easy to be lax in writing them, or lazy in constructing them. * Can't test interactions with live external services. Functional tests ---------------- These are generally also isolated tests, though sometimes they may interact with other services running locally. The key difference between functional tests and unit tests, however, is that functional tests are written from the perspective of the user (who knows nothing about the code) and only knows what they put in and what they get back. Essentially this is a higher-level testing of "does the result match the spec?". Pros: * Ensures that your code *always* meets the stated functional requirements. * Verifies things from an "end user" perspective, which helps to ensure a high-quality experience. * Designing your code with a functional testing perspective in mind helps keep a higher-level viewpoint in mind. Cons: * Requires an additional layer of thinking to define functional requirements in terms of inputs and outputs. * Often requires writing a separate set of tests and/or using a different testing framework from your unit tests. * Doesn't offer any insight into the quality or status of the underlying code, only verifies that it works or it doesn't. Integration Tests ----------------- This layer of testing involves testing all of the components that your codebase interacts with or relies on in conjunction. This is equivalent to "live" testing, but in a repeatable manner. Pros: * Catches *many* bugs that unit and functional tests will not. * Doesn't rely on assumptions about the inputs and outputs. * Will warn you when changes in external components break your code. * Will take screenshot of the current page on test fail for easy debug Cons: * Difficult and time-consuming to create a repeatable test environment. * Did I mention that setting it up is a pain? Screenshot directory could be set through horizon.conf file, default value: ``./integration_tests_screenshots`` So what should I write? ----------------------- A few simple guidelines: #. Every bug fix should have a regression test. Period. #. When writing a new feature, think about writing unit tests to verify the behavior step-by-step as you write the feature. Every time you'd go to run your code by hand and verify it manually, think "could I write a test to do this instead?". That way when the feature is done and you're ready to commit it you've already got a whole set of tests that are more thorough than anything you'd write after the fact. #. Write tests that hit every view in your application. Even if they don't assert a single thing about the code, it tells you that your users aren't getting fatal errors just by interacting with your code. What makes a good unit test? ============================ Limiting our focus just to unit tests, there are a number of things you can do to make your unit tests as useful, maintainable, and unburdensome as possible. Test data --------- Use a single, consistent set of test data. Grow it over time, but do everything you can not to fragment it. It quickly becomes unmaintainable and perniciously out-of-sync with reality. Make your test data as accurate to reality as possible. Supply *all* the attributes of an object, provide objects in all the various states you may want to test. If you do the first suggestion above *first* it makes the second one far less painful. Write once, use everywhere. To make your life even easier, if your codebase doesn't have a built-in ORM-like function to manage your test data you can consider building (or borrowing) one yourself. Being able to do simple retrieval queries on your test data is incredibly valuable. Mocking ------- Mocking is the practice of providing stand-ins for objects or pieces of code you don't need to test. While convenient, they should be used with *extreme* caution. Why? Because overuse of mocks can rapidly land you in a situation where you're not testing any real code. All you've done is verified that your mocking framework returns what you tell it to. This problem can be very tricky to recognize, since you may be mocking things in ``setUp`` methods, other modules, etc. A good rule of thumb is to mock as close to the source as possible. If you have a function call that calls an external API in a view , mock out the external API, not the whole function. If you mock the whole function you've suddenly lost test coverage for an entire chunk of code *inside* your codebase. Cut the ties cleanly right where your system ends and the external world begins. Similarly, don't mock return values when you could construct a real return value of the correct type with the correct attributes. You're just adding another point of potential failure by exercising your mocking framework instead of real code. Following the suggestions for testing above will make this a lot less burdensome. Assertions and verification --------------------------- Think long and hard about what you really want to verify in your unit test. In particular, think about what custom logic your code executes. A common pitfall is to take a known test object, pass it through your code, and then verify the properties of that object on the output. This is all well and good, except if you're verifying properties that were untouched by your code. What you want to check are the pieces that were *changed*, *added*, or *removed*. Don't check the object's id attribute unless you have reason to suspect it's not the object you started with. But if you added a new attribute to it, be damn sure you verify that came out right. It's also very common to avoid testing things you really care about because it's more difficult. Verifying that the proper messages were displayed to the user after an action, testing for form errors, making sure exception handling is tested... these types of things aren't always easy, but they're extremely necessary. To that end, Horizon includes several custom assertions to make these tasks easier. :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`, :meth:`~horizon.test.helpers.TestCase.assertMessageCount`, and :meth:`~horizon.test.helpers.TestCase.assertNoMessages` all exist for exactly these purposes. Moreover, they provide useful output when things go wrong so you're not left scratching your head wondering why your view test didn't redirect as expected when you posted a form. .. _debugging_unit_tests: Debugging Unit Tests ==================== Tips and tricks --------------- #. Use :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors` immediately after your ``client.post`` call for tests that handle form views. This will immediately fail if your form POST failed due to a validation error and tell you what the error was. #. Use :meth:`~horizon.test.helpers.TestCase.assertMessageCount` and :meth:`~horizon.test.helpers.TestCase.assertNoMessages` when a piece of code is failing inexplicably. Since the core error handlers attach user-facing error messages (and since the core logging is silenced during test runs) these methods give you the dual benefit of verifying the output you expect while clearly showing you the problematic error message if they fail. #. Use Python's ``pdb`` module liberally. Many people don't realize it works just as well in a test case as it does in a live view. Simply inserting ``import pdb; pdb.set_trace()`` anywhere in your codebase will drop the interpreter into an interactive shell so you can explore your test environment and see which of your assumptions about the code isn't, in fact, flawlessly correct. #. If the error is in the Selenium test suite, you're likely getting very little information about the error. To increase the information provided to you, edit ``horizon/test/settings.py`` to set ``DEBUG = True`` and set the logging level to 'DEBUG' for the default 'test' logger. Also, add a logger config for Django:: }, 'loggers': { + 'django': { + 'handlers': ['test'], + 'propagate': False, + }, 'django.db.backends': { Coverage reports ---------------- It is possible for tests to fail on your patch due to the npm-run-test not passing the minimum threshold. This is not necessarily related directly to the functions in the patch that have failed, but more that there are not enough tests across horizon that are related to your patch. The coverage reports may be found in the 'cover' directory. There's a subdirectory for horizon and openstack_dashboard, and then under a directory for the browser used to run the tests you should find an ``index.html``. This can then be viewed to see the coverage details. In this scenario you may need to submit a secondary patch to address test coverage for another function within horizon to ensure tests rise above the coverage threshold and your original patch can pass the necessary tests. Common pitfalls --------------- There are a number of typical (and non-obvious) ways to break the unit tests. Some common things to look for: #. Make sure you stub out the method exactly as it's called in the code being tested. For example, if your real code calls ``api.keystone.tenant_get``, stubbing out ``api.tenant_get`` (available for legacy reasons) will fail. #. When defining the expected input to a stubbed call, make sure the arguments are *identical*, this includes ``str`` vs. ``int`` differences. #. Make sure your test data are completely in line with the expected inputs. Again, ``str`` vs. ``int`` or missing properties on test objects will kill your tests. #. Make sure there's nothing amiss in your templates (particularly the ``{% url %}`` tag and its arguments). This often comes up when refactoring views or renaming context variables. It can easily result in errors that you might not stumble across while clicking around the development server. #. Make sure you're not redirecting to views that no longer exist, e.g. the ``index`` view for a panel that got combined (such as instances & volumes). #. Make sure your mock calls are in order before calling ``mox.ReplayAll``. The order matters. #. Make sure you repeat any stubbed out method calls that happen more than once. They don't automatically repeat, you have to explicitly define them. While this is a nuisance, it makes you acutely aware of how many API calls are involved in a particular function. Understanding the output from ``mox`` ------------------------------------- Horizon uses ``mox`` as its mocking framework of choice, and while it offers many nice features, its output when a test fails can be quite mysterious. Unexpected Method Call ~~~~~~~~~~~~~~~~~~~~~~ This occurs when you stubbed out a piece of code, and it was subsequently called in a way that you didn't specify it would be. There are two reasons this tends to come up: #. You defined the expected call, but a subtle difference crept in. This may be a string versus integer difference, a string versus unicode difference, a slightly off date/time, or passing a name instead of an id. #. The method is actually being called *multiple times*. Since mox uses a call stack internally, it simply pops off the expected method calls to verify them. That means once a call is used once, it's gone. An easy way to see if this is the case is simply to copy and paste your method call a second time to see if the error changes. If it does, that means your method is being called more times than you think it is. Expected Method Never Called ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This one is the opposite of the unexpected method call. This one means you told mox to expect a call and it didn't happen. This is almost always the result of an error in the conditions of the test. Using the :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors` and :meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily apparent what the problem is in the majority of cases. If not, then use ``pdb`` and start interrupting the code flow to see where things are getting off track. horizon-13.0.3/doc/source/contributor/topics/javascript_testing.rst0000664000175000017500000002304513553660754025707 0ustar zuulzuul00000000000000.. _topics-javascript-testing: ================== JavaScript Testing ================== There are multiple components in our JavaScript testing framework: * `Jasmine`_ is our testing framework, so this defines the syntax and file structure we use to test our JavaScript. * `Karma`_ is our test runner. Amongst other things, this lets us run the tests against multiple browsers and generate test coverage reports. Alternatively, tests can be run inside the browser with the Jasmine spec runner. * `PhantomJS`_ provides a headless WebKit (the browser engine). This gives us native support for many web features without relying on specific browsers being installed. * `ESLint`_ is a pluggable code linting utility. This will catch small errors and inconsistencies in your JS, which may lead to bigger issues later on. See :ref:`js_code_style` for more detail. Jasmine uses specs (``.spec.js``) which are kept with the JavaScript files that they are testing. See the :ref:`js_file_structure` section or the `Examples`_ below for more detail on this. .. _Jasmine: https://jasmine.github.io/2.3/introduction.html .. _Karma: https://karma-runner.github.io/ .. _PhantomJS: http://phantomjs.org/ .. _ESLint: http://eslint.org/ Running Tests ============= Tests can be run in two ways: 1. Open /jasmine in a browser. The development server can be run with ``tox -e runserver`` from the horizon root directory. 2. ``tox -e npm`` from the horizon root directory. This runs Karma, so it will run all the tests against PhantomJS and generate coverage reports. The code linting job can be run with ``tox -e npm -- lint``, or ``tox -e npm -- lintq`` to show errors, but not warnings. To decipher where tests are failing it may be useful to use Jasmine in the browser to run individual tests to see where the tests are specifically breaking. To do this, navigate to your local horizon in the browser and add '/jasmine' to the end of the url. e.g: 'http://localhost:8000/jasmine'. Once you have the jasmine report you may click on the title of an individual test to re-run just that test. From here, you can also use chrome dev tools or similar to set breakpoints in the code by accessing the 'Sources' tab and clicking on lines of code where you wish to break the code. This will then show you the exact places where the code breaks. Coverage Reports ---------------- Our Karma setup includes a plugin to generate test coverage reports. When developing, be sure to check the coverage reports on the master branch and compare your development branch; this will help identify missing tests. To generate coverage reports, run ``tox -e npm``. The coverage reports can be found at ``cover/horizon/`` (framework tests) and ``cover/openstack_dashboard/`` (dashboard tests). Load ``/index.html`` in a browser to view the reports. Writing Tests ============= Jasmine uses suites and specs: * Suites begin with a call to ``describe``, which takes two parameters; a string and a function. The string is a name or title for the spec suite, whilst the function is a block that implements the suite. * Specs begin with a call to ``it``, which also takes a string and a function as parameters. The string is a name or title, whilst the function is a block with one or more expectations (``expect``) that test the state of the code. An expectation in Jasmine is an assertion that is either true or false; every expectation in a spec must be true for the spec to pass. ``.spec.js`` files can be handled manually or automatically. To use the automatic file discovery add:: AUTO_DISCOVER_STATIC_FILES = True to your enabled file. JS code for testing should use the extensions ``.mock.js`` and ``.spec.js``. You can read more about the functionality in the :ref:`auto_discover_static_files` section of the settings documentation. To manually add specs, add the following array and relevant file paths to your enabled file: .. code-block:: python ADD_JS_SPEC_FILES = [ ... 'path_to/my_angular_code.spec.js', ... ] Examples ======== .. Note:: The code below is just for example purposes, and may not be current in horizon. Ellipses (...) are used to represent code that has been removed for the sake of brevity. Example 1 - A reusable component in the **horizon** directory ------------------------------------------------------------- File tree: .. code-block:: none horizon/static/framework/widgets/modal ├── modal.controller.js ├── modal.module.js ├── modal.service.js └── modal.spec.js Lines added to ``horizon/test/jasmine/jasmine_tests.py``: .. code-block:: python class ServicesTests(test.JasmineTests): sources = [ ... 'framework/widgets/modal/modal.module.js', 'framework/widgets/modal/modal.controller.js', 'framework/widgets/modal/modal.service.js', ... ] specs = [ ... 'framework/widgets/modal/modal.spec.js', ... ] ``modal.spec.js``: .. code-block:: javascript ... (function() { "use strict"; describe('horizon.framework.widgets.modal module', function() { beforeEach(module('horizon.framework')); describe('simpleModalCtrl', function() { var scope; var modalInstance; var context; var ctrl; beforeEach(inject(function($controller) { scope = {}; modalInstance = { close: function() {}, dismiss: function() {} }; context = { what: 'is it' }; ctrl = $controller('simpleModalCtrl', { $scope: scope, $modalInstance: modalInstance, context: context }); })); it('establishes a controller', function() { expect(ctrl).toBeDefined(); }); it('sets context on the scope', function() { expect(scope.context).toBeDefined(); expect(scope.context).toEqual({ what: 'is it' }); }); it('sets action functions', function() { expect(scope.submit).toBeDefined(); expect(scope.cancel).toBeDefined(); }); it('makes submit close the modal instance', function() { expect(scope.submit).toBeDefined(); spyOn(modalInstance, 'close'); scope.submit(); expect(modalInstance.close.calls.count()).toBe(1); }); it('makes cancel close the modal instance', function() { expect(scope.cancel).toBeDefined(); spyOn(modalInstance, 'dismiss'); scope.cancel(); expect(modalInstance.dismiss).toHaveBeenCalledWith('cancel'); }); }); ... }); })(); Example 2 - Panel-specific code in the **openstack_dashboard** directory ------------------------------------------------------------------------ File tree: .. code-block:: none openstack_dashboard/static/dashboard/launch-instance/network/ ├── network.help.html ├── network.html ├── network.js ├── network.scss └── network.spec.js Lines added to ``openstack_dashboard/enabled/_10_project.py``: .. code-block:: python LAUNCH_INST = 'dashboard/launch-instance/' ADD_JS_FILES = [ ... LAUNCH_INST + 'network/network.js', ... ] ADD_JS_SPEC_FILES = [ ... LAUNCH_INST + 'network/network.spec.js', ... ] ``network.spec.js``: .. code-block:: javascript ... (function(){ 'use strict'; describe('Launch Instance Network Step', function() { describe('LaunchInstanceNetworkCtrl', function() { var scope; var ctrl; beforeEach(module('horizon.dashboard.project.workflow.launch-instance')); beforeEach(inject(function($controller) { scope = { model: { newInstanceSpec: {networks: ['net-a']}, networks: ['net-a', 'net-b'] } }; ctrl = $controller('LaunchInstanceNetworkCtrl', {$scope:scope}); })); it('has correct network statuses', function() { expect(ctrl.networkStatuses).toBeDefined(); expect(ctrl.networkStatuses.ACTIVE).toBeDefined(); expect(ctrl.networkStatuses.DOWN).toBeDefined(); expect(Object.keys(ctrl.networkStatuses).length).toBe(2); }); it('has correct network admin states', function() { expect(ctrl.networkAdminStates).toBeDefined(); expect(ctrl.networkAdminStates.UP).toBeDefined(); expect(ctrl.networkAdminStates.DOWN).toBeDefined(); expect(Object.keys(ctrl.networkStatuses).length).toBe(2); }); it('defines a multiple-allocation table', function() { expect(ctrl.tableLimits).toBeDefined(); expect(ctrl.tableLimits.maxAllocation).toBe(-1); }); it('contains its own labels', function() { expect(ctrl.label).toBeDefined(); expect(Object.keys(ctrl.label).length).toBeGreaterThan(0); }); it('contains help text for the table', function() { expect(ctrl.tableHelpText).toBeDefined(); expect(ctrl.tableHelpText.allocHelpText).toBeDefined(); expect(ctrl.tableHelpText.availHelpText).toBeDefined(); }); it('uses scope to set table data', function() { expect(ctrl.tableDataMulti).toBeDefined(); expect(ctrl.tableDataMulti.available).toEqual(['net-a', 'net-b']); expect(ctrl.tableDataMulti.allocated).toEqual(['net-a']); expect(ctrl.tableDataMulti.displayedAllocated).toEqual([]); expect(ctrl.tableDataMulti.displayedAvailable).toEqual([]); }); }); ... }); })(); horizon-13.0.3/doc/source/contributor/topics/angularjs.rst0000664000175000017500000003110113553660754023762 0ustar zuulzuul00000000000000.. _topics-angularjs: ===================== AngularJS Topic Guide ===================== .. Note:: This guide is a work in progress. It has been uploaded to encourage faster reviewing and code development in Angular, and to help the community standardize on a set of guidelines. There are notes inline on sections that are likely to change soon, and the docs will be updated promptly after any changes. Getting Started =============== The tooling for AngularJS testing and code linting relies on npm, the node package manager, and thus relies on Node.js. While it is not a prerequisite to developing with Horizon, it is advisable to install Node.js, either through `downloading `_ or `via a package manager `_. Once you have npm available on your system, run ``npm install`` from the horizon root directory. .. _js_code_style: Code Style ========== We currently use the `Angular Style Guide`_ by John Papa as reference material. When reviewing AngularJS code, it is helpful to link directly to the style guide to reinforce a point, e.g. https://github.com/johnpapa/angular-styleguide#style-y024 .. _Angular Style Guide: https://github.com/johnpapa/angular-styleguide ESLint ------ ESLint is a tool for identifying and reporting on patterns in your JS code, and is part of the automated tests run by Jenkins. You can run ESLint from the horizon root directory with ``tox -e npm -- lint``, or alternatively on a specific directory or file with ``eslint file.js``. Horizon includes a `.eslintrc` in its root directory, that is used by the local tests. An explanation of the options, and details of others you may want to use, can be found in the `ESLint user guide `_. Application Structure ===================== OpenStack Dashboard is an example of a Horizon-based Angular application. Other applications built on the Horizon framework can follow a similar structure. It is composed of two key Angular modules: **app.module.js** - The root of the application. Defines the modules required by the application, and includes modules from its pluggable dashboards. **framework.module.js** - Reusable Horizon components. It is one of the application dependencies. .. _js_file_structure: File Structure ============== Horizon has three kinds of angular code: 1. Specific to one dashboard in the OpenStack Dashboard application 2. Specific to the OpenStack Dashboard application, but reusable by multiple dashboards 3. Reusable by any application based on the Horizon framework When adding code to horizon, consider whether it is dashboard-specific or should be broken out as a reusable utility or widget. Code specific to one dashboard ------------------------------ Code that isn't shared beyond a single dashboard is placed in ``openstack_dashboard/dashboards/mydashboard/static``. Entire dashboards may be enabled or disabled using Horizon's plugin mechanism. Therefore no dashboards other than ``mydashboard`` can safely use this code. The ``openstack_dashboard/dashboards/static`` directory structure determines how the code is deployed and matches the module structure. For example: :: openstack_dashboard/dashboards/identity/static/dashboard/identity/ ├── identity.module.js ├── identity.module.spec.js └── identity.scss Because the code is in ``openstack_dashboard/dashboards/identity`` we know it is specific to just the ``identity`` dashboard and not used by any others. Code shared by multiple dashboards ---------------------------------- Views or utilities needed by multiple dashboards are placed in ``openstack_dashboard/static/app``. For example: :: openstack_dashboard/static/app/core/cloud-services/ ├── cloud-services.module.js ├── cloud-services.spec.js ├── hz-if-settings.directive.js └── hz-if-settings.directive.spec.js The ``cloud-services`` module is used by panels in multiple dashboards. It cannot be placed within ``openstack_dashboard/dashboards/mydashboard`` because disabling that one dashboard would break others. Therefore, it is included as part of the application ``core`` module. Code in ``app/`` is guaranteed to always be present, even if all other dashboards are disabled. Reusable components ------------------- Finally, components that are easily reused by any application are placed in ``horizon/static/framework/``. These do not contain URLs or business logic that is specific to any application (even the OpenStack Dashboard application). The modal directive ``horizon/static/framework/widgets/modal/`` is a good example of a reusable component. One folder per component ------------------------ Each component should have its own folder, with the code broken up into one JS component per file. (See `Single Responsibility `_ in the style guide). Each folder may include styling (``*.scss``), as well as templates (``*.html``) and tests (``*.spec.js``). You may also include examples, by appending ``.example``. For larger components, such as workflows with multiple steps, consider breaking the code down further. For example, the Launch Instance workflow, has one directory per step. See ``openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/`` SCSS files ---------- The top-level SCSS file in ``openstack_dashboard/static/app/_app.scss``. It includes any styling that is part of the application ``core`` and may be reused by multiple dashboards. SCSS files that are specific to a particular dashboard are linked to the application by adding them in that dashboard's enabled file. For example, `_1920_project_containers_panel.py` is the enabled file for the ``Project`` dashboard's ``Container`` panel and includes: :: ADD_SCSS_FILES = [ 'dashboard/project/containers/_containers.scss', ] Styling files are hierarchical, and include any direct child SCSS files. For example, ``project.scss`` would includes the ``workflow`` SCSS file, which in turn includes any launch instance styling: :: @import "workflow/workflow"; This allows the application to easily include all needed styling, simply by including a dashboard's top-level SCSS file. Module Structure ================ Horizon Angular modules use names that map to the source code directory structure. This provides namespace isolation for modules and services, which makes dependency injection clearer. It also reduces code conflicts where two different modules define a module, service or constant of the same name. For example: :: openstack_dashboard/dashboards/identity/static/dashboard/identity/ └── identity.module.js The preferred Angular module name in this example is ``horizon.dashboard.identity``. The ``horizon`` part of the module name maps to the ``static`` directory and indicates this is a ``horizon`` based application. ``dashboard.identity`` maps to folders that are created within ``static``. This allows a direct mapping between the angular module name of ``horizon.dashboard.identity`` and the source code directory of ``static\dashboard\identity``. Services and constants within these modules should all start with their module name to avoid dependency injection collisions. For example: :: $provide.constant('horizon.dashboard.identity.basePath', path); Directives do not require the module name but are encouraged to begin with the ``hz`` prefix. For example: :: .directive('hzMagicSearchBar', hzMagicSearchBar); Finally, each module lists its child modules as a dependency. This allows the root module to be included by an application, which will automatically define all child modules. For example: :: .module('horizon.framework', [ 'horizon.framework.conf', 'horizon.framework.util', 'horizon.framework.widgets' ]) ``horizon.framework`` declares a dependency on ``horizon.framework.widgets``, which declares dependencies on each individual widget. This allows the application to access any widget, simply by depending on the top-level ``horizon.framework`` module. Testing ======= 1. Open /jasmine in a browser. The development server can be run with ``tox -e runserver`` from the horizon root directory; by default, this will run the development server at ``http://localhost:8000``. 2. ``tox -e npm`` from the horizon root directory. The code linting job can be run with ``tox -e npm -- lint``. If there are many warnings, you can also use ``tox -e npm -- lintq`` to see only errors and ignore warnings. For more detailed information, see :ref:`topics-javascript-testing`. Translation (Internationalization and Localization) =================================================== See :ref:`making_strings_translatable` for information on the translation architecture and how to ensure your code is translatable. Creating your own panel ======================= .. Note:: This section will be extended as standard practices are adopted upstream. Currently, it may be useful to look at the Project Images Panel as a complete reference. Since Newton, it is Angular by default (set to True in the ANGULAR_FEATURES dict in ``settings.py``). You may track all the changes made to the Image Panel `here `__ .. Note:: Currently, Angular module names must still be manually declared with ``ADD_ANGULAR_MODULES``, even when using automatic file discovery. This section serves as a basic introduction to writing your own panel for horizon, using AngularJS. A panel may be included with the plugin system, or it may be part of the upstream horizon project. Upstream -------- JavaScript files can be discovered automatically, handled manually, or a mix of the two. Where possible, use the automated mechanism. To use the automatic functionality, add:: AUTO_DISCOVER_STATIC_FILES = True to your enabled file (``enabled/.py``). To make this possible, you need to follow some structural conventions: - Static files should be put in a ``static/`` folder, which should be found directly under the folder for the dashboard/panel/panel groups Python package. - JS code that defines an Angular module should be in a file with extension of ``.module.js``. - JS code for testing should be named with extension of ``.mock.js`` and of ``.spec.js``. - Angular templates should have extension of ``.html``. You can read more about the functionality in the :ref:`auto_discover_static_files` section of the settings documentation. To manually add files, add the following arrays and file paths to the enabled file: :: ADD_JS_FILES = [ ... 'path-to/my-angular-code.js', ... ] ADD_JS_SPEC_FILES = [ ... 'path-to/my-angular-code.spec.js', ... ] ADD_ANGULAR_MODULES = [ ... 'my.angular.code', ... ] Plugins ------- Add a new panel/ panel group/ dashboard (See :ref:`tutorials-dashboard`). JavaScript file inclusion is the same as the Upstream process. To include external stylesheets, you must ensure that ``ADD_SCSS_FILES`` is defined in your enabled file, and add the relevant filepath, as below: :: ADD_SCSS_FILES = [ ... 'path-to/my-styles.scss', ... ] .. Note:: We highly recommend using a single SCSS file for your plugin. SCSS supports nesting with @import, so if you have multiple files (i.e. per panel styling) it is best to import them all into one, and include that single file. You can read more in the `SASS documentation`_. .. _SASS documentation: http://sass-lang.com/documentation/file.SASS_REFERENCE.html#import Schema Forms ============ `JSON schemas`_ are used to define model layout and then `angular-schema-form`_ is used to create forms from that schema. Horizon adds some functionality on top of that to make things even easier through ``ModalFormService`` which will open a modal with the form inside. A very simple example:: var schema = { type: "object", properties: { name: { type: "string", minLength: 2, title: "Name", description: "Name or alias" }, title: { type: "string", enum: ['dr','jr','sir','mrs','mr','NaN','dj'] } } }; var model = {name: '', title: ''}; var config = { title: gettext('Create Container'), schema: schema, form: ['*'], model: model }; ModalFormService.open(config).then(submit); // returns a promise function submit() { // do something with model.name and model.title } .. _JSON schemas: http://json-schema.org/ .. _angular-schema-form: https://github.com/json-schema-form/angular-schema-form/blob/master/docs/index.md horizon-13.0.3/doc/source/contributor/topics/index.rst0000664000175000017500000000047313553660754023113 0ustar zuulzuul00000000000000============ Topic Guides ============ Information on how to work with specific areas of Horizon can be found in the following topic guides. .. toctree:: :maxdepth: 1 workflows tables policy microversion_support angularjs testing javascript_testing styling translation packaging horizon-13.0.3/doc/source/contributor/topics/translation.rst0000664000175000017500000002540213553660754024341 0ustar zuulzuul00000000000000====================== Translation in Horizon ====================== What is the point of translating my code? ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ You introduced an awesome piece of code and revel in your glorious accomplishment. Suddenly your world comes crashing down when a core hands you a -1 because your code is not translated. What gives? If you are writing software for a global audience, you must ensure that it is translated so that other people around the world are able to use it. Adding translation to your code is not that hard and a requirement for horizon. If you are interested in contributing translations, you may want to investigate `Zanata `_ and the `upstream translations `_. You can visit the internationalization project IRC channel **#openstack-i18n**, if you need further assistance. Overview and Architecture ~~~~~~~~~~~~~~~~~~~~~~~~~ You can skip this section if you are only interested in learning how to use translation. This section explains the two main components to translation: message extraction and message substitution. We will briefly go over what each one does for translation as a whole. Message Extraction ------------------ .. The source can be found at: https://drive.google.com/open?id=0B5nlaOV3OEj5MTNMdG9WV1RiVEU .. figure:: ../../images/message_extraction.png :width: 80% :align: center :alt: Message extraction diagram Message extraction is the process of collecting translatable strings from the code. The diagram above shows the flow of how messages are extracted and then translated. Lets break this up into steps we can follow: 1. The first step is to mark untranslated strings so that the extractor is able to locate them. Refer to the guide below on how to use translation and what these markers look like. 2. Once marked, we can then run ``tox -e manage -- extract_messages``, which searches the codebase for these markers and extracts them into a Portable Object Template (POT) file. In horizon, we extract from both the ``horizon`` folder and the ``openstack_dashboard`` folder. We use the AngularJS extractor for JavaScript and HTML files and the Django extractor for Python and Django templates; both extractors are Babel plugins. 3. To update the .po files, you can run ``tox -e manage -- update_catalog`` to update the .po file for every language, or you can specify a specific language to update like this: ``tox -e manage -- update_catalog de``. This is useful if you want to add a few extra translatabale strings for a downstream customisation. .. note:: When pushing code upstream, the only requirement is to mark the strings correctly. All creation of POT and PO files is handled by a daily upstream job. Further information can be found in the `translation infrastructure documentation `_. Message Substitution -------------------- .. The source can be found at: https://drive.google.com/open?id=0B5nlaOV3OEj5UHZCNmFGT0lPQVU .. figure:: ../../images/message_substitution.png :width: 80% :align: center :alt: Message substitution diagram Message substitution is not the reverse process of message extraction. The process is entirely different. Lets walk through this process. * Remember those markers we talked about earlier? Most of them are functions like gettext or one of its variants. This allows the function to serve a dual purpose - acting as a marker and also as a replacer. * In order for translation to work properly, we need to know the user’s locale. In horizon, the user can specify the locale using the Settings panel. Once we know the locale, we know which Portable Object (PO) file to use. The PO file is the file we received from translators in the message extraction process. The gettext functions that we wrapped our code around are then able to replace the untranslated strings with the translated one by using the untranslated string as the message id. * For client-side translation, Django embeds a corresponding Django message catalog. Javascript code on the client can use this catalog to do string replacement similar to how server-side translation works. If you are setting up a project and need to know how to make it translatable, please refer to `this guide `_. .. _making_strings_translatable: Making strings translatable ~~~~~~~~~~~~~~~~~~~~~~~~~~~ To make your strings translatable, you need to mark it so that horizon can locate and extract it into a POT file. When a user from another locale visits your page, your string is replaced with the correct translated version. In Django --------- To translate a string, simply wrap one of the gettext variants around the string. The examples below show you how to do translation for various scenarios, such as interpolation, contextual markers and translation comments. .. code-block:: python from django.utils.translation import pgettext from django.utils.translation import ugettext as _ from django.utils.translation import ungettext class IndexView(request): # Single example _("Images") # Plural example ungettext( "there is %(count)d object", "there are %(count)d objects", count) % { "count": count } # Interpolated example mood = "wonderful" output = _("Today is %(mood)s.") % mood # Contextual markers pgettext("the month name", "May") # Translators: This message appears as a comment for translators! ugettext("Welcome translators.") .. note:: In the example above, we imported ``ugettext`` as ``_``. This is a common alias for gettext or any of its variants. In Django templates ------------------- To use translation in your template, make sure you load the i18n module. To translate a line of text, use the ``trans`` template tag. If you need to translate a block of text, use the ``blocktrans`` template tag. Sometimes, it is helpful to provide some context via the ``comment`` template tag. There a number of other tags and filters at your disposal should you need to use them. For more information, see the `Django docs `_ .. code-block:: django {% extends 'base.html' %} {% load i18n %} {% block title %} {% trans "Images" %} {% endblock %} {% block main %} {% comment %}Translators: Images is an OpenStack resource{% endcomment %} {% blocktrans with amount=images.length %} There are {{ amount }} images available for display. {% endblocktrans %} {% endblock %} In JavaScript ------------- The Django message catalog is injected into the front-end. The gettext function is available as a global function so you can just use it directly. If you are writing AngularJS code, we prefer that you use the gettext service, which is essentially a wrapper around the gettext function. .. code-block:: javascript Angular .module(myModule) .controller(myCtrl); myCtrl.$inject = [ "horizon.framework.util.i18n.gettext" ]; function myCtrl(gettext) { var translated = gettext("Images"); } .. warning:: For localization in AngularJS files, use the AngularJS service ``horizon.framework.util.i18n.gettext``. Ensure that the injected dependency is named ``gettext`` or ``nggettext``. If you do not do this, message extraction will not work properly! In AngularJS templates ----------------------- To use translation in your AngularJS template, use the translate tag or the translate filter. Note that we are using `angular-gettext `_ for message substitution but not for message extraction. .. code-block:: html Directive example
Attribute example
Interpolated {{example}}
{$ ‘Filter example’| translate $} This is a bad example because it contains HTML and makes it harder to translate. However, it will still translate. .. note:: The annotations in the example above are guaranteed to work. However, not all of the angular-gettext annotations are supported because we wrote our own custom babel extractor. If you need support for the annotations, ask on IRC in the **#openstack-horizon** room or report a bug. Also note that you should avoid embedding HTML fragments in your texts because it makes it harder to translate. Use your best judgement if you absolutely need to include HTML. .. _pseudo_translation: Pseudo translation tool ~~~~~~~~~~~~~~~~~~~~~~~ The pseudo translation tool can be used to verify that code is ready to be translated. The pseudo tool replaces a language's translation with a complete, fake translation. Then you can verify that your code properly displays fake translations to validate that your code is ready for translation. Running the pseudo translation tool ----------------------------------- #. Make sure your .pot files are up to date .. code-block:: console $ tox -e manage -- extract_messages #. Run the pseudo tool to create pseudo translations. This example replaces the German translation with a pseudo translation .. code-block:: console $ tox -e manage -- update_catalog de --pseudo #. Compile the catalog .. code-block:: console $ tox -e manage -- compilemessages #. Run your development server. .. code-block:: console $ tox -e runserver #. Log in and change to the language you pseudo translated. It should look weird. More specifically, the translatable segments are going to start and end with a bracket and they are going to have some added characters. For example, "Log In" will become "[~Log In~您好яшçあ]" This is useful because you can inspect for the following, and consider if your code is working like it should: * If you see a string in English it's not translatable. Should it be? * If you see brackets next to each other that might be concatenation. Concatenation can make quality translations difficult or impossible. See `Use string formatting variables, never perform string concatenation `_ for additional information. * If there is unexpected wrapping/truncation there might not be enough space for translations. * If you see a string in the proper translated language, it comes from an external source. (That's not bad, just sometimes useful to know) * If you get new crashes, there is probably a bug. Don't forget to remove any pseudo translated ``.pot`` or ``.po`` files. Those should **not** be submitted for review. horizon-13.0.3/doc/source/contributor/topics/tables.rst0000664000175000017500000003541713553660754023264 0ustar zuulzuul00000000000000.. _topics-datatables: ====================== DataTables Topic Guide ====================== Horizon provides the :mod:`horizon.tables` module to provide a convenient, reusable API for building data-driven displays and interfaces. The core components of this API fall into three categories: ``DataTables``, ``Actions``, and ``Class-based Views``. .. seealso:: For a detailed API information check out the :ref:`ref-datatables`. Tables ====== The majority of interface in a dashboard-style interface ends up being tabular displays of the various resources the dashboard interacts with. The :class:`~horizon.tables.DataTable` class exists so you don't have to reinvent the wheel each time. Creating your own tables ------------------------ Creating a table is fairly simple: #. Create a subclass of :class:`~horizon.tables.DataTable`. #. Define columns on it using :class:`~horizon.tables.Column`. #. Create an inner ``Meta`` class to contain the special options for this table. #. Define any actions for the table, and add them to :attr:`~horizon.tables.DataTableOptions.table_actions` or :attr:`~horizon.tables.DataTableOptions.row_actions`. Examples of this can be found in any of the ``tables.py`` modules included in the reference modules under ``horizon.dashboards``. Connecting a table to a view ---------------------------- Once you've got your table set up the way you like it, the next step is to wire it up to a view. To make this as easy as possible Horizon provides the :class:`~horizon.tables.DataTableView` class-based view which can be subclassed to display your table with just a couple lines of code. At its simplest, it looks like this:: from horizon import tables from .tables import MyTable class MyTableView(tables.DataTableView): table_class = MyTable template_name = "my_app/my_table_view.html" def get_data(self): return my_api.objects.list() In the template you would just need to include the following to render the table:: {{ table.render }} That's it! Easy, right? Actions ======= Actions comprise any manipulations that might happen on the data in the table or the table itself. For example, this may be the standard object CRUD, linking to related views based on the object's id, filtering the data in the table, or fetching updated data when appropriate. When actions get run -------------------- There are two points in the request-response cycle in which actions can take place; prior to data being loaded into the table, and after the data is loaded. When you're using one of the pre-built class-based views for working with your tables the pseudo-workflow looks like this: #. The request enters view. #. The table class is instantiated without data. #. Any "preemptive" actions are checked to see if they should run. #. Data is fetched and loaded into the table. #. All other actions are checked to see if they should run. #. If none of the actions have caused an early exit from the view, the standard response from the view is returned (usually the rendered table). The benefit of the multi-step table instantiation is that you can use preemptive actions which don't need access to the entire collection of data to save yourself on processing overhead, API calls, etc. Basic actions ------------- At their simplest, there are three types of actions: actions which act on the data in the table, actions which link to related resources, and actions that alter which data is displayed. These correspond to :class:`~horizon.tables.Action`, :class:`~horizon.tables.LinkAction`, and :class:`~horizon.tables.FilterAction`. Writing your own actions generally starts with subclassing one of those action classes and customizing the designated attributes and methods. Shortcut actions ---------------- There are several common tasks for which Horizon provides pre-built shortcut classes. These include :class:`~horizon.tables.BatchAction`, and :class:`~horizon.tables.DeleteAction`. Each of these abstracts away nearly all of the boilerplate associated with writing these types of actions and provides consistent error handling, logging, and user-facing interaction. It is worth noting that ``BatchAction`` and ``DeleteAction`` are extensions of the standard ``Action`` class. Some ``BatchAction`` or ``DeleteAction`` classes may cause some unrecoverable results, like deleted images or unrecoverable instances. It may be helpful to specify specific help_text to explain the concern to the user, such as "Deleted images are not recoverable". Preemptive actions ------------------ Action classes which have their :attr:`~horizon.tables.Action.preempt` attribute set to ``True`` will be evaluated before any data is loaded into the table. As such, you must be careful not to rely on any table methods that require data, such as :meth:`~horizon.tables.DataTable.get_object_display` or :meth:`~horizon.tables.DataTable.get_object_by_id`. The advantage of preemptive actions is that you can avoid having to do all the processing, API calls, etc. associated with loading data into the table for actions which don't require access to that information. Policy checks on actions ------------------------ The :attr:`~horizon.tables.Action.policy_rules` attribute, when set, will validate access to the action using the policy rules specified. The attribute is a list of scope/rule pairs. Where the scope is the service type defining the rule and the rule is a rule from the corresponding service policy.json file. The format of :attr:`horizon.tables.Action.policy_rules` looks like:: (("identity", "identity:get_user"),) Multiple checks can be made for the same action by merely adding more tuples to the list. The policy check will use information stored in the session about the user and the result of :meth:`~horizon.tables.Action.get_policy_target` (which can be overridden in the derived action class) to determine if the user can execute the action. If the user does not have access to the action, the action is not added to the table. If :attr:`~horizon.tables.Action.policy_rules` is not set, no policy checks will be made to determine if the action should be visible and will be displayed solely based on the result of :meth:`~horizon.tables.Action.allowed`. For more information on policy based Role Based Access Control see :ref:`topics-policy`. Table Cell filters (decorators) =============================== DataTable displays lists of objects in rows and object attributes in cell. How should we proceed, if we want to decorate some column, e.g. if we have column ``memory`` which returns a number e.g. 1024, and we want to show something like 1024.00 GB inside table? Decorator pattern ----------------- The clear anti-pattern is defining the new attributes on object like ``ram_float_format_2_gb`` or to tweak a DataTable in any way for displaying purposes. The cleanest way is to use ``filters``. Filters are decorators, following GOF ``Decorator pattern``. This way ``DataTable logic`` and ``displayed object logic`` are correctly separated from ``presentation logic`` of the object inside of the various tables. And therefore the filters are reusable in all tables. Filter function --------------- Horizon DatablesTable takes a tuple of pointers to filter functions or anonymous lambda functions. When displaying a ``Cell``, ``DataTable`` takes ``Column`` filter functions from left to right, using the returned value of the previous function as a parameter of the following function. Then displaying the returned value of the last filter function. A valid filter function takes one parameter and returns the decorated value. So e.g. these are valid filter functions :: # Filter function. def add_unit(v): return str(v) + " GB" # Or filter lambda function. lambda v: str(v) + " GB" # This is also a valid definition of course, although for the change of the # unit parameter, function has to be wrapped by lambda # (e.g. floatformat function example below). def add_unit(v, unit="GB"): return str(v) + " " + unit Using filters in DataTable column --------------------------------- DataTable takes tuple of filter functions, so e.g. this is valid decorating of a value with float format and with unit :: ram = tables.Column( "ram", verbose_name=_('Memory'), filters=(lambda v: floatformat(v, 2), add_unit)) It always takes tuple, so using only one filter would look like this :: filters=(lambda v: floatformat(v, 2),) The decorated parameter doesn't have to be only a string or number, it can be anything e.g. list or an object. So decorating of object, that has attributes value and unit would look like this :: ram = tables.Column( "ram", verbose_name=_('Memory'), filters=(lambda x: getattr(x, 'value', '') + " " + getattr(x, 'unit', ''),)) Available filters ----------------- There are a load of filters, that can be used, defined in django already: https://github.com/django/django/blob/master/django/template/defaultfilters.py So it's enough to just import and use them, e.g. :: from django.template import defaultfilters as filters # code omitted filters=(filters.yesno, filters.capfirst) from django.template.defaultfilters import timesince from django.template.defaultfilters import title # code omitted filters=(parse_isotime, timesince) Inline editing ============== Table cells can be easily upgraded with in-line editing. With use of django.form.Field, we are able to run validations of the field and correctly parse the data. The updating process is fully encapsulated into table functionality, communication with the server goes through AJAX in JSON format. The javascript wrapper for inline editing allows each table cell that has in-line editing available to: #. Refresh itself with new data from the server. #. Display in edit mode. #. Send changed data to server. #. Display validation errors. There are basically 3 things that need to be defined in the table in order to enable in-line editing. Fetching the row data --------------------- Defining an ``get_data`` method in a class inherited from ``tables.Row``. This method takes care of fetching the row data. This class has to be then defined in the table Meta class as ``row_class = UpdateRow``. Example:: class UpdateRow(tables.Row): # this method is also used for automatic update of the row ajax = True def get_data(self, request, project_id): # getting all data of all row cells project_info = api.keystone.tenant_get(request, project_id, admin=True) return project_info Updating changed cell data (DEPRECATED) --------------------------------------- Define an ``update_cell`` method in the class inherited from ``tables.UpdateAction``. This method takes care of saving the data of the table cell. There can be one class for every cell thanks to the ``cell_name`` parameter. This class is then defined in tables column as ``update_action=UpdateCell``, so each column can have its own updating method. Example:: class UpdateCell(tables.UpdateAction): def allowed(self, request, project, cell): # Determines whether given cell or row will be inline editable # for signed in user. return api.keystone.keystone_can_edit_project() def update_cell(self, request, project_id, cell_name, new_cell_value): # in-line update project info try: project_obj = datum # updating changed value by new value setattr(project_obj, cell_name, new_cell_value) # sending new attributes back to API api.keystone.tenant_update( request, project_id, name=project_obj.name, description=project_obj.description, enabled=project_obj.enabled) except Conflict: # Validation error for naming conflict, raised when user # choose the existing name. We will raise a # ValidationError, that will be sent back to the client # browser and shown inside of the table cell. message = _("This name is already taken.") raise ValidationError(message) except: # Other exception of the API just goes through standard # channel exceptions.handle(request, ignore=True) return False return True Defining a form_field for each Column that we want to be in-line edited. ------------------------------------------------------------------------ Form field should be ``django.form.Field`` instance, so we can use django validations and parsing of the values sent by POST (in example validation ``required=True`` and correct parsing of the checkbox value from the POST data). Form field can be also ``django.form.Widget`` class, if we need to just display the form widget in the table and we don't need Field functionality. Then connecting ``UpdateRow`` and ``UpdateCell`` classes to the table. Example:: class TenantsTable(tables.DataTable): # Adding html text input for inline editing, with required validation. # HTML form input will have a class attribute tenant-name-input, we # can define here any HTML attribute we need. name = tables.Column('name', verbose_name=_('Name'), form_field=forms.CharField(required=True), form_field_attributes={'class':'tenant-name-input'}, update_action=UpdateCell) # Adding html textarea without required validation. description = tables.Column(lambda obj: getattr(obj, 'description', None), verbose_name=_('Description'), form_field=forms.CharField( widget=forms.Textarea(), required=False), update_action=UpdateCell) # Id will not be inline edited. id = tables.Column('id', verbose_name=_('Project ID')) # Adding html checkbox, that will be shown inside of the table cell with # label enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True, form_field=forms.BooleanField( label=_('Enabled'), required=False), update_action=UpdateCell) class Meta(object): name = "tenants" verbose_name = _("Projects") # Connection to UpdateRow, so table can fetch row data based on # their primary key. row_class = UpdateRow horizon-13.0.3/doc/source/contributor/contributing.rst0000664000175000017500000006772713553660754023231 0ustar zuulzuul00000000000000================== Contributing Guide ================== First and foremost, thank you for wanting to contribute! It's the only way open source works! Before you dive into writing patches, here are some of the basics: * Project page: http://launchpad.net/horizon * Bug tracker: https://bugs.launchpad.net/horizon * Source code: https://github.com/openstack/horizon * Code review: https://review.openstack.org/#q,status:open+project:openstack/horizon,n,z * Continuous integration: * Jenkins: https://jenkins.openstack.org * Zuul: http://status.openstack.org/zuul * IRC Channel: #openstack-horizon on Freenode. Making Contributions ==================== Getting Started --------------- We'll start by assuming you've got a working checkout of the repository (if not then please see the :ref:`quickstart`). Second, you'll need to take care of a couple administrative tasks: #. Create an account on Launchpad. #. Sign the `OpenStack Contributor License Agreement`_ and follow the associated instructions to verify your signature. #. Join the `Horizon Developers`_ team on Launchpad. #. Follow the `instructions for setting up git-review`_ in your development environment. Whew! Got all that? Okay! You're good to go. .. _`OpenStack Contributor License Agreement`: http://wiki.openstack.org/CLA .. _`Horizon Developers`: https://launchpad.net/~horizon .. _`instructions for setting up git-review`: https://docs.openstack.org/infra/manual/developers.html#development-workflow Ways To Contribute ------------------ The easiest way to get started with Horizon's code is to pick a bug on Launchpad that interests you, and start working on that. Bugs tagged as ``low-hanging-fruit`` are a good place to start. Alternatively, if there's an OpenStack API feature you would like to see implemented in Horizon feel free to try building it. If those are too big, there are lots of great ways to get involved without plunging in head-first: * Report bugs, triage new tickets, and review old tickets on the `bug tracker`_. * Propose ideas for improvements via `Launchpad Blueprints`_, via the mailing list on the project page, or on IRC. * Write documentation! * Write unit tests for untested code! * Help improve the `User Experience Design`_ or contribute to the `Persona Working Group`_. .. _`bug tracker`: https://bugs.launchpad.net/horizon .. _`Launchpad Blueprints`: https://blueprints.launchpad.net/horizon .. _`User Experience Design`: https://wiki.openstack.org/wiki/UX#Getting_Started .. _`Persona Working Group`: https://wiki.openstack.org/wiki/Personas Choosing Issues To Work On -------------------------- In general, if you want to write code, there are three cases for issues you might want to work on: #. Confirmed bugs #. Approved blueprints (features) #. New bugs you've discovered If you have an idea for a new feature that isn't in a blueprint yet, it's a good idea to write the blueprint first, so you don't end up writing a bunch of code that may not go in the direction the community wants. For bugs, open the bug first, but if you can reproduce the bug reliably and identify its cause then it's usually safe to start working on it. However, getting independent confirmation (and verifying that it's not a duplicate) is always a good idea if you can be patient. After You Write Your Patch -------------------------- Once you've made your changes, there are a few things to do: * Make sure the unit tests and linting tasks pass by running ``tox`` * Take a look at your patch in API profiler, i.e. how it impacts the performance. See `Profiling Pages`_. * Make sure your code is ready for translation: See :ref:`pseudo_translation`. * Make sure your code is up-to-date with the latest master: ``git pull --rebase`` * Finally, run ``git review`` to upload your changes to Gerrit for review. The Horizon core developers will be notified of the new review and will examine it in a timely fashion, either offering feedback or approving it to be merged. If the review is approved, it is sent to Jenkins to verify the unit tests pass and it can be merged cleanly. Once Jenkins approves it, the change will be merged to the master repository and it's time to celebrate! Profiling Pages --------------- In the Ocata release of Horizon a new "OpenStack Profiler" panel was introduced. Once it is enabled and all prerequisites are set up, you can see which API calls Horizon actually makes when rendering a specific page. To re-render the page while profiling it, you'll need to use the "Profile" dropdown menu located in the top right corner of the screen. In order to be able to use "Profile" menu, the following steps need to be completed: #. Enable the Developer dashboard by copying ``_9001_developer.py`` from ``openstack_dashboard/contrib/developer/enabled/`` to ``openstack_dashboard/local/enabled/``. #. Copy ``openstack_dashboard/local/local_settings.d/_9030_profiler_settings.py.example`` to ``openstack_dashboard/local/local_settings.d/_9030_profiler_settings.py`` #. Copy ``openstack_dashboard/contrib/developer/enabled/_9030_profiler.py`` to ``openstack_dashboard/local/enabled/_9030_profiler.py``. #. To support storing profiler data on server-side, MongoDB cluster needs to be installed on your Devstack host (default configuration), see `Installing MongoDB`_. Then, change the ``bindIp`` key in ``/etc/mongod.conf`` to ``0.0.0.0`` and invoke ``sudo service mongod restart``. #. Collect and compress static assets with ``python manage.py collectstatic -c`` and ``python manage.py compress`` #. Restart the web server. #. The "Profile" drop-down menu should appear in the top-right corner, you are ready to profile your pages! .. _installing MongoDB: https://docs.mongodb.com/manual/tutorial/install-mongodb-on-ubuntu/#install-mongodb-community-edition Etiquette ========= The community's guidelines for etiquette are fairly simple: * Treat everyone respectfully and professionally. * If a bug is "in progress" in the bug tracker, don't start working on it without contacting the author. Try on IRC, or via the launchpad email contact link. If you don't get a response after a reasonable time, then go ahead. Checking first avoids duplicate work and makes sure nobody's toes get stepped on. * If a blueprint is assigned, even if it hasn't been started, be sure you contact the assignee before taking it on. These larger issues often have a history of discussion or specific implementation details that the assignee may be aware of that you are not. * Please don't re-open tickets closed by a core developer. If you disagree with the decision on the ticket, the appropriate solution is to take it up on IRC or the mailing list. * Give credit where credit is due; if someone helps you substantially with a piece of code, it's polite (though not required) to thank them in your commit message. Code Style ========== As a project, Horizon adheres to code quality standards. Python ------ We follow PEP8_ for all our Python code, and use ``pep8.py`` (available via the shortcut ``tox -e pep8``) to validate that our code meets proper Python style guidelines. .. _PEP8: http://www.python.org/dev/peps/pep-0008/ Django ------ Additionally, we follow `Django's style guide`_ for templates, views, and other miscellany. .. _Django's style guide: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/ JavaScript ---------- The following standards are divided into required and recommended sections. Our main goal in establishing these best practices is to have code that is reliable, readable, and maintainable. Required ~~~~~~~~ **Reliable** * The code has to work on the stable and latest versions of Firefox, Chrome, Safari, and Opera web browsers, and on Microsoft Internet Explorer 11 and later. * If you turned compression off during development via ``COMPRESS_ENABLED = False`` in local_settings.py, re-enable compression and test your code before submitting. * Use ``===`` as opposed to ``==`` for equality checks. The ``==`` will do a type cast before comparing, which can lead to unwanted results. .. note:: If typecasting is desired, explicit casting is preferred to keep the meaning of your code clear. * Keep document reflows to a minimum. DOM manipulation is expensive, and can become a performance issue. If you are accessing the DOM, make sure that you are doing it in the most optimized way. One example is to build up a document fragment and then append the fragment to the DOM in one pass instead of doing multiple smaller DOM updates. * Use “strict”, enclosing each JavaScript file inside a self-executing function. The self-executing function keeps the strict scoped to the file, so its variables and methods are not exposed to other JavaScript files in the product. .. Note :: Using strict will throw exceptions for common coding errors, like accessing global vars, that normally are not flagged. Example: :: (function(){ 'use strict'; // code... })(); * Use ``forEach`` | ``each`` when looping whenever possible. AngularJS and jQuery both provide for each loops that provide both iteration and scope. AngularJS: :: angular.forEach(objectToIterateOver, function(value, key) { // loop logic }); jQuery: :: $.each(objectToIterateOver, function(key, value) { // loop logic }); * Do not put variables or functions in the global namespace. There are several reasons why globals are bad, one being that all JavaScript included in an application runs in the same scope. The issue with that is if another script has the same method or variable names they overwrite each other. * Always put ``var`` in front of your variables. Not putting ``var`` in front of a variable puts that variable into the global space, see above. * Do not use ``eval( )``. The eval (expression) evaluates the expression passed to it. This can open up your code to security vulnerabilities and other issues. * Do not use '``with`` object {code}'. The ``with`` statement is used to access properties of an object. The issue with ``with`` is that its execution is not consistent, so by reading the statement in the code it is not always clear how it is being used. **Readable & Maintainable** * Give meaningful names to methods and variables. * Avoid excessive nesting. * Avoid HTML and CSS in JS code. HTML and CSS belong in templates and stylesheets respectively. For example: * In our HTML files, we should focus on layout. 1. Reduce the small/random `` {% endblock %} In your dashboard's own base template ``openstack_dashboard/dashboards/ my_custom_dashboard/templates/my_custom_dashboard/base.html`` override ``block js`` with inclusion of dashboard's own ``_scripts.html``:: {% block js %} {% include "my_custom_dashboard/_scripts.html" %} {% endblock %} The result is a single compressed js file consisting both Horizon and dashboard's custom scripts. Additionally, some marketing and analytics scripts require you to place them within the page's tag. To do this, place them within the ``horizon/_custom_head_js.html`` file. Similar to the ``_scripts.html`` file mentioned above, you may link to an existing file:: or you can paste your script directly in the file, being sure to use appropriate tags:: Customizing Meta Attributes =========================== To add custom metadata attributes to your project's base template, include them in the ``horizon/_custom_meta.html`` file. The contents of this file will be inserted into the page's just after the default Horizon meta tags. .. _Font Awesome: https://fortawesome.github.io/Font-Awesome/ horizon-13.0.3/doc/source/configuration/index.rst0000664000175000017500000000024013553660754022077 0ustar zuulzuul00000000000000=================== Configuration Guide =================== .. toctree:: :maxdepth: 1 settings pluggable_panels customizing themes branding horizon-13.0.3/doc/source/configuration/branding.rst0000664000175000017500000001270613553660754022566 0ustar zuulzuul00000000000000================ Branding Horizon ================ As of the Liberty release, Horizon has begun to conform more strictly to Bootstrap standards in an effort to embrace more responsive web design as well as alleviate the future need to re-brand new functionality for every release. Supported Components -------------------- The following components, organized by release, are the only ones that make full use of the Bootstrap theme architecture. * 8.0.0 (Liberty) * `Top Navbar`_ * `Side Nav`_ * `Pie Charts`_ * 9.0.0 (Mitaka) * Tables_ * `Bar Charts`_ * Login_ * Tabs_ * Alerts_ * Checkboxes_ Step 1 ------ The first step needed to create a custom branded theme for Horizon is to create a custom Bootstrap theme. There are several tools to aid in this. Some of the more useful ones include: - `Bootswatchr`_ - `Paintstrap`_ - `Bootstrap`_ .. note:: Bootstrap uses LESS by default, but we use SCSS. All of the above tools will provide the ``variables.less`` file, which will need to be converted to ``_variables.scss`` Top Navbar ---------- The top navbar in Horizon now uses a native Bootstrap ``navbar``. There are a number of variables that can be used to customize this element. Please see the **Navbar** section of your variables file for specifics on what can be set: any variables that use ``navbar-default``. It is important to also note that the navbar now uses native Bootstrap dropdowns, which are customizable with variables. Please see the **Dropdowns** section of your variables file. The top navbar is now responsive on smaller screens. When the window size hits your ``$screen-sm`` value, the topbar will compress into a design that is better suited for small screens. Side Nav -------- The side navigation component has been refactored to use the native Stacked Pills element from Bootstrap. See **Pills** section of your variables file for specific variables to customize. Charts ------ Pie Charts ~~~~~~~~~~ Pie Charts are SVG elements. SVG elements allow CSS customizations for only a basic element's look and feel (i.e. colors, size). Since there is no native element in Bootstrap specifically for pie charts, the look and feel of the charts are inheriting from other elements of the theme. Please see ``_pie_charts.scss`` for specifics. .. _Bar Charts: Bar Charts ~~~~~~~~~~ Bar Charts can be either a Bootstrap Progress Bar or an SVG element. Either implementation will use the Bootstrap Progress Bar styles. The SVG implementation will not make use of the customized Progress Bar height though, so it is recommended that Bootstrap Progress Bars are used whenever possible. Please see ``_bar_charts.scss`` for specifics on what can be customized for SVGs. See the **Progress bars** section of your variables file for specific variables to customize. Tables ------ The standard Django tables now make use of the native Bootstrap table markup. See **Tables** section of your variables file for variables to customize. The standard Bootstrap tables will be borderless by default. If you wish to add a border, like the ``default`` theme, see ``openstack_dashboard/themes/default/horizon/components/_tables.scss`` .. _Login: Login ----- Login Splash Page ~~~~~~~~~~~~~~~~~ The login splash page now uses a standard Bootstrap panel in its implementation. See the **Panels** section in your variables file to variables to easily customize. Modal Login ~~~~~~~~~~~ The modal login experience, as used when switching regions, uses a standard Bootstrap dialog. See the **Modals** section of your variables file for specific variables to customize. Tabs ---- The standard tabs make use of the native Bootstrap tab markup. See **Tabs** section of your variables file for variables to customize. Alerts ------ Alerts use the basic Bootstrap brand colors. See **Colors** section of your variables file for specifics. Checkboxes ---------- Horizon uses icon fonts to represent checkboxes. In order to customize this, you simply need to override the standard scss. For an example of this, see themes/material/static/horizon/components/_checkboxes.scss Bootswatch and Material Design ------------------------------ `Bootswatch`_ is a collection of free themes for Bootstrap and is now available for use in Horizon. In order to showcase what can be done to enhance an existing Bootstrap theme, Horizon now includes a secondary theme, roughly based on `Google's Material Design`_ called ``material``. Bootswatch's **Paper** is a simple Bootstrap implementation of Material Design and is used by ``material``. Bootswatch provides a number of other themes, that once Horizon is fully theme compliant, will allow easy toggling and customizations for darker or accessibility driven experiences. Development Tips ---------------- When developing a new theme for Horizon, it is required that the dynamically generated `static` directory be cleared after each change and the server restarted. This is not always ideal. If you wish to develop and not have to restart the server each time, it is recommended that you configure your development environment to not run in OFFLINE mode. Simply verify the following settings in your local_settings.py:: COMPRESS_OFFLINE = False COMPRESS_ENABLED = False .. _Bootstrap: http://getbootstrap.com/customize/ .. _Bootswatch: http://bootswatch.com .. _Bootswatchr: http://bootswatchr.com/create#! .. _Paintstrap: http://paintstrap.com .. _Google's Material Design: https://www.google.com/design/spec/material-design/introduction.html horizon-13.0.3/doc/source/configuration/settings.rst0000664000175000017500000021621013553660754022636 0ustar zuulzuul00000000000000.. _install-settings: ================== Settings Reference ================== Introduction ============ Horizon's settings broadly fall into three categories: * `General Settings`_: this includes visual settings like the modal backdrop style, bug url and theme configuration, as well as settings that affect every service, such as page sizes on API requests. * `Service-specific Settings`_: Many services that Horizon consumes, such as Nova and Neutron, don't advertise their capabilities via APIs, so Horizon carries configuration for operators to enable or disable many items. This section covers all settings that are specific to a single service. * `Django Settings`_, which are common to all Django applications. The only ones documented here are those that Horizon alters by default; however, you should read the `Django settings documentation `_ to see the other options available to you. To modify your settings, you have two options: * **Preferred:** Add ``.py`` settings snippets to the ``openstack_dashboard/local/local_settings.d/`` directory. Several example files (appended with ``.example``) can be found there. These must start with an underscore, and are evaluated alphabetically, after ``local_settings.py``. * Modify your ``openstack_dashboard/local/local_settings.py``. There is an file found at ``openstack_dashboard/local/local_settings.py.example``. General Settings ================ .. _angular_features: ANGULAR_FEATURES ---------------- .. versionadded:: 10.0.0(Newton) Default: .. code-block:: python { 'images_panel': True, 'key_pairs_panel': True, 'flavors_panel': False, 'domains_panel': False, 'users_panel': False, 'groups_panel': False, 'roles_panel': True } A dictionary of currently available AngularJS features. This allows simple toggling of legacy or rewritten features, such as new panels, workflows etc. .. note:: If you toggle ``domains_panel`` to ``True``, you also need to enable the setting of `OPENSTACK_KEYSTONE_DEFAULT_DOMAIN`_ and add `OPENSTACK_KEYSTONE_DEFAULT_DOMAIN`_ to `REST_API_REQUIRED_SETTINGS`_. API_RESULT_LIMIT ---------------- .. versionadded:: 2012.1(Essex) Default: ``1000`` The maximum number of objects (e.g. Swift objects or Glance images) to display on a single page before providing a paging element (a "more" link) to paginate results. API_RESULT_PAGE_SIZE -------------------- .. versionadded:: 2012.2(Folsom) Default: ``20`` Similar to ``API_RESULT_LIMIT``. This setting controls the number of items to be shown per page if API pagination support for this exists. .. _available_themes: AVAILABLE_THEMES ---------------- .. versionadded:: 9.0.0(Mitaka) Default: .. code-block:: python AVAILABLE_THEMES = [ ('default', 'Default', 'themes/default'), ('material', 'Material', 'themes/material'), ] This setting tells Horizon which themes to use. A list of tuples which define multiple themes. The tuple format is ``('{{ theme_name }}', '{{ theme_label }}', '{{ theme_path }}')``. The ``theme_name`` is the name used to define the directory which the theme is collected into, under ``/{{ THEME_COLLECTION_DIR }}``. It also specifies the key by which the selected theme is stored in the browser's cookie. The ``theme_label`` is the user-facing label that is shown in the theme picker. The theme picker is only visible if more than one theme is configured, and shows under the topnav's user menu. By default, the ``theme path`` is the directory that will serve as the static root of the theme and the entire contents of the directory is served up at ``/{{ THEME_COLLECTION_DIR }}/{{ theme_name }}``. If you wish to include content other than static files in a theme directory, but do not wish that content to be served up, then you can create a sub directory named ``static``. If the theme folder contains a sub-directory with the name ``static``, then ``static/custom/static`` will be used as the root for the content served at ``/static/custom``. The static root of the theme folder must always contain a _variables.scss file and a _styles.scss file. These must contain or import all the bootstrap and horizon specific variables and styles which are used to style the GUI. For example themes, see: /horizon/openstack_dashboard/themes/ Horizon ships with two themes configured. 'default' is the default theme, and 'material' is based on Google's Material Design. .. _custom_theme_path: CUSTOM_THEME_PATH ----------------- .. versionadded:: 2015.1(Kilo) .. deprecated:: 9.0.0(Mitaka) Themes are now controlled by `AVAILABLE_THEMES`_. Default: ``"themes/default"`` This setting tells Horizon to use a directory as a custom theme. By default, this directory will serve as the static root of the theme and the entire contents of the directory will be served up at ``/static/custom``. If you wish to include content other than static files in a theme directory, but do not wish that content to be served up, then you can create a sub directory named ``static``. If the theme folder contains a sub-directory with the name ``static``, then ``static/custom/static`` will be used as the root for the content served at ``/static/custom``. The static root of the theme folder must always contain a _variables.scss file and a _styles.scss file. These must contain or import all the bootstrap and horizon specific variables and styles which are used to style the GUI. For example themes, see: /horizon/openstack_dashboard/themes/ Horizon ships with one alternate theme based on Google's Material Design. To use the alternate theme, set your CUSTOM_THEME_PATH to ``themes/material``. DEFAULT_THEME ------------- .. versionadded:: 9.0.0(Mitaka) Default: ``"default"`` This setting tells Horizon which theme to use if the user has not yet selected a theme through the theme picker and therefore set the cookie value. This value represents the ``theme_name`` key that is used from `AVAILABLE_THEMES`_. To use this setting, the theme must also be configured inside of ``AVAILABLE_THEMES``. Your default theme must be configured as part of `SELECTABLE_THEMES`_. If it is not, then your ``DEFAULT_THEME`` will default to the first theme in ``SELECTABLE_THEMES``. DEFAULT_THEME_PATH ------------------ .. versionadded:: 8.0.0(Liberty) .. deprecated:: 9.0.0(Mitaka) Themes are now controlled by `AVAILABLE_THEMES`_. Default: ``"themes/default"`` This setting allows Horizon to collect an additional theme during static collection and be served up via /static/themes/default. This is useful if CUSTOM_THEME_PATH inherits from another theme (like 'default'). If DEFAULT_THEME_PATH is the same as CUSTOM_THEME_PATH, then collection is skipped and /static/themes will not exist. DISALLOW_IFRAME_EMBED --------------------- .. versionadded:: 8.0.0(Liberty) Default: ``True`` This setting can be used to defend against Clickjacking and prevent Horizon from being embedded within an iframe. Legacy browsers are still vulnerable to a Cross-Frame Scripting (XFS) vulnerability, so this option allows extra security hardening where iframes are not used in deployment. When set to true, a ``"frame-buster"`` script is inserted into the template header that prevents the web page from being framed and therefore defends against clickjacking. For more information see: http://tinyurl.com/anticlickjack .. note:: If your deployment requires the use of iframes, you can set this setting to ``False`` to exclude the frame-busting code and allow iframe embedding. DROPDOWN_MAX_ITEMS ------------------ .. versionadded:: 2015.1(Kilo) Default: ``30`` This setting sets the maximum number of items displayed in a dropdown. Dropdowns that limit based on this value need to support a way to observe the entire list. FILTER_DATA_FIRST ----------------- .. versionadded:: 10.0.0(Newton) Default: .. code-block:: python { 'admin.instances': False, 'admin.images': False, 'admin.networks': False, 'admin.routers': False, 'admin.volumes': False } If the dict key-value is True, when the view loads, an empty table will be rendered and the user will be asked to provide a search criteria first (in case no search criteria was provided) before loading any data. Examples: Override the dict: .. code-block:: python { 'admin.instances': True, 'admin.images': True, 'admin.networks': False, 'admin.routers': False, 'admin.volumes': False } Or, if you want to turn this on for an specific panel/view do: .. code-block:: python FILTER_DATA_FIRST['admin.instances'] = True HORIZON_CONFIG -------------- A dictionary of some Horizon configuration values. These are primarily separated for historic design reasons. Default: .. code-block:: python HORIZON_CONFIG = { 'user_home': 'openstack_dashboard.views.get_user_home', 'ajax_queue_limit': 10, 'auto_fade_alerts': { 'delay': 3000, 'fade_duration': 1500, 'types': [ 'alert-success', 'alert-info' ] }, 'bug_url': None, 'help_url': "https://docs.openstack.org/", 'exceptions': { 'recoverable': exceptions.RECOVERABLE, 'not_found': exceptions.NOT_FOUND, 'unauthorized': exceptions.UNAUTHORIZED }, 'modal_backdrop': 'static', 'angular_modules': [], 'js_files': [], 'js_spec_files': [], 'external_templates': [], } ajax_poll_interval ~~~~~~~~~~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: ``2500`` How frequently resources in transition states should be polled for updates, expressed in milliseconds. ajax_queue_limit ~~~~~~~~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: ``10`` The maximum number of simultaneous AJAX connections the dashboard may try to make. This is particularly relevant when monitoring a large number of instances, volumes, etc. which are all actively trying to update/change state. angular_modules ~~~~~~~~~~~~~~~ .. versionadded:: 2014.2(Juno) Default: ``[]`` A list of AngularJS modules to be loaded when Angular bootstraps. These modules are added as dependencies on the root Horizon application ``horizon``. auto_fade_alerts ~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) Default: .. code-block:: python { 'delay': 3000, 'fade_duration': 1500, 'types': [] } If provided, will auto-fade the alert types specified. Valid alert types include: ['alert-default', 'alert-success', 'alert-info', 'alert-warning', 'alert-danger'] Can also define the delay before the alert fades and the fade out duration. bug_url ~~~~~~~ .. versionadded:: 9.0.0(Mitaka) Default: ``None`` If provided, a "Report Bug" link will be displayed in the site header which links to the value of this setting (ideally a URL containing information on how to report issues). disable_password_reveal ~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``False`` Setting this to True will disable the reveal button for password fields, including on the login form. exceptions ~~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: .. code-block:: python { 'unauthorized': [], 'not_found': [], 'recoverable': [] } A dictionary containing classes of exceptions which Horizon's centralized exception handling should be aware of. Based on these exception categories, Horizon will handle the exception and display a message to the user. help_url ~~~~~~~~ .. versionadded:: 2012.2(Folsom) Default: ``None`` If provided, a "Help" link will be displayed in the site header which links to the value of this setting (ideally a URL containing help information). js_files ~~~~~~~~ .. versionadded:: 2014.2(Juno) Default: ``[]`` A list of javascript source files to be included in the compressed set of files that are loaded on every page. This is needed for AngularJS modules that are referenced in ``angular_modules`` and therefore need to be include in every page. js_spec_files ~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``[]`` A list of javascript spec files to include for integration with the Jasmine spec runner. Jasmine is a behavior-driven development framework for testing JavaScript code. modal_backdrop ~~~~~~~~~~~~~~ .. versionadded:: 2014.2(Kilo) Default: ``"static"`` Controls how bootstrap backdrop element outside of modals looks and feels. Valid values are ``"true"`` (show backdrop element outside the modal, close the modal after clicking on backdrop), ``"false"`` (do not show backdrop element, do not close the modal after clicking outside of it) and ``"static"`` (show backdrop element outside the modal, do not close the modal after clicking on backdrop). password_autocomplete ~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.1(Grizzly) Default: ``"off"`` Controls whether browser autocompletion should be enabled on the login form. Valid values are ``"on"`` and ``"off"``. password_validator ~~~~~~~~~~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: .. code-block:: python { 'regex': '.*', 'help_text': _("Password is not accepted") } A dictionary containing a regular expression which will be used for password validation and help text which will be displayed if the password does not pass validation. The help text should describe the password requirements if there are any. This setting allows you to set rules for passwords if your organization requires them. simple_ip_management ~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.1(Grizzly) Default: ``True`` Enable or disable simplified floating IP address management. "Simple" floating IP address management means that the user does not ever have to select the specific IP addresses they wish to use, and the process of allocating an IP and assigning it to an instance is one-click. The "advanced" floating IP management allows users to select the floating IP pool from which the IP should be allocated and to select a specific IP address when associating one with an instance. .. note:: Currently "simple" floating IP address management is not compatible with Neutron. There are two reasons for this. First, Neutron does not support the default floating IP pool at the moment. Second, a Neutron floating IP can be associated with each VIF and we need to check whether there is only one VIF for an instance to enable simple association support. user_home ~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: ``settings.LOGIN_REDIRECT_URL`` This can be either a literal URL path (such as the default), or Python's dotted string notation representing a function which will evaluate what URL a user should be redirected to based on the attributes of that user. MESSAGES_PATH ------------- .. versionadded:: 9.0.0(Mitaka) Default: ``None`` The absolute path to the directory where message files are collected. When the user logins to horizon, the message files collected are processed and displayed to the user. Each message file should contain a JSON formatted data and must have a .json file extension. For example: .. code-block:: python { "level": "info", "message": "message of the day here" } Possible values for level are: ``success``, ``info``, ``warning`` and ``error``. NG_TEMPLATE_CACHE_AGE --------------------- .. versionadded:: 10.0.0(Newton) Angular Templates are cached using this duration (in seconds) if `DEBUG`_ is set to ``False``. Default value is ``2592000`` (or 30 days). OPENSTACK_API_VERSIONS ---------------------- .. versionadded:: 2013.2(Havana) Default: .. code-block:: python { "data-processing": 1.1, "identity": 3, "volume": 2, "compute": 2 } Overrides for OpenStack API versions. Use this setting to force the OpenStack dashboard to use a specific API version for a given service API. .. note:: The version should be formatted as it appears in the URL for the service API. For example, the identity service APIs have inconsistent use of the decimal point, so valid options would be "2.0" or "3". For example: .. code-block:: python OPENSTACK_API_VERSIONS = { "data-processing": 1.1, "identity": 3, "volume": 2, "compute": 2 } OPENSTACK_CLOUDS_YAML_NAME -------------------------- .. versionadded:: 12.0.0(Pike) Default: ``"openstack"`` The name of the entry to put into the user's clouds.yaml file. OPENSTACK_CLOUDS_YAML_PROFILE ----------------------------- .. versionadded:: 12.0.0(Pike) Default: ``None`` If set, the name of the `vendor profile`_ from `os-client-config`_. .. _vendor profile: https://docs.openstack.org/os-client-config/latest/user/vendor-support.html .. _os-client-config: https://docs.openstack.org/os-client-config/latest/ OPENSTACK_ENDPOINT_TYPE ----------------------- .. versionadded:: 2012.1(Essex) Default: ``"publicURL"`` A string which specifies the endpoint type to use for the endpoints in the Keystone service catalog. The default value for all services except for identity is ``"publicURL"`` . The default value for the identity service is ``"internalURL"``. OPENSTACK_HOST -------------- .. versionadded:: 2012.1(Essex) Default: ``"127.0.0.1"`` The hostname of the Keystone server used for authentication if you only have one region. This is often the **only** setting that needs to be set for a basic deployment. If you have multiple regions you should use the `AVAILABLE_REGIONS`_ setting instead. OPENSTACK_PROFILER ------------------ .. versionadded:: 11.0.0(Ocata) Default: ``{"enabled": False}`` Various settings related to integration with osprofiler library. Since it is a developer feature, it starts as disabled. To enable it, more than a single ``"enabled"`` key should be specified. Additional keys that should be specified in that dictionary are: * ``"keys"`` is a list of strings, which are secret keys used to encode/decode the profiler data contained in request headers. Encryption is used for security purposes, other OpenStack components that are expected to profile themselves with osprofiler using the data from the request that Horizon initiated must share a common set of keys with the ones in Horizon config. List of keys is used so that security keys could be changed in non-obtrusive manner for every component in the cloud. Example: ``"keys": ["SECRET_KEY", "MORE_SECRET_KEY"]``. For more details see `osprofiler documentation`_. * ``"notifier_connection_string"`` is a url to which trace messages are sent by Horizon. For other components it is usually the only URL specified in config, because other components act mostly as traces producers. Example: ``"notifier_connection_string": "mongodb://%s' % OPENSTACK_HOST"``. * ``"receiver_connection_string"`` is a url from which traces are retrieved by Horizon, needed because Horizon is not only the traces producer, but also a consumer. Having 2 settings which usually contain the same value is legacy feature from older versions of osprofiler when OpenStack components could use oslo.messaging for notifications and the trace client used ceilometer as a receiver backend. By default Horizon uses the same URL pointing to a MongoDB cluster for both purposes, since ceilometer was too slow for using with UI. Example: ``"receiver_connection_string": "mongodb://%s" % OPENSTACK_HOST``. .. _osprofiler documentation: https://docs.openstack.org/osprofiler/latest/user/integration.html#how-to-initialize-profiler-to-get-one-trace-across-all-services OPENSTACK_SSL_CACERT -------------------- .. versionadded:: 2013.2(Havana) Default: ``None`` When unset or set to ``None`` the default CA certificate on the system is used for SSL verification. When set with the path to a custom CA certificate file, this overrides use of the default system CA certificate. This custom certificate is used to verify all connections to openstack services when making API calls. OPENSTACK_SSL_NO_VERIFY ----------------------- .. versionadded:: 2012.2(Folsom) Default: ``False`` Disable SSL certificate checks in the OpenStack clients (useful for self-signed certificates). OPERATION_LOG_ENABLED --------------------- .. versionadded:: 10.0.0(Newton) Default: ``False`` This setting can be used to enable logging of all operations carried out by users of Horizon. The format of the logs is configured via `OPERATION_LOG_OPTIONS`_ .. note:: If you use this feature, you need to configure the logger setting like a outputting path for operation log in ``local_settings.py``. OPERATION_LOG_OPTIONS --------------------- .. versionadded:: 10.0.0(Newton) .. versionchanged:: 12.0.0(Pike) Added ``ignored_urls`` parameter and added ``%(client_ip)s`` to ``format`` Default: .. code-block:: python { 'mask_fields': ['password'], 'target_methods': ['POST'], 'ignored_urls': ['/js/', '/static/', '^/api/'], 'format': ("[%(domain_name)s] [%(domain_id)s] [%(project_name)s]" " [%(project_id)s] [%(user_name)s] [%(user_id)s] [%(request_scheme)s]" " [%(referer_url)s] [%(request_url)s] [%(message)s] [%(method)s]" " [%(http_status)s] [%(param)s]"), } This setting controls the behavior of the operation log. * ``mask_fields`` is a list of keys of post data which should be masked from the point of view of security. Fields like ``password`` should be included. The fields specified in ``mask_fields`` are logged as ``********``. * ``target_methods`` is a request method which is logged to a operation log. The valid methods are ``POST``, ``GET``, ``PUT``, ``DELETE``. * ``ignored_urls`` is a list of request URLs to be hidden from a log. * ``format`` defines the operation log format. Currently you can use the following keywords. The default value contains all keywords. * ``%(client_ip)s`` * ``%(domain_name)s`` * ``%(domain_id)s`` * ``%(project_name)s`` * ``%(project_id)s`` * ``%(user_name)s`` * ``%(user_id)s`` * ``%(request_scheme)s`` * ``%(referer_url)s`` * ``%(request_url)s`` * ``%(message)s`` * ``%(method)s`` * ``%(http_status)s`` * ``%(param)s`` OVERVIEW_DAYS_RANGE ------------------- .. versionadded:: 10.0.0(Newton) Default:: ``1`` When set to an integer N (as by default), the start date in the Overview panel meters will be today minus N days. This setting is used to limit the amount of data fetched by default when rendering the Overview panel. If set to ``None`` (which corresponds to the behavior in past Horizon versions), the start date will be from the beginning of the current month until the current date. The legacy behaviour is not recommended for large deployments as Horizon suffers significant lag in this case. POLICY_CHECK_FUNCTION --------------------- .. versionadded:: 2013.2(Havana) Default:: ``openstack_auth.policy.check`` This value should not be changed, although removing it or setting it to ``None`` would be a means to bypass all policy checks. POLICY_DIRS ----------- .. versionadded:: 13.0.0(Queens) Default: .. code-block:: python { 'compute': ['nova_policy.d'], 'volume': ['cinder_policy.d'], } Specifies a list of policy directories per service types. The directories are relative to `POLICY_FILES_PATH`_. Services whose additional policies are defined here must be defined in `POLICY_FILES`_ too. Otherwise, additional policies specified in ``POLICY_DIRS`` are not loaded. .. note:: ``cinder_policy.d`` and ``nova_policy.d`` are registered by default to maintain policies which have ben dropped from nova and cinder but horizon still uses. We recommend not to drop them. POLICY_FILES ------------ .. versionadded:: 2013.2(Havana) Default: .. code-block:: python { 'compute': 'nova_policy.json', 'identity': 'keystone_policy.json', 'image': 'glance_policy.json', 'network': 'neutron_policy.json', 'volume': 'cinder_policy.json', } This should essentially be the mapping of the contents of `POLICY_FILES_PATH`_ to service types. When policy.json files are added to `POLICY_FILES_PATH`_, they should be included here too. POLICY_FILES_PATH ----------------- .. versionadded:: 2013.2(Havana) Default: ``os.path.join(ROOT_PATH, "conf")`` Specifies where service based policy files are located. These are used to define the policy rules actions are verified against. REST_API_REQUIRED_SETTINGS -------------------------- .. versionadded:: 2014.2(Kilo) Default: .. code-block:: python [ 'OPENSTACK_HYPERVISOR_FEATURES', 'LAUNCH_INSTANCE_DEFAULTS', 'OPENSTACK_IMAGE_FORMATS', 'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN' ] This setting allows you to expose configuration values over Horizons internal REST API, so that the AngularJS panels can access them. Please be cautious about which values are listed here (and thus exposed on the frontend) SELECTABLE_THEMES --------------------- .. versionadded:: 12.0.0(Pike) Default: ``AVAILABLE_THEMES`` This setting tells Horizon which themes to expose to the user as selectable in the theme picker widget. This value defaults to all themes configured in `AVAILABLE_THEMES`_, but a brander may wish to simply inherit from an existing theme and not allow that parent theme to be selected by the user. ``SELECTABLE_THEMES`` takes the exact same format as ``AVAILABLE_THEMES``. SESSION_TIMEOUT --------------- .. versionadded:: 2013.2(Havana) Default: ``"3600"`` This SESSION_TIMEOUT is a method to supercede the token timeout with a shorter horizon session timeout (in seconds). So if your token expires in 60 minutes, a value of 1800 will log users out after 30 minutes. SHOW_KEYSTONE_V2_RC -------------------- .. versionadded:: 13.0.0(Queens) Default: ``True`` Controls whether the keystone v2 openrc file is accessable from the user menu and the api access panel. THEME_COLLECTION_DIR -------------------- .. versionadded:: 9.0.0(Mitaka) Default: ``"themes"`` This setting tells Horizon which static directory to collect the available themes into, and therefore which URL points to the theme collection root. For example, the default theme would be accessible via ``/{{ STATIC_URL }}/themes/default``. THEME_COOKIE_NAME ----------------- .. versionadded:: 9.0.0(Mitaka) Default: ``"theme"`` This setting tells Horizon in which cookie key to store the currently set theme. The cookie expiration is currently set to a year. USER_MENU_LINKS ----------------- .. versionadded:: 13.0.0(Queens) Default:: [ {'name': _('OpenStack RC File v2'), 'icon_classes': ['fa-download', ], 'url': 'horizon:project:api_access:openrcv2', 'external': False, }, {'name': _('OpenStack RC File v3'), 'icon_classes': ['fa-download', ], 'url': 'horizon:project:api_access:openrc', 'external': False, } ] This setting controls the additional links on the user drop down menu. A list of dictionaries defining all of the links should be provided. This defaults to the standard OpenStack RC files. Each dictionary should contain these values: name The name of the link url Either the reversible Django url name or an absolute url external True for absolute URLs, False for django urls. icon_classes A list of classes for the icon next to the link. If 'None' or an empty list is provided a download icon will show WEBROOT ------- .. versionadded:: 2015.1(Kilo) Default: ``"/"`` Specifies the location where the access to the dashboard is configured in the web server. For example, if you're accessing the Dashboard via ``https:///dashboard``, you would set this to ``"/dashboard/"``. .. note:: Additional settings may be required in the config files of your webserver of choice. For example to make ``"/dashboard/"`` the web root in Apache, the ``"sites-available/horizon.conf"`` requires a couple of additional aliases set:: Alias /dashboard/static %HORIZON_DIR%/static Alias /dashboard/media %HORIZON_DIR%/openstack_dashboard/static Apache also requires changing your WSGIScriptAlias to reflect the desired path. For example, you'd replace ``/`` with ``/dashboard`` for the alias. Service-specific Settings ========================= The following settings inform the OpenStack Dashboard of information about the other OpenStack projects which are part of this cloud and control the behavior of specific dashboards, panels, API calls, etc. Cinder ------ OPENSTACK_CINDER_FEATURES ~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2014.2(Juno) Default: ``{'enable_backup': False}`` A dictionary of settings which can be used to enable optional services provided by cinder. Currently only the backup service is available. Glance ------ CREATE_IMAGE_DEFAULTS ~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 12.0.0(Pike) Default: .. code-block:: python { 'image_visibility': "public", } A dictionary of default settings for create image modal. The ``image_visibility`` setting specifies the default visibility option. Valid values are ``"public"`` and ``"private"``. By default, the visibility option is public on create image modal. If it's set to ``"private"``, the default visibility option is private. HORIZON_IMAGES_ALLOW_UPLOAD ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.1(Grizzly) .. deprecated:: 10.0.0(Newton) Use `HORIZON_IMAGES_UPLOAD_MODE`_ instead. Default: ``True`` If set to ``False``, this setting disables **local** uploads to prevent filling up the disk on the dashboard server since uploads to the Glance image store service tend to be particularly large - in the order of hundreds of megabytes to multiple gigabytes. The setting is marked as deprecated and will be removed in P or later release. It is superseded by the setting HORIZON_IMAGES_UPLOAD_MODE. Until the removal the ``False`` value of HORIZON_IMAGES_ALLOW_UPLOAD overrides the value of HORIZON_IMAGES_UPLOAD_MODE. .. note:: This will not disable image creation altogether, as this setting does not affect images created by specifying an image location (URL) as the image source. HORIZON_IMAGES_UPLOAD_MODE ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Default: ``"legacy"`` Valid values are ``"direct"``, ``"legacy"`` (default) and ``"off"``. ``"off"`` disables the ability to upload images via Horizon. It is equivalent to setting ``False`` on the deprecated setting ``HORIZON_IMAGES_ALLOW_UPLOAD``. ``legacy`` enables local file upload by piping the image file through the Horizon's web-server. It is equivalent to setting ``True`` on the deprecated setting ``HORIZON_IMAGES_ALLOW_UPLOAD``. ``direct`` sends the image file directly from the web browser to Glance. This bypasses Horizon web-server which both reduces network hops and prevents filling up Horizon web-server's filesystem. ``direct`` is the preferred mode, but due to the following requirements it is not the default. The ``direct`` setting requires a modern web browser, network access from the browser to the public Glance endpoint, and CORS support to be enabled on the Glance API service. Without CORS support, the browser will forbid the PUT request to a location different than the Horizon server. To enable CORS support for Glance API service, you will need to edit [cors] section of glance-api.conf file (see `here`_ how to do it). Set `allowed_origin` to the full hostname of Horizon web-server (e.g. http:///dashboard) and restart glance-api process. .. _here: https://docs.openstack.org/oslo.middleware/latest/reference/cors.html#configuration-for-oslo-config .. note:: To maintain the compatibility with the deprecated HORIZON_IMAGES_ALLOW_UPLOAD setting, neither ``"direct"``, nor ``"legacy"`` modes will have an effect if HORIZON_IMAGES_ALLOW_UPLOAD is set to ``False`` - as if HORIZON_IMAGES_UPLOAD_MODE was set to ``"off"`` itself. When HORIZON_IMAGES_ALLOW_UPLOAD is set to ``True``, all three modes are considered, as if HORIZON_IMAGES_ALLOW_UPLOAD setting was removed. IMAGE_CUSTOM_PROPERTY_TITLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2014.1(Icehouse) Default: .. code-block:: python { "architecture": _("Architecture"), "kernel_id": _("Kernel ID"), "ramdisk_id": _("Ramdisk ID"), "image_state": _("Euca2ools state"), "project_id": _("Project ID"), "image_type": _("Image Type") } Used to customize the titles for image custom property attributes that appear on image detail pages. IMAGE_RESERVED_CUSTOM_PROPERTIES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2014.2(Juno) Default: ``[]`` A list of image custom property keys that should not be displayed in the Update Metadata tree. This setting can be used in the case where a separate panel is used for managing a custom property or if a certain custom property should never be edited. IMAGES_ALLOW_LOCATION ~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Default: ``False`` If set to ``True``, this setting allows users to specify an image location (URL) as the image source when creating or updating images. Depending on the Glance version, the ability to set an image location is controlled by policies and/or the Glance configuration. Therefore IMAGES_ALLOW_LOCATION should only be set to ``True`` if Glance is configured to allow specifying a location. This setting has no effect when the Keystone catalog doesn't contain a Glance v2 endpoint. IMAGES_LIST_FILTER_TENANTS ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.1(Grizzly) Default: ``None`` A list of dictionaries to add optional categories to the image fixed filters in the Images panel, based on project ownership. Each dictionary should contain a `tenant` attribute with the project id, and optionally a `text` attribute specifying the category name, and an `icon` attribute that displays an icon in the filter button. The icon names are based on the default icon theme provided by Bootstrap. Example: .. code-block:: python [{'text': 'Official', 'tenant': '27d0058849da47c896d205e2fc25a5e8', 'icon': 'fa-check'}] OPENSTACK_IMAGE_BACKEND ~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) Default: .. code-block:: python { 'image_formats': [ ('', _('Select format')), ('aki', _('AKI - Amazon Kernel Image')), ('ami', _('AMI - Amazon Machine Image')), ('ari', _('ARI - Amazon Ramdisk Image')), ('docker', _('Docker')), ('iso', _('ISO - Optical Disk Image')), ('qcow2', _('QCOW2 - QEMU Emulator')), ('raw', _('Raw')), ('vdi', _('VDI')), ('vhd', _('VHD')), ('vmdk', _('VMDK')) ] } Used to customize features related to the image service, such as the list of supported image formats. Keystone -------- AUTHENTICATION_PLUGINS ~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: .. code-block:: python [ 'openstack_auth.plugin.password.PasswordPlugin', 'openstack_auth.plugin.token.TokenPlugin' ] A list of authentication plugins to be used. In most cases, there is no need to configure this. AUTHENTICATION_URLS ~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``['openstack_auth.urls']`` A list of modules from which to collate authentication URLs from. The default option adds URLs from the django-openstack-auth module however others will be required for additional authentication mechanisms. AVAILABLE_REGIONS ~~~~~~~~~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: ``None`` A list of tuples which define multiple regions. The tuple format is ``('http://{{ keystone_host }}:5000/v3', '{{ region_name }}')``. If any regions are specified the login form will have a dropdown selector for authenticating to the appropriate region, and there will be a region switcher dropdown in the site header when logged in. You should also define `OPENSTACK_KEYSTONE_URL`_ to indicate which of the regions is the default one. DEFAULT_SERVICE_REGIONS ~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 12.0.0(Pike) Default: ``{}`` The default service region is set on a per-endpoint basis, meaning that once the user logs into some Keystone endpoint, if a default service region is defined for it in this setting and exists within Keystone catalog, it will be set as the initial service region in this endpoint. By default it is an empty dictionary because upstream can neither predict service region names in a specific deployment, nor tell whether this behavior is desired. The key of the dictionary is a full url of a Keystone endpoint with version suffix, the value is a region name. Example: .. code-block:: python DEFAULT_SERVICE_REGIONS = { OPENSTACK_KEYSTONE_URL: 'RegionOne' } ENABLE_CLIENT_TOKEN ~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Default: ``True`` This setting will Enable/Disable access to the Keystone Token to the browser. ENFORCE_PASSWORD_CHECK ~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``False`` This setting will display an 'Admin Password' field on the Change Password form to verify that it is indeed the admin logged-in who wants to change the password. KEYSTONE_PROVIDER_IDP_ID ~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 11.0.0(Ocata) Default: ``"localkeystone"`` This ID is only used for comparison with the service provider IDs. This ID should not match any service provider IDs. KEYSTONE_PROVIDER_IDP_NAME ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 11.0.0(Ocata) Default: ``"Local Keystone"`` The Keystone Provider drop down uses Keystone to Keystone federation to switch between Keystone service providers. This sets the display name for the Identity Provider (dropdown display name). OPENSTACK_KEYSTONE_ADMIN_ROLES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``["admin"]`` The list of roles that have administrator privileges in this OpenStack installation. This check is very basic and essentially only works with keystone v2.0 and v3 with the default policy file. The setting assumes there is a common ``admin`` like role(s) across services. Example uses of this setting are: * to rename the ``admin`` role to ``cloud-admin`` * allowing multiple roles to have administrative privileges, like ``["admin", "cloud-admin", "net-op"]`` OPENSTACK_KEYSTONE_BACKEND ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2012.1(Essex) Default: .. code-block:: python { 'name': 'native', 'can_edit_user': True, 'can_edit_group': True, 'can_edit_project': True, 'can_edit_domain': True, 'can_edit_role': True, } A dictionary containing settings which can be used to identify the capabilities of the auth backend for Keystone. If Keystone has been configured to use LDAP as the auth backend then set ``can_edit_user`` and ``can_edit_project`` to ``False`` and name to ``"ldap"``. OPENSTACK_KEYSTONE_DEFAULT_DOMAIN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) Default: ``"Default"`` Overrides the default domain used when running on single-domain model with Keystone V3. All entities will be created in the default domain. OPENSTACK_KEYSTONE_DEFAULT_ROLE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2011.3(Diablo) Default: ``"_member_"`` The name of the role which will be assigned to a user when added to a project. This value must correspond to an existing role name in Keystone. In general, the value should match the ``member_role_name`` defined in ``keystone.conf``. OPENSTACK_KEYSTONE_DOMAIN_CHOICES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 12.0.0(Pike) Default: .. code-block:: python ( ('Default', 'Default'), ) If `OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN`_ is enabled, this option can be used to set the available domains to choose from. This is a list of pairs whose first value is the domain name and the second is the display name. OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 12.0.0(Pike) Default: ``False`` Set this to True if you want available domains displayed as a dropdown menu on the login screen. It is strongly advised NOT to enable this for public clouds, as advertising enabled domains to unauthenticated customers irresponsibly exposes private information. This should only be used for private clouds where the dashboard sits behind a corporate firewall. OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 9.0.0(Mitaka) Default: ``False`` Set this to True to enable panels that provide the ability for users to manage Identity Providers (IdPs) and establish a set of rules to map federation protocol attributes to Identity API attributes. This extension requires v3.0+ of the Identity API. OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) Default: ``False`` Set this to True if running on multi-domain model. When this is enabled, it will require user to enter the Domain name in addition to username for login. OPENSTACK_KEYSTONE_URL ~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2011.3(Diablo) .. seealso:: Horizon's `OPENSTACK_HOST`_ documentation Default: ``"http://%s:5000/v3" % OPENSTACK_HOST`` The full URL for the Keystone endpoint used for authentication. Unless you are using HTTPS, running your Keystone server on a nonstandard port, or using a nonstandard URL scheme you shouldn't need to touch this setting. OPENSTACK_TOKEN_HASH_ALGORITHM ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2014.2(Juno) Default: ``"md5"`` The hash algorithm to use for authentication tokens. This must match the hash algorithm that the identity (Keystone) server and the auth_token middleware are using. Allowed values are the algorithms supported by Python's hashlib library. OPENSTACK_TOKEN_HASH_ENABLED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 8.0.0(Liberty) .. deprecated:: 9.0.0(Mitaka) PKI tokens currently work with hashing, and Keystone will soon deprecate usage of PKI tokens. Default: ``True`` Hashing tokens from Keystone keeps the Horizon session data smaller, but it doesn't work in some cases when using PKI tokens. Uncomment this value and set it to False if using PKI tokens and there are 401 errors due to token hashing. PASSWORD_EXPIRES_WARNING_THRESHOLD_DAYS ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 12.0.0(Pike) Default: ``-1`` Password will have an expiration date when using keystone v3 and enabling the feature. This setting allows you to set the number of days that the user will be alerted prior to the password expiration. Once the password expires keystone will deny the access and users must contact an admin to change their password. Setting this value to ``N`` days means the user will be alerted when the password expires in less than ``N+1`` days. ``-1`` disables the feature. PROJECT_TABLE_EXTRA_INFO ~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) .. seealso:: `USER_TABLE_EXTRA_INFO`_ for the equivalent setting on the Users table Default: ``{}`` Adds additional information for projects as extra attributes. Projects can have extra attributes as defined by Keystone v3. This setting allows those attributes to be shown in Horizon. For example: .. code-block:: python PROJECT_TABLE_EXTRA_INFO = { 'phone_num': _('Phone Number'), } SECURE_PROXY_ADDR_HEADER ~~~~~~~~~~~~~~~~~~~~~~~~ Default: ``False`` If horizon is behind a proxy server and the proxy is configured, the IP address from request is passed using header variables inside the request. The header name depends on a proxy or a load-balancer. This setting specifies the name of the header with remote IP address. The main use is for authentication log (success or fail) displaing the IP address of the user. The commom value for this setting is ``HTTP_X_REAL_IP`` or ``HTTP_X_FORWARDED_FOR``. If not present, then ``REMOTE_ADDR`` header is used. (``REMOTE_ADDR`` is the field of Django HttpRequest object which contains IP address of the client.) TOKEN_DELETION_DISABLED ~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Default: ``False`` This setting allows deployers to control whether a token is deleted on log out. This can be helpful when there are often long running processes being run in the Horizon environment. TOKEN_TIMEOUT_MARGIN ~~~~~~~~~~~~~~~~~~~~ Default: ``0`` A time margin in seconds to subtract from the real token's validity. An example use case is that the token can be valid once the middleware passed, and invalid (timed-out) during a view rendering and this generates authorization errors during the view rendering. By setting this value to a few seconds, you can avoid token expiration during a view rendering. USER_TABLE_EXTRA_INFO ~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) .. seealso:: `PROJECT_TABLE_EXTRA_INFO`_ for the equivalent setting on the Projects table Default: ``{}`` Adds additional information for users as extra attributes. Users can have extra attributes as defined by Keystone v3. This setting allows those attributes to be shown in Horizon. For example: .. code-block:: python USER_TABLE_EXTRA_INFO = { 'phone_num': _('Phone Number'), } WEBSSO_CHOICES ~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: .. code-block:: python ( ("credentials", _("Keystone Credentials")), ("oidc", _("OpenID Connect")), ("saml2", _("Security Assertion Markup Language")) ) This is the list of authentication mechanisms available to the user. It includes Keystone federation protocols such as OpenID Connect and SAML, and also keys that map to specific identity provider and federation protocol combinations (as defined in `WEBSSO_IDP_MAPPING`_). The list of choices is completely configurable, so as long as the id remains intact. Do not remove the credentials mechanism unless you are sure. Once removed, even admins will have no way to log into the system via the dashboard. WEBSSO_ENABLED ~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``False`` Enables keystone web single-sign-on if set to True. For this feature to work, make sure that you are using Keystone V3 and Django OpenStack Auth V1.2.0 or later. WEBSSO_IDP_MAPPING ~~~~~~~~~~~~~~~~~~ .. versionadded:: 8.0.0(Liberty) Default: ``{}`` A dictionary of specific identity provider and federation protocol combinations. From the selected authentication mechanism, the value will be looked up as keys in the dictionary. If a match is found, it will redirect the user to a identity provider and federation protocol specific WebSSO endpoint in keystone, otherwise it will use the value as the protocol_id when redirecting to the WebSSO by protocol endpoint. Example: .. code-block:: python WEBSSO_CHOICES = ( ("credentials", _("Keystone Credentials")), ("oidc", _("OpenID Connect")), ("saml2", _("Security Assertion Markup Language")), ("acme_oidc", "ACME - OpenID Connect"), ("acme_saml2", "ACME - SAML2") ) WEBSSO_IDP_MAPPING = { "acme_oidc": ("acme", "oidc"), "acme_saml2": ("acme", "saml2") } .. note:: The value is expected to be a tuple formatted as: (, ) WEBSSO_INITIAL_CHOICE ~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``"credentials"`` Specifies the default authentication mechanism. When user lands on the login page, this is the first choice they will see. Neutron ------- ALLOWED_PRIVATE_SUBNET_CIDR ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Default: .. code-block:: python { 'ipv4': [], 'ipv6': [] } A dictionary used to restrict user private subnet CIDR range. An empty list means that user input will not be restricted for a corresponding IP version. By default, there is no restriction for both IPv4 and IPv6. Example: .. code-block:: python { 'ipv4': [ '192.168.0.0/16', '10.0.0.0/8' ], 'ipv6': [ 'fc00::/7', ] } OPENSTACK_NEUTRON_NETWORK ~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.1(Grizzly) Default: .. code-block:: python { 'default_dns_nameservers': [], 'enable_distributed_router': False, 'enable_fip_topology_check': True, 'enable_ha_router': False, 'enable_ipv6': True, 'enable_quotas': False, 'enable_router': True, 'extra_provider_types': {}, 'physical_networks': [], 'segmentation_id_range': {}, 'supported_provider_types': ["*"], 'supported_vnic_types': ["*"], } A dictionary of settings which can be used to enable optional services provided by Neutron and configure Neutron specific features. The following options are available. default_dns_nameservers ####################### .. versionadded:: 10.0.0(Newton) Default: ``None`` (Empty) Default DNS servers you would like to use when a subnet is created. This is only a default. Users can still choose a different list of dns servers. Example: ``["8.8.8.8", "8.8.4.4", "208.67.222.222"]`` enable_distributed_router ######################### .. versionadded:: 2014.2(Juno) Default: ``False`` Enable or disable Neutron distributed virtual router (DVR) feature in the Router panel. For the DVR feature to be enabled, this option needs to be set to True and your Neutron deployment must support DVR. Even when your Neutron plugin (like ML2 plugin) supports DVR feature, DVR feature depends on l3-agent configuration, so deployers should set this option appropriately depending on your deployment. enable_fip_topology_check ######################### .. versionadded:: 8.0.0(Liberty) Default: ``True`` The Default Neutron implementation needs a router with a gateway to associate a FIP. So by default a topology check will be performed by horizon to list only VM ports attached to a network which is itself attached to a router with an external gateway. This is to prevent from setting a FIP to a port which will fail with an error. Some Neutron vendors do not require it. Some can even attach a FIP to any port (e.g.: OpenContrail) owned by a tenant. Set to False if you want to be able to associate a FIP to an instance on a subnet with no router if your Neutron backend allows it. enable_ha_router ################ .. versionadded:: 2014.2(Juno) Default: ``False`` Enable or disable HA (High Availability) mode in Neutron virtual router in the Router panel. For the HA router mode to be enabled, this option needs to be set to True and your Neutron deployment must support HA router mode. Even when your Neutron plugin (like ML2 plugin) supports HA router mode, the feature depends on l3-agent configuration, so deployers should set this option appropriately depending on your deployment. enable_ipv6 ########### .. versionadded:: 2014.2(Juno) Default: ``False`` Enable or disable IPv6 support in the Network panels. When disabled, Horizon will only expose IPv4 configuration for networks. enable_quotas ############# Default: ``False`` Enable support for Neutron quotas feature. To make this feature work appropriately, you need to use Neutron plugins with quotas extension support and quota_driver should be DbQuotaDriver (default config). enable_router ############# .. versionadded:: 2014.2(Juno) Default: ``True`` Enable (``True``) or disable (``False``) the panels and menus related to router and Floating IP features. This option only affects when Neutron is enabled. If your Neutron deployment has no support for Layer-3 features, or you do not wish to provide the Layer-3 features through the Dashboard, this should be set to ``False``. extra_provider_types #################### .. versionadded:: 10.0.0(Newton) Default: ``{}`` For use with the provider network extension. This is a dictionary to define extra provider network definitions. Network types supported by Neutron depend on the configured plugin. Horizon has predefined provider network types but horizon cannot cover all of them. If you are using a provider network type not defined in advance, you can add a definition through this setting. The **key** name of each item in this must be a network type used in the Neutron API. **value** should be a dictionary which contains the following items: * ``display_name``: string displayed in the network creation form. * ``require_physical_network``: a boolean parameter which indicates this network type requires a physical network. * ``require_segmentation_id``: a boolean parameter which indicates this network type requires a segmentation ID. If True, a valid segmentation ID range must be configured in ``segmentation_id_range`` settings above. Example: .. code-block:: python { 'awesome': { 'display_name': 'Awesome', 'require_physical_network': False, 'require_segmentation_id': True, }, } physical_networks ################# .. versionadded:: 12.0.0(Pike) Default: ``[]`` Default to an empty list and the physical network field on the admin create network modal will be a regular input field where users can type in the name of the physical network to be used. If it is set to a list of available physical networks, the physical network field will be shown as a dropdown menu where users can select a physical network to be used. Example: ``['default', 'test']`` segmentation_id_range ##################### .. versionadded:: 2014.2(Juno) Default: ``{}`` For use with the provider network extension. This is a dictionary where each key is a provider network type and each value is a list containing two numbers. The first number is the minimum segmentation ID that is valid. The second number is the maximum segmentation ID. Pertains only to the vlan, gre, and vxlan network types. By default this option is not provided and each minimum and maximum value will be the default for the provider network type. Example: .. code-block:: python { 'vlan': [1024, 2048], 'gre': [4094, 65536] } supported_provider_types ######################## .. versionadded:: 2014.2(Juno) Default: ``["*"]`` For use with the provider network extension. Use this to explicitly set which provider network types are supported. Only the network types in this list will be available to choose from when creating a network. Network types defined in Horizon or defined in `extra_provider_types`_ settings can be specified in this list. As of the Newton release, the network types defined in Horizon include network types supported by Neutron ML2 plugin with Open vSwitch driver (``local``, ``flat``, ``vlan``, ``gre``, ``vxlan`` and ``geneve``) and supported by Midonet plugin (``midonet`` and ``uplink``). ``["*"]`` means that all provider network types supported by Neutron ML2 plugin will be available to choose from. Example: ``['local', 'flat', 'gre']`` supported_vnic_types #################### .. versionadded:: 2015.1(Kilo) .. versionchanged:: 12.0.0(Pike) Added ``virtio-forwarder`` VNIC type Clarified VNIC type availability for users and operators Default ``['*']`` For use with the port binding extension. Use this to explicitly set which VNIC types are available for users to choose from, when creating or editing a port. The VNIC types actually supported are determined by resource availability and Neutron ML2 plugin support. Currently, error reporting for users selecting an incompatible or unavailable VNIC type is restricted to receiving a message from the scheduler that the instance cannot spawn because of insufficient resources. VNIC types include ``normal``, ``direct``, ``direct-physical``, ``macvtap``, ``baremetal`` and ``virtio-forwarder``. By default all VNIC types will be available to choose from. Example: ``['normal', 'direct']`` To disable VNIC type selection, set an empty list (``[]``) or ``None``. Nova ---- CREATE_INSTANCE_FLAVOR_SORT ~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) Default: .. code-block:: python { 'key': 'ram' } When launching a new instance the default flavor is sorted by RAM usage in ascending order. You can customize the sort order by: id, name, ram, disk and vcpus. Additionally, you can insert any custom callback function. You can also provide a flag for reverse sort. See the description in local_settings.py.example for more information. This example sorts flavors by vcpus in descending order: .. code-block:: python CREATE_INSTANCE_FLAVOR_SORT = { 'key':'vcpus', 'reverse': True, } CONSOLE_TYPE ~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) .. versionchanged:: 2014.2(Juno) Added the ``None`` option, which deactivates the in-browser console .. versionchanged:: 2015.1(Kilo) Added the ``SERIAL`` option .. versionchanged:: 2017.11(Queens) Added the ``MKS`` option Default: ``"AUTO"`` This setting specifies the type of in-browser console used to access the VMs. Valid values are ``"AUTO"``, ``"VNC"``, ``"SPICE"``, ``"RDP"``, ``"SERIAL"``, ``"MKS"``, and ``None``. ENABLE_FLAVOR_EDIT ~~~~~~~~~~~~~~~~~~ .. versionadded:: 12.0.0(Pike) .. deprecated:: 12.0.0(Pike) Default: ``False`` This setting enables the ability to edit flavors. .. warning:: Historically, Horizon has provided the ability to edit Flavors by deleting and creating a new one with the same information. This is not supported in the Nova API and causes unexpected issues and breakages. To avoid breaking standard deprecation procedure, this code is still in Horizon, but disabled by default. It will be removed during the 14.0.0 ('R') release cycle. See `this email thread `_ for further information. INSTANCE_LOG_LENGTH ~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``35`` This setting enables you to change the default number of lines displayed for the log of an instance. Valid value must be a positive integer. LAUNCH_INSTANCE_DEFAULTS ~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 9.0.0(Mitaka) .. versionchanged:: 10.0.0(Newton) Added the ``disable_image``, ``disable_instance_snapshot``, ``disable_volume`` and ``disable_volume_snapshot`` options. .. versionchanged:: 12.0.0(Pike) Added the ``create_volume`` option. Default: .. code-block:: python { "config_drive": False, "create_volume": True, "disable_image": False, "disable_instance_snapshot": False, "disable_volume": False, "disable_volume_snapshot": False, "enable_scheduler_hints": True, } A dictionary of settings which can be used to provide the default values for properties found in the Launch Instance modal. An explanation of each setting is provided below. config_drive ############ .. versionadded:: 9.0.0(Mitaka) Default: ``False`` This setting specifies the default value for the Configuration Drive property. create_volume ############# .. versionadded:: 12.0.0(Pike) Default: ``True`` This setting allows you to specify the default value for the option of creating a new volume in the workflow for image and instance snapshot sources. disable_image ############# .. versionadded:: 10.0.0(Newton) Default: ``False`` This setting disables Images as a valid boot source for launching instances. Image sources won't show up in the Launch Instance modal. disable_instance_snapshot ######################### .. versionadded:: 10.0.0(Newton) Default: ``False`` This setting disables Snapshots as a valid boot source for launching instances. Snapshots sources won't show up in the Launch Instance modal. disable_volume ############## .. versionadded:: 10.0.0(Newton) Default: ``False`` This setting disables Volumes as a valid boot source for launching instances. Volumes sources won't show up in the Launch Instance modal. disable_volume_snapshot ####################### .. versionadded:: 10.0.0(Newton) Default: ``False`` This setting disables Volume Snapshots as a valid boot source for launching instances. Volume Snapshots sources won't show up in the Launch Instance modal. enable_scheduler_hints ###################### .. versionadded:: 9.0.0(Mitaka) Default: ``True`` This setting specifies whether or not Scheduler Hints can be provided when launching an instance. LAUNCH_INSTANCE_LEGACY_ENABLED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 8.0.0(Liberty) .. versionchanged:: 9.0.0(Mitaka) The default value for this setting has been changed to ``False`` Default: ``False`` This setting enables the Python Launch Instance workflow. .. note:: It is possible to run both the AngularJS and Python workflows simultaneously, so the other may be need to be toggled with `LAUNCH_INSTANCE_NG_ENABLED`_ LAUNCH_INSTANCE_NG_ENABLED ~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 8.0.0(Liberty) .. versionchanged:: 9.0.0(Mitaka) The default value for this setting has been changed to ``True`` Default: ``True`` This setting enables the AngularJS Launch Instance workflow. .. note:: It is possible to run both the AngularJS and Python workflows simultaneously, so the other may be need to be toggled with `LAUNCH_INSTANCE_LEGACY_ENABLED`_ OPENSTACK_ENABLE_PASSWORD_RETRIEVE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2014.1(Icehouse) Default: ``"False"`` When set, enables the instance action "Retrieve password" allowing password retrieval from metadata service. OPENSTACK_HYPERVISOR_FEATURES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2012.2(Folsom) .. versionchanged:: 2014.1(Icehouse) ``can_set_mount_point`` and ``can_set_password`` now default to ``False`` Default: .. code-block:: python { 'can_set_mount_point': False, 'can_set_password': False, 'requires_keypair': False, 'enable_quotas': True } A dictionary containing settings which can be used to identify the capabilities of the hypervisor for Nova. The Xen Hypervisor has the ability to set the mount point for volumes attached to instances (other Hypervisors currently do not). Setting ``can_set_mount_point`` to ``True`` will add the option to set the mount point from the UI. Setting ``can_set_password`` to ``True`` will enable the option to set an administrator password when launching or rebuilding an instance. Setting ``requires_keypair`` to ``True`` will require users to select a key pair when launching an instance. Setting ``enable_quotas`` to ``False`` will make Horizon treat all Nova quotas as disabled, thus it won't try to modify them. By default, quotas are enabled. OPENSTACK_INSTANCE_RETRIEVE_IP_ADDRESSES ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 13.0.0(Queens) Default: ``True`` This settings controls whether IP addresses of servers are retrieved from neutron in the project instance table. Setting this to ``False`` may mitigate a performance issue in the project instance table in large deployments. If your deployment has no support of floating IP like provider network scenario, you can set this to ``False`` in most cases. If your deployment supports floating IP, read the detail below and understand the under-the-hood before setting this to ``False``. Nova has a mechanism to cache network info but it is not fast enough in some cases. For example, when a user associates a floating IP or updates an IP address of an server port, it is not reflected to the nova network info cache immediately. This means an action which a user makes from the horizon instance table is not reflected into the table content just after the action. To avoid this, horizon retrieves IP address info from neutron when retrieving a list of servers from nova. On the other hand, this operation requires a full list of neutron ports and can potentially lead to a performance issue in large deployments (`bug 1722417 `__). This issue can be avoided by skipping querying IP addresses to neutron and setting this to ``False`` achieves this. Note that when disabling the query to neutron it takes some time until associated floating IPs are visible in the project instance table and users may reload the table to check them. OPENSTACK_NOVA_EXTENSIONS_BLACKLIST ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 8.0.0(Liberty) Default: ``[]`` Ignore all listed Nova extensions, and behave as if they were unsupported. Can be used to selectively disable certain costly extensions for performance reasons. Sahara ------ .. warning:: The Sahara dashboard was removed from Horizon during the Newton cycle, and any settings here should be considered legacy. For more up to date information, see the `Sahara Dashboard repo `_ SAHARA_AUTO_IP_ALLOCATION_ENABLED ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2014.2(Juno) .. deprecated:: 10.0.0(Newton) Default: ``False`` This setting notifies the Data Processing (Sahara) system whether or not automatic IP allocation is enabled. You would want to set this to ``True`` if you were running Nova Networking with ``auto_assign_floating_ip`` set to ``True``. Swift ----- SWIFT_FILE_TRANSFER_CHUNK_SIZE ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2015.1(Kilo) Default: ``512 * 1024`` This setting specifies the size of the chunk (in bytes) for downloading objects from Swift. Do not make it very large (higher than several dozens of Megabytes, exact number depends on your connection speed), otherwise you may encounter socket timeout. The default value is 524288 bytes (or 512 Kilobytes). Trove ----- .. warning:: The Trove dashboard was removed from Horizon during the Newton cycle, and any settings here should be considered legacy. For more up to date information, see the `Trove Dashboard repo `_ TROVE_ADD_DATABASE_PERMS ~~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) .. deprecated:: 10.0.0(Newton) Default: ``[]`` Trove database extension support. By default, support for creating databases on database instances is turned on. To disable this extensions set the permission to something unusable such as ``[!]``. TROVE_ADD_USER_PERMS ~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 2013.2(Havana) .. deprecated:: 10.0.0(Newton) Default: ``[]`` Trove users extension support. By default, support for creating users on database instances is turned on. To disable this extensions set the permission to something unusable such as ``[!]``. Django Settings =============== .. note:: This is not meant to be anywhere near a complete list of settings for Django. You should always consult the `upstream documentation `_, especially with regards to deployment considerations and security best-practices. ADD_INSTALLED_APPS ------------------ .. versionadded:: 2015.1(Kilo) .. seealso:: `Django's INSTALLED_APPS documentation `_ A list of Django applications to be prepended to the ``INSTALLED_APPS`` setting. Allows extending the list of installed applications without having to override it completely. ALLOWED_HOSTS ------------- .. versionadded:: 2013.2(Havana) .. seealso:: `Django's ALLOWED_HOSTS documentation `_ Default: ``['localhost']`` This list should contain names (or IP addresses) of the host running the dashboard; if it's being accessed via name, the DNS name (and probably short-name) should be added, if it's accessed via IP address, that should be added. The setting may contain more than one entry. .. note:: ALLOWED_HOSTS is required. If Horizon is running in production (DEBUG is False), set this with the list of host/domain names that the application can serve. For more information see `Django's Allowed Hosts documentation `_ .. _debug_setting: DEBUG ----- .. versionadded:: 2011.2(Cactus) .. seealso:: `Django's DEBUG documentation `_ Default: ``True`` Controls whether unhandled exceptions should generate a generic 500 response or present the user with a pretty-formatted debug information page. When set, `CACHED_TEMPLATE_LOADERS`_ will not be cached. This setting should **always** be set to ``False`` for production deployments as the debug page can display sensitive information to users and attackers alike. SECRET_KEY ---------- .. versionadded:: 2012.1(Essex) .. seealso:: `Django's SECRET_KEY documentation `_ This should absolutely be set to a unique (and secret) value for your deployment. Unless you are running a load-balancer with multiple Horizon installations behind it, each Horizon instance should have a unique secret key. .. note:: Setting a custom secret key: You can either set it to a specific value or you can let Horizon generate a default secret key that is unique on this machine, regardless of the amount of Python WSGI workers (if used behind Apache+mod_wsgi). However, there may be situations where you would want to set this explicitly, e.g. when multiple dashboard instances are distributed on different machines (usually behind a load-balancer). Either you have to make sure that a session gets all requests routed to the same dashboard instance or you set the same SECRET_KEY for all of them. .. code-block:: python from horizon.utils import secret_key SECRET_KEY = secret_key.generate_or_read_from_file( os.path.join(LOCAL_PATH, '.secret_key_store')) The ``local_settings.py.example`` file includes a quick-and-easy way to generate a secret key for a single installation. STATIC_ROOT ----------- .. versionadded:: 8.0.0(Liberty) .. seealso:: `Django's STATIC_ROOT documentation `_ Default: ``/static`` The absolute path to the directory where static files are collected when collectstatic is run. STATIC_URL ---------- .. versionadded:: 8.0.0(Liberty) .. seealso:: `Django's STATIC_URL documentation `_ Default: ``/static/`` URL that refers to files in `STATIC_ROOT`_. By default this value is ``WEBROOT/static/``. This value can be changed from the default. When changed, the alias in your webserver configuration should be updated to match. .. note:: The value for STATIC_URL must end in '/'. This value is also available in the scss namespace with the variable name $static_url. Make sure you run ``python manage.py collectstatic`` and ``python manage.py compress`` after any changes to this value in settings.py. TEMPLATES --------- .. versionadded:: 10.0.0(Newton) .. seealso:: `Django's TEMPLATES documentation `_ Horizon's usage of the ``TEMPLATES`` involves 3 further settings below; it is generally advised to use those before attempting to alter the ``TEMPLATES`` setting itself. ADD_TEMPLATE_LOADERS ~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Template loaders defined here will be loaded at the end of `TEMPLATE_LOADERS`_, after the `CACHED_TEMPLATE_LOADERS`_ and will never have a cached output. CACHED_TEMPLATE_LOADERS ~~~~~~~~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) Template loaders defined here will have their output cached if `DEBUG`_ is set to ``False``. TEMPLATE_LOADERS ~~~~~~~~~~~~~~~~ .. versionadded:: 10.0.0(Newton) These template loaders will be the first loaders and get loaded before the CACHED_TEMPLATE_LOADERS. Use ADD_TEMPLATE_LOADERS if you want to add loaders at the end and not cache loaded templates. After the whole settings process has gone through, TEMPLATE_LOADERS will be: .. code-block:: python TEMPLATE_LOADERS += ( ('django.template.loaders.cached.Loader', CACHED_TEMPLATE_LOADERS), ) + tuple(ADD_TEMPLATE_LOADERS) horizon-13.0.3/doc/source/configuration/themes.rst0000664000175000017500000001723713553660754022273 0ustar zuulzuul00000000000000====== Themes ====== As of the Kilo release, styling for the OpenStack Dashboard can be altered through the use of a theme. A theme is a directory containing a ``_variables.scss`` file to override the color codes used throughout the SCSS and a ``_styles.scss`` file with additional styles to load after dashboard styles have loaded. As of the Mitaka release, Horizon can be configured to run with multiple themes available at run time. It uses a browser cookie to allow users to toggle between the configured themes. By default, Horizon is configured with the two standard themes available: 'default' and 'material'. To configure or alter the available themes, set ``AVAILABLE_THEMES`` in ``local_settings.py`` to a list of tuples, such that ``('name', 'label', 'path')`` ``name`` The key by which the theme value is stored within the cookie ``label`` The label shown in the theme toggle under the User Menu ``path`` The directory location for the theme. The path must be relative to the ``openstack_dashboard`` directory or an absolute path to an accessible location on the file system To use a custom theme, set ``AVAILABLE_THEMES`` in ``local_settings.py`` to a list of themes. If you wish to run in a mode similar to legacy Horizon, set ``AVAILABLE_THEMES`` with a single tuple, and the theme toggle will not be available at all through the application to allow user configuration themes. For example, a configuration with multiple themes:: AVAILABLE_THEMES = [ ('default', 'Default', 'themes/default'), ('material', 'Material', 'themes/material'), ] A configuration with a single theme:: AVAILABLE_THEMES = [ ('default', 'Default', 'themes/default'), ] Both the Dashboard custom variables and Bootstrap variables can be overridden. For a full list of the Dashboard SCSS variables that can be changed, see the variables file at ``openstack_dashboard/static/dashboard/scss/_variables.scss``. In order to build a custom theme, both ``_variables.scss`` and ``_styles.scss`` are required and ``_variables.scss`` must provide all the default Bootstrap variables. Inherit from an Existing Theme ------------------------------ Custom themes must implement all of the Bootstrap variables required by Horizon in ``_variables.scss`` and ``_styles.scss``. To make this easier, you can inherit the variables needed in the default theme and only override those that you need to customize. To inherit from the default theme, put this in your theme's ``_variables.scss``:: @import "/themes/default/variables"; Once you have made your changes you must re-generate the static files with: .. code-block:: console python manage.py collectstatic By default, all of the themes configured by ``AVAILABLE_THEMES`` setting are collected by horizon during the `collectstatic` process. By default, the themes are collected into the dynamic `static/themes` directory, but this location can be customized via the ``local_settings.py`` variable: ``THEME_COLLECTION_DIR`` Once collected, any theme configured via ``AVAILABLE_THEMES`` is available to inherit from by importing its variables and styles from its collection directory. The following is an example of inheriting from the material theme:: @import "/themes/material/variables"; @import "/themes/material/styles"; All themes will need to be configured in ``AVAILABLE_THEMES`` to allow inheritance. If you wish to inherit from a theme, but not show that theme as a selectable option in the theme picker widget, then simply configure the ``SELECTABLE_THEMES`` to exclude the parent theme. ``SELECTABLE_THEMES`` must be of the same format as ``AVAILABLE_THEMES``. It defaults to ``AVAILABLE_THEMES`` if it is not set explicitly. Bootswatch ~~~~~~~~~~ Horizon packages the Bootswatch SCSS files for use with its ``material`` theme. Because of this, it is simple to use an existing Bootswatch theme as a base. This is due to the fact that Bootswatch is loaded as a 3rd party static asset, and therefore is automatically collected into the `static` directory in `/horizon/lib/`. The following is an example of how to inherit from Bootswatch's ``darkly`` theme:: @import "/horizon/lib/bootswatch/darkly/variables"; @import "/horizon/lib/bootswatch/darkly/bootswatch"; Organizing Your Theme Directory ------------------------------- A custom theme directory can be organized differently, depending on the level of customization that is desired, as it can include static files as well as Django templates. It can include special subdirectories that will be used differently: ``static``, ``templates`` and ``img``. The ``static`` Folder ~~~~~~~~~~~~~~~~~~~~~ If the theme folder contains a sub-folder called ``static``, then that sub folder will be used as the **static root of the theme**. I.e., Horizon will look in that sub-folder for the _variables.scss and _styles.scss files. The contents of this folder will also be served up at ``/static/custom``. The ``templates`` Folder ~~~~~~~~~~~~~~~~~~~~~~~~ If the theme folder contains a sub-folder ``templates``, then the path to that sub-folder will be prepended to the ``TEMPLATE_DIRS`` tuple to allow for theme specific template customizations. Using the ``templates`` Folder ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Any Django template that is used in Horizon can be overridden through a theme. This allows highly customized user experiences to exist within the scope of different themes. Any template that is overridden must adhere to the same directory structure that the extending template expects. For example, if you wish to customize the sidebar, Horizon expects the template to live at ``horizon/_sidebar.html``. You would need to duplicate that directory structure under your templates directory, such that your override would live at ``{ theme_path }/templates/horizon/_sidebar.html``. The ``img`` Folder ~~~~~~~~~~~~~~~~~~ If the static root of the theme folder contains an ``img`` directory, then all images that make use of the {% themable_asset %} templatetag can be overridden. These assets include logo.svg, splash-logo.svg and favicon.ico, however overriding the SVG/GIF assets used by Heat within the `dashboard/img` folder is not currently supported. Customizing the Logo -------------------- Simple ~~~~~~ If you wish to customize the logo that is used on the splash screen or in the top navigation bar, then you need to create an ``img`` directory under your theme's static root directory and place your custom ``logo.svg`` or ``logo-splash.svg`` within it. If you wish to override the ``logo.svg`` using the previous method, and if the image used is larger than the height of the top navigation, then the image will be constrained to fit within the height of nav. You can customize the height of the top navigation bar by customizing the SCSS variable: ``$navbar-height``. If the image's height is smaller than the navbar height, then the image will retain its original resolution and size, and simply be centered vertically in the available space. Prior to the Kilo release the images files inside of Horizon needed to be replaced by your images files or the Horizon stylesheets needed to be altered to point to the location of your image. Advanced ~~~~~~~~ If you need to do more to customize the logo than simply replacing the existing PNG, then you can also override the _brand.html through a custom theme. To use this technique, simply add a ``templates/header/_brand.html`` to the root of your custom theme, and add markup directly to the file. For an example of how to do this, see ``openstack_dashboard/themes/material/templates/header/_brand.html``. The splash / login panel can also be customized by adding ``templates/auth/_splash.html``. See ``openstack_dashboard/themes/material/templates/auth/_splash.html`` for an example. horizon-13.0.3/doc/source/images/0000775000175000017500000000000013553661042016627 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/images/message_substitution.png0000664000175000017500000027774313553660754023652 0ustar zuulzuul00000000000000PNG  IHDR.TR iCCPICC ProfileHWTSIWR -)7Az;FHC ؑEׂEEW@lkdba`XPQł _ϙ7߻s7 , U /a&&%3Ib@8j@xGE( HX@ Ns > tB|XU @$Kp kIp [Kmb}!Le(H3 0bk>LJx; b1rrfBH48:Jò\B YsroΡ5C-֭6kfCq~jD$*_q|;C7b5 PaB k2DYq#ؖ%B{4;S3G숰823j 4HaFl'ZXμ6BQ!oӄ2L='o4/̊͒ΥW~FlK%rpe07 'zķD5bUqeuČv&0%㏽Gʸ80T0d^Gc?|t#Q>c@!".rAʞ M:Z O 5q O/lqgeԏ8:+џG "x!l؄ot$\9|GxB"<$\' @wvdC3eV6H0.}*ӿX{7kVp-tAwd  DXêgzPV`3vZ88΃ˠ\w/x!!4h b"Έ⏄!H#|DC eH9ف!"ǐ3E <@'C*Qg Ecih:Jt#ZE3e:*F_1YbΘ/%ci[bX5k*&8L <g|V*h-LH$fJ݄spDфf18xxE|D$H$ ;)"JHH{IHݤ^ݯYsRO]{J9Sfnd2Dz;:M383h105 7gXoxH(hQ{cƍLMM MML=MsMM͜Ͳ̶uW,P G V .'XR-- ,-X1¬^N4. ]|tutw=[gL&q']ߝ]H!dyV{>2xzm{_W0@R8" f8i Ƅn}f& kGC׆ߍ0G4FȵLr~L5rhym11{bĮg'kW_>/yw)NuZ24i]9={3X3RR|fEY[Rؾ /:Nם[}V,=}mz_gFEF?ϗ*3(s[Ȭ9䜔c|~u385w}0T;ɛה :"SOf:<[y6v9<- (e.>=e޼c uAB {.]LY"K4k/*~SO% %’nK-×u,[iRN2벊++.lƟWX帪j5q55kj˕  _۰t3_ضAA1lc&M7}ޜzO-Z[oyʫj6me>mm#pGCquN΂Ovjݚv׈kk[hYU֋N۹o_~;0ECZ;Ȗ HÜƌFqSRSױc-nGj'V,>9|i3ghs6ɭB]8plw۩ _txˎڏǎ+NW:]:&u>sk._u#͞=⛜nezuНEw wK)ݫu?'=h#-~B{RTi3g:OyBbO?4}y/z_ _ ^FM[-Qz_ACGm>=y/_C,Q MKu $xv8%Dvg"&xr naRTK߱^k#fg+E‡7": 1070 1108 {iDOT*(**#A@IDATxU B/J ]M)]J7AD)H&RD@% ]@&%$go{23;|۝r;{vvv!   pa0*4 @@@r     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>   f(\ C@@@     `V…0@@@p>0qn{}7iJmKpM? y!7|UٗԼ~<7 @L7`jIJ0$Pd=mp1ځ n 7}:ybְyvP7›9N3<3ѱ@y#Ti,1w*4:],UE-gW7q4q:sy.? ò yf9:Mb3L- %ԘS wm.R;ж}և2kn j Es^}K!ŋڸ bRb38Lc1ԘS ĜwYW^ UXNurGޒyY>ZE1ן5_hv.GVPY~@"o*4fXM;`@lyumpQuYRvܼKlϱ /0gݘg|Qm3/6J*X<Ug B:X75&Tibɻ:X eY|减[3Q0QL@j~O_<+O)`-T|S uojLީ: Xϻ:Y ejY\Q!FB3J䂝}gc䙚o*M;`ZGyWGD,ICSB#x vϤε嬴k! X3E#Ti],w*eEMۻO'Ot#ݚ_1S\.qܟ[rVϵgc,䙢o*M;`ZWyWW2E EC8q}Cr*vCg_ܕ6 @R(gpYAI hȳBʊ 3 Ti M;`KH)\3,ФB 4Ye!eBRo*4J0MAJޥ`].\`PhRe򬲐bL)7`@|ScN% `%R.G E .@c0(42P yVYHYAPyJ0MA t1yLSw)X#"ze1T@<,ؠ@R(gpYAI hȳBʊ 3 Ti M;`KH)\3,ФB 4Ye!eBRo*4J0MAJޥ`].\`PhRe򬲐bL)7`@|ScN% `%R.G E .@c0(42P yVYHYAPyJ0MA t1yLSw)X#"ze1T@<,ؠ@R(gpYAI hȳBʊ 3 Ti M;`KH)\3,ФB 4Ye!eBRo*4J0MAJޥ`].\`PhRe򬲐bL)7`@|ScN% `%R.G E .@c0(42P yVYHYAPyJ0MA t1yLSw)X#"ze1T@<,ؠ@R(gpٺ 4\zРܰ3fZIg(m}Ȋ[bM{񮫫+ g62L풓'O5hРɊ(3GCX7m[||[q30Ol}Y5{(3zLT|Řwv Ǎ&|Ҟ*]eu dΉ{\EQAcˇhf~kL;-U&L-[XlDyA9/8=+=_v;n-9=]#w0_?\~oXkyqní pm}6Xa|ދ+7tLU3hY_ʜs̚k/l|~;K5cecok|}=fWƼnsR7C.y׸{{,ϡ_G] O[p/NmZh]p<ӆ7]Oiɧwyʛzty?>l/WVuL3Mo>w' Avc˯8$'ا턼_v>՗:rܨ^2_w;k믳s-zy8δww[PhCU4lWsO<\Y=~?tm#yZccu7p ӽ>'Feuї7EVwe'ofT5Chy'.l_8{b ;]Q߻rG3Iw@}dkszFn~o[+۸o"B`~8ɭHؿ߰loI>nI?֭ 3'7+My׿fufoˮ[aA~ ~BFl?lޗܴMHz{oOq}n/,?k{&gɧ |mcon!mOץ~[|}Fx[yucDž[f`μQnl41Ti曮Tǝ~~n~[븽w-8gn3*ׄ}/\\}q=>Xhػ3/%WNǯޢQ-3x|Uwu9nò3,'~{oŸux;Gᄌg(5:Y@gZɻl_EB\@ӎz;=?gj?S7vo77]s +/_xr_x|@i.z|[? ?^GOwPMs>jQxEǵ,nW;QKNϸOBe^S'_̧:ߴ}U]]Lauco>ݮn'ce]&1ǙV.ww (E4OM;  pS.)hqY9ƃ-/h[w/6TYYir!C;9g_N}aź}>tό~}]dB~vΖg:?}z:^~ͽ[\nhC+owCR[YQ\_xqZ/`OMfTghy_E}_~~[yťܛo^uƙIyﭷ~կ(^\|٧Sn}\|It_9о+\|_.\3Gd[x|nYfSɁɓ?i\@WN/I}/ל[{G.|/Kɶfml[9<\['g.6(7yg~S8og  OH|ku^YC盶꼫qL)n>DP?[l)u?㉌rDEW1Ok>1d:-]oeCMw.7sS=1q(\tvB@r7p@~-߁7,zY PΝ~d^n%n =ao]}1ڌܚ߼EGk*79p.2)=W>%#܏Os鋫,{5÷;:N}Ȟnmx}uȅ6q:kcVXN.z釹mIcj)gjי='RZ6;?v;~k1YMڷ}_w׹oq]rݮ1eg 뱬p]{okXܑ7Mǟt;w{\8n;~۩ZPm >ma:{czٿv[}{z?y-ܗ5玮Cg|בple@cO֘r GkZ*ϴEMnY( /ͼ{q9w7^[Y|s}O~e:\qL-6j9Vk!գv׸ ZwX|VVwu8nOe]Qu+ȼO48+~v̮1ljl7,rqP5bϸod KsKr|L+y' Pho3U4P3ɩr_r}M3ίm!/k#}aGDo>)v/_Sd2ϯO֩:NM}C: 2XviwZыufmPMuӋ&7J<*ϴ9EMn5jwz :N5绚˵b>r oږǍ~4C؟iߖ.~X~L1lSWqSg(\vѪv0_9hW~܋vѯ!ȥ~[;=A)G'ocvT/W2\ ro|:^QOϋ3rr/ie\N ϸ'Sy/߸aDw2[J쭼6SGyR#W'~'̿k5lް㗞s[Ke_A:-NW3?yrJv'g%u_&q^v'K"X~C)_'׹-'$Srpȧ_yN% j9Uޖ<'}z"k,~+땛\o>>XR͖j?ÉGSrz Ugqe~] @ycN.zEE~ `}n~Z}->V\s$ ~j)gњO-;gp߈F8~% IyǴoreu^4ku fS%[}z7_kͧjom[NW;|ߞv㥋S'柍ɘw׸e3mW|1밿Vy_nM:N8S=M[UuQ_ϥXufYs>;s3sӷƽ>`gآ =Nlu}8OiW!f_B,Ͽ_h/\ hDvg3wp$GhPy;0S࿰;[_V6.'lF \TmuP&[֣z'E4{&Wq_`)}mWzE]Lh[|7m3ι2\B>>z#~Ty 535F ~! F}=h(TF^4\')>N0exsjwR-FE/_36.,&gi? +n̋VzLSgjZ6])voX옿Ɖ\%,3Z]pX_$'Ti曮Tv[G9wu-?&\?Io:7mWyWFϛ5v>H1c2[5?eǑ~o:V^}8J1~O =M~ꁦ(>o]waҤAVh/{͍,n+7[tZ}7 ^lĻ𵪺ەťͷZ;:r2 zá˺t7n7oos;{}t~@LC 4ELMӽ~x#f.g!{sf >ź\F֫lw֩.j;u[|=' G>uZ{м='ݝc{m<1L֥h:ߴUq5`N7d CC.ȊEkQ`O㉿\_3su)hͫ_miM)qwepQV=д_sք_'s-\`Ѽ^}d}yEWm/̺\QuiD[mW.n|@jU=j)gjߋ.+E'gN2~*<7N˛erʇ^ş߿߼K+~'gm݁7mzt>\sBk7{=/|w]V~%I rj/A>nꬌo/J,ox<7]O6|;n8#6ˊx#ppS@vz!@[.D)wKөS'~Ջ n^Ev}Xm38mN_Xz!>9-ʵV}n6}oOihfڗۿlrVﯣf:D#~~y=;Ǽa훿ʧe~yovQ_(womu~NO4~;6 Φ:N/\[9g_Y ʯM Js9vɟd?Ps6B噶hzM5[6t>Jdկ߭~Zכ} r_vz?&RvR':ߴmU]c*ɁV_UO}MHk3KMۜߖVlQ9R}6Wyi%蘭 Pho3U4Pzl>_XS>`Y:L{Mlowܗ6ϧHf ټ>VtoӻsdV̨}O[UO; ǾG"O(ϭ.OLD~qկn'5ڐ1@hYo}Ʌ9s@n6]~=V7kUV|]/ |wȿڠʙ=z&Cvr)Pw%6:I_贸"J,7N+'u[5NzW<9?mw돜5t5Ǵ.yէ~Y)r -^Gygڞi7UVqC磶nο"%K@)?|/Ϳ|;}ߗtuǺ=󺭔I MC盶'ږVS+Ǎzm1irܬd!>O걛ox2K<7.?es& 8J~ lV#'߲Kz7Z weXW8U۾ܣe*+-ݕ|u]^Sw]uq]ًzc>?~̝mw>̗&}_+7{<Η޺ƽxk>F_@z7|8F^͟~ub ~2uo.釬Nsblrᙿ굍zp7;y֛i'6O>xE#LK9k7w>%W ]믳Z7=v罗4K}9(۽S}6n,Ec[Yx>}OxN}ZXN34$.cy=8{7Y2V5ͿwsYvvUVyIԿ@U{.h,t@{ٿ6e*M>O<~o>ykl*󭷾sVq}-+χGm?2fkLO91W_N:vng?mq>/掎Cc)c hhGEyWF1?$[/u}ck3?Y.}]S˿uqW@g6@f;,tBΪm߯g禳aBJ.T$|׹'Qf_X}w``Vv=9;w/`horAـV^q)7Zϯ}0ݞlcv:^bgݮc%=B^4Լ?w7;:c{}šw=Jխ۴1'n/?2gh|C~WZO?yn5oczn?y05y_,S"eWhWfqHoNgIF-MaӺSs g֢i7ǚ/ 军m\皧r!':> rO=xEbRS~|VVwu9n<[q(]Ӭ`.Ԍ'Yȭ?׃_yi%4>L[ PhU4`v˴{\~2M@ɛ_uTZ='>ҭ*fS:xL3 i'z\I8袋Ly'3详+}U.䢍ʲO`vGs)/,XasH|wҫnq⨍ x'@S4ԴY3 \~zzS{nm~7~C/ ۾,H|_g@bfx᧸SpyqG~Lu'o^k<,:cz?mZȍ,vyyЛEVpAٿkܗ3WZ)_Lz,Py)ovS}KGw?q_V+?#]hy¹DOoϷ:6~X퐩\JO :ǙVΏ{ Pi#U43ձ Ofa{8əqSd~F? P>$]MȮ 246̙ܥmFj@,m#}(@c-dzݸl*2ht%WZ`m<-gu,)m>䬌}1ߗ>;g-Z2g>fg,ײLȐ! U)[N.idzg#״h-dY.K)7ic!Ծ]o^KI.Hλ"^|z\\߆/'Ltg&"oByT} gwkj7 ~{Ͽei\n5]ˇ_|6tib;iFy/#_q^|C@yi%0QnEakؿq:{5[Z?xr2EB 4_˾~}` ozES埑ժԟ3mc*m7 ڕeS_^MGީs7b q(XC"ȵi7Mof^n )ý^SL+Z~bf+ *Wu~W[XSPyH)ߴ!]Rx|+ti;2帱G q5S=Mgii󧜆Wqc|EÞ|S~R{uf2FkzPMjy?>ۺ{_?ݐ|f%7qRlz4؅PKC6:|~veS_^MGީĔ)Ǎ=+qҵpQ(z}`0{ӭ 6ݳX4uvznWhPMjy&W;5#^u7]sJfgO8k?ۍ}nmp~pwB6<|~veS_^MGީ7Ϣ{!3]uX7:D hȳZtgM% :ԘS )XɻE=2 ML @CURVlP T)LSojLީ] EHᢈeh &U&j!* )+6(*ϔ|S )75&Ti V."}pQD 4B*5АggJABw*4+yu>R(gpYAI hȳBʊ 3 Ti M;`KH)\3,ФB 4Ye!eBRo*4J0MAJޥ`].\`PhRe򬲐bL)7`@|ScN% `%R.G E .@c0(42P yVYHYAPyJ0MA t1yLSw)X#"ze1T@<,ؠ@R(gpYAI hȳBʊ 3 Ti M;`KH)\3,ФB 4Ye!eBRo*4J0MAJޥ`].\`PhRe򬲐bL)7`@|ScN% `%R.G E .@c0(42P yVYHYAPyJ0MA t1yLSw)X#"ze1T@<,ؠ@nE3:@y?w7a7B_s,9w&$f@_1T7?s-Y?=: yhJ0|ScN%Ubպ~Q(C:>{{sn, |Xh:>bqs-ENȳ@c,䙂o*M;`ZWyWW2E E}۞̮sNv &!0u<^V79b_=eVRRYIƜ*_IKg%b*3La7`Z'J0u}pQu{:i]bcCs' ̘gw~2)m_٤+hn"XVDj)L `=ԘS u%`]v(\-jl}c;սyYdk#+k!AoqcʾrK>,]v9ȳfS X38Lc%ԘS 1 Ėw1[Wv U[VʥY3Yl]7t S@[=g-iZYc)Ԕ|S ĘojLީbλجl/*u [fIcج f_YM;h2lnC) |8|'o=&R`!giW\ȳxނ@yJ0,P|ScN%Z[YȶQHmے?0@1Ex4Eíi >M7P7d+ξt]'Ϣ_m_<@o*Ԓ@]M;`jIygz Bb Vmfo]]]#C y:l?%-hW+䝕H.,F6E'@]hpYA oцG,@E<^ʉ@  4)D> BG$@mjE aQ…ŨЦh  P<0h49Z-ȻG+pQ91HA&(YȷM_wV"A;, PMt! g&G+@E:yqhz.*'f)0ФeZ< RJ$hE B`.d48B, hȷhCG# "M\El LC g#S R6}"@Y(@bThSt 4хG(@E4mhx] P @Bch,t~J[JѦV;+(\X mN&ȳF ߢ  X8x4r hR2} -@OI|K)Պyg%â QM 0D2yahr[ wW.@rb6M Qȳ`) o)EZ DvXpa1*):BF# " MV|6t4 BG$@mjE aQ…ŨЦh  P<0h49Z-ȻG+pQ91HA&(YȷM_wV"A;, PMt! g&G+@E:yqhz.*'f)0ФeZ< RJ$hE B`.d48B, hȷhCG# "M\El LC g#S R6}"@Y(@bThSt 4хG(@E4mhx] P @Bch,t~J[JѦV;+(\X mN&ȳF ߢ  X8x4r hR2} -@OI|K)Պyg%â QM 0D2yahr[ wW.@rb6M Qȳ`) o)EZ DvXpa1*):BF# " MV|6t4 BG$@mjE aQ…ŨЦh  P<0h49Z-ȻG+pQ91HA&(YȷM_wV"A;, PMt! g&G+@E:yqhz.*'f)0ФeZ< RJ$hE B`.d48B, hȷhCG# "M\El LC g#S R6}"@Y(@bThSt 4хG(@E4mhx] P @Bch,t~J[JѦV;+(\X mN&ȳF ߢ  X8x4r hR2} -@OI|K)Պyg%â QM 0D2yahr[ wW.@rb6M Qȳ`) o)EZ DvXpa1*):BF# " MV|6t4 BG$@mjE aQ…ŨЦh  P<0h49Z-ȻG+pQ91HA&(YȷM_wV"A;, PMt! g&G+@E:yqhz.*'f)0ФeZ< RJ$hE B`.d48B, hȷhCG# "M\El LC g#S R6}"@Y(@bThSt 4хG(@E4mhx] P @Bch,t~J[JѦV;+(\X mN&ȳF ߢ  X8x4r hR2} -@OI|K)Պyg%â QM 0D2yahr[ wW.@rb6M Qȳ`) o)EZ DvXpa1*):BF# " MV|6t4 BG$@mjE aQ…ŨЦh  P<0h49Z-ȻG+pQ91HA&(YȷM_wV"A;, PMt! g&G+@E:yqhz.*'f)@B_#  @u]]]խ5#HGm PZ *@"*(\T˺@VZ•Va3 @BH\E;G8܊9@/R>}G*(\T:@2t (]tRV PH| й[1' Eѧ P*TY'RZN! 0^N @ q t:@s+DR`H9pQ*DZ p Z˰)@tƋIY!$.@"#@vnŜ @)G#@.Pe PKDkV:.xQ:)+D(\$}\έHY"wBEj)h-J@/J'e w p ڹs") 0^}UPBu"@-8eX PE餬p@@sD;bN@ eƋO@  UN +BJ`("@.>t.hV̉,xr;T!@ U։@aS @BH\E;G8܊9@/R>}G*(\T:@2t (]tRV PH| й[1' Eѧ P*TY'RZN! 0^N @ q t:@s+DR`H9pQ*DZ p Z˰)@tƋIY!$.@"#@vnŜ @)G#@.Pe PKDkV:.xQ:)+D(\$}\έHY"wBEj)h-J@/J'e w p ڹs") 0^}UPBu"@-8eX PE餬p@@sD;bN@ eƋO@  UN +BJ`("@.>t.hV̉,xr;T!@ U։@aS @BH\E;G8܊9@/R>}G*(\T:@2t (]tRV PH| й[1' Eѧ P*TY'RZN! 0^N @ q t:@s+DR`H9pQ*DZ p Z˰)@tƋIY!$.@"#@vnŜ @)G#@.Pe PKDkV:.xQ:)+D(\$}\έHY"wBEj)h-J@/J'e w p ڹs") 0^}UPBu"@-8eX PE餬p@@sD;bN@ eƋO@  UN +BJ`("@.>t.hV̉,xr;T!@ U։@aS @BH\E;G8܊9@/R>}G*(\T:@ :=#n=lNDO9˻W_zx@w @.dm h4ȝx- Jb}q'Ov]]]+t@E @.R:}FmZ0Z YpM7uW]uU  @Ƌ4zN/@j(\TZ@ R= }NΨh.XmL@t"]z'@ P PY%$-@"yK@>ǧidxHW@ƈtz P(ד!@z;낳-jl$@$HVW Ї6'i" :n0N? P(Ǒ @Zu58]C P( @&DZ A>ǧh*@Y@ ƊfFNԹ$&u"pO0fGpO#*J0EM7C/@f,@{ n ϗ V@ ZM?lXչWYͽʪnƹN P0 P>t FYq^V7bmF u܈Ei$D-@"x0(@`PhR~{?'4xc%l#rg074{%=rQ/8t rzR9_}kwpgSY_4Ž5<^/䅀 @R. 7Ep1l"]9[i%:qjT{j}fycÙDs*pQ-+FD(\$x]\BrW\-B Mf1Ł l<{˔\Sljl(\m!@b FR3.Xl1omi}JSlҽR%@,IփLp@z]gvl7M)7% ?Is\Vlh  P= P {Ed-rο@}Uѣݿ|uK#kz4lX]J@`(\ 6B$(\$f:9/K,j>/of႒',_}e[Ŷz _.TEMK@ `lwfg[|0 [ .m=Kuv35[+; PTEQAG P__s]t3VcO~=_QWd"Q>#) #^YD Pr=Y[/^w{ꜳܟ_-FُGd?ry7ןz}~n&H#@Q EY.@ᢻ!PHC~~q6p,n4^;^{ 7WYխz`a@ Pr=Y[ZXno׷m;e <ǕW}r-[_%JF@,tp݃($p~/ww+GԱ*s)'O #@&@(WE-q-\Ofo_cC,OʍE/t(\*@O…] hbM;E 6:%EWBG  @.dm hbvMkdoZ933C=_> P(! vJZY+t4٥~Guig\ȪHTE PhYqZXsݸ~#ׯ+k_u- )zgpQ'kC(\ P. \vrQ&3k2wqzj@EV@. hgyL"g( @"-(\@!V@ k~jWr H`9g[p(l PQ0 pa" 4.ZX3+\te*".Lq`?#F{NRD.p.ٳezƕ^n/Jķnu9+@/l PQ0 pa" 4. ud:s" uyEpΞ-#@=(\3*.V6|M5Ⱦ.5W ^@,5pQ`0!@DhD]pFVq .E QM G hb+\d)mWǽB7LZz‡܌3^ nsC>ͷA_f21r[7| hX`F]xAQhn<p,fE:p Щ. H_y1n~\.n5guý<ɿuyT߯ovq#_wu^t;[Iw9G^<<sgk?c M_OC~![ݬ02An9tsdy:~DoۙinlN9l;rms>y],(\ @ .J@d '@.E.W__b.mVs컟nyvw{t~!N]\w4Sc)g^~Y/#sKdE]v(NvKnu|{L[୷rMIۅv;4V]bq7~|YzͱnhZVSݭ}/Ĵݗ^3. B8d5h>n (\7d 4-j<Ɲȋ?7_;H";㢗A/ssQnFL)~d '(\+&K?g~QnYg69|;'ﺳnuˮpGv7#܊.ײly@ڷ~VL6oٳS Lݗ]/GbX< w@(\*Pn & zz )g\󀏪1$Q(5 @ 5*JT@4iJ HAz(EZ%@BHwf6wM6ݻɦsww̝}̙PxLeJӥl1Pǻbi%LWWx)Ӹ8nvgߤ T-#D=7 s C]?rcQԃ#ѪZUy񲎣Sqei@(JQ4}st!}vOÜVy/y/r5z谼6][,OSr5}B-X@…@ pa @@-Ekψ][h/fb?N=mfcAu$,|EYH^w^!7iD z|ox?[^Br|կnتKtlP%3rR\YHrwOBc[_b"#{ж9xT(u>0y"o:\L#F|ؖFuh-GCRT׍h;z vtz…Ā @. @A%uYx^8p=p1Q/:0sqH.L@JŅ1h₅ /7W/cb(3FNydII/(y<>u1K%Q= ?HՅrݕl,L)kJ/Ϟ&Med·xyT6S9aYv Bb  \XIA@ P v/?i㬞8fpуC5sʖ8…[\1X#\Μ,6$:xD&Niu_|DJ}oC=/-ѭrݛw'|viT-Bq`x)|46,P);#/[}ptJpVw 9 `11gC8@@,'r. ⫊ڂzrGp1<"xav9+JO!ńY3"엽gSk.7RL%'M #jע…n}뢬P.aL4&E7qO EiG:iyީ W^7> ^@, xH  @0A "\m6 ! !d.zEZGXh R!-4>.8;ps{}hnSjS:C2<l:{8Lq]y;ԖU}A_[\u,\$˨ {6|}JQG>կ\9r`)Saa^ :Ϧܠ…@0 @pҁ/ċr|k,խ d!-;v҈~24ؿ4|%1asKN35 j{-"8Tu`8ޢ 6aR8w`BǴA.mTX8i4f<{aC?׋/L?6_n iʚeχS١35c:۱#-'%%81{:.\Di ơq Bb  \XIA@   x#dw,[e=hN. R:B#\̘JxȺ%Kei*7n>:ׯ+ˋ^+hZ'=?7mL!\H x !) ZpQT@/1.2GxQC{wFai xD=X!֪I+zMQ7TZ\|_nn$.O!/WWJLJVZ0;4({ʻh;;Sogamk֐Jnup/p3 @@*.R!A'~,\జz{9gZU¿Z5*V hj)&p!sCaTիΟKɸ~eҶhlj ;lB/{rιԉwG7L/,z ' ae0[{4\:/\,ÂxKA$8w&PD  ;P#x@21,.3DJ    \d,OfN6-,Y]1J !S'R~^/nݡXrw"Ǐ5[OՆ7Ɂtѱq"\'x?j9uFKJzAܦʎ ^cnm%~-rpa9@E@D  C… K (… ⁗Wv:CX\p!kh1wch4\0n5Gm.…Q0s@0@ \1@dpQ%rΙMR̻T5Z]h'K藰pe|40Xز l.[e)P{pj"@@!IEhp'.Z" =b}x;D~#C1h92 gGe9ŅĀ @. @A%72s2GY{ [Nj>'B> \XJA@"\a 9خnŅΤ @P @@ M.D `>E]li`.9iKcvɛ…sRp@@R.,% C@.p[-3㏍N1zApua;pf_/ݻ BgR[U \†D  &i0"\nH@;\~0M۪A73M!uYuz&oJII/hT4_UA&{0 @L.  e0"\a %qVإbt{aTNJJ!K5||ȷd p.?IȯJ 8R7t?&rIDATv|^_.ҽ@ .  `&fCt0F@.j7nRgCm.֢ļO:QWP[0~F g qߎ$/WgTwT x@S>lK}>TYp6n>姶]Hgx8…x "#B>e ϼ6 :jO%F͛=$0?h@i[Tk qGtmZi'W{ZŅ.BͦkXŅe Rt%<!pXN… h h2!+*%JAB@֢92"FHc4)r*XNnyZ#bb`kyR|;R,.%&&}h9w?n܀X,qpMOĻp-Ery>vTpa**_EĎŕ yz/R;鲪?sR2 픆7 <~RJ 2E #/q'8\.5➳V,@ {c4nqQ9<17'SIuFo׆-e')?`s R e_>3~IBi_6OZ"M; OiҖx%H4mL kקb%pEl@Æ LJƎkP0&\btU:<;ءG׮$iB a_J:szJ.b L@u%ݻwO%`.bժU2~ @Hb6 f }؟ߩ#u[G!Pt~1mZQF ёyȎ3ˊڌ0<1_Pɥ"c[ӗ 6dog's)Tg_E{ \$OYHd?跐Pymhf-]{X*G:hxT}.r~gad;8#|=wi9ϲnG5e/8TYOn+alQwm>y\M'P=9݃PuVUirw/g*_Ǡ#?'/ &HiuaBX[ q@@ )#XB@.jխSԉVݹ|-.̐]0[|ؒTB\Lv9tT Oq tiqon~#"Xp.ǂoc+-`e׃!U.fjEg'% 䧂yݨ;ۤ>J3۱ŅC :Il&hTȾAJbںy݇6ɾ*eٺ/oޢ~0DYX.úFp'xPFDHE (0PAH Fcuk # q @pa. 8P\AXOw@_Gԩz4}b.(QT1y,JL{ߟ@yIH#ŲB,jqtErGUwYl]џ|Y%/:)^J8Dl*:r%'f̖XHcْ*c]Ep6Jбc3 / f0fETHAE Pk%8UE~WE_ywzx>x,^J'8ҋvU*nQڵٝwI7nRgDžsW/\m~W"h_]E QﵡK1!H!-,DEQd_T$iqq…'Pl}B# `-TEH&C2"\bBa?< nwh{'[N Cpu8y O) ԁKG,G٪x|K/^n?ͷ[ULaA,وef K- kMr!өO*R#Rb U%)GıÇ [s}N -! dkCd'իhF^JR". "~jvy)`6$cX\H x01 X[ A@ )#XB@.j֬_-89naSAzfZqxzI\#p@:5cM75L' {~Aaר%[(QTIAi'=;]i%CZ\pbA06p @ Ƭ.`m<\t@H.9 7^b;lm,>!_[}]xRGz>|%.D,\|….'pqr iy'ת%$b۴"tF c:сu&Srd ] <288XŅ2p0@Æ z`m@@@!&5k0\c;쬲 /> &;ּK0wE"㧱yC5:.L@ 5] X[PC…jHiPժm3јn#!)~k{gjT /%a4ϝǩ3֞vZ308/…Āt.`m"@ .RGp/R< &SB3zEoWs8PQbu~ϣ@ @H fxaR!N{…Sd0$d]b2nQb[QP1~[tVEܖwXNұF,! فZ.ԒC:0@@.jT/Fd;S] u~ -,\ܨOS*΂*~ǐ7xJ(@ m.f+ `6E…A 0r}rZ, >$ ؏SenE9"N9Ib١Iz*xa՚r~mQ[X^͍ \^Epe@ j=+B =ipp\R$G(X6ҳh($L`(E*``P[.r!\p-3(q!˴?ū_ᰳgљ1 Ot@H.9 X ls?/owzk)ů ;lX=998*C pq$$s)v߅҃:.qC*0H@./,Zk7cA} L )-;J.[ ۝A/k|yid@) H TP,\`7&Drn"J?!\DmCme-AuAQ8/l1w~6Ԡ@pRApQr+g=/[BpapbC`ӯ\k*5=$vqb5 Ybu@gb#z7 j?- \T-tu/:|K5: Op=D!"VG$['h NS6^( X\Xmʴ~ EJTx4&??7\SQ•}lzCA \X6IqTd)rrr0| ŷМ-il3:w7kn'uʶnf}P}JXtt.M Xs@Y6Lp y`Y!AGQ^.lxGӭN…ՑNҺ[e9sҷ ݽGqqTGTc>E}ل"\'2~ <3'șE6PQ &&'O`a'.,eA i,]b`q9kFjD…o!p^ݕkɟB `.C|0d@^-Lvv&BkHLJwQ|bUӟ6jlQPw|?NNxpa8% \d vj kaBe ދcX.EI \d հ .lȬ$uL>Ȏf ggV)--"yqĀ,"M+wog3,8QMpaݏ[\6doO%<=ȃKQ>؆c+nDF#D >3B p pض-$D h8bEN8@ Pƕ:|/NJ EvIQ["–zm͖!AYz΂5e˺R e BpߪHj’a6^7EhR|[|0/N3,.l|"GJ…Uq0@@,X@PF-ձpfCPG…:nH ` \p A1 a>|GwơV \dЄl@.L(  K…. PC@.VjxhC/Y@P3! XF…e@ @NGA (E#.6sO?8`q## \K  ` CT `Ehv[8fnjRf8i9;!:f'O*w+53usTV[['  owz pa?A.ld`ZRۗOCx:Tt|MڤIM]1W.W+7F=H\_/ YS;TԪ3Xpa-H ! \dFd `K \Ro 9Eq&un.\R.4[Zߍ]̙'R P|F\G/)\{T&o z>U@ f @M@.n^.֟Zwjiٌմm.BXs1.͟_zCvg>TS|mǡ3ŅĀ VB@r7pQ?_ry9ܸr4%; _J C<(`*mƹb'~3nݧQBB999@J?? hOC|{>EGŐz͛{JyH U+ڣWjDQH Tt1*X@(p6p5&Jm.lzėGq/z8@@@ p-P- 27nSFx.NQңZ"l|ruwW8?{9<zZDaR=Ŵ۳ Mj5l܂&{QIdogM'}/M:O7*U-GhK|/awFImiz^:]wlHgv6ꪍY`+>ErE4.2 -2ȭ \֞E@z❊mlY/Sytm6ZSWŲtyyt<U{ 7i+C,1)F :$,߾y~֛!SQNrX s5 צ]dlz2mm:7Ҵ]D٠vD_XرܸMTnl!\hހ@pQ@n#"('+\i Ki Wh,\|…tGvј^Sd 7,:tkGv.(Ga1 _e+f !~h{Ņ; o^h͖ٳgvD>t:6Уf [KBQiOȞ}ar:$-&V\H2Oa]jśfIN^mvtuQn氅,^n0gJ@Țy6h= .T@C=…|PE@*RE+E"@JeB\*yY8#c(iys6EXbbtyI"|{%ykػM8yHo?Su2D)K)G޷Bh\oͲ%]H0ѳe|X\ A".,‡ H…-: K@.bN&8pkvoWvҀ=.lbxL*vž"5+ń#/q/Qc OGbJ>\%ZiGUibu{wK2΢3ehմ.`,!݁h *W ٟvcԠu]f cBpb(Z[|tU~"Xl)viaiG#wh܁ W^7v錧s"@ 7@IDAT`EǟI(+J ED&XPT&"6t,RH@̄=/%w[%w;fw'&l kgqIkd$22syW꿓?^wv:[G'#z?[w9q_=1-]B)^"F/]z7tl+]XJeb%`,4.>?Ƽ0y~uuaL9aɳ)=MtZjppgƙW@'pE!S .'@(x munylr=mM7{]r7VpS,BysߘUҕsnfU7#\ G"""峎[uEozZN-s_(=6lU)R,Z-ct}dyf3&S"vD`i&=…OH <@>2CH"N!,Wva<.ziۡM<f 1 ޜ:EFdV'i)Xtt]{΋Cc71^"WD5Y`70qRu6UK 5ϯ*ǎn7 `ʕ4{@F|jeo^}W 90E0@ f*DM֤/ K6:nzxo7[?n:~CO'?o&\3%Q1.&*O~Djhy]SR7yq1Z 7{"yJkJ(QTRSNȨ>oT96M +I=y.- @k/G?!*w;s:.K.<o ,"M=k \Ɣt!#^Y3\S5qorO-8 TpFRBLiT-~ Z(f<84xq՗ȣ=^5i ?(Kf.4!L1.\u.ڳM ]EU^Mm(Z](CŸ(3Jn ~dg*_>.v>ΆwF`ɟ? x\ƍ #/ .tJ.:_˥0 hO=…U(QQR|))U6:$w+'N袅hL~_ߤ :EzKH;e>wfOJRTq_AXL48 |$p0) agz @H/\7]wܻΝ:Dz E)3cRamt:t'S>V9K^2\ .9 %) \'@%pqZD$NC.i' H8(Vmӗˤ4[G'~-<ќ9Gr)<8LiT acC:t>蜂m;׷ 4#!.)%B.'pr=G8W 8\ӱ!7lʣ)!QAmמctU6yD+GʄArYf9_|mx@8b*Fm?>KNWNDjkvMcyt"] }nTy^ռL5Nn}I );F`9.!"'  \@& -2cG ܲ@!7"QʉDRBJqA:A`!agz #|IQSpqQAso=?>WIAnEHS) dNm=x:J 0z%@)R{rX; $OiEx@;K@  \#L@ k_) (ָBp Et;.BI!G@ph4lE pa1IG%PODT.$jyQ6/k"+IC .rϊ  N@ ,Ay-PLxc:͝Mʵw[3A/%ڟMR#N" )3afp H/\0}!/L0OZ̙ddX6oO_k~K=p(3˽ߑJcw+ڷȔ-)9j?D6;Syl%y5RCycS up &uM0@ f*DM֤/ K_qhZO8!)5^wipYl{1܋ksH3㖜tLvcR$J/U.VM-).Yj 9siJ06"?gԹ(X ]Ñ S+UM))T9+^$])Z"D: idTDDVw?=-6.Nϊ/Er !`,?ApqRX8Cǫ֭(o}XkJKdDdu'v>Rzy[ S.w̘0_V1.2nZ^hA5lȘ+ItBr#{y١jEi^N{91JYRTy?Yψ q)*']Ípw{Hlcs)o;Oʹאj*HD $TyTDH".5z&iƎ+);B H2RNE9|<\{iœ@P \3@N!pTms#\l޼YbccͿl  , ÖA~u\?B˓JEj:\% OVtNΪ\ΈZcZ)Q:FL,%JŤ+K ڴBF ټnty ̓-˕3G~T;ycZ<ֶwX^^_}4])2(Q^~2e}30Ki]Ov;]a?I~ɇp>2A/~a# f-[88p`FN…,,2g7co@x ޱ9{́LWZlx!RD:wby׻ZkL̉=;-tZhשMrty{έ4>"^y$'ۿh)mjgLz]ίVɐ^1_i}A5O ݋)0ib?ݬUB>0~x=g=#Ӧp Syj<7O Y,pqHV$V"%\\m<.Gi V=ދ~\.2JV˞٠'iIW(5m|ʮpvxpqo[–uXtxSebXr%\lWK.mnG*4 ݎE?~J %%%œO_QgSV ի?=GdNF? z9X}R/:kb_9^apJ"x)p2^ D@#\ԼԞ\z ^}WMܲ9ѓKlː0LyHl7y}QEv+}]E&{K:.kVl0׫,EceM2gbգxگ{umܩvFyj*>=޴?H/:j3RlJ귎_B>C p.ǖ!"nmAK8jOKz_w0xk^'{U׃SRWcXA )A|22T3yLV}J{d]Ҧexڄ =UKgMD;O'OȨ'ޑM1m)VDn)6c@ٺ~J|#F0dS>^+ sA%pTT8/^x[8ͺ#-\XHz\Z/ ޛC쭦T1f#s\O=Rt1I9"s]"|jչ M̪":0M^(*1EԎ(ٹYy\.:YZXƦzņ3rͽpBrX-:J)ͱ;j'tnko=?DsjLo C ,fsU@F~x3m\cFTV.t+o -J_pQZmb\b \MCG"tp(u7-bA%\P1 ך=~䁀.F.;ݻeS99>@@L8)PfH$\9w9%\TZ)fo63#…^h6w:%Nl~l=Kvzwh?lK l99R$ꔨaҰ ` ի"1>q/77EX t&.lb~v̙-kWR&G*$kԔH%`ta)@]>v:k$'Kr`zIUUԹв%\TZQsO:,uV "T#'3p>} =%C=@JT$q͚KlJ!iB غUϓo5Iz)$`|.)ĸNM 4˕.\iV:eW:fҁEť,ii7mNm~}|h4xh &` qU+ Alڴ 9/ \{P 8^k,Snګ 'tujȟSȾ iž%\TU…Si'.di MpC9H"*| hOg_tT3~R⟀7le^61ϋCsKW1#EHM* sa~1-hpsERuk)%_믙iک M H iG.6`~mۤZۤzC2nPiqmO{eڧ /{mݼ V3f@ hK S{vKx_͎v;^uz$.[N+ 6+3rV R 2Ŋ ʡLwqٹc/~<ܼ ʆ \x-^Id^Y4ҕ1E 7 ?߂_cǤkXa$Dɒ GʖI3J…LN@~d=RT ؤH R.5, --S'IuUW>r8C6?M.);/mځ]M.J))EeU em&}F9z$AWj/5qAN.d K>#ZjRʟ{.7;ʲpS)~.6fL##$~L_=A-"ʖT t7;q߳_&+ ĵB R.5, -K-&r댛݃۷_%%=O ~. }2vf?uv xᑇG=,[1[ ѥ]=N9؉…A[\C`arxs4)33Yv={dIL8i2bk$:bOVβg qqsu()1I/%KI/s`Up) n7487!6 ;[<,{u^5$zsOy(Ѣ)_HY(puN6bϮ}r,IpyI$tV.le n;Ytm1|ګf(Bpˈl~$?*")&E"R1` <b²MN][BQc_*Rf crPON1_rC.ĴG \a"|#N RwpC΃m{zȴ.]/Z(T""稂Q\_x}IIIʣ"5SɉmV4h,yjh!pa[xUE0MIT濋p]I:~T-bLؕ@RF$.PĮͤ][akz:HpL RI-n~~3|.r?HO"=>A+$? ` Mヌi"LaæK-CFbpc +D"@&pkT$@ XeJ`E`#\8nv"pa'k@ .`E`;pp_eqqb~=i}G tl?8 f'1vC+ c \8T4p f:%\\ VɟD9"z]PBY:fDEF:r:NW_gΒk^,%?Ĝcwi-DN֠- \v,w0O1.lppHq׻˛&\)Jqoޝ֯g||'p;3r@ȉENt8? XeJpC۫p%,ҷzÏrB:!ߎK^9g<"׍.|ER@ p H$,m@Usزk1Ó7*6J\rSp&GShtaR0%&{Hr)PP*// ʔϲCef GɢE\ɒƎ'NJdꋗ]%$2'$%f:^=$By\x'-wrRQt)TN#AMSNocRYR\=cz>}Du#= EF"| 7yGndI pe vB?oc>v^R|]u2q2NJG_#SOv[TO6|:{JtF01LbE/K%dˣdbewwEP…ev6OW6@~3M5ތpDw>㟙=pJ.>< "HdI&tl wygH D p4t*|b0!Xer<5U? eKFHt޴gGO=a<3NMRNjR+CISd'/o)}oUΌ-!l*4FjZ.FNQvxIy\^H;n#\<Ӕ@qAw&]F 0?~XB96RZBU׳ax?B㕇JeH_c'N0. @~@Y @"8,ɭ:p$! syF|XĂJ4#O9E7J{/<'Vӫ=IL&}Uwӷ$T}I#ꞃqV7MSge56O?)3X% aʞ`ZLvlܩqWBآE以SÕEt;H5C'j*_cg*F$-\.8ccjT#@Y8E;UCs._VuEdzֻɼ&ꪕ4AN={<|.2\X6PEnjYѝrqᆭjej?L!F Uˡ ?/_1DʗP^yoO *ORSΪK"TNUɪ+1F*gjwTTosTl oUKN<~pa0…@ 9%.n1K} tIϚreeKtx+Wʭ*hEzΤ.~ZP Y*OϞ&/ ry%ݮHJ88|&|೦=1E#BxҤq.63xY=ŪW%bw~kx"\  p4@ȁEp8 .xzT}{z\)^Z uUNqJ oϬơEV#;-iSE(8j;^>;Ϙf@Uwt`:Cശ45UYA4@_+_^.|GV.T@O#cK7~=yYűXJ<*W))*YM}zVQ1SENoULb + ΠF8^!_ \Jr&p3B/pqi3fofXe$UVG~ I+dQ'b?3sICIOe qZ_)OmG{\|1cq/{LY1"İ$HI#d_i)rʳlf UmGȮk^ee CqX>q\sr~|~Iy+  @v.#~䁀%\4Qo/ US#I͚2^ٕ4[-z{2SC1I:h) _KS͛++J8QT SrVxWOv8 ZړGykK#_QJhxNGG4V:i,Ւ&W׭O BOugFU^ڪkr ᨱw٘&ܞ[6ZXDx:.?!9I,wmG[8eN#\dE.A @. \ Kh cW{sHTr&nIz\wm:IJ+Ulc_{,:|.|H x@{@y'pwL,k5y^6r"U6RŪ`9s5KJLRZO/iZAh"Y7 ?.F@9@ /KhpuwbFnP(rnJFyD.EoN r"pA…K۶UiI?//L(7lLgg7˽͛eHMꋽp 3 @.rAG#\\ ?ɧ؅N{NLVV8!*f٘bj(Ӗ¿\z |%p8) i,UWGV`p>hc9 7yGn@ \d$gKT au(#COO30R”EnC#p0,WdR, P/O7E8y#p7~ EF"|@>KpL9$&"#Uh;0É3(p!E@ L \6 0CKL.iw˨˧g*U`reDl~2.G@@@< .'AKpqR^ßI?=YvvXէ["O8!_,]&a\4\cGTkד `9^|Xھ4x\ ”eit~w1.|gG@ g %p<) %\\ҢЭCw|&xu*EG>6C( -p!)}fyVV7ʇ Z"6SrAJxڛxS|$ZXYbZMѱ46.Ҭf KqK:όOJ1XIs k ?.F@9@ /Kh`2oVkLXmV?o(שi'z;[MטCrYg15Ey=˕E}{Ad#r+Aa@kͥ`Ti"m_kR*V* -<{J F2k\qH4!Az*OVjqns%7ٯ۷}pfH1O9s$wy,7`  ?@.r!K..iL=x*1AǫzMZYEM (Ǵg}@IDAT\H窫ԏ(0O(SL=[J+.+o?rVoxR,T^ET } n2+-B/%hDi1Cfxkr-o߷.2}D'\$ pZEDN.+B\P깙W8yk)[,S%+g²ǟfkYݟ +WmvG[i<Rw\A=5\sRw˚zmګnm֬1?MUdI`2Ow_n[hU~N?2rYjjYQq-6ni=O[: iSSPq]ƌʥJ;?Jj,Y@c ?.F@9@ /t…t2I7Ky*4XQ6Q/yGFgH-Q̥)59i+=4\I&(/ U<cڴpp9gKU='٩V(%Tb KwSCo{I+W} :-j]i9!kTNy\D>2ɮ~^"~|.)@ أ7=A@ \!%@ KD <W=T+Ҹ*]@TZU\1u#xT<RJ-vl]#w\|*B߰vP *hJi,yoQ6'홫m.7rJֻ`&~A:6ndgWHџp̿t ?.F@9@ /KhԨ_PS?Z<6W&_\-p=.t:Ž}*׬sYgQڡdzT/[ֈzcOamol'5m"GZq[,qR!:o+wXŨ:Zi\Q Q橩"W8`xSy; R}mRtiOzm%jyҶT9B 4å/xSη?!AyL*#JJㅡ*GXM>o&tDhW "EyF Yy~ Ʃqvl “ oNWK-3}F;a|CIA  N%\\ }=ͨp"!&.tp"` .(3,UZpN6}@@_@s NH-uuuD˥+"\`pH 014݄F"h(XE XUDń`:COg`LΫ \xM_!J@8@gFJ`E`鯿q,Z؉…A[ 7@pXE .H%>I1Ido1vC+ c \8T4p f:%\4& p؎%\4W xs5ZJ3 \nhAp  ǘB!pCLgFJ8;C+8=X3 \8kܥ ;Y@n p+u2?ΘB .* "pV榳"pxs/+&cKB@UT`hMF*q;@y%pW Ez|@X.t% R\I5}W8p{*Aވka0G ("yt3fHHTW˥m18r@<q!?E@@@KO@ X.Ez pyMN+`A)^,Q 8,Ň35@ z%ExS,EoM Kz %RMUZ(i)Pi$Laө7EJ 9~"U%JH=y|3uڝ`\S$#p7rAȎEvd HQo:YwOj 'eʵ$Jy8A$@ҥl@  \- )$-Wq%hhQͶl+ i Z(:kx SBl0@pQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!Op+F@!u>%; \Î n@! .A!%pRT87Nm `\7c Z…;H/ 4p) ppQ R!O@ 8p@*Ud۔܀n޼YbccͿl  8 Ǜ@$p@d@  lRdY 9 Z.c Z؀/ma@!&u#z@ , \$ [zEn{ ppm `-lB 7mac @6 uF  \ڼt@n~= N\7iWz؇…}lAK 3-ld(@&n4p% WNAy%ӯgx[.! ٔA!pa[@f o @62M\EU3@~3-0eAp'C>% xz D ، @ p 3 @ P="P){pp-  `-lJ@z7}3@8#q@@!-Û%qnIܳy(>^R&~*IO`垽fGeSPD)*L]eItrS%N5lN no69ptHgߒ滮0};3KT4M.-EHH3/pBi# , & -V_J. v())yyFplGh8|$ -Vlٷ޼[Z*)Z`Av.%sfa1r隵$:-aŔ+/ KԤ$9{jҁx_ٷ~Q얗K!`xChb޺EVB\!-TB1J)+" KrՄÒ|PvؤyPZ s4+lLơi% ["LL(SY[ʪW ڞ-hY^_7i%Uk'NͿ( !&t,%7qVl]T(Y"zgЂƎ77?MUH˥p"@@C>{G(_5om$=1͕]VhvV;C^~{L[=#XT*]G=-Yᕔ@. $;,n[,[19qy~&,d@0 \6u=-Xl:p(Q,b+U{.@~8u0njv3}$ʧt%X,i-S21{xV#iU[l'k^18…S-GE@O 㵱^άWQ}p+W)Lu }'Y!~x+r:D`5<~)]ǾRy_0u$@)~@Y -Z,,oUmA i!}jIIN6˧64? 2-Z|hY4*4q^AՅ'}_S$%Y>=/T6$paC$-J!u E˗wO lN a.Y3;IL8Ahxz^V<( @ H$fH?G/j paÇ!ZhpwGV LBOz_?ЈzHݮY xahѢE[X-$ȩGʈztʔ@p \7k`٣H ')!AOUkBQҭ 3@ .A2Ú Ɖ_HJA!`Cz[2bCۄk殟,O2*)"+ K{d0eĶ&aa@" LG qnYثhCĵzj@ XSFE%Ze@H'ob-"*+P/r&`M^ϔ9G!|'pH)0 +cl)_ԹpFA!`kk&M]VJǺٺ4mܶH*# ҧl= ,SKnݷFUFn`{V…KI@{[TX+["S e_ׅHXBoW׿Oɿ) W ˏo|\ź"_RNKⴈH2uYS!wH/'FBfxILz'Mjb؟=M +T4Ѕ.\hTQv/[J"O𙀵HA>'Je]H^AA"`0R|}a J5&py|"0Mf{JdT*@"$G4_5@UCȖ)i ,m:@ p<5Y&24uih \0v/]"*?pj @ ?,-94ktI~Iu; ߐEHUA'OѽG֙AaB"L M7K`Äew'RNEB)ٗZЊhATJZ)D$ZϮ[%T;יΝ;ssL3<;Ys̿z̐g) tʷɪwޖ[JWŭ\ B`_͗ GU>Erھg?7k??/j]* j6Mt E:&&^>fͥlډ W@u.x,j\8)$ӏT*><39)`׹౨D :mom-U{*쩩R:r{sJE UK#HU#&HE[ȢoH6oK*R'R"-Fo-E|)%v8T H^A b ,l{}ݷ˦@H}H'J܅(0a/*!MO@ E /"/UnEZu'I}*Ez"{ɘ"#Qsoș G\n$[`q <5=$@pަ  H-#psP؄O.|QT3.Ҫ;iL.%uȻE )!v8T HA Roք \$HE‰@(lB'>(Vi՝4&Yɒ]"Ef@\JOP \ik.FK$\"\ E6! tL+N,ds].@pwCJ]"v3@ U.R'G.۴5a `.@ppb.A" O:jEZu'IE.y Ȼ!%.@pg *#HAmښ0R0  H81 @pMD'E5J"$K"Y\ \ݐb ݌3HT $ 6mMEh) \$ D &|"@pᓎi%@pVIc%@p,y@.nH \nEEz&L"a@.N"\D@a> IGQʹ H1 H(Vi՝4&Yɒ]"Ef@\JOP \ik.FK$\"\ E6! tL+N,ds].@pwCJ]"v3@ U.R'G.۴5a `.@ppb.A" O:jEZu'IE.y Ȼ!%.@pg *#HAmښ0R0  H81 @pMD'E5J"$K"Y\ \ݐb ݌3HT $ 6mMEh) \$ D &|"@pᓎi%@pVIc%@p,y@.nH \nEEz&L"a@.N"\D@a> IGQʹ H1 H(Vi՝4&Yɒ]"Ef@\JOP \ik.FK$\"\ E6! tL+N,ɯ۸Q@\{r5i,yOEF,\$Ζ \Du?1CJJG-bǎrI L2Qˎ7RKɰ CXby)sSP";*Ew1 9SnRi3s{ˍ-[E^ɴ/muIuX5s .2{  O=hv+䃷? Ify #l=ʖ++ߙgS>@K _vN5?$uhbxήngL ]jm۱CNjy\6e)z"EvLW"l-~ӥf횙.M`΂L-z^jԮi[Nxa fdEx@a=r\ \~Q H>EI :EΜܣ{ÚujM[ܾv%gi'_._. /]yEnJ.ҮK} w gˑujg]edCS̾k_#}!p@cJN=$<߾ 5MKZD$ufYf]7TPAUOYHew?S~_%*%Vm0޽ԭT,]B֮`6-RX:p)Sdô;vQX/QV['%9ZCt}鶿 ?dŚ5kn)ZԩQS |M͈A]n.\-T-?h .^ /OՍx۷U^-?,Anx{|WI‘Cb=O_<@}j 3O8|/Rzhp◤:'ٲy-[T9)+ڗQ֮Ο?~RYu/P5U+KlR r$@p#&B q夰 \ .4;v~,U8 yAri}= g̚!G:̷2bRX1R\9 czJyhbk膱}nN͚ʁگ猞:UMf:ӆ'ս}i[uiF7=,=ڴv*(^9㏗W(7bSklGe鮻9yQr։'Mx~w˴ۂ"6\Ŀl`%g\MiN7rYo 0/N]:#uwvŖM[Z+W{ oB3;S?4LJ,n_*}ipmV.RD>EѢMsucoӎޛ^}bBw;s/@p{;D < #т ]mEG(WԗW}h#r [, ;`EZAGTzM+3Β#م.m~|w`_C{tn]K"_yԐ`*=s exMr{fsxX\9ʄ4n{>qҰ~}v[=' .o3E \4^c#w[ΘkRJLNm{E64J8r`(,Q~q(}ə)/{9MÐ9o-iFӬU({Eb@.nH E>тyW#tCo_ z[Z}{/ܲAի'3gd:@ QƦ/H}}l\r&prbf{;I϶mE|hftp9{zwDG iW˿={0E7R|2YBmH .ct$ ܐnӋhprzzfo)mg_gg)%ږ;v2׷'sR\S7[~Qy74USrn=7vۙ˖.Tؾi{Ys>HטPϭྃeެfߍo0#/}&KbE她/I[0蔘SƅB/ 5'"k\q"{.Zd.Zp̋/uh3o/;½y#zsGW1dԓO`9Ϛu%ܑ:uD5Wm~Ҭw9v=GyM幅oxaJ i}YSbpp TMy\N=P0L4PZ]Q~6pn9vTqӦI͏6aqN;EV$^"n`pG~]AVhhxRB9@e&PiZᎄ= ohcʰ@MibFU.xEݹ1ftǗ~)W]t)srK{EAg=׳/{Jё$ǟrLh;9|ϙEΜ8 x \S+@p\:n,%5tL&XZvaF 7_*sw7Sy ~hFܚ肖k7jqpٲf-[Ϩ2WYFd .t#†cts{Ӆ2缾ЄZxpD9yhh+o.[wb,LkO1 >ŋoݧOY*|7>\:U5L~9#yÆ]=f"vECK߻{ކHHݶsܺD )"ms { ~Np{7htY#=Uy88-.rǸF?:Z^z9N9g'h#TVխB6yHyl&R؛a i|VƔG{CϒqTv?Ef~&țg#ܨqa{A= ,т׮:dFI-);]N7_:=hG88׾~'kΖ0`m2ir龜Ցo<ԬVMuBn6]wcG )"ms { ~Np{hָGS_}7S:O=Z鴌."h. }$va{+Ln:ahĵ("g[S{q& ȭ!\ŰQzDBF }(Z4C+WreJN9|D'kk֯e mqa#93M545Xܵ7Z9ț#\bEEҢmR^|L$&ݽǴN|52NYї}Ԟ !"yqv˄C5RHiKOA2.>֫L~15hT:oϓ7_}Sui#64`ן5STds$|T=G/r4󞆢/x:D[|VS_b ȳE )"·hhP 7Md{#^e:gϞ3MK dT O DqNfêVB1NC @g"E'>vo̧_|1TqA쏽'TT19n[ߟ^=^nvlxBpf~OpكE]"W[|\vfƨя7^y,iDCP#ZX`kl hڀD(p,U~bғ2af]ց" WB7\$(@p&^vDu+_{a9L0Pi3S+'uѺmTEO@ ! B4t,]Be9r!i':EL}J .4\r5-vg}PB -')d=M! xu_64bKp>}r]TzVj|'?\W= ;S|2K;uD[S_kF*矲^ #nf;6O/PNGk&dxv(sq|ty=_GdT7_9tzoIJXpsUD z'Fv'9@4,Mm}G_S%ZxFO }oQm!Ϟ8s;.ˡ/M@IDATuB={zOp)ibw~Np|hv$CnbG?s64{1Rd΋TOw} -OmzUW6n ]ZZցB5ߋbYs%;y ؋nF9{7Dx>T{:tL9+‘Y7H}7^FLyxv~>lH`>MaryC>[9kO;#T49rxPFO.XqgZG-|{̂7 ۂ"6\Ŀ"kɍB:yg;D٠b˦-rqKB?Rr_Ou֡C$'zB(:eeCO!q7!`. ̈_-ތ.ЩڵF.tK—=g,lh" I-2"= =iCo5ѐ$aӽEB7Et;SԑxO 3G眝iѬ{PYv7RfMI#TuDǨ޽2Maу~wz 3BG£KuW[39}6 G7Q"?I.:bSpn*bk_tC^{5)w'kw޵[">f7nSHq[OQ޿-]&]uիքcC6SnQFF-m!ի'h+y7b U @p۴rV"K0?G/DGlp)*U ]B9T^E*+6WTRrc[w) 5t*}#[7ll֊)&eK?,t=2ڞ%KJ ݹ~o ^Ǻ MtGC _DfqqoQgtj"í\zs(P=$lG x/Zh}A>7EDc:)SLn\$"؇>؍@N.r1E%NP!JOV; .R?ڸ#"=4L.2c{ -@p H;WM34o\5"cS)Y=어Ejuj/7*pf]hS=R&PA 'E".C}-b8_ >  Hzkn59I%(Z5~{+@pخI H">NK?>EEE;u4 \ގ3s/@p{D#mްS}IgZ3ץL \ߨ.>E.R# Y"τq/`=k.3.lƽ2)T$@l8R2 H0G 6!@p~%@p^Ik$@p$x.@.H1 \L Et E:&N"q@.-L." \ez \WҚ$ \$ "8 RD1q)#@p2]AE$@pΦ؟_%}ǎG6hFQ3qKpzV>9nt?vʇ_~)%qK!5h yFI*n=.\ /\W=d*U2bӶo! S8U7/rܺɨ{G.X^{x;r(9m[sRj9=%%7twF;IS^T T 4L .>[9kCѦ kgA2erYg̑cuֳtm*Ӓ~mo^ɴ/mzS]9N- ȭE`+l-WVs1_b\\ߙ'Jʴ?ؾ}\uաGާLz'֋唤~ff<]W7u}ܗ#_tTX!e҉%(AiڙP\7^FLy"Ԟ,>;SJ)ǰ2a \5"fţg̔>L;~n&3_y5T|ƗIC? 岳M!'JVY+~ ql\X O\AVx1UgnB _ɼY35}3sfod\޵[bF p! .]qQhٴqvgO~xLS3~v~ qE4#8RrWp?pBtŊO99F:m޳/&PyvH9y/vəmډ=4\wIkS^/LpWAύ@2#JVY o:瘦M+V2?Mi'*9k~Z#Nд-⧕?E_l%E*u EPzv&T`/\ѵiˠ.iOˆMH7q_hQsޯ76Ȯ{ o+K~g4A8w2)YR.[͗/_u$n+VHu ߿i=cmG ZJ-__z}l*sڇ*%7rE9]{sS);v?$;騌JIr\FۿrZe?([) i5kWFAˬ\|-xW^>_v#{زuϗ_^{e8CG3B?]ڻn,zjo~d7I5\,\y\\s[qxlf瞝^7?S='zޟKo*ժHj豻@R[[=Wˮ\rȶ?yD֛Sy#*R9TÏvG..)gg锉?u#t]7"'>;w4۷m"Ea5 ڭZJtY*fT[q3oO *h6z~ydM9n EtoJU*J 7_|#W_|)g''{M[}svxk|xSDquTks"@.rQd+? G5o!U"K?#ͺ.Y":HO: ހ/.Swlt Y55]g/~is=~Zsrmi +tٯ^^{yڧ7uh":ufk9|f)˩f:1w4Ѧ{ҢmYJelC}G״ߓ n 5H+&zLEirkL#j~w2i/٬[Դ 7w7ۖ^KߏزEhs,>z/0$Tȿo"%Lp[9ˋ@ږͿm\!mM^Uf/mnlT{_~)Zuh%]nEJ,ڿ`}ra Ez7t}d x>˵tvɽ}56Fο!iIwx}Y}Ǜ^tǠ>&\ٵsX\-},T-3V-Ź/y^wmħ' ږ7\ \Da9׾4c5 w^vzNo4o0SL;~Л1tvxG77ЁޛhMh6{ԗW}-R^9ㄌ"b'D٦k9/j!a;>T<F 1'8Edy{ Bj"Aa&y7g{t!*M?#aU-"xgn2_n[B8R9  /\<:{2>SOf͐kԐ5K͋.1"Mi.tѣVq-]C'ģ6e| 7vyP淒7e˥U>ћ°[ )썴hM\Sbf3 ff[oK2~ȆZ+y75{tKNaᡏթy}=zW-" }L=f iFm'r-]>.Ӟ]wJ Ia<Ь{3HGy!Rt ;tZ57>M#GGiڵrm&DSk2/bwBR5XBz]ƔD{ioXBl}y곯FXm;{%o/|;˓Ho_.irqMz[0x2LSv3y&oJNoۗ:Za{K̮>HV1MwphQ`F^2rݺ񑂋G=2wE+ǀCE[nwtF^\o 1i\ӛyӵ7osEj*RHf>1Savuq ;"3St^ڎ\g[gaYO}ĝd}++^xѧ#T̋O˱'+χ~Np"X}tIfLyޙrf}J']v5"i@\ȡE8 \77o'>< ?t.2[:o7}c\ox&o' 77'6oinzZL>Xum 776,H7ϼ\Vצ Ei U7n0{OML 45ƛBSt>n˗)DwWsHj2z J]פ1պq͢st[=4 ~u̝1WO(X@Ux sSSA*UX4TO@7e_~\uQzuZ󟝟%\ UhoH:{:/Xѧh3[ Uשp}]?]\{Yk=ݸNͰ#ܺE8 k)o9^`z=͌Vivm38ns^=~YΔIn|nc0ə2=Yw!LYuƒ wK$S;garŹf_{=M;&)a& TVn9>&Q K/C*V4nӛ}8}!$$BG5[o{_Ϛ42K(`o gx { y @:4h6ٛf{N0˭n5?zaMnl1:Bo޶J; )'vvѰAX,?ud!='ZY)_|(;f. 'zN^r.@ps+D @ˈ)O7|9o~eO&|2sFh{sa7nNwR7XSSSe䫯BSl]lg7xSX.{-S e]hm[G{ݰ[H*UB׳LJvzo" #iw:vjʩϢ%E\`}+3ϐӎZ%}%%%\  m>Mwן%~]t 1opY2Λ ~#mFעj0/?yO贌ٮqhCeφn9e+YkGzޔۛ~{mmq,B׷ixkW˽=WpNHew />x/d&qnݢe۩N/sULթW'b_g$r*@pS)C D4l޴7Bwa qc"m U{mi]7E|[nso#=o_]}*/ҔzxHF3t{n"mˍw:v:^ i5KWx.J۳m3G=HpK8N˓@3S[Ԩ]#4u vU~aէ 8,~{ޖM[k97"vf<}w.W9߶Ͷ?{={-;R숋Het]EIkq^nݢeLNݓl O= ‹q0r$@p#&B {D/,Zd{5 kz ʹQ[/w\FwH{C)"MAЋ:H4t{nE,cr} -ɡ߸\>B&LuHj9}.C{t ٱkg"mdڰ7({{=v~7GZo]>='hSExGmo_?|lxluP" -.Zff >+a:Z]BLwO?ITLYz85ߞ:3>^fB~1zGm!R[t{OQo{toAXwS.=Y%ZZnn_㼼bpSeCMU:vhۛO&hp 2OoYzʛrK.QD1 .Ι݂z+^Է gnM?~q{|{۳ .yu?3U{OGl^/}Ӻ]VTkȸ!ryơJ7[ڛq዆F{nVgN==^plO|fžg7 cuJsӟ3lV=> .*.@pg E Qc(+aćSNtCf>ͧ{M]fzƒ5qfvM=|= }I /rn^Pp>œ}q*u7̑jw%VG[V(Xql]2#ݘSxYvnϩϡ+K+5LxRKcعS?üL\qJ\S(jt (R 7vC{iÄJF4c4_xg)Z$T.ٯ[?[pݯO)^ooXHZ8_m~۴2e>I\h٨%7t6Hz-7| sr6_\ ih zb{ p#H>l;,}+ccJ}L58[S:# vT=Pj֮i8rg;3[ٖs{MSoD4&ÿ_px'"e El^@DDzVwfBnpaכI} _._.nݛL{C<Ђ_&|ou=2~uo@$9{4+WJw:?Hb?e|߾RRE{?{ݮ9#nijw%<Λ/h[8r]w f>I f {=v~7rc8>u}riDreu'#[ ʭ[g =*X0f#'`B)1O4 7ziި/񒤜X\;\wuQdfiF|0AG\^`cn_-΄ m~\k7P}lg媕h H~XCZսם+Œjشq'[+fcX=4

(u#;<>0A(qٙOIvBv7Z^KzB7z'?֍[Ni-'zB( NuhѰe!z5/vzbkp[Nqb׸׋]GpmtˏV.1&1z-37>vyMf=7#o.IQP"P@v.)4^׵ˮ fǧȀz^FPpa,h8bAu&Μ)}ǎ 7o-v1,_~(K~a}s[汘o*t@Æ'|Rܩ0zÇF}4)TXNjylMH:_iG~=-ﮋ>[Czcƺԑ' ٻ0WE{H~4H)wą}f!9$.SR}&ϘGy7mm>X?{~\[r" wh{\]fJU*e9Ⱥaị}Y׵1:v c =]IdK ugˮ#+tj  -x]i!]Cw,(zPN,[L}1mv_N6 ?oo2dbc+ַ`ǽ4蹻5Z_F+3-N ]oʊ>N}T,G=2ٝNwnuS.zh;N7EQER4=>Vezz׭*uOb@F  @ $"H{ȎLu)l1`H 60+SZʖ.m_։UZJߠcbudNa9TưXˏZ %IJIrQte|1gL=vcLjxI#rM|/R$?cٺmXF~nj> Mu%; .r iy_E*т ݭS~GD+7º}90߁RPBvsu^;M~Aw~GvYsW~ <6CZGi {l_[)Ų8%6РhCD^=SjWsZAyVk8PbZ(zN=5N+*%Jm򗷸~X}4y/fړ.̩kԨui-3 .KD VbH2aڛuzi#CnGvĉ;ܹqV\Z75țľyHylm!;.]ra z2'[\$֗$@pIm(Nlx *;F?47>:7ud+}-## @p>%"BG6|ǰŅ2m|H}BN)$ NC9h>5!BbqftrU&H.B @' SpZ %Ftפm /E,))~. s*ڟ/Fp g{I]csl N])ӅxzR9 ȉ t .~ K tU_yCN-Jh4Srę$TW$UwMe\ص-]wfE2:aOk䡑eY.psc6 ǔN~`48r`Y.ۦ\dN"@pV @:ݼYzj.XL, 0c.k U,T0%u&Iʥ'eϔhﺌ Et"MH@:ץ$V"Y/Eڳ` \i}r.UL":J"Pݝ2%H",@p3' g<R"| 5'sQ \@!@p uviץ4(@j*E uUA F88\" 3.w~9{]"ڟ dsʹ H.A PgPS .R3 1 \A"+@p߾s .{=Ad\$CkEu) E:;JpBAUQ"F0G q@. _ kNpޣA ''C"\3.ҮKiP.)T @1q8q "E @pg \\s ?uE??Pi'@pv]J$@pN\PgPb E) >W¿}\{.  H:L;R uv 5": E`@.H\@;?לϽG݃.@pOOE2Թf \]Ҡ \S)T.bp @pD@ o~=th2.5N"H"@BM%HΠ*(@p##8 R|5'sQ \@!@p uviץ4(@j*E uUA F88\" 3.w~9{]"ڟ dsʹ H.A PgPS .R3 1 \A"+@p߾s .{=Ad\$CkEu) E:;JpBAUQ"F0G q@. _ kNpޣA ''C"\3.ҮKiP.)T @1q8q "E @pg \\s ?uE??Pi'@pv]J$@pN\PgPb E) >W¿}\{.  H:L;R uv 5": E`@.H\@)o!FPk_ \ۨ4]2IR(}D@`? \'h./ZV=KH#-?Ϟ*e# 7ZFSR]FȪM˥M\^] lܺF};Gt. .4Io_Z۷2T!@d.lE @|:>ٰdӬU;m/o_'#5D|"0cYs9Kr}Rk6.V&V=].?  .4IogNf͐g) KH#o-U-5-JєTXl9)rtRfrVKApA$@pLz lX|:~)~rRNXZ@ |4sz9Wpʩi2߭Lf~*ZNίsMW!}8Ԯx. .4Id3{& NB|.`(h49Q>Y 7H|@E.X-@pi#`׹8RcӦ]4t[T89Lڕv@;*kJ@@'-O ؏K)IZnXZ@|LٸlԽT94hM+ʫ_ϔJG0WoG}'˖Ҹ^[wHb(@pD|.^;6ln44FpR@Z@ |0aiYN"*Qh_M/k{aX~:D pvo?ܴyCt3$S"\;|`M9rTi>@,7OAMq;l|}V(9 |@z ,YRLRX";O@G]׫}wmk)]AHs-?Ϟ*G3";՛.^4HvCDʕU~N`59R(F[@SA"z:3fHVӪm4tlTٲjhRj*D|.h|yk ^hQ /5TXlٸuURiPhүi.KWm&nތ #:*$m`ÒX&=eօ #[/hI":ڢEEF@{H *nk|Cw?[ )"Z$^Lz=LI~eSDU=Y"Mf[G} :mn<&F/kZ|5kBh R BKyLj>5iŻ_0C- \Y؈@|4Xdyћ`6__JC \>=DHp$~Ni /^z|}SO6E@>=DrlӥQH~ #@p>}IK| `Ԫ^]=,F_ߨtŏo%[V2Ѽlq@M vr%H*2"̈ȋz퇲qZSY..K  H*E">*)ag¥Kew@ [~7ź/?7G/ZTlAsn6g RuŮv W/{U4)V?@-HAm5Ȫߖ_ČwH-UB >$A@`F#jK#kK#j$ >-dey߿3 %ɡ_1Qtȇ+ʇ+eejH҇ Of}Z[Vϛ[ah`qɩNJE Dص/l $k)K+m6P@=PPE_;ݷI!.eBx_@ l,n~h N}I25hHfn#аB|T}mlߵUn>ZZ j_=wHQ, 06}lX`5" '"N>US"? 0VmN],[1EZד#+'&\\ \䚎H? 4vvZILN(qabEW0EI zF$I;/Xjٲo&4$I$E@T,Y\[G.Z@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#iU IDAT@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)@p~   B"L#@@@?Z#   @t3D@@@7j   @ .4@@@ \ߨ5    D7H@@@)B'?3IENDB`horizon-13.0.3/doc/source/images/message_extraction.png0000664000175000017500000030634113553660754023241 0ustar zuulzuul00000000000000PNG  IHDR.XTz iCCPICC ProfileHWTSIWR -)7Az;FHC ؑEׂEEW@lkdba`XPQł _ϙ7߻s7 , U /a&&%3Ib@8j@xGE( HX@ Ns > tB|XU @$Kp kIp [Kmb}!Le(H3 0bk>LJx; b1rrfBH48:Jò\B YsroΡ5C-֭6kfCq~jD$*_q|;C7b5 PaB k2DYq#ؖ%B{4;S3G숰823j 4HaFl'ZXμ6BQ!oӄ2L='o4/̊͒ΥW~FlK%rpe07 'zķD5bUqeuČv&0%㏽Gʸ80T0d^Gc?|t#Q>c@!".rAʞ M:Z O 5q O/lqgeԏ8:+џG "x!l؄ot$\9|GxB"<$\' @wvdC3eV6H0.}*ӿX{7kVp-tAwd  DXêgzPV`3vZ88΃ˠ\w/x!!4h b"Έ⏄!H#|DC eH9ف!"ǐ3E <@'C*Qg Ecih:Jt#ZE3e:*F_1YbΘ/%ci[bX5k*&8L <g|V*h-LH$fJ݄spDфf18xxE|D$H$ ;)"JHH{IHݤ^ݯYsRO]{J9Sfnd2Dz;:M383h105 7gXoxH(hQ{cƍLMM MML=MsMM͜Ͳ̶uW,P G V .'XR-- ,-X1¬^N4. ]|tutw=[gL&q']ߝ]H!dyV{>2xzm{_W0@R8" f8i Ƅn}f& kGC׆ߍ0G4FȵLr~L5rhym11{bĮg'kW_>/yw)NuZ24i]9={3X3RR|fEY[Rؾ /:Nם[}V,=}mz_gFEF?ϗ*3(s[Ȭ9䜔c|~u385w}0T;ɛה :"SOf:<[y6v9<- (e.>=e޼c uAB {.]LY"K4k/*~SO% %’nK-×u,[iRN2벊++.lƟWX帪j5q55kj˕  _۰t3_ضAA1lc&M7}ޜzO-Z[oyʫj6me>mm#pGCquN΂Ovjݚv׈kk[hYU֋N۹o_~;0ECZ;Ȗ HÜƌFqSRSױc-nGj'V,>9|i3ghs6ɭB]8plw۩ _txˎڏǎ+NW:]:&u>sk._u#͞=⛜nezuНEw wK)ݫu?'=h#-~B{RTi3g:OyBbO?4}y/z_ _ ^FM[-Qz_ACGm>=y/_C,Q MKu $xv8%Dvg"&xr naRTK߱^k#fg+E‡7": 1070 1112 b2XiDOT,(,,w@IDATx WS7RF(( Zc ٕ%IaY%یBؗ2mߧ8|]=ysW9X@@@P` YB@@@#@ @@@ pmՐ1@@@ p5    j   @@@@o\x[5d @@@\p    .2   .@@@@[V C@@@\    o!   @@@VUC@@@@   x+@۪!c    @k@@@ pmՐ1@@@ p5    j   @@@@o\x[5d @@@\p    .2   .@@@@[V C@@@\    o!   @@@VUC@@@@   x+@۪!c    @k@@@ pmՐ1@@@ p5    j   @@@@o\x[5d @@@\p    .2   .@@@@[V C@@@\    o!   @@@VUC@@@@   x+@۪!c    @k@@@ pmՐ1@@@ p5    j   @@@@o\x[5d @@@\p    .2   .@@@@[V C@@@\    o!   @@@VUC@@@@   x+@۪!c    @k@@@ pmՐ1@@@ p5    j   @@@@o\x[5d @@@\p    .2   .@@@@[V C@@@\    o!   @@@VUC@@@@   x+@۪!c    @k@@@ pmՐ1@@@ p5    j   @@@@o\x[5d @@@\p    .2   .@@@@[V C@@@\    o!   @@@VUC@@@@   x+@۪!c    @k@@@ pmՐ1@@@ p5    j   @L 2g,`)WϐEKƀ"`Lny۲Ykiټz{vKc]}dIoe"9PB@<J*$_@;5PKG8wTVEe}Ij S'ț3_:{-;*M[fh4n4k? %1Eh[|1`ݬwnioo 9s f @:./|/y3rij#i۪t\D|w/|bRa谫n?ʍxN{/#[ .=ݵD@@\p @"ޜ1ExLb֛f 4Y>"TY`̗3kc{IuV9'٧g{rQ6@ RR/dxgCUAjS5w0fI[ǽeǎ{+H#q3|j3 XZ @t7oLm6M~)!5>}y퓧u݃պu^J紩e{O-:G.Ұ|N/07]Q{1 #o_c_Yly|Q xˇL=fDd$@JМ'vbI!t-x;)!@s|#}yZJ/hV@5{ @ .b[ud hs{-mrO I@k(O#XFҧ"WD$P)S pꦰ_w5dX^误s[ge&y/2u-+߼luu)kUk)@z!W ^ydH׍E@S> _DyATR^|6$OE!({_^}&OITR$ 't\ J{\^̓XUE })>[E7j>J@,[$wsZ" b?(Qrq p"P`Wo}ȮB@<~.~,i.^;!@1ݻB!^UDHidGdӶ! KȎn J&b;x @TQ&`{Iۖv^N ޣ<^9G#P+b{y@ .[w > mf//D )ΔI<(믱E{/N]!x/@*"WLz랒[ =uN+PL{O%Gb\+q Pq&".= P>+53i1 ޽+B\x_Ed G#H5@+0kM }$*=uMC * P5:2UD] jG^* A&W-c#@"1UIAH)%J@Ԏ ==%M@<J pQiaGMǁ\ jG^*#-}"D HmSp#C DIwKD@7A /@Ԏ =vULDmy(@@(vC Б9gD\Q;2rɓޫCΈq p$$XL+%^ jGK&X j{O0EC ,"@tdJ7$j%#C{UMq^JK?#) @\)/1#"vdh!@"#=FE# O*l @]:2uMX@\vdhqY@]n A pQ[@tdjϩ(Q jG^"8#PCY S18@Ȥ)cRvdhI(W4PF(zQL1I * Dޫ\A2 Dme<5I!@J\)&q#Z#,ڑs _=%% P+ РovdhV%CA #O] Dޣ ?Dm䜜 @\\ĥ')#Jȉڑ')H Qd(QE#S9[RFQ;2J#P9r9!eH,B tdP!Q;2^*<Q{L@. @p:2[' D}A@Q{)' \Wx+@G۪!c4(#C{oV j{ d  pmՐ1#5@|vdhkr@ UET1G Б5'BQ;2W "P5jD J @$N)Qzvdh6(i P"J >-@Gh:DQ;2W@hq VEj#"O jGO/_"r Fdd(x@vdhﱫb2@F j{ @@BT_L9#ڑKt@^rF G td\-Q;2_0Q{)(THE`IJ#S!) P+{j"P@^I&@"m5NyUYE $#C{ Dm1*YEO\xRd Бk"#C{K͒O DmuS`  P}؊5#SC|N@Q;29 Dm5*F iőm @G& L*#C{O@ 2"@y\ד@tdʈIRTY jG^ tQ j{/I R"@"%M18yF`@Ԏ + Dm-)9GZ Ңe KeUW)%9P j{d < py=,@GÏe1;xv.|=)Oy?(j-R]{_9Mx炓LB;gKϽ$M6};aѺMkxۘ Dm1/.G:Dd;w=\{׃rgJUtϟ/=w)3,93Q;2WеvQN2{ַ胏f?aٰӆY }GȊp#tCMo5B~@ #@G&CQ )Н\Kٸ`Yt4iD4>E ɡ=MG/~@$Ԏ 0dr%ʘc̶&C.:KVXa:6 Oʩǜ&Gx}C=>Q{|JFN@@:td\a;1_g|t֗HpwίmY%Ȓ%KDJskٲe\8GxѢvIYc](otiRcYaa]ijW$}n4jHZZڭΤO?|6aZk]۾]۪%fv{-N(H j{/(QvB/@/:2ՇN7+ OIL;Xv[>˝7i{xҸ:uÕ^%7LؔGͯڈ^sm2uΡ+ξl9w/Yq31/#/Yg_hӑѲ =nڿ (Gpt&``oQ\Nfr[37^k8j7uBQmnkv ޸i}Ԏ WP_B;踝)g_r2{L C }̀nr7vŜo瘹6n\x0KL=`]vG_&f{ ۧ\}i fFni!'hzǝ3B_:={HU'$׍6k$?E DmE@ .R]V?n=ǥq_gД1Gܾ~ F-ߚUtGB<#wy4Dyf7\f: 1v=F;+:S-\= Ret;or.3l9]Q;2lrs?6r3囯']4`goӀ{lrze/ZᎄpGg٠iaGA8ఞfT;}3δ{ ptƉ6:>$+/=]Eё$[n+aoaQ{a _,x Б)BNy$=Т/WS>39験L=>4!z%%1A&s:A4nXڬ&aoϻ\ ~[K쯛nB;*]stަb)iOfL#+8?xQ0( &A휌y:# vvNr79F-)bsv])#C{/~64-nfVLivc6mҶ6GG].{#v DmRgo@ >>˳  Б)NIK'3:υw;/=;ɐ {w9K_s7pMC ow]3O#=tkNᄊA؎ȵ-=ͫ{L Eu1i~#C{/~6\ǡmO8ho;߄ۖ;᪛oo l!;w͇ݮm:k[G>}v\>gmVAzΣM Dmŝ@ .\RAM43v`Ì3I(+ݬè\/#NS;s[z ifkHB}=ƞ/]ռՙ{]][$rk[ ,vΩfDV-eȿ&KN>z`=^Ӄ'2IYKͣK \-+G6 D./nwn=]q^ۉZ鳈Dո 6 ,7Evy$/~&'OAp[ysΛO5iQˁPty|y1Od= 8R/@"+@G旺\>Oj7O 9Y%K7N Sߞ $aޚ{i&' /|~b$?]#b=3aKun^c\gK3 W(uԎ =ۯ-2Es\+_O۴\#l> v͓ @ؑ_uךkY'v |nϗ:'aEAQ{A p0x~ Б>\Cq6m5qXIuR;*S5r2R12>y}yg/Z#Ze1rWd0-h::?`\{kP Bݏ%[n,]6Rnw3EԎ I@Գ䛓>YM~?l> vM.DZO.sztga@JUo^\qH$&eA atd~P룮[Nrf y̎ Ivfs)^SI\Gd m_pW?C2Lg_xwe3k舌{WnD}꫙|ѧrZ8T nj FX4ZQfd8$O=3̱n&u^\ ;n(s#%[t\.X(wz6yvQؤioڑj(,/aύ|ƴm|z \mKvsVېCgMਾy|y4X Dm vi(? Бrfotr_Vx>To謋1س*t䇳nBԽu> @;'`rBQ 3O. D){/=&vxtko&+( \uY':}9fBS{ 7jG^$W@Ig{[ H|'xё9g֙nЀ_yz LȰUE^LqHDU'A Ytds";>O'wڙuHtwi6ۡpw h$\:~Eh|oB]p{DžĄ'‡lإ.YtnzȤe7}^=L6dcJuDǐʺEw;l0C-zZtQwͨG&[l⮖pr{eU7foäeoo*&aw=>׭T~rQ0|@fGOcN;YAMsN> L8:!pstHyyl&GK@\ U3c Yfc`~}R-VϽs˙_7tf:zK85k&-Zeț` :t_?$[]汭znorѧ}dn[p-&h)zFӕ8UZ~ +`ʽ:k7u(ڔI:m^-}Š+HUg1oZG2Eȹr=ӲqƦ=':}j' mAUVQa Dm I#@B\$b)I#ZOl"%)9ڑQorᎈIxQe]:t*Qj(EE)z#SQ^ \Ю-@ jG^jwQ o`jS~.7JգT Dml!.<@Ȥ nг٢vdh~չ>g0;a;'_%7k_Ώ p:#FLjڋڧfҳ/#C{/޺GHڽWf2S '͚%x Dm,%FZ >FzFڑW:EmS:, |'@" @tdj^dvdhESs 5ka2 p*#GLzꚒ&O jGkG j{O %Er ($ @Ȕ@Ԏ jUÉ(@^ $^E⫘"_:2;r@Ԏ k Dm-)9GZ Ɨk틃joF~hkW^~Kn]ef+g_}Sht3;\q ./m^^'? uD!}%l7]ye˯LȖC;1vdXh\4t3i민nԶ~3fYA@xg{͔׎9A|uK @>dX5VGF;xIӦY fx!@:E:!K,1Sߛf*#oNVXaiC-\$8Tˁ'FT|:DG-额'_Ǽ_R=4Z\Y?"3?){nOC6N5SjO>0 eS CE<\"Jjud&?=Y?c|S!g͖i::˸,{۵'_nuxJ+2 \ :+JVK5ۘ]CԨQhEi޼yz}/oo#|t Nm6g 58_mO97hYuU) M爽0nX`| _hqm~M_˿(G??̕ƍ{y6fL!nyAZK/Lod~)u[@9,-G{q)󆯥B}l4O ktdciպ}[Q;2jn'tC尣Yvm`ޛ[l?~r'fնKnt^7ډpe,'C?V1װ^M5c5mmG#kY*Huj\g.zmwظ֚ Wj0 )7BQn赯͗f͛~:UimWgԜeA4icqPZOm nKS.iv,f{ 8yI^[mU&/9m;#eyYi5G=r@ .QϔX T#S9Mz)Ywzܿ_ p~B³/۳u?Q:m)k~I_a&ckp?9I|Cmב|2+ OdK9.un_صS1Sϕwxl:M|<3rm2 ?e_Yg]k.rp0e-Ao9ȓF>=!N0h=gOջ=y}HC ۦ>asGz?oəǟi!s%Ovd޵,a[].ڷ~&]4R?Oo 8M&~ˀ$Zfݣ>*gW1QǝiΗ!x@9y:Aq>\\:DmBÓF6$!')^̬/) iտE_7!W\0BwZOxL|rIA:&+=}s-Wˮ=vͬlEg@Gʶ]+OpKyg_EϬ 6/G[8p!7ull+'=H1%D hG~m0F ve]y/fߧ]P[;wae~_H7 =zگC;U;(uiT`;H./n½%!m:X{ge`SLnG6\5C7a!&I'[ 5m`t.w?vlLNt{[~f渨>zI:Ϸ yqSJ489G0Ie/%f2P2@ %.RR8 T#飂N~*J;[MQ /6ǃ7~=urd_tNv|sܾAEQ?h^f۱_uorv^/=.r~M>~aҽO[/KygnCF5tSfqG_Pm$cL5!sOMP;Jw'Ch=u*t믾.G}v¿c˨#hsO] 韨Jw[,qu=׽&lզK2mI;bG;v7}uGvvq)ȸ{ǙϨɴ[ 2v5Nu!&yLp˄ P}靃岞]:Z#8OWe1:L0}E{l&l`7j{/I/@"uL @;2oNE.+?ls s둕-Xo[,n&ꢫ: K{c2orW@g_;;-ZI_?`4I~gw/D xẹCQ.Luy?{>L.\\/Өe_Y5+bXjgu^NMA;Mڑt{r\ׅׄPF]z6z-2b؈ii6]kv&|m^2@=ٞGv}ZN|=kmp[gv6߶-,n |mɶU kߝĖ-{ 6hݶl23 No^esɗV1>kf&pbz;^?-)zL\%Y(/@IDATH5%E vȸ|{SW˿v8 G! :pAX`w~)uc~AKorXൗOiY߻__Q.|4u۹lq@Cgsgl ^2qcz˳-G>/J Fv&]钙Ŧ]Q;2n\egkB۶kp۪_oyW7 5kwuGv̵u:v=N;V|,xZG?,alϳO?h|ߏ},츏Yes7ں~r6-;E?N4sWokKj}w ^4%y3L' -_Zzl{^<yNJľ'ܩQ;2ln]K}mouv^F==&3:|4u թ79ңM>t͟r-,^i;N rͷ=.{>M?_[m52zlgʤ)r[l9skMSo'I6z1Jl뢶@ 5.RS T#'m{*:am ˖. fIOO6/[1s`n/u 3f.\ss u:?>v뵓5Z!l-}yYmbeC0iT94NrgM/lyzO}{vwWwwMýS5S>UniF+sc^s6tqnBou_͟(o:,-o/v<̛qФ#nvfBL7&M,_ncf/Y-sbKa?l>r<7Km=EweS2\#t}9&-Ht3D ȸojg D U0~uZdwCa#b턻 xz>=Fӵ_}:Y®KͳKn.6_={"3S0q>r6rҳi ]ݎO8bٖS,>EOM*kSlOVɗ[#S!oC.=R>.Y4ĶE$]'شl=FkMv{N[߄z㟐ӂ'b?tϡ6G_xn/fn3ÝKgͼ0>)s|&a=]w5~l95-]6ŶU>T_F W`ކ?gt= *:ڨF2Wv/gf0H ɼ'[ow{9_Gm<7i!@:\)%TG1ʰÌI /:c82 ~)C5ON2LC9ٞ2k%W;)l]/BQ0cȀLE{rnr8vAv鹲NuDO53x_M`=f*bi֤tRa Z.~r癳CmHO \tڬqw6?3O _յbCNXo7z:mҴy$bK!>z[qQ턈a+~t7D%#S.ft}vO8zuC3qCĒ,E 970G{ ԠMw&؁现ϖ$͋mvPcles@ݷci\q~rcLE'}qrՅW٬%#/ۼw۾o)O6p;Zd%z#~1Q6ZJ-Q{A \ɭ[J@*ёqڅsoю5+t0=G}DP욞znQ8~M^{v\ٟ<#S)^3Ϊz_Zw'{ݹ{m:zKV׉ ihq; 9r#3h{[gAf^Izu׹V^yrs {,ڹvzb5p~޸y {|rYO7|i .;MwTO/`? ,X‹Sq(TEJ+b#Jtd[6{麛䪋6̍78p4lAM;vy:|hQh$gW/ru㠿9yaѿ+_} )8۬#t]/zϾ8WcvF1 -|9K`&l!Zf͚e/GMk>>d¿MSM97 v^tjefDz^/Ȅb?o=Uq?GIn]ͼ +]:Є (.|dԎLڻlx7Ǟ;of#FGr}Bȁ`n9Yzˆao*vp%s$*[^|qH%%A qqDAOXwN\YWYڑC{O#tƍ6]޵=uNES1<&e e/n^ $^E⫘"_8tdo_ya :xā6VO DAr#nZҧo ~VE&6-*L=↬'t~'<56/2xL#oxYV_o p"N b+`Rtfl& OXlQ;2qizO~&M,zʷ:DG~f&Pd"vQ{ H@.j^d ĥ#/G Q;24_-=Q{K@.o@@:2B D=D@Q{ɲ  p&@GƷ!?.#C{/ܖ=M j{- pCR+@G&UO #C{O@S Dm海 P8*-@G¤@vdh RFQ{C <ɫSJ@b$*)H vdh)H(rb @\T!@T:2Qvdh9A @5}G\p td2@Q;2Iowo B@[V C:2\W jGߺ&Dm!Q\DcQs".#C{/{ Uޫ1N p $OLG jGk&O j{O%BJ 0#@td@j.#C{yދ>"@j\)8 Б!vdh$YQۻ%" j @FLN jG*&d́@  pQ !@Tߜ3"P.{I Dm!gD { ,@G&K/#C{O%A,'!@\Td@t:2ڑת8/ Dm寧@ m.V БQeUBQ;2 o@UD'A6@&A .Q;2,D@^7  ߇ PC:25(#C{/@^ìrjVF tdP˔1Q;2^ + Q{L(#WEy=I (@G$@vdhU N@2@ %.RR8 Бcg D޹r@ߒsZs^hPLD쀀Q;2wo!Р@`\@xБ. QvdhQwrNN@ ..RS БIaSDS$Q{ (2(@D@G Б-)#Pi{k@^2$UERkr!:2 DZ{j/ =E PeUt P­vdh ApX.@+#mՐ1ڑ7Hx+{[2 jБ@ K-4]^;!@1ݻB!^UDHS~(6)mVk^J@f͝)>xP_cc9^;!@1ݻB!^UDH=%@1{ܧW^-J (V@zLMn㽤m?'@LSed.} J^;!@1ݻB!^UDHidGdӶ! KȎn.( i !."2@z~hiѼa酠Lwϒ^/RPi1 ޽+B\x_Edt ' _qƠ@>a@z^c{$.vFg @\Ī,on~M@߽ZmO{{$.vFg @\Ī,xgwuZn ]7.^)Qbr'Oyݮ2F{\^̓XUE }s̖b ge&#y7o5pi٬u#q35(4b'@"vUFHof(T[(11xGz jhSϒ+QB@ K@q0YttۤY}v @f͝)>xPLvNw+_(W{ | p[)0i< h.^sV"@&}0Vf\v츗tX|4#gFrB> %` uFbQed2E:`n7/8 W4$ p $_SdrHC$|a=T{]83EE\j|"CWjDmz@p @,F&?V.[\[DŠEx@*kW2Όq pZ" %p˴ߔۘ.7j7 Py%q*)" >eސ@{J@ wqA@ _!_0/ ȋ^fkO5h/(c,~aT֐ C{oH4,pI.@"5LHe {->N]yՂպoâOedwͬ:EN{OimWyUuO=%[ PU@ PL5U> gE ^,EoۀEX= ıV2B$ HRmRW`j2g̯_03s[I kޔJGl:xt IXhIEP.r9+@ߺ!g @VXaS9p B{*@(EE)z P8 cWedJWAf@БIO]SRh\ (Ec@#S4";{쪌 #^ :  :2kJk@q, @td@b'@{]a@+^UA#@G&=uMIs "@=E(ZLt@hﱫ22x%@« 3 @zȤ))w@R\DZ E Б)=vUF@\xUdH5%E5 PR8@h:2Eq Ǯ0 #޹@J pQ"-@Gh:D vUF pUu@ =tdSה; @).JX@MǁN*# W.2GLzꚒ"@{@(EE)z P8 cWedJWAf@БIO]SRh\ (Ec@#S4";{쪌 #^ :  :2kJk@q, @td@b'@{]a@+^UA#@G&=uMIs "@=E(ZLt@hﱫ22x%@« 3 @zȤ))w@R\DZ E Б)=vUF@\xUdH5%E5 PR8@h:2Eq Ǯ0 #޹@J pQ"-@Gh:D vUF pUu@ =tdSה; @).JX@MǁN*# W.2GLzꚒ"@{@(EE)z P8 cWedJWAf@БIO]SRh\ (Ec@#S4";{쪌 #^ :  :2kJk@q, @td@b'@{]a@+^UA#@G&=uMIs "@=E(ZLt@hﱫ22x%@« 3 @zȤ))w@R\DZ E Б)=vUF@\xUdH5%E5 PR8@h:2Eq Ǯ0 #޹@J pQ"-@Gh:D vUF pUu@ =tdSהޭ?l_@\4H :0%Mvo% PB@#C${o!^ z \:0ɭ[J@>}>#'@>!TLLhIohV C pu9@ t`[ ||2GOE}:lCђ0 2x-@!s @r$n)hdX Pt؆ P1:0%a{[5d ZC@ ЁInR2 ɰ@> J6m̚5KfϞm^Ϙ1C,X`^OznFSc=6 SVYf=Zn-mڴ1;v(  p5x% ̟?_|M:u'JLt`_ǔ>,\ҥtI:w,͛7ϵ@ E.RTJ 2eʔ {mʆnhpҮ];/%,XeU y AH>E@QunoE@yﲎ]v.k=o@ =.Sה 訊/s `-7!@&H!$@ĕڝ5?zKI^h  gJ@YVGy$3¢UV{`E'`/]?r^H&/\.\(oL813CG`^V~!1VUCO@p=:Xyewey/`;0*}UA&@/e*zW䡇 f`ݛ90Ry5P H[S^'1LnݺɎ;h,$/*쟾*È ~i略w Z<2yd9 yBI_s8 < py=|Љ7u. HCҿE_>8>=\UW]\:"vt_2vzE{QF/Ls_, LɬWJ@t. &v[9d^J_^k٩uM_״}~}ꫯ){キl_H pMU_2t>TC=e__^spY=w%{}W:B' gAHN]R*0f[B+mQY'1lF`AtQϵ(_[̼ӧO-9@B.*KY@o [D4hq'J=C>0VXFy]bȑ&xK[GX@ .Q DvIN`EdI(,`;0 [w1#/4?T&f (͏HS`.Jg;0XF\vO{F{s^D}M0) p #`t2!CGC4p@%lT&mUI΍}H֭f$W6eKTT3Da#FȴiӤm۶r7|{ P9 cXi1W\!:E.]㏏qI:@:u\yf2s9Gth% ̙3G{Y&SHfҢE iٲet؀(ǧ4w Ȱadvt,?#@" Hm˞{S t.:묜:0E>%I#@5kJĉ';ʠASpJ@\$B)Qx =zmaYk?ɓG<#d_:h]ہ;wnQ} X4ibl^k_|>g"ͨMGYh+\>|X}եUVϋp:: _7/\QQ? R 9gϞm&I9Y( ;v`trN;,tv؁DV1eSƍs)+=E .QL"v?s嗭+/VŮE;2E6Hv{wQ]݊^ !;ڿKpwwwB7.!r^ݙydolJKFD'(mߑu8z lْJ(a߁0 . PH0 mƍRmQP!L 490LZ,1W]>!b]iA77=[+IPJ4iҐ,VwޥcǚHLqC(invtqZf eΜƉ30 . TX0XwwwٳR3&&xmɂؠaړvJ(~ބ?CSL㞠Ms@? .¿ `29sR۶mAZ iVUop; (Ў~g͚Eor) .tZ00 %KѣG_\kZրZma*=ڽjPZ?PN:/NZ'׀) .tZ00 =zϟIl A(.BLf-0dGMq^*xx~/]ȑ#)F4uT[?VDąDT@(\v&OL)R޽{lipNA^vX*#6rBzY:9qa%  06m͛7 X*7w՛·aXCc7 Vhzkok׮ԬYjժel".*WLUT0 @`o`5 I#XWI+$p vA;w$a6CąDL@0(ǜ7… njTVM2ύm!@!`ͫ\+$p+~z*_<5j(E:@8q"ݸq:uD2e2b`o\ \0Km^AWHVܼy̙C3g>}UHP"""80" .XjeXqEn%ۼ9,qh#- `=@\XK Ux֎3&BVaСCFlBKF=D= zHl{5j;)_o2޿G/B<pXkB V+u81%.QJMf/VPVqa)* ".xk'[|v  #&SWl}"}rh%{  PąQ_>}3ѻWdi%͙MK&I|'gz3z9w] gl){)F )Eަooһϯ> s0X"->%qLIIbT2 *30 .WfE\Ņ^dqiIZDsCع[||D_#_={lLޓ /rsu;v';̣A ʟx*!J[]S۠Džmb@ą-PE@@(wU+H["2k+~w8]//:d$/7iF)*W 1y#xaTWDvJif$qRQܘ(f8Bc4Vܽ>;C*}"%wkJ+! (.t>Py@ .,K@Pą!O QTh? `x޲_yoKv Ŋ%i7FB*:Ŋk1Uջ^V^J- 6 A"  ."`#@@BqpzJ=eon؀O<~ǵٕ$T+U v7z:%JeAGb{{ɋ|)KQL͇ua8GE1D @p(B ы#(+O|fn| zm0O.5^%HҾcG;;"GJese!bhq\nMCG]|:a' ]a0"@ "" +.DSM(Vza9a8O^Щի)f4rHp٣wwб{(]✔;M` @Tlãt%*O|Up> ""<{{̑"wB\iĪt|l oހ }Kf͔ĢeԏΦnS,)>/Gn*@}_çoۻ)CœKAmpP!"T!00&F#.~ԙ'/& P̙%; FIr5e"ys<'AG{Igbu=@qq@" .,‚@0qQNbЮA;&~']Ȏ4}dZaֱ_eۙ(o .{' .\+ (0ajzq.RA=0N> 0oةE d_RE|M!b]0w*CM CPąa`;ֺkք۫CK0J^;L`꩙]vŅǂ F+1 ". %ǘ`r`690d% BކPZAif)=V!  " =>ږ >J-b r C5'}Bq~fځ y@A8:SzdV3a;z `ۢEƀ@((͚c;' p0L=8RsRE<p]"?.(.tY<0 .>B". hLonM >O֜TqQ6O]@ׁ4kJ}Ɂ !!#"`lp- =jHFţ/iت5ё^}@ڴxc£ׯi?+O|6iD)% |;\-1 JI5 * Ы1>6Y-V(.s"er`21 es =X*ҁm@2 .,カ@PąQ%6 Xk7udZT)_^o#}G(*1C߽WҞԦ5uZ9Xa"{/\QP?S4>л}!_*.JK5L*a'ꓭ6@` v".(Q>o/_PԭۦPDqZGg)&| i<={˼5 f.#:Ϛ5>}իRCd_P)q8trJO [sRE\5í}]/#;F^_賷EsLѣE5]WtKo=cw?YUKEN[CQ"GTloR[ū>>_Iv?O}Α:%9|$ڋj Eȑ"QQHd>x, $P̨Z|)x>yyQ)NL޷>{ՇHH#+_c2#?yzs#alGʙ&E|nXѣ1zГϨӠT/|pz/\H*r@ $ j ላڿ2Vd#8:{ɝFw+s:TixJdB㷨ضoS~ΒXܥ XNݾΔK7ףo s'lAA-2iFR-wJrݹG{t Opt > dH5^A/sq-QLϯ?~~1ZZ-C击 Bprh{s{T:ś Ak8^U ӓXߡp mi/ie.ʧm+j2eVpCԣ5}.|}4"/~|'Zs0upo.tl9DzL|5G>/ ' g d!'.rmab~&.Fv(w22dOW. !2&ҽZ]L>>a :ܹ5*Ŋ߳/dbT suՓԮuטTQ]wQ.G/ą@XVQ `4xZ2q5?G' 3b|dpVu'NP˙ڄn9;'kv9I&q'obdLRu''dh5^S\Bo>| vv"2pVT`RF-]qTh@uyH,ȗ/?.k ںqH_?NܺewfV@R;XE5W_Jd[_SS|d/Ear5a}>ݍ\L\?dюO?S#qˇPM U X?^Ƣd؈*!ϰ&:,x >~x\o+1I) |"3Ջt}u2/~{{T?޽@ ħly[ۥ-0+"Š`"* `F\Ycr6>y'NLY';]]cxڹr%?cOQ&*>S8q{Tf*aH3jD^Nlє-J1YkN%*9n&NU] }s DӥU'xYƬx^ gBl8ɊYsuQ1wNU%(  clv4j@E2f$~'E4M'oJ^+Ӯ_-؞n2B4$)y&4Xq0ZB.9?%m$ jA&LE6;ɒ^J5iA4zB2 V-$U0'2ָ~tf|H9**|pLX?F: Ȉ˧QφZڰ<$"W^G =!&,D˦e1?M+qӞM0:iM1bǠIN <Yukh"m٠/L$-7:v;@|T0./NyR<{b_/? xbv|L,q6I^{wچ8 ;Kݸlm:wֆ~iQTU+RzZ?-`&tbS&+[I5W_s&wK,ȅKSs]HJdL6dDcÇ2ܶ'hoy*]$:ەG:2cQ8S|&;TOpyq52%O}8u]@Xԡ,SPQڄG*4TcF8fp dɾbŨ?J;EY;jѝDu FNՋSi]9d>fYDWjoEK.dz⣃s Ӧ)sG,rS/3֏LCk>1?ń|IlslN*f5=#ѝacLG@Pe%PJ]9\y<$:Lu&ԨSmY߼U0s>m<5ULaʄȬ(p|tbYS'JdvFp*{7E })P>{Rf#ՋхcWLqO2Β;Lߛ}t=М'_#M}j7?~ q2E@((ۡZ5'],)))OXy-&c6mZ}xEACdT뼄aA6mZLI,QL\ݼ]~$Ӛ?ث]Xn}sT"7?;!+=D<Ѩ'K^F Y% *C(UBv:jf>$9oǏБ߅R6ױ09an?'bΜ&E:BFIPZ >}M0cnEvɃd gʌ- ҄fM+;}L<6z 8⺱g/c*[KCnau>ChTqQ8G9zPjz^+=O&w0͓$@,:8fh4xN/.v9.묘vڒ'Gwyor-LozE@1vѝr.犩څӰVr.{fZējqTiPuKDJetir%'bs X=ҧ0&Q4v ^8nsyO>nOYQ."Ky¾Iwb+--OPEE/J!ÈH#.fgcLmA?"ϩUTUU^Y."Ipn\Ssw 6/M8Fc8๽d^~Bhoz1Y &|Eb4$;}0h꺑o5{>L]j9S7Z zT$Ar@!ڏgo[ [\wNX1xr>vQfn-y}_42tx*~&.616A=&oe*#;OHDBer4\ xN{sݳP96-UmT*P~`sBg!{%Jq~Kg$$EL35% *>q_S|:*F/$E޿H9Ȗ/_=Oy9`F21f_YPMl^.[O^/L!(.$ @" E\Ņ1߭]+ ixzƃt3wS9&.rI숒'&/)Ȼ^ 2'ue/NJ 1Qx[t$3~ qX^#Ζ[J_ؽca eǎҦl8}Z2qu0O`/(A ;" I-'T/EFho.\Ą. i vR*:T_-~rL\h)'Fs Al]$Xd9$٠"y]S*]ⲹ}>]?w뻲Q>{ڕ#CvՆ*7,?~d*M.#wa=Fz%l0/6|/zXŅEt@ T PąQʕ 9OT b¿}[l,xן<".eR vd)߶x1b"^ޔ] ZD9K1[ 'UJC[j~.?FUr夕lgd&T8oWIJN }m5+f"U&iKB1Zt?) p#\G%Mv{όK@3ywKd{*ʟ/_Q?v9fu?^}HXq"k2?8v".\|C۾XBȗ WLJYq1liCE[l 僛sڦ5i4:$:KNgw@4:y; C7廩TOpGEl\V%1F: O>}4~_2mpJ#wt繬dE;F|z^}&w8o񶭃~/Ia`@%4)hƤoؼ܎g_y]y_P>f+r1_} ReQ e:{ڥ/) (>׎J055qa 0 =jHem/RS^R ~z[ U&O cl:Կ/%qCy˂L|kqg|̛1}>=yE־|- QK炃_ TI4H(Hy+H!fU{:q:,[!=Dْ%2&GS?ؿJR_RD'h\&i|?܄nlKdȒ}^L>Uu'.cqVJc􇯏J9\S_B"rW%jHE; a9%.&3X*.ZFs=Y{]vUSix?*\> B韹[ibݲo}J5(E:,2yc_ Fb_&.QEWv4nR(3ϧO<3{(zQMS^^՟ry^7ڷ׀Y\NF-l9=Nܑ+4D?5:ap34Z޼䦯$.RƪF Z˷Ɗo8stMFsQ(i'Als*[MxuqiElO+_|aR}p;M=O2٥3AzP|57P\R=lqa\v". ii'BVg &R GQa\X^֕5 ϩXm2ܟ A/ݓIE,oꈫOɸ&ԯq޴'}7K y-'AP;_W^1aVD"w>RIvm%'A?~.UQ<\Q^|űLh;\7F x<\T8O6A-s;+Bӥ".K~sbvt;'yzx]YűK'{QBA Pԩ$Yj2y}-;7-]Ud&ݼ%][*jf9u\z%HOXVTE{3xئE3܆sz$(^Q"S@_ilwJ-/RswQ#:1q1!&r*v)WX<(m "O"W*vw@NgHgb'uMǘpǨNHiV=6w߅j`@tS6V:զI .z!MIL r@{|Ok6d M눕m^9Ym$E⾣Wm5Rt]:bFC{iGAMdRvX:w3KEQ^|KSfcܮ_795Ŋ A\0VRqC5"2>_;^>yMhd6 5uT;ם*궯FgO[3}#JV+L/^>}>3Blޔxꙛԟ8wU #/}qi#| `E@\XLD". (6koxIGآh֨>)}gϩ f i#qk*4 dqvd)v1?n"CǞxHj6] |T͙Vi$D$ pBa,QzA뗢Nl @,j_L_ [Di n욾e%e+Fu*up| `E@\XLD".(Z"lֺoc_M}'xY2 \kv%7V,ȉ0/xc&PʸqPߞV?+}}~CwFqEٓ&5޶V uxhJ;/_t]3T|'ޓ6 T8צ(dj^0MkPW-K#>qeْ& g* y%O..򇎼8$Щm|!]twGgs;dAhQ0TrE)ť}L$b*=+Tqoք2OV[ioxr\}T/Yqk֘*dB5fpr1m;jǴ@4I1PyYjWޖ6$;/InqUjX:h&8 j2?,cߗvBq!aVDąDT@((("Eyx3ɓA{=弉"^^tVK/9g1I1'mK3~eD*usN!L$&V[XQ%!?=a$QyyQX~27 t3mP%Ge-y>sMw&NtV(pN- >:Z#O¤VdP|yFTq#s>Λim> M>?]=(WQ^af8T\nI\(G̘cA㝸~ ˖ѶFrrj_4ğc~N+ف}8ЛoiPI&2$DD~֡FLzĿM+QHqeUV qy =85})axԱ@S)SL)PL\DwPuZ75ʒ**E#0d|?H,>M2w@N󇯦keQ•V  >K啿tN:s)Z9Q|Ypp7GS\W gP\ _ .$FBhE"Ey(fۡ7IVͬ(K"̇7xZ#ǪV-Zva=۾KEMh8KVmV~O/.2>PY̪htm̄:J4;>=Opʈ YQfMFE{֖7wVD>ŵ!Ɉ+"&Sƥ)JT׀?E.[S~W$fչIԆJ2y ڎ9 .K߁ ."ChEB p}kȕ<9y[K찳-/Ѹ˾3ǎEx)PBzGImR"[ܜEۑjgXD*Py#=Ǡ)qʄ5 ĖdE 'ҙe(qN!$WbjF;L"@'y[$ "92aBX(ux_z'V?ED%ݗ_Z?Î<#q>3AXu;^L?8|doT()QxLvD#?|E/OSI(I~ҳU mWojj\\8* . '"@p!_ky4r@+Ђ na\#rֺȚ)WA7WMH@숓'auxA&ԸGMӱ==iH)~y~)nj~=@\A@ @\ ;F@ƍ9~}Gk%Kp|GAH][:n/_8Rf`8?_;>~挬E/VUdΘ|-ݿ6/ˎ0oRr9όX+r&];}G;Z5i٪KCu3Fd͐LeZJ '''i7ǫ>- .t[40 E\9g޼,E^aǑ/ؓt cƊ0r:qNTq9bvxio{%jЭ/`r) s 6p?2ʕ am Vx߼uU}(.L] `%@\X HD". -$ba7Gj " .&FA@zW\\>>iRS]wc泧']bJMy Z%'Ϸ"MJreb! />#rZqVk >.2b@ .ao@ qkzy0Kq (]HoVj`xAm|y)y}%IH1bƀg gzOjhvkđ= .B!bC@zW\<ܸm@ cŦtIa5WLPNן>RFM(y*"E؎(nX.2? uÏ}-}HVu08DE\]q%]>|<<(KҤ@@|ϞC{H(Q}0OIR&&Ht0жzn^kIV݁R]t|q4 P\ .{F(ą(wҽ?WSTȔ#yRr }QNT[$X9D]9%Nz|P?œۇ(I3v@\%H XC!`$B{yXp.A|5*㐏ZBiq&-|1KVѷE/iI /'GXy8pⰼ%i!r6߃EVA\HX1 `4ݝ͚. r1ky4%2Ey} #Jy{{.">} -vF1c!x1maGbAňƈBF#QZ)U_goSȂ9J&@\)H XVc!cJ <ܰN0?§Eu(iJVW|-ҦH@JevaJ~ {ϋ&pBEÀ@Q {ȜyY5QDqJ(m8B?~NOooZyNdn# .w/ .l3  F!.}F=SSyk\y@e&r8  .} BP@(b,嵱yKULo}ZX6KEkٱ]kQ(?~CݮRV\\Z]/@\z #( ."h#E\};ټL/]>/%pA^TI,&Mbq=\} {Qѩ3/B@v".Ŀկ a8@?~Ga.G{@\=H; .v [vAbղvqiǛv(nA?~Ae-r,suu 1 @ hNx 0Eߴ23p@=@;@?~5 >"3@ \q#q >((浊M+޴MA޴§Ai'qݯtLpqR {,U E\ŅTi `K8@?~@q!zl=.OP\0pE@FQ\4VoQo~ %.R {*ME\Eqѐ  0^##¢rDEfx " `((U _! G@ whvZ@\H7F#.1qPY}[P{?v'!} `oE~@0qQR  z@`ݮ (.P . ,F\0 0>(!OB?v  C@\"< IDATE\5J[F[vAKW@;E`㿧dΆȥqNK +SE\9g$*v7ynhpH" .".QÏa(#7h3#)wݝ'})Be֣x 3SV΍4e9٬~oʲ9'8} ڬ~m߯q BP@((*x|u9:h:UVG. w!L\Ld uZd@B`ǁ32P\DbGfMqaSx9'0R)۽5,ɄV1+.:ӈ}'PuV\Xo߽?w_HUF>5e;z> bWLGQF&Z(.>..ԕ)}\=z }a1n|J)wi:P\sqaR@ ((Bߎ`ʇgˆߤ ɞ .7ԥaI\1j7lzΓ +g*2&R)R$Ԧt}:yhw` 3Qt_{Jcy^_up@Se_Z4%{)S&CŇ~Cw:+#Aq!aV@ą@D@h(B(.|(K14"v8ؤE 4iTQ,69OqF/TR]s,4u);/HC]l݅~idP\k[ݼnx(Nǻ?K9#|>FLGp'ʐMge>.,g msX#.BsT`・@.PąQJQ ( 1FHPU@JK.<biލtV',YzZBѢEdS]b *QWjkK6޽uFtC޼BӸ[zyyRMąXN*J1;|G.G1sBť3GhHz$)h(mE7,H!(Q<Rll/ 3UqN"p{lP\Hqa00LRL8 (aTt627EQYq9==hy:YR4k% +2'JF|/ꉾ yMŅ0*, ". @(3X  6@@BqC-ɝ3;ޠBqw7PAavnv~9 =X*RM@ p@\D@FQ\b@ ` .^҈ (.Q^F(%".ȑ=+.~ҥv\(FDD7P\Xy@GE ""`4"; k*/W^B"R > ."FChE֬Y,ߜ-B0#BE0q azoznׯ_(.$ @  2g8qąƆz/!lш_b/([^^}J/u8D-qa T'9 =oqtJ$E2qA^B?~wʒʑMqH KEt>`y@ f`+((B}Ӂ /1n/ H"iSSSx5}xJNLe$" .O `c@\`D".DmE?EDRHMo޹Ddqpf@#LQẌ;_8a 2`#Q.tL&M|2ЙhԕYNd^"^tě 﫯׉tۏ;שSL?&v\Gy>X>~ {Y^Oƅ m@ \$i4%` ͸pfRIѲMz&3==|^wPMe"c!KEu W \| /@_Vp=7髒ɟ̒%Eqg;7d00086}3"o_E ouͽXK %po _%`$d\Oȵ?5 dK/Y@'dސ8888^"/ ;eRYI7$yB z$t"@a2#PRRbl6 &phf"p1.>.J-AՁ-Q#pAEX3d63l49vӧ3x' +XsrCMIig&` 2. MpAqNE FGGСCRVVu \[,얨*\F6L;3qo?:}n~իWr)B5BEIM*䜀-йuVS5~N?_|4ey Q!<.ioo5kH}}3>K\ p1@9Ap{ @Hh:"/Zb``4Hjjjd͚5d`P(pͨf.qAd^̖yw`1::j⎊ ieee\"&p1pNիWeddY*^Zʈ*hp 0+\q1@TMǏ?e!a`   `\]gl= *d>.,,4/J*oENd|A`bbB3z*4B Ya߮Z[⛖Ex>b䌀,{ @A{e&ի=5,  \ 5*b˗ ٳgfV?@.t;FC3'V\il2YtybKA>c 3  @b|zŒ @l.fk ENrP@.t+FA&ph @%.y@!p1 Y! @ \}l D"& @~@IDATE?DD%(N UϬ)3 &0pF@1 HP@]PCnLtUoLwuշfglrn @ȡ@*Uܣϐs(@,BpaiQm@, : 9 18C@ ^  W¯! @`QR  |@@ 0O  \3 @ \Dsi  \ Jq  PEFl .x%  @.rN@Vڮ +@paoQs@r-@pkq \  Mņ  3.@/@p> '@pa\P!@ 0k  n2@ Z@@RJ@\R@@ \䜜" `]G@W޾ Z"@*@-@pᛊ @@ (f\%I9 D_"}L @0N¸.B +@palP1@+@pݾe -@p(! se˖e˖J .fϞ- 4p+ @@ \Di  ֭jJ 4(PŐ!Cd֬Y2zW#" QFuA@ *p޽ۘ} 0:>|ߨQD @@ Z @T0f̘D\,]veEB@E# 5 ATgUxnl ?# ox?G@ g]9-(   O_R@r.Pެ*ltx@ \įi1 9He-r5 @+.&* ̺`L@ȖEd)@~f]0"   `# dGϬ f[dǞR@] X"PѬ f[X҉T@.B@ QY̶+6# OŸ[! @f]0"X@@ \ if]0´^> %@paVP@"/ulw7 D@ c )@Rκ`E*rl @<.@PԬ uS!7@@"tx@-K Kъٲaj)^9 {eP m!ժזZJ&HA@A@ rR edɬwd &P5@mu4jsJ֎A Z"@ 5bdݚEn} K;JFjR63ȈWhBٸHV.&&Ņ_k5&qgaD!@"L@RXoے&M;GU4!nK# f~6~Ҵq#  Pg@, 5ۢjҴRw۶(rt?YeZ紑>̋C F@ }@ BjM-jDBm‹S^t k^@@.,< ẕ݅89=$SJ1G@6lAs*FM@)@p@+OQ q6V`a2 GS~  Ṳq}p:W ЕPW3yW fNŨ  >.|  @t N9nҺӹm(-uѢMRp{ `=}EM@ `pY:=粧IópD 3?'8W\e  & 3n.i"bZE@.Rа2@ ( @6f +H}fqD ew%n i}]FԉJ  G DV`{mkЀȶ!X~{@ 0  @6.K٦ \#@%AEd% HC  \ET@ ԥl.L G DV"]KÒ\$A!@."* MlRiA#@pGm@"+@pٮaI. wDȦE6u)4 z  6  lҰ$IPx@x 㻈 "dS"mi=B}@\Qb@ \DkiX$(< `]D@)@pM]6M´> ~.(  @d."۵4,EB0^.  Ȧ.e&@paZP@?~@ ZD" !/@pa|QA@l \dSM 0G  ?Jl Yv- K"@p@ 0  @6.K٦ \#@%AEd% HC  \ET@ ԥl.L G DV"]KÒ\$A!@."* MlRiA#@pGm@"+@pٮaI. wDȦE6u)4 z  6  lҰ$IPx@x 㻈 "dSbŊUrUKWMjԒ:u I 6$?٨@qڜ\t7@;.'j %lwk]jդ_]FtWiצEmԃEۣ[ٮW>˱5'_~[ 6JyuHlaerƩ:N.ϿA~JmS^.3[ϓH[nHj̗AH[H;ɧ_ԓzTڜ*:n;[Zjʱ @asl@\^F~4u6>o2>edᢥӼY-ۦZxϞއޕߔ/V"'^~cgl׸Lefzc)n~w}I:@.Jشi[WDz^9ȃ+' .ҕc?@0.  d+(**._=X).^hoQqĈ'n>"!gF'MiPO,\"/%Y菉tپI#xkkKθPanu֖OwwUHr-;Av*x_/Y&{[񣟖ΝvKq% .N-;R"H9\VdW/HZ5ݰc/џ}>~CkeInH8㷔5{pkEuWt}O(U1% .:VqX~Np%߷t>~E;\Ts  \3 @ '.*;e)OUCUtڢNԺCyo괚.Y>day_eo]=.`4$ .K5.CFh/콻8ĺ"1\sB.q{cxL]1gZGH] ~!B2@r-@pkq ` 9g7\Ԫm֢PWG*S3 rz #;*tU^3.ԩ ]+ OᄊLnpe5}2ܝKo^]{ҳMۮeV&:Կ:~|j} KE=ږxT/왭So.Jp  %tDȎ@. }@3\xmS粕G#^L ZSs  .!ӏ O/9;@]nT]"U w_{w{Q>9L oԓT9l+*]B=sF#'_ԩ'5@ThLT-47]_^qtO=An3$ Όg[qIf?əXY,mn\%I9  ȥ6B0N E+w,wN xKx|#rh}D2T\yə%+C/qTbȤk)xd_+fȞ>Tj"OI;cw>xucPbƈ:u܋] D C9,u7r1NpOKͪ潌 YZ4op1D Jr7/"=EK",y `@. ? Z~/oF2՗Cz] q -2-?%2s[_z;<E-Vj_y.9CK*DgyC;s"Y@5C]5B͞ %PP)kEuTء/IoJ%^t&HW@ Sc#.^iκ j'hPW8Cd'~pEU .W&N.jOޚt @J/-_Wե>SAB?v!GJ$z7{㽲>^y})lo~.{ )@ x F "Heք,]2WEOg/zderg\,^P۩[@/c򽏩W\-<$Mm?]x~>Yp᝭]3.ҕc?@0.  "H 7P@O(7=>=[寽HLnd_suەrTe͚"{_z m%[.tPQ/겨j/QgcuܫY3?\g> ]^U?=E k\s<@L .г/+gɵR UWZ\;U>o%z&nr\ޗ;zTW8GtS Ժ֥R^ ֆj_=2W^#v?'Y,Sϖ!EU7Dyyo }鳢NI Fp&e!J"W@HS?\!v>Ž$SWi`WР*y% *帼/-O;ͼ3FM;½HbG|E݇|evzyC>cw_n-<\|ޠdž^V1m+eVkz$zYҮ:`"Y}yo}t㿝-wF\A(R @.r-@05PHz6ge-KƟV ?7+3gs72oPޗkmy+˹v5νĬ*@]\,q˛6csr nd.jG|bċ3F݃i± @sp@L.koμŇOJp{oPޗ pTk@u :ˮ[|3E-uR .TYԳ.{6n7\$Ho_~N9c`<U/Ex ҭ6('.@c@.B* @.LY5{ ;# n"ܓ$?FMJ=~ѧi!ٚK}k2E##Dwn<*:-c#;O:?x.{OqxD7U'PU;}Gˉg\#*e&jLk# tK<5?]/Cbu===UJ4@ tл )"m"w"g6CեFҬiciբi 8nyezO(Qu̘]Njլ)MwhT¡7lؐXC]dUNٸqrdwf@TT9 -t\9:immVDۥ>M&r\x5@ [zz"dE"+ײa)̹f}Ƽr`O6[;ۛ/㞂QbC~Yn|Qs+;gsEТ \(s @c.2uCz]~~dV`{biHw/5SuPfDL4]N;zQ w:KZ?bԕdWa AIR @..rͱ@ ȼK%<۵i!\Kf~K_CpO*rDۄwз#ZU.FK Y ".E#/@pLqp28>Pqr [zw׮rKǡ]Zt.zuu"Xg6*Ip UDȶE)@h 'ʩE:,\*ERJiРl۰AJeD}c0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >.|BY$."э4E캜#x.u0C)@p~U >f +H}f|f'Pznm!˾Pc@ \Ķi8 3n."-:.v UL|^ ]G4 @ zSZ )eaNҤ)ɦ%`Gtiز4i׮S[@X \ĺi< @ά%t. DV`Ge]rgMά#N =)-BHQk8]$E76G@&R5c= 8 @b/hKxRPGi_c@fN&ŅQSqk -BE!1YWYPg~wc;X:Y0^  (- X`̝0m{'JmҁFGK`2wk_םMVi  @,.b4@Ȃ)OKjҴQ~XZ̟lP\Es5+C@"tx@ v'>,qݤai#{΀/t! ߬4p  mp@ Lmz;9#`z3>vTb1]CE@2 ]@+ּXpwNJ`nsH;/5ko2kV/t.[,+L~Mj!&eM kz"T$@pQ!^@]*Uظ~M-0_j^-w- .yj~_QC@@ K'QblXW(+X"_T^ 5tgW4ݔQ@ @@@*UM7mwC@ \Ĵi6 a \ϱ@K® DB"H#@ȉEN9 x.  \Ts  R(  d(@ 0 & \@\Ĥi& \H  Ej^l @.@.B X(@paaQe@l ?  ȝ5GB@-@@_)C@. @"/@p. '@pa^P#@L 0g  p4@ @@rʍ@6 \J@@ \䜜" `]G@W޾ Z"@  +v  K`ٲekٲe .fϞ- 4p+ @@ \Di  ֭jJ 4(PŐ!Cd֬Y2zW#" QFuA@ *p޽ۘ} 0:>|ߨQD @@ Z @T0f̘D\,]veEB@E# 5 ATgUxnl ?# ox?G@ g]9-(   O_R@r.Pެ*ltx@ \įi1 9He-r5 @+.&* ̺`L@ȖEd)@~f]0"   `# dGϬ f[dǞR@] X"PѬ f[X҉T@.B@ QY̶+6# OŸ[! @f]0"X@@ \ if]0´^> %@paVP@"/ulw7 D@ c )@Rκ`E*rl @<.@PԬ uS!7@@"tx@*%+_zyVY* ב<*4=wn$֫iBը @ .b4'\j .!@v:~o/:6Z[BR@()@pQ҃@, uQj6RN ɯQ]5(*69^ed |U[\f],kmpǙ -NF춽; @.-L  SBi M']l)͜{q qB qu8pq_t)$ MlR6 @s5+{wErH?"@rYsʧfȪµ)#K;!7@%@p-YE\@cSr[6k(wi'?,Wq&p܌uˇ_M̞_>)@G jŝ~ֵCۦr`VY]-ME~;.ՙVRI@> #PkZB-¹ZԱS Z8WMGLodޢUNA gsGqdjyY:rYM~n \XQTv8MX}k_+ \dǾ YY;#@VNpX Eb*@pӎ :80' s0M7.L~ M6uEb$5؟-XۃM,[7r߭8U$Fo4ȢEq)@ }\}~̷po`|7 .cO@E@opaDx·"T.@pQ[  5.>sNlϾŌ69$ Yv- C\\ ss*q7 ָ}#\@.8 -4߀"(v/  \d |kw9[4j;̸;#" gj@x\}o}k*# . M6uEb$5mmעyݯw+ָћ6ME(@pE\FH_@7p=' @Y&< `@"8j_B BB+»cƅoTEd E#4NЧ pK\D a \@ @"pNaS.raה7׬q@R H@r#S؇pQ.bEU^&%GAb"@p m²lB}ɚ5>3.l{ϥ &uCb,zvf ָ]"o4ȂEP)@ sss 4^M5.2/@_A &{t;;myFpa[*A < ]op|&+yCf\Dݙ! Ƚ9GD!9Is&qu`8x# osl S'!V`kp*|Ycsx3[ 5.r@ \Dgi `.;lo[B\įi1 =R2 @ž33/y! Q- @Ei~G0B@ډ-Xۂ-,YBE'̸0⍔J  HG @ j[ Tu*ǁׁQܷ$ָ;3A ǝ"T"wrN@!;qQ[O# @p"N`kp3¹vqu`8xw43.rnɑ@ \Di! `.zuSk\0߀67Jpa.F 0k oopKȷV{cqwpZ+@p'!$#,¤ηv^Nxc.z'@%@p@HDpѵk[kX~羯pU#^ `u]F@xC{N 7Iժj'0[[,^OUPLfBD"ӴȕE9 @JB*bѹ$ZTZ>0mTPacyRn'pv369ąU+e=:KݜK\_S1@cC@J.*%b@0tp㠽*+S"yl@ٴqTZ5i֨YK~?Tp_KTZ~_BfyW*ǟy .@2 ܐ@ 5!xtovICN3޽x 8̞>I.~qYuuXxh5.É@2 ]@'+s‚lbի'us`''2򬱱z Y8WYظqԮl߬TQvjBYnԪU[V˓5WȢxj_K8լU~x+-α6nXhhKѱ~YBd3⬋ozMU+ꧼ &v[O]_Nt!P @PAIR @"Ђ)ee'ʤoǺϵ~roJ^^Ķo<|c.vqҹN`4nL>zwu:p]}rPJ<$_|z/+?*{u>;/>Vf-G>Jye ⁴>i @R,< .=/q?|vyɻ.Mcdەpk`7l}9uqǼ?BY⡗'8T(QoL>~Y'!p^}@ Jr@!upr) O[P]d͋qv:,8+:̩?O!3~/?㞢t@\ժVsvnp[JQ?_z?UIߍ)(3&[X9 ~:y%늋7ϸ>jyz?wcbƅwl3 @f7 @΢ UJg g҅~\'L8 .J~~--]\rҞnTqK5 %zuDnwN=OrָpS\T$7^5Џ/^0?O܍"1@ c )@ :8p=ˬ=^2#S'fuw*ZB9YJntNØ;g9oN0S_rk\s~+'quNxryWk\2LO<^_A\/S@  )@ XsNO[F sF󖻸!uo09go>u5w<'mv$ׯs|%5#?:urwNyʹW::UT?;x\}j`w!ν;\οf4~G)\B~f[j^s5Ό#bCOpE8Y"JדSݾbK@2 ]@'|@3@>|ɔ[ *>LrBoR a>lh*諂 gE雮 .i2Ý=ֳ|!5kՖGoL>}od=1)VAD9 @R,< @IDAT7(ze#ԭ 7 G? H}uN9V9͏~(#|ǞUF>r5~vYo=$ <.yġTf{ȯ)=}N+4iɐN98?LTq/aE  \dLH ~{|ytιw=rJTEE-g;$kR\T(+-5l+UU˨,_}iSTu^V^KQjɯU 6HgsʚUv$fSM:zKFpwED+@p߾ ࢳsNOLuI k\6K@k.**K@]:|iw^<8xaZ@ Yx@sH8vNC3%qf\^ . @:ا5 Xs 5X#5Rr9~.B  @."՝49UD·u`yQ㿛Etޓi  E@\\tkw:O9'{SE}s @.R&c@\l .5.Uq=~ό\Or @ .qiډX&NQk\YC p. `D@ xnl3.l;(@pabP'@E9,L”,L뵊|7q@.$@k|Ɖek|?'݀HQ @.b40Y@s7[8|14 .L~n mEb" .bd@d~ L_@ z: E=;fF_^'NbE75@H["m:vDȦb7RKQV^'OqX"G">}MK@tpo8a< n, `]D@x x ;ľ&Čxsj@ ;qT@ tpn:%9!6; W« l .ڳk[%k[h~2}/a cR `G@ :_Zܹǁׁ"ʹ C" u P@"h+ eԩ̸M @| \bC@\ b]v4NЧ pK\ݒc! ~B@JZmZ?;qa/F 0K Ev𝝋XL#@ @1) @ 8opaw68/xg-ӧ1"wCJB 5 `.ڴmq%q-$.oT Vڮ @Ω"ap G3OsߠX"ӴȕE9 @J:عM;YYؓY@b}x@ $opaw68xg-3g0"NDR"J@tpjΗ*N{xX2fBpa0-@ 0/  G`kp*|Yoau`8tw4ƅM@ H@)f0Y=" V"]O@=gf^B ` `eFu@\gRv4ݱTϫmZ$Ųa,Y}qyˢ Y ".E#/sKK~~Mg*Mrǁׁ㠸P>O4/{" E 9I~e4lDjשԑF8QlW=wn$/T @. "5a:f8Ems&Z8Du=!kV="dO"{ jyyC%Y2%dR̟-֯gud. l  `M),FMI~Z6̒lxY<٦noc[ ]vEb%Oϯ%n ”T% IqN;5E/@p}c f]ˢYkd `Cz).Z#Ks/f[Ks %.Jz azEjyxYUö%kpZ笑LDٸa,^lذR@ QEڀD\g˼ūVA]\a+:Z B eMJi֨\w>Gy  ȵ8CHY`UrߋIsHH ɞvZKprɯQM8#WI@* L@ЗGUUkؙxəxP{x4V.["kV/w#.뽗m * -h'A"-?mQ@o6š9_KcƍυR\T3{*}h @xsd@4i#vNQW,Y~#Q!7ȍZ'X|ltr9 1 qt@V%+o.ةڠ.Zv}Q6k_VkE>ufmYj[[u8l[o%@ K:j"P2z;B=[9mD;_KUMUH|` Hq ɦYdmqY6mteqrR-G@$@p%XEȍ2j\j Cm;aҽSsC EX@ pu E]dM:YS>q:I@",NU3 (W ЦDi `=D@@*Υ@&&7@@ px @#@pWJE@ QUڄ wC@  :  4D@ s )@R H@E;# EX@.3j w! @@ g9@  .  PEeB<  \NJ DV"]K@0Wܾf &@paZP@b @pN $@p$   Vl @.   @p:D@R K;j# `ͽG@ȭEn9 8 @@_)C@. @"/@p. '@pa^P#@L 0g  p4@ @@rʍ@6 \J@@ \䜜" `]G@W޾ Z"@*@-@pᛊ @@ (f\%I9 D_"}L @0N¸.B +@palP1@+@pݾe -@p(! @ l ी 9 99D@Z k# `}G@ȵE9 p9T^  [7" @P̸Jr@E" `q]B@0Vخb DW"}K@Z"hQC@J.*%b@"@pK@r.@psr vG@^ {# k\s<@r@@o*6D@q$  }1-D@8 㺄 ! `]Cl@@M6Sj@!wE  a \%qM 0W:.0,$[dX$j.rF́ Mz6-8 8~oqmjΔ& \z>hB`̂N`E+iE;:L"g(|ġic{Iަ0L aIA].0,$[dX$j.rF́ Mz6-8 8~oqmjΔ& \z>hB`̂N`E+iE;:L"g(|ġic{Iަ0L aIA].0,$[dX$j.rF́ Mz6-8 8~oqmjΔ& \z>hB`̂N`E+iE;:L"g(|ġic{Iަ0L aIA].0,$[dX$j.rF́ Mz6-8 8~oqmjΔ& \z>hB`̂N`E+iE;:L"g(|ġic{Iަ0L aIA].0,$[dX$j.rF́ Mz6-8 8~oqmjΔ& \z>hB`̂N`E+iE;:L"g(|ġic{Iަ0L aIA]XlZlYn=|̞=[4hWnA<@Lg1x-vs]_4?m1ݺuVZɠA*2d̚5KFG>ڌ/ƙ/&6B [ @J!oQFmrF_߾}79!D}s6j[*/8+߆gZ(!Pr#@~@b]&BFx NJ9/7\qV[ -(IAο["8U6 Tiݻw/`ǽ?{7tvQ!@}x oAjRw -@p%G >3&=Dd]ԇ7'8V!x B2HMq[["O(/%8f[Ts`5%x˖,"P|AEi~G@*)9-|g@, 0޲K#+(%@pQ _+JJl l@IYI~C lR6w]xE̶HMH"8KCdI%XE]8< ^ d '%gE슀#8e@oHhƝ.ʷ| T3!!PR"6@ 0[`oƝo*6EL;f'PQJl))x?ϭ- %u@,̶EF`bC2`eLH,Kb$@pΦH3"{ޔOYt!@vgqT 0ޒ`eח uҟdRblXZW l B ]-ZR^Kd)h[Ën6toZ{:޴1NKpo@ǝIֹ E. ;edɬwdBjGu(+P5@mu4jsJ' }qfhPrlg17--67m̸"qgu6Ip UT+LyJ֭Y2nmNV)5kogpZ\V/dizoR\ZVciw3{iǙnMKpo@Tƛ6fi Mڸ3:u#ȅ!XN*hwY:#ƙn(MKpoM3 ,qk֤ҰigkIX`odO܍7*M;\\9~qcpǙn4MKpo-M36 6lAjZejHvGKmZS@+LӜY6uN̼8r,"g3 xGAwZ(2`T.4u C5o"DRP6su׼`lmiL1޴Q0uicƝ>J(Y  -C/ܻ^!8ݍ0ai$ƛ>&7m̸GUqULEp.,s,us )UC 3r4=Rܛq"[+8h7-}Loژq%I.鶏"]9[0e,sNҤז!r27ؓqX)8`7-}Loژq%I.鶏"]93n.i"\@O+h^Zty)8KMsi8ƛ>&7m̸G]qutGp;@WΑVJ^[@EʬK~/(=gia3 xG]wZ 4nn.ҕ3|)ְA )C s)c7{ya)8KMki8ƛ>a7m̸AqtHpAcA'Qaօd@XL0޴q{icƝ>8XFt,؇ :*& ,. g%@M3q0e:6\f>|XIT10>hgu!Y 847-}oژq%).鴑"5 ƂN A8 )ƙai xƌ;-}LwqNY4tU L Y`]HA54 MKpǛ6fi `ʸu:m$HG͂}b`a}0B @ qioZ8=޴1NKpS]i#E:jD냆qXRa3MxA wZ82`N .Q`>h,$@X4,kiƛ>a7m̸AqtHpAcA'Qaօd@XL0޴q{icƝ>8XFt,؇ :*& ,. g%@M3q0e:6\f>|XIT10>hgu!Y 847-}oژq%).鴑"5 ƂN A8 )ƙai xƌ;-}LwqNY4tU L Y`]HA54 MKpǛ6fi `ʸu:m$HG͂}b`a}0B @ qioZ8=޴1NKpS]i#E:jD냆qXRa3MxA wZ82`N .Q`>h,$@X4,kiƛ>a7m̸AqtHpAcA'Qaօd@XL0޴q{icƝ>8XFt,؇ :*& ,. g%@M3q0e:6\f>|XIT10>hgu!Y 847-}oژq%).鴑"5 ƂN A8 )ƙai xƌ;-}LwqNY4tU L Y`]HA54 MKpǛ6fi `ʸu:m$HG͂}b`a}0B @ qioZ8=޴1NKpS]i#E:jD냆qXRa3MxA wZ82`N .Q`>h,$@X4,kiƛ>a7m̸AqtHpDfŊUrU+WMjԒ:u ⵒ_#R)na}И8 di&eXvԯ_'ݲ+' kiǛ[y&WɒˤFaF|v6lX*xA >xqܩի׸Ǫ>*7\9eiʸz>t /3]U&UZ]FtWiצEmԃ|=Zn9k^z8@sde6 ?(#UwLaڕpR9c]* '~}NSOQiUx'njժUO6&qVLF6p^v_ o3vh+;EN>02|`|ףuf2V3o&cAWmDF/3P?9'dT9q+xw8Y2W?,N{*wT붯TR~2NqrˍIyޝ[ = *(׭Wq9TY[o~wQwRG &ß{sC%Oz(ի2@w) F~MyV*]޷9ԙmRM\ѧ?ԛA=EKKl͚gl4>6~*c#em_rvWʕws#m~pS+R[/+ypdk>h2g܏G$=~K+WwX?'oG׶ލTw?Fovo^N-o]?\!-/>!Z6a3]LǛ.(ǟz]_zKyMMGT-)l ~1uK[TԖNvOEd㹰>h2gڼnj75H\TKdgMcK*[^ɧu_B<.Jbr`}mQ"S)rh?,$ 0uli]1TYo~wQwpRMv?˻|.w~ڝ##9C) 3MwEvwvm?hc~U4=W>[wƍc/J|S_?|!ٵ]2InH(/xyc~+ݎ@)z8[yDZqT ozk a}d:δzO^ Ou<)V>\uAy~_$'8!q5y R/fI]wU_O"?hG C{w77Uq\TAD*EEDQʎE"`e \WvdY"H)K,-{z&o3,3|>mMro$''s/8Sۍ7'} FZn7ӑDҺ'.~m_Q:-J6uVNpUv)ݎ^9o'vD7mko u4?8ϬJ6jݩJ]dфpi']VqMc|-up=> SGemvf=Z#hFtÏNV\aنMyI7ݷ 27t" >1b>(zuNy1jqE.DV 񇽺3Ͻ;Qn.=UB '&MvoΜF [fԻvaoZF,i7d>~v%p[gߟo:ǭ _rc?Yd_n9{Y_NqŅgj%+D7ʁ-7g ǖoyظo1[o #Qdl aMŚ-k/k(FZ:3gS3j)ES@i65#<7.;l[$?Ddg%I?ۭUB'G/+ΔvMkk1tEt /A/|-<*q7=#tbk/m?G']zx(ky6<`=zf~Gg܅''Q,/esQVS|sʎ7q ָPl1[9]Iw4Tش'?;Op )'qnʥ]~tԠBkjr %""3wfL7X&=1MR+=*wEmaLԗ[[EwS|K"k*g}p8~O^%?4E]7R Hշ2*jԮً vL[ӞGD,w}cWt2o)POmrDݦ;8[r9hL<.?K~SF*{)~?t?mE\U$Qn\v49阽n"G}aMv՟mdwhrr}Sg18Svڍ7'm_x c+m?Zp>qaEʩd4L)!Vv)/ݎ^8o c&Yɱ~I,}sLyY2ngV%LQFMTX镃髳/GtgԻvLk*<#^džɖNw! N߫pXxxPo{\O8Ռcf.q?/mTTqGn:!Fvq3?J>]쭾ζѨ1\Gi[|ݱd|[ . 6~YG>w ~rW*C8h+i7δnd'=dXg|tsN}IĆ-fڹĭ/鴜0ѝ ~l᰻oi^. ku+1uϣM};Ƕ|ƃQSjQ"59}nU; D1_#Шٸ֓b';k>xme6 +,(#;JRݳ/LW^%jLk74}]1,Ҟ$Zp>6ᄉɗxt뽣$rZpK<%.;ޔn]/7xn_TɅ]DeZz~nI qO<9žs-9Hw?znN:fo7.[x/|UƊ:ϬJi7 Pq3uɂ ;oxeZAj0.|Bϋ=A1\ #\'ò~[ma]xg xSwpިU6տ+o\''?O8%w;eYl|ExYGYv_v!6>cE壛]YMq&flol+y(j t['-T *n =zU>}rOtqye="6THk=G44,fOi{xwe*9t8w~nqۏ>Ŭ>yZLǡݥӱL5nt3k Ye||ͭ'1GZia++ΔvMk?b '$cWӨ<2 fTb'xCVxSwpިo]1cf|Ebe ?pa^=%0pɻd8vpų?7$~M'|ir-5=Գ;*@ Ĵ y𳋿cN8Q䲒k5>,40ʷ/i7dj]?}B^fEs/nh@yUֵW:ҲV(]runs}ÑLy{&<${rxw,69O-+-W:Ipq}_uW^Zѫ0rCMon4ug,ul:NVLyl74m]l4cƇq2 f)dueǛr u=oTr={[ܨsEF˝+3EtLVE͖'a/i|d'OF12/4v.K`yJ˧ DWĺ<ŭשM[xl _6jrxkBEr:P730m Ƈ] eo W-W뜵4> (iְ vL6kJkE3ONX0*ldmwXN *]r|-pw23iw^쩢ͶI|1Lei>N}WdŅE&[pWZJU$S"k/$WiOe\w]+uFljYˊ3x|ڶu_|,ej<#NsW]s[dܥr,0NUl!2 x~!wo6|.Niݧ [ӿ1]=m=* $.yBoؗ_VNi .Nˆ[>hٕUдgip~zæ _*]9ԬiU f=cHa*#s5E ==0MyYdƇGw6:zCvw+].j]^ɰCY|i҅u0˨a7j;ag~~OA{F7a;wY_&++ΔvM6׳0&yB*|Ǟ'lҾ}<oƩ<ɔx2ֺMvܥaZFń*qsNMD*CG6$Lym|hxPT{ngV%l= Pq1ؤ'tBNm QZ{xtꇟBr3wZ;@eWV8ݬZ-mw>-bǺy}_|26O0 ,5gp\BsV4>,dAӸ"e4Pùs+}zh2k";<_i=7ڝyڅ-owFm_(׶kв4}1iD\?n"&klYiqm k+WVC:PqJs4hzm)%^óqCl|3]YqoOV_~(㬴eǣ_Bf}چSp"~k_r0u<ټdeSm|e&cԾ46]Zs73wtlC]d! wJa6 ?`Oh:{Mv/#oˣ>Hf@< N;&`ouz=bP&x/Vo.sHKߡ:\634O7~ dq? 8t8:kΞ'/\}/{i]M ivFaw?Ng=kOI {\rXA܋& jEN;o8˭a*WlͲK/_= vpO(kYAWcLIDATGqԘErnq)jؓX^+|(_60bTv:dGi~gOdevJeǣqEH߶>ŝڿ~.lY1ᾤr(fc-O}ײ)d/;ޔn]xn(/i7m1ˣ|3(򁇟 R͖'pYiӄLOi3 +_8ϬJ܅ۏ߁/lzP6Si6 ]ls-<'?1v| ?{0pz<~u-M-<`Fi[kP:b|<4>sn̓쿀20b#/fN3?:<5ެ|y;#7 [Eg/_:gm77yx/8挳Lymi?0pYG 6~8qUPyfo,vhQދ]kȳ̆J~U7M;98o_ۯ=ᓇՐRZCE6YXʻiw/龱> F1r[n%CՔo}V_mh@Zgvfjb;DA3Ҁjۍ3sw{3] C{ܪkoL$vnVg h)4p{>+p\sڴazIj%uXŎvg˯f6{kGE~^Qa3L-vӕgkdZ/{rPh\oO*w6U>xqeC$S)lw\v;zѼ{nݐ}_aοѓX$<ϬJioѣgvA@dzd O&/En仟J.mXN[] 鑿f8 V.t{pt/t{A2wo`tg]jJV%K.^嗉ki^Q?l9g߭²`svd/͢ fGe4ƙL[ϒt;~Se+ۺE[^PCv?k>j~w>(ɱŏ?k,,6+4l':6~|tOqDy6ɊY$`( UAkpIs6:}s'GQִL%PV);ƛ׶kԸ*5]ҧ5><6n(ַv}xPtZyfU.-@l킦Hz>15׈ .Z͏t!ΞƭΧp{ܛ3gG Ou{|u&so^P Ď-dUT-lz葉n[9{-V*цaGS+fVC,ȞxIѾl/Bnp)`&XW<?Գ]9/52Xʱg1w|cmZd-ڳ=%}lY>{﹢e=j\[5H˯E_ ^n5.cO^Í_,NUǺL^xSeck3bg[nILȊ0}ٿ{9-;ޔŝ V.NeeU+yт ̪]VVֱi~:3ex1~?ޙ\f|+n]wR_,ﻺEvz⢝Go>VJ^ˊ3_M/;;Oyoq' 8omQԯ3wEm9T\m5_ w)R2 \Q #eȔUS^Jn7{Xqwl^!GBdXb;wˊ3m~7sVxS? d< Uv)ĝ$f9ol_EgV%ZSqQ-7L)hzw=uϿRC#ͥLmw>m|uK)8gtC~Pk2X4/ʦgbZrrZV)oZ~Y񘕟^xΫxS;IsͿ8ϬJuӵ[oS u w655Ĥ/n+߳U`B~U[Ykv>ȷ/wj7Cw:@+˭67p~m{uʊ3eM/+;Oyoq'}g[tWUnyT\y w !pse4YJeřDI~?o2&$Aw`gȣV445Hdce4Y6!3@Yq&MAx1q' Pk37G\x/F>Q= Lzш/[t]OTD|1ǙVx: %dLI~wudީf祚r˦}&C~эw!cG=5*=i>YR"P8)& ucɘ 9fݍRq Q;-V|1H7 U8d_ ꋏ_z28^a\mZ _Ǚ 7IЯ@ě;IЯ@]E(Wpi+1iYBQ`9ڲ(듧i/y+,_q&9M Px1q' u蕸u7HE7Tk47>|+Ogf:F'*0r1=#FQ.=]1z^U֛g3kq EI~z5dLI~z=d]D^(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@e   F"@@@(B"Y   "@@@@*.Pf    K\l$B@@@"(Be    @.*.r@@@`UJsIENDB`horizon-13.0.3/doc/source/conf.py0000664000175000017500000002506713553660754016704 0ustar zuulzuul00000000000000# Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. # # Horizon documentation build configuration file, created by # sphinx-quickstart on Thu Oct 27 11:38:59 2011. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. from __future__ import print_function import logging import os import sys import django # NOTE(amotoki): Sphinx 1.6.x catches warnings from imported modules. # Ignore warnings from openstack_dashboard.settings in the doc build. # This can be dropped once Sphinx correctly ignore such warnings. logging.getLogger('openstack_dashboard.settings').setLevel(logging.ERROR) BASE_DIR = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", "..")) sys.path.insert(0, ROOT) # This is required for ReadTheDocs.org, but isn't a bad idea anyway. os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openstack_dashboard.settings') import horizon.version django.setup() # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # sys.path.insert(0, os.path.abspath('.')) # -- General configuration ---------------------------------------------------- # If your documentation needs a minimal Sphinx version, state it here. # needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. # They can be extensions coming with Sphinx (named 'sphinx.ext.*') # or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.viewcode', 'openstackdocstheme', ] # openstackdocstheme options repository_name = 'openstack/horizon' bug_project = 'horizon' bug_tag = 'documentation' html_last_updated_fmt = '%Y-%m-%d %H:%M' # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. # source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'Horizon' copyright = u'2012, OpenStack Foundation' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = horizon.version.version_info.version_string() # The full version, including alpha/beta/rc tags. release = horizon.version.version_info.release_string() # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. # language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: # today = '' # Else, today_fmt is used as the format for a strftime call. # today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['**/#*', '**~', '**/#*#'] # The reST default role (used for this markup: `text`) # to use for all documents. # default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. # add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). # add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. modindex_common_prefix = ['horizon.', 'openstack_dashboard.'] primary_domain = 'py' nitpicky = False # -- Options for HTML output -------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme_path = ['.'] html_theme = 'openstackdocs' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. html_theme_options = { "nosidebar": "false" } # Add any paths that contain custom themes here, relative to this directory. # html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". # html_title = None # A shorter title for the navigation bar. Default is the same as html_title. # html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. # html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. # html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. # html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. # html_use_smartypants = True # Custom sidebar templates, maps document names to template names. # html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. # html_additional_pages = {} # If false, no module index is generated. # html_domain_indices = True # If false, no index is generated. # html_use_index = True # If true, the index is split into individual pages for each letter. # html_split_index = False # If true, links to the reST sources are added to the pages. # html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. # html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. # html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. # html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). # html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'Horizondoc' # -- Options for LaTeX output ------------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', # Additional stuff for the LaTeX preamble. # 'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass # [howto/manual]). latex_documents = [ ('index', 'Horizon.tex', u'Horizon Documentation', u'OpenStack Foundation', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. # latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. # latex_use_parts = False # If true, show page references after internal links. # latex_show_pagerefs = False # If true, show URL addresses after external links. # latex_show_urls = False # Documents to append as an appendix to all manuals. # latex_appendices = [] # If false, no module index is generated. # latex_domain_indices = True # -- Options for manual page output ------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'horizon', u'Horizon Documentation', [u'OpenStack'], 1) ] # If true, show URL addresses after external links. # man_show_urls = False # -- Options for Texinfo output ----------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'Horizon', u'Horizon Documentation', u'OpenStack', 'Horizon', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. # texinfo_appendices = [] # If false, no module index is generated. # texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. # texinfo_show_urls = 'footnote' # -- Options for Epub output -------------------------------------------------- # Bibliographic Dublin Core info. epub_title = u'Horizon' epub_author = u'OpenStack' epub_publisher = u'OpenStack' epub_copyright = u'2012, OpenStack' # The language of the text. It defaults to the language option # or en if the language is not set. # epub_language = '' # The scheme of the identifier. Typical schemes are ISBN or URL. # epub_scheme = '' # The unique identifier of the text. This can be an ISBN number # or the project homepage. # epub_identifier = '' # A unique identification for the text. # epub_uid = '' # A tuple containing the cover image and cover page html template filenames. # epub_cover = () # HTML files that should be inserted before the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_pre_files = [] # HTML files shat should be inserted after the pages created by sphinx. # The format is a list of tuples containing the path and title. # epub_post_files = [] # A list of files that should not be packed into the epub file. # epub_exclude_files = [] # The depth of the table of contents in toc.ncx. # epub_tocdepth = 3 # Allow duplicate toc entries. # epub_tocdup = True horizon-13.0.3/doc/source/locale/0000775000175000017500000000000013553661042016621 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/locale/ko_KR/0000775000175000017500000000000013553661042017626 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/locale/ko_KR/LC_MESSAGES/0000775000175000017500000000000013553661042021413 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/locale/ko_KR/LC_MESSAGES/doc.po0000664000175000017500000000760613553660754022542 0ustar zuulzuul00000000000000# minwook-shin , 2018. #zanata msgid "" msgstr "" "Project-Id-Version: horizon 13.0.2.dev15\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2018-09-07 05:50-0500\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" "PO-Revision-Date: 2018-08-28 02:15+0000\n" "Last-Translator: minwook-shin \n" "Language-Team: Korean (South Korea)\n" "Language: ko_KR\n" "X-Generator: Zanata 4.3.3\n" "Plural-Forms: nplurals=1; plural=0\n" msgid ":ref:`genindex`" msgstr ":ref:`genindex`" msgid ":ref:`modindex`" msgstr ":ref:`modindex`" msgid "" "A Python class representing a sub-navigation item (e.g. \"instances\") which " "contains all the necessary logic (views, forms, tests, etc.) for that " "interface." msgstr "" "하위 탐색항목(예: \"인스턴스\")를 나타내는 파이썬 클래스는 인스턴스에 필요한 " "모든 로직(보기, 양식, 테스트, 기타.)을 포함합니다." msgid "" "A Python class representing a top-level navigation item (e.g. \"project\") " "which provides a consistent API for Horizon-compatible applications." msgstr "" "최상위 탐색 항목(예: \"프로젝트\")을 나타내는 파이썬 클래스는 호라이즌 호환 " "응용프로그램에 일관된 API를 제공합니다." msgid "Contributor Docs" msgstr "컨트리뷰터 가이드" msgid "Dashboard" msgstr "대시보드" msgid "" "For a more in-depth look at Horizon and its architecture, see the :ref:" "`contributor-intro`." msgstr "" "호라이즌과 그 구조에 대한 깊이있게 살펴보려면 :ref:`contributor-intro`를 보세" "요." msgid "" "For those wishing to develop Horizon itself, or go in-depth with building " "your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes, the " "following documentation is provided." msgstr "" "호라이즌 자체를 개발하거나, 더 나아가 자신만의 :class:`~horizon.Dashboard`" "나 :class:`~horizon.Panel` 클래스를 만드려는 분들을 위해, 다음 문서가 제공됩" "니다." msgid "Glossary" msgstr "용어집" msgid "Horizon" msgstr "Horizon" msgid "" "Horizon is the canonical implementation of `OpenStack's Dashboard `_, which provides a web based user interface " "to OpenStack services including Nova, Swift, Keystone, etc." msgstr "" "호라이즌은 canonical에서 구현한 `오픈스택의 대시보드 `이고 _, 호라이즌은 노바, 스위프트, 키스톤 등의 오픈스택 " "서비스를 포함하는 웹 기반 사용자 인터페이스를 제공합니다." msgid "Horizon: The OpenStack Dashboard Project" msgstr "호라이즌: 오픈스택 대시보드 프로젝트" msgid "How to use Horizon in your own projects." msgstr "당신의 프로젝트에서 호라이즌을 사용하는 방법" msgid "Information" msgstr "정보" msgid "Introduction" msgstr "소개" msgid "Panel" msgstr "패널" msgid "Project" msgstr "프로젝트" msgid "Release Notes" msgstr "릴리즈 노트" msgid "See https://docs.openstack.org/releasenotes/horizon/." msgstr "https://docs.openstack.org/releasenotes/horizon/ 를 보세요." msgid "" "The OpenStack dashboard project. Also the name of the top-level Python " "object which handles registration for the app." msgstr "" "오픈스택 대시보드 프로젝트. 또한 앱 등록을 다루는 최상위 파이썬 객체의 이름이" "기도 합니다." msgid "To learn what you need to know to get going, see the :ref:`quickstart`." msgstr "시작하기 위해 무엇을 알아야할 지 배우려면, :ref:`quickstart`를 보세요." msgid "" "Used in user-facing text in place of the term \"Tenant\" which is Keystone's " "word." msgstr "" "키스톤에서 쓰이는 \"tenant\"라는 용어 대신 사용자에게 보여지는 단어로 사용됨." msgid "Using Horizon" msgstr "호라이즌 사용하기" horizon-13.0.3/doc/source/glossary.rst0000664000175000017500000000114313553660754017767 0ustar zuulzuul00000000000000.. _glossary: ======== Glossary ======== Horizon The OpenStack dashboard project. Also the name of the top-level Python object which handles registration for the app. Dashboard A Python class representing a top-level navigation item (e.g. "project") which provides a consistent API for Horizon-compatible applications. Panel A Python class representing a sub-navigation item (e.g. "instances") which contains all the necessary logic (views, forms, tests, etc.) for that interface. Project Used in user-facing text in place of the term "Tenant" which is Keystone's word. horizon-13.0.3/doc/source/index.rst0000664000175000017500000000345413553660754017242 0ustar zuulzuul00000000000000.. Copyright 2012 OpenStack Foundation All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ======================================== Horizon: The OpenStack Dashboard Project ======================================== Introduction ============ Horizon is the canonical implementation of `OpenStack's Dashboard `_, which provides a web based user interface to OpenStack services including Nova, Swift, Keystone, etc. For a more in-depth look at Horizon and its architecture, see the :ref:`contributor-intro`. To learn what you need to know to get going, see the :ref:`quickstart`. Using Horizon ============= How to use Horizon in your own projects. .. toctree:: :maxdepth: 2 install/index configuration/index User Documentation admin/index Contributor Docs ================ For those wishing to develop Horizon itself, or go in-depth with building your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes, the following documentation is provided. .. toctree:: :maxdepth: 2 contributor/index Release Notes ============= See https://docs.openstack.org/releasenotes/horizon/. Information =========== .. toctree:: :maxdepth: 1 glossary * :ref:`genindex` * :ref:`modindex` horizon-13.0.3/doc/source/admin/0000775000175000017500000000000013553661042016452 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/admin/admin-manage-roles.rst0000664000175000017500000000401613553660754022656 0ustar zuulzuul00000000000000======================= Create and manage roles ======================= A role is a personality that a user assumes to perform a specific set of operations. A role includes a set of rights and privileges. A user assumes that role inherits those rights and privileges. .. note:: OpenStack Identity service defines a user's role on a project, but it is completely up to the individual service to define what that role means. This is referred to as the service's policy. To get details about what the privileges for each role are, refer to the ``policy.json`` file available for each service in the ``/etc/SERVICE/policy.json`` file. For example, the policy defined for OpenStack Identity service is defined in the ``/etc/keystone/policy.json`` file. Create a role ~~~~~~~~~~~~~ #. Log in to the dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Identity` tab, click the :guilabel:`Roles` category. #. Click the :guilabel:`Create Role` button. In the :guilabel:`Create Role` window, enter a name for the role. #. Click the :guilabel:`Create Role` button to confirm your changes. Edit a role ~~~~~~~~~~~ #. Log in to the dashboard and select the :guilabel:`Identity` project from the drop-down list. #. On the :guilabel:`Identity` tab, click the :guilabel:`Roles` category. #. Click the :guilabel:`Edit` button. In the :guilabel:`Update Role` window, enter a new name for the role. #. Click the :guilabel:`Update Role` button to confirm your changes. .. note:: Using the dashboard, you can edit only the name assigned to a role. Delete a role ~~~~~~~~~~~~~ #. Log in to the dashboard and select the :guilabel:`Identity` project from the drop-down list. #. On the :guilabel:`Identity` tab, click the :guilabel:`Roles` category. #. Select the role you want to delete and click the :guilabel:`Delete Roles` button. #. In the :guilabel:`Confirm Delete Roles` window, click :guilabel:`Delete Roles` to confirm the deletion. You cannot undo this action. horizon-13.0.3/doc/source/admin/sessions.rst0000664000175000017500000001537213553660754021073 0ustar zuulzuul00000000000000======================================== Set up session storage for the Dashboard ======================================== The Dashboard uses `Django sessions framework `__ to handle user session data. However, you can use any available session back end. You customize the session back end through the ``SESSION_ENGINE`` setting in your ``local_settings.py`` file. After architecting and implementing the core OpenStack services and other required services, combined with the Dashboard service steps below, users and administrators can use the OpenStack dashboard. Refer to the :doc:`OpenStack User Documentation ` chapter of the OpenStack End User Guide for further instructions on logging in to the Dashboard. The following sections describe the pros and cons of each option as it pertains to deploying the Dashboard. Local memory cache ~~~~~~~~~~~~~~~~~~ Local memory storage is the quickest and easiest session back end to set up, as it has no external dependencies whatsoever. It has the following significant drawbacks: - No shared storage across processes or workers. - No persistence after a process terminates. The local memory back end is enabled as the default for Horizon solely because it has no dependencies. It is not recommended for production use, or even for serious development work. .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default' : { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache' } } You can use applications such as ``Memcached`` or ``Redis`` for external caching. These applications offer persistence and shared storage and are useful for small-scale deployments and development. Memcached --------- Memcached is a high-performance and distributed memory object caching system providing in-memory key-value store for small chunks of arbitrary data. Requirements: - Memcached service running and accessible. - Python module ``python-memcached`` installed. .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'my_memcached_host:11211', } } Redis ----- Redis is an open source, BSD licensed, advanced key-value store. It is often referred to as a data structure server. Requirements: - Redis service running and accessible. - Python modules ``redis`` and ``django-redis`` installed. .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { "default": { "BACKEND": "redis_cache.cache.RedisCache", "LOCATION": "127.0.0.1:6379:1", "OPTIONS": { "CLIENT_CLASS": "redis_cache.client.DefaultClient", } } } Initialize and configure the database ------------------------------------- Database-backed sessions are scalable, persistent, and can be made high-concurrency and highly available. However, database-backed sessions are one of the slower session storages and incur a high overhead under heavy usage. Proper configuration of your database deployment can also be a substantial undertaking and is far beyond the scope of this documentation. #. Start the MySQL command-line client. .. code-block:: console $ mysql -u root -p #. Enter the MySQL root user's password when prompted. #. To configure the MySQL database, create the dash database. .. code-block:: console mysql> CREATE DATABASE dash; #. Create a MySQL user for the newly created dash database that has full control of the database. Replace DASH\_DBPASS with a password for the new user. .. code-block:: console mysql> GRANT ALL PRIVILEGES ON dash.* TO 'dash'@'%' IDENTIFIED BY 'DASH_DBPASS'; mysql> GRANT ALL PRIVILEGES ON dash.* TO 'dash'@'localhost' IDENTIFIED BY 'DASH_DBPASS'; #. Enter ``quit`` at the ``mysql>`` prompt to exit MySQL. #. In the ``local_settings.py`` file, change these options: .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.db' DATABASES = { 'default': { # Database configuration here 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dash', 'USER': 'dash', 'PASSWORD': 'DASH_DBPASS', 'HOST': 'localhost', 'default-character-set': 'utf8' } } #. After configuring the ``local_settings.py`` file as shown, you can run the :command:`manage.py syncdb` command to populate this newly created database. .. code-block:: console # /usr/share/openstack-dashboard/manage.py syncdb #. The following output is returned: .. code-block:: console Installing custom SQL ... Installing indexes ... DEBUG:django.db.backends:(0.008) CREATE INDEX `django_session_c25c2c28` ON `django_session` (`expire_date`);; args=() No fixtures found. #. To avoid a warning when you restart Apache on Ubuntu, create a ``blackhole`` directory in the Dashboard directory, as follows. .. code-block:: console # mkdir -p /var/lib/dash/.blackhole #. Restart the Apache service. #. On Ubuntu, restart the ``nova-api`` service to ensure that the API server can connect to the Dashboard without error. .. code-block:: console # service nova-api restart Cached database ~~~~~~~~~~~~~~~ To mitigate the performance issues of database queries, you can use the Django ``cached_db`` session back end, which utilizes both your database and caching infrastructure to perform write-through caching and efficient retrieval. Enable this hybrid setting by configuring both your database and cache, as discussed previously. Then, set the following value: .. code-block:: python SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" Cookies ~~~~~~~ If you use Django 1.4 or later, the ``signed_cookies`` back end avoids server load and scaling problems. This back end stores session data in a cookie, which is stored by the user's browser. The back end uses a cryptographic signing technique to ensure session data is not tampered with during transport. This is not the same as encryption; session data is still readable by an attacker. The pros of this engine are that it requires no additional dependencies or infrastructure overhead, and it scales indefinitely as long as the quantity of session data being stored fits into a normal cookie. The biggest downside is that it places session data into storage on the user's machine and transports it over the wire. It also limits the quantity of session data that can be stored. See the Django `cookie-based sessions `__ documentation. horizon-13.0.3/doc/source/admin/manage-instances.rst0000664000175000017500000000503013553660754022430 0ustar zuulzuul00000000000000================ Manage instances ================ As an administrative user, you can manage instances for users in various projects. You can view, terminate, edit, perform a soft or hard reboot, create a snapshot from, and migrate instances. You can also view the logs for instances or launch a VNC console for an instance. For information about using the Dashboard to launch instances as an end user, see the :doc:`OpenStack End User Guide `. Create instance snapshots ~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Instances` category. #. Select an instance to create a snapshot from it. From the Actions drop-down list, select :guilabel:`Create Snapshot`. #. In the :guilabel:`Create Snapshot` window, enter a name for the snapshot. #. Click :guilabel:`Create Snapshot`. The Dashboard shows the instance snapshot in the :guilabel:`Images` category. #. To launch an instance from the snapshot, select the snapshot and click :guilabel:`Launch`. For information about launching instances, see the :doc:`OpenStack End User Guide `. Control the state of an instance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Instances` category. #. Select the instance for which you want to change the state. #. From the drop-down list in the Actions column, select the state. Depending on the current state of the instance, you can perform various actions on the instance. For example, pause, un-pause, suspend, resume, soft or hard reboot, or terminate (actions in red are dangerous). .. figure:: figures/change_instance_state.png :width: 100% **Figure Dashboard — Instance Actions** Track usage ~~~~~~~~~~~ Use the :guilabel:`Overview` category to track usage of instances for each project. You can track costs per month by showing meters like number of VCPUs, disks, RAM, and uptime of all your instances. #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, click the :guilabel:`Overview` category. #. Select a month and click :guilabel:`Submit` to query the instance usage for that month. #. Click :guilabel:`Download CSV Summary` to download a CSV summary. horizon-13.0.3/doc/source/admin/manage-projects-and-users.rst0000664000175000017500000000751313553660754024201 0ustar zuulzuul00000000000000Manage projects and users ========================= OpenStack administrators can create projects, and create accounts for new users using the OpenStack Dasboard. Projects own specific resources in your OpenStack environment. You can associate users with roles, projects, or both. Add a new project ~~~~~~~~~~~~~~~~~ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Projects`. #. Select the :guilabel:`Create Project` push button. The :guilabel:`Create Project` window will open. #. Enter the Project name and description. Leave the :guilabel:`Domain ID` field set at *default*. #. Click :guilabel:`Create Project`. .. note:: Your new project will appear in the list of projects displayed under the :guilabel:`Projects` page of the dashboard. Projects are listed in alphabetical order, and you can check on the **Project ID**, **Domain name**, and status of the project in this section. Delete a project ~~~~~~~~~~~~~~~~ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Projects`. #. Select the checkbox to the left of the project you would like to delete. #. Click on the :guilabel:`Delete Projects` push button. Update a project ~~~~~~~~~~~~~~~~ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Projects`. #. Locate the project you wish to update, and under the :guilabel:`Actions` column click on the drop down arrow next to the :guilabel:`Manage Members` push button. The :guilabel:`Update Project` window will open. #. Update the name of the project, enable the project, or disable the project as needed. Add a new user ~~~~~~~~~~~~~~ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Users`. #. Click :guilabel:`Create User`. #. Enter a :guilabel:`Domain Name`, the :guilabel:`Username`, and a :guilabel:`password` for the new user. Enter an email for the new user, and specify which :guilabel:`Primary Project` they belong to. Leave the :guilabel:`Domain ID` field set at *default*. You can also enter a decription for the new user. #. Click the :guilabel:`Create User` push button. .. note:: The new user will then appear in the list of projects displayed under the :guilabel:`Users` page of the dashboard. You can check on the **User Name**, **User ID**, **Domain name**, and the User status in this section. Delete a new user ~~~~~~~~~~~~~~~~~ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Users`. #. Select the checkbox to the left of the user you would like to delete. #. Click on the :guilabel:`Delete Users` push button. Update a user ~~~~~~~~~~~~~ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Users`. #. Locate the User you would like to update, and select the :guilabel:`Edit` push button under the :guilabel:`Actions` column. #. Adjust the :guilabel:`Domain Name`, :guilabel:`User Name`, :guilabel:`Description`, :guilabel:`Email`, and :guilabel:`Primary Project`. Enable or disable a user ------------------------ #. Log into the OpenStack Dashboard as the Admin user. #. Click on the :guilabel:`Identity` label on the left column, and click :guilabel:`Users`. #. Locate the User you would like to update, and select the arrow to the right of the :guilabel:`Edit` push button. This will open a drop down menu. #. Select :guilabel:`Disable User`. .. note:: To reactivate a disabled user, select :guilabel:`Enable User` under the drop down menu. horizon-13.0.3/doc/source/admin/set-quotas.rst0000664000175000017500000001277413553660754021335 0ustar zuulzuul00000000000000.. _dashboard-set-quotas: ====================== View and manage quotas ====================== .. |nbsp| unicode:: 0xA0 .. nbsp :trim: To prevent system capacities from being exhausted without notification, you can set up quotas. Quotas are operational limits. For example, the number of gigabytes allowed for each project can be controlled so that cloud resources are optimized. Quotas can be enforced at both the project and the project-user level. Typically, you change quotas when a project needs more than ten volumes or 1 |nbsp| TB on a compute node. Using the Dashboard, you can view default Compute and Block Storage quotas for new projects, as well as update quotas for existing projects. .. note:: Using the command-line interface, you can manage quotas for `the OpenStack Compute service `__, `the OpenStack Block Storage service `__, and the OpenStack Networking service (For CLI details, see `OpenStackClient CLI reference `_). Additionally, you can update Compute service quotas for project users. .. NOTE: Admin guide contents on the networking service quota has not been migrated to neutron. Update the link once it is recovered. The following table describes the Compute and Block Storage service quotas: .. _compute_quotas: **Quota Descriptions** +--------------------+------------------------------------+---------------+ | Quota Name | Defines the number of | Service | +====================+====================================+===============+ | Gigabytes | Volume gigabytes allowed for | Block Storage | | | each project. | | +--------------------+------------------------------------+---------------+ | Instances | Instances allowed for each | Compute | | | project. | | +--------------------+------------------------------------+---------------+ | Injected Files | Injected files allowed for each | Compute | | | project. | | +--------------------+------------------------------------+---------------+ | Injected File | Content bytes allowed for each | Compute | | Content Bytes | injected file. | | +--------------------+------------------------------------+---------------+ | Keypairs | Number of keypairs. | Compute | +--------------------+------------------------------------+---------------+ | Metadata Items | Metadata items allowed for each | Compute | | | instance. | | +--------------------+------------------------------------+---------------+ | RAM (MB) | RAM megabytes allowed for | Compute | | | each instance. | | +--------------------+------------------------------------+---------------+ | Security Groups | Security groups allowed for each | Compute | | | project. | | +--------------------+------------------------------------+---------------+ | Security Group | Security group rules allowed for | Compute | | Rules | each project. | | +--------------------+------------------------------------+---------------+ | Snapshots | Volume snapshots allowed for | Block Storage | | | each project. | | +--------------------+------------------------------------+---------------+ | VCPUs | Instance cores allowed for each | Compute | | | project. | | +--------------------+------------------------------------+---------------+ | Volumes | Volumes allowed for each | Block Storage | | | project. | | +--------------------+------------------------------------+---------------+ .. _dashboard_view_quotas_procedure: View default project quotas ~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`System` tab and click the :guilabel:`Defaults` category. #. The default quota values are displayed. .. note:: You can sort the table by clicking on either the :guilabel:`Quota Name` or :guilabel:`Limit` column headers. .. _dashboard_update_project_quotas: Update project quotas ~~~~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`System` tab and click the :guilabel:`Defaults` category. #. Click the :guilabel:`Update Defaults` button. #. In the :guilabel:`Update Default Quotas` window, you can edit the default quota values. #. Click the :guilabel:`Update Defaults` button. .. note:: The dashboard does not show all possible project quotas. To view and update the quotas for a service, use its command-line client. See `OpenStack Administrator Guide `_. horizon-13.0.3/doc/source/admin/figures/0000775000175000017500000000000013553661042020116 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/admin/figures/create_volume_type_encryption.png0000664000175000017500000025247713553660754027023 0ustar zuulzuul00000000000000JFIFHH8Photoshop 3.08BIM8BIM%ُ B~ XICC_PROFILE HLinomntrRGB XYZ  1acspMSFTIEC sRGB-HP cprtP3desclwtptbkptrXYZgXYZ,bXYZ@dmndTpdmddvuedLview$lumimeas $tech0 rTRC< gTRC< bTRC< textCopyright (c) 1998 Hewlett-Packard CompanydescsRGB IEC61966-2.1sRGB IEC61966-2.1XYZ QXYZ XYZ o8XYZ bXYZ $descIEC http://www.iec.chIEC http://www.iec.chdesc.IEC 61966-2.1 Default RGB colour space - sRGB.IEC 61966-2.1 Default RGB colour space - sRGBdesc,Reference Viewing Condition in IEC61966-2.1,Reference Viewing Condition in IEC61966-2.1view_. \XYZ L VPWmeassig CRT curv #(-27;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Km" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC  C - ?(((((((((((((((((((((((((((((((((((((((((((͗S>i..1<޽Ңnnu{ly+w_G^62i/Oʽ"|6AtDF~Lڟ)OCoC!m\\"b)Kܬyg AJQٽǪ-Aiod7WN/r7 J\&:?R~<}D$ ?&i%lSYv`jE9l0m57+^ծ57)p/&rmgtb#֥|ko죪|W 2= []\9fHJ#5Pm44z~Tc&l}9q][X.6owsArC ,,r A%V{wQ-f Л{;K+<6%r7ϯڛMt/ۋ~/ z4Kk6ᴈn~F{cuk?PT[o(d-*0 :T3ڳԷVR2_/!ӭ^=:ImY1 I$0Bt6 $* W6:okۛvU]dc 1!H?l]Eum;gSOcG֑ ʭڦdU3Z7 s;'7-5+I4M>"kYI-dlGSr8GSIk񺦟[\P{ſ asy=Nd!E{g\|8|ykxM^Ҏ-ٺ6D7`vTRj+kWLy-W=kGߋ#xf+ts\Q"c"1B_W|1u~C&HYZ5L#px_ ج A47m~5~<_c:ݥ^jRO*ȋ=+N2@W,dTZ7]_*>Ӹ>Y6s\OQ}.͔hd`c \(PyҸ]+ %.zΑ&wc ${sc2[N"@i2Nѩc򏘀*ux R+Ѓo "<9ftɭ6D%@ǖ /L5߂0k/+/n5W֦C0f7I,~~xoxSuB:mĮJ`:2ׁ|u%IN3Qw+K⽧<_o kTmb[oEO5U X.[3I?j׿?f~|NG xA"WD둕`# ~~9S O.-^N)eY$s9o-s rG=;5O/ae>Tlm,UdD"xV@U\m-]hz"ݟϟ[~?~#xot^GkraQԞD#0$|\N>5/>;I[^H%0(I^U Iqk'W;YEp_sGUOM5m?3tF W[|d!w+r$Y'ӭ̖LJ6ۂpA>ߍO9WT kG4(,n#A RT_~gIJj&A;s5v?fn. yg_'4#=z_M͹E3\ $/0 v KX[m.Ɵ~8w|.ᫍCXx%N 'B8'_ǖPGYN}kXU8C&"gSd?V6]漢)YETzy~ߴ?|Us?4ky6sכ撂@$c:;Gkþ2=KMI cn''=es &.ߕXk[kqA8)%s_=~~.?loګAڼzG?Vf Ӽ!<gAm.nO^ߑ??uWC5]k)-O2ݙeeg U*rwVGT)>ex4-!l8䘉n; h5;O\k7`%kU7) 9E9 o Ꟶ?01B ٽ2ҋT<7ImK.)?p~1įƗo-ֵZX#HZAI'M}xcg{_ x}bOK]FN>|ϳK.f!r77"<>jTLi0C%, "V A#4Ⱥ݋ `w͚ܩ48 `2v[* oQ~i~U[C|[s;z[__&Ԯl5Mnmv"p^2)`r"~x[Ok#gҼ2DB1{Ook Y[If$1 y8>h t)O˻hJKwu ;GbɟG8$t'3Ok[9r};WCio},q;JmYF {ْ)E"}Wt#I>OPqUqޔ{1QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( YcX4r>Vg,?哊z(μ gq{bp*C<kh`?xOL֓xop#)૯Ue!5QIcN>e xksqjdQ]@:fdp=$ ;rχ|QGσ7:T|}RE"#ѩĊÎ,ASRzymKIm&V^fxsD"a7\_ٟ뫽C᷆ӯʖYe1 dܢ* @${'%r>AfO&3Eo'qŸD?f8ێ+ELQQ'YJelWӀ;.^ϝ_a—M{%Nc3G4Ѿ2:34>߳x‘k1,WS=ܐ76 Y@b "G֟3պ +~$/JGo Gj"j+YaE H ϸRpE%}ѣß|QDŽ<D3A0#k@xJn8teaxßuQX|_m&OFn@&)zPw[Yamݧ@O pLHnw^[?\[F2"v}iJAݟ7K&~Na/3&Kk_񔩓b6^㿃~%kt>f7<&}ѾE"+!Ætק4jĒ[_|soZMsHO|7E}tQyI"F< ?sxKbpk{Fo o#vh4Px 炾j!ѴLNXwv%cܚ)1KMJVo|*?/j|({wڋGw<y lc^̇Fs__~&Fiy$*vE 6(e+/ ?g|WxS׼ek#GLC(ǽ3gτ&_©iĬOqqvo(O#b ݂FpH?L`~t)S\*w}|SN\_@wЏwK*DA! @ #}KW24qngF%I#NkEd'>|/Xt_=6cqnh+.ۭ䉏$jwBU5?Kh[ޓ5 ieڮ1F$[RVs[ z|fm'>1K_ >~-Wgy= f}@f?̛+X+߰o\]Z}C5ٮbF劀I|߄4_0L}Yo$jȥ~$DIO59.%oWv|Lx_֟ / qn,7Z$I2Lyy1?>"7цWh(H ._jqǭ([}/-R]ʟf|^|et;1H5;G%²88>|qCX]uI;, ^8ֈEEmda5ϖtjТO[5^ބݦIj~"dhF08~Νºܝ%G>;^9%wI'O$:2bS?|bp([OxC復sJiƛ?my9_`PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPş_Ascqa{#T Z&p y"=Φ8&k*)~`~s> 5۴ۨX!(Mȉ)'Ꮘ|Yg^ ;vEoE?S}jλ"z>]FEr?z追GZsȣ"追G_ޣrCdQ\_ޣz/Q9֡(ȮGz/QPudW#ދ}NAw:2+ދoE> ;vEoE?S}jλ"z>]FEr?z追GZsȣ"追G_ޣrCdQ\_ޣz/~.w:cum8qY΋´ej(MB((((((((((((((((((((((꿓dg+(5,nE"#qiN3'G_aJRb"R'G  -O&kL6Z3eq>:__}J[[{9ekhnZ7Pb3FBB~A1F#ʱ𔒿*߄h cSx]d/.HH~\:>qZl6i"YfDqe1hCGcMck=W^ܫGm i~']`fVw)"߄h [E^'T}_m?4߄kVwԩ*߄h [E^RU#  i~>[RG% i~']m}z>KW܎K'G  -O&(ox}J  -O&@<@[OMuQ/_r9/@<@[OMx/?hտ*_ʾr_x/??_m?5G׫;T}_m?4߄kVwԩ*߄h [E^RU#  i~>[RG% i~']m}z>KW܎K'G  -O&(ox}J  -O&@<@[OMuQ/_r9/@<@[OMx/?hտ*_ʾr_x/??_m?5G׫;T}&Lmtm&XCث( v>5?iwmkX$s΀#aDtok]0*dgN4AAZzKM^E~ _ÏWfo41d\r\ #;5į?;ǟ ޽qSMGvvTӺxgHAO/ᕇ'Z/~")okWuݨED^;lczWfLM.Q?o![m+Q15me/ay& `Kn$Pp_WĿ|~;S%>Ե].orn&)IDǓF*>/+OCx?,3-^Q&d7ɔ[•2I'mj7ʹ]nDǴq[,` hQ>`Z|Mac/!OKh݄̱Pq9;b{k>%!ͥh]ϓn/rUy6WI S&*_ gn"/~)|=׾KY=2T3HXˆHC1Uܡ±~SKm[Kሖwr_#prE7 ~ԡ_nbK)P#w lgfVd#*#˅=|m_R-;:Ѵɼ6-V#$",,U(qmuj]ON?Ěcke1Ω-*3=BUPC;1#sbdv0ͯ$!aRnqe:'4~!:)ZEpc;O80=(hw686frJݶu9UFoԶ?ǂh{ٺ;P_A,?b+t°ɝ1sۚ?{( |qcIZhdqpG~ f_ _hh}Q@Š((((((((((((y?%?O$_|g:wjr;'ar'Dl.ќL:r(]1Q I]G_{\l6@O@X_)^?&Ӭ"-[R6EYI@;OCOv|/}Ϯҵ޿^O'N&6nW4pИ?,D`!9 R >yZiE7//'WFIte !-v__vq[EV{/wҁ-s?;G&EQ*/_z:j+?5wl?*/*UW 딿}騮g_v>W_\GMEs?;G&EQ*/_z:j+?5wl?*/*UW)2Q\&EQ ;aQT}J>Kގ5wl?*/*M|A U_̾tW3 ;aQTkT_URReM|A _v_p}r/5kT_U;GԪ+딿}騮g_v>W_\GMEs?;G&EQ*/_z:j+?5wl?*/*UW)2Q\&EQ ;aQT}J>Kގ5wl?*/*M|A U_̾tW3 ;aQTkT_URReM|A _v_p}r/5kT_U;GԪ+딿}7O #וZΛƾ9խ$쪈K{+qxsK][FD+:2RVrTd=zSJZxkvYdѬw9ff1XO$ױH{Þ<%r6BHWO>@Vσ~$3[h$s(z C> !GC>~h?#oS_~SgtmQ .l pH(p<_ |F xSK Ū[8n$ UwRIq2xSE/3쐛"m6|7sy]}nr/`x"}OVik6r^|j׼yOLj gx_7Mk[3P0򎃥}3Iڄ%.ܖX Emd Ѵ=M6V[ƀ==O_ >P4]Y.%,`%'vF*3]3< WuCu-e][Z lȊB 8,OS\e_&KԚmJ>c1L;99]@r~'W|mioa-^O W]G eV O_hV҉W" I#U8SE08h|W/!gO.MQlΛBmki@\+(((((((((((((t0kLI*8L$ Y]v%r?1v]v ; _ݣ/]F\}VG/kH>#h`QG$Uc`(ñ`?bu`Qab1v 0(r?1v]v}rAXv9G _ݮ > ; _ݣ/]F\}VG/kH>#h`QG$Uc`(ñ`?bu`Qab1v 0(r?1v]v}rAXv9G _ݮ > ; _ݣ/]F\}VG/J(wñãGۂmVspQEbjdk:hE$m*X,0K=8#O&Oy}W.V? CkrV_gb°fϝ?dWRR0A_1,xRX|1 6[001J?n>mL0FP {}߶ ƥy2+ "-zV:ޣi3;&aI$8$ru[L]NJv,3هRNGoA^˨|߇?m5=ON^%k4iyeM,4$јȞH#uhثn<<^ nYI}[Rj -~SĐ.R'#jvZI+V}kW%0S4,mȇl`k.Scz_hT^ V`d9u4o.f&<I՞%#[-nit/Mѭ|E }7GմgfM՘16q82 j|%o5;Y#F-C 1*N6q4M^o'^~"5m;wKGg܊neT;K>jm_bHEiCfʒaȫ$lIkW,v)qR/Cvk &; XeU >Z }kRֽ}m5 .HVa Q<2%S$rlF*2+)VKX'nVkxW^3ҭ5?>CKݵΫVosqk4 *Epݼ_ȹbMoFk)K~:~麖}cSn"\, rUCx/ dž-GSH4^ibywK ?p "X]t*F`Dm4 !yn 2q/M ~Қ厷<#nDoC / RFF AKԿh]x'~{OR-nfE^M#y9h2Ș:|5Oco 'G_xKm` ~X,q 1n,Uhlu/j|%7x[Q"0D6׷~a6ai_ğ|S{M/M\Ai=._lΈyn'GQ0rZ O>:ƗNgj l-ܺ#cd1\:7?Mk#XՠԼ3}{.5)" Lw)X2~^ _ xJE}:Q- D: n;n8[kUm5OxwKɵtB( R'Hv* ڟ=| ?=wKgu]MP~F `]<mxoڬ4v(dM6VB-&l{ox F񵖏Z&caeViD+VQ D⹴v&-O ,.w [{ٻPɣ <0" {⽎]?Aq-q%jрs@g|#.-h I幏)%O0l>  x_IY-ZfV>|3dᜁO5IZ?iCzm֍cluYxMo{[>&oaB-Dʌc|2v[_"@)VWgNL!LhsAZ{FޯOCo?iOS< zΨ57A+8I%22j5.w\xo&8t}spo#˙Bh@0skO5sPZ еF-_Q"!^^dд;ʎ )/|<4鵧[ ï]ݼySvA"m|䙾m`Dy쟵G5 -{~Ԏ;;UKU{5c%oF6fL k hZ 5;_M_KP\oV(e$fOHI>aA n<%SZ]x{ibiɾO3pGbx_t|E>);o$3RĖssosM :r9B8?/_//o&Gg[alӝ+TX\D偍DhՔA>aęu4ߊ 6~S0y-Dr&-_FMƫ^WF|ZhvV8( F$a.́ Sg<WG :Q<~T+;R IY;|.[_Z?i/RNmkFVV[d㼞ML=ЈI c''=@ӭ$R]-gXLp;B$7e]e=>]Fռ7j^ Ѵ ZhVR[.qGpՁG9֟f𝝶o}[[iBrK\$FigL3":́%VnpV})ZNĿ>߅ɵˏ=:%:ڵ~tK4BgЧQ w1mC=[h9nQ-$DgJ7WxY+S]_i2ZM[\}i=i 2 n&_ًC)/+Ӯ W;iQR1QBA% ]m}=kYKDT]rfXO[!R՜I>\@N؍m7 ^#`fu2}5X3i`9*IO;Xi:/|KagMzIdbG ,`X:mG6 OÑ6Z-ޞ['fɑKc a@!{.?m/ 7A},âUխήoDG4ye3_3~>+g]FO~"mMZB/`Դ3LAL?kL&eh0^$zir]+ڄm)2"DJ`f ž.ay]oķ~ғU E|Y");QT5Ē\NW1J#AK]7V[\Jnu{+c :d\rE_2V7GI糟SO6UH=5d _Z(t8|k Z5p5~T.Z[if]Ȓ,o:X7qៃ𮯧k_^^x{&,s\8pv x†_"ӿs;m-KZ6u-658c$'F#mzO$:xz]go аi ڭh-cK Y<<ڈ 8K*|ĎXF[=A%#/%]wZM}au|<1e> ҾW&>m7W=/}z!Z53K(-kRYG@p.0z [i4w~KOӼ)TX͵ J`c4Ȱ9Q#rѝf Eokjvqc=qi]pJvGki4 kQkMo^HLaaQȄBNV1RAy|}5ںZ~/O.{gi \EO%hёc9̑ڷϑ_}>|?]=.Doo=᧌Ib4"2 \1\ql܎ *; >|<xJy綷w啦{Zy-QyGEPJqSyxPN]I\x3E. WxwpZFT80| a} >ӟº$K]+rVVD8ٽ?l'gz=xu+=A!yB@E+B3g 98<)H[}Q(\~aHI)Fk 3xmt5ҴM/U%eӑܶK&M{>|MӼESsMG$,Rk˸km^NHX kzϻ-~ xHhSլvg9ԋžV :ՔwZf2%ݹW+čC\Mmb zH)l9P 0;#źLO]=1yԦIY #1Zj+蚧i>Atڕ 26c BH5zr+cƙm&ݗM`e\>7 `9Mt'殼Ӷh4E>(N(((((((((((((((((ݮk=mml$#DAff '+3 y_hmDywi}Vk+İ\#G"0ʲ0=8f?jfeRg ~ׁ&>ўv~>˺|v[=5jM#MjV2%[6cXB_x\H/OGZkC^A%Ges%JЫo=Q:5< )md:MƋ,~|$I_ v>׼53sT&R-Q#V̛e* !g%__s7x /nBt{b8<K^|':uQ8SW~|f{+6ڍ_A4tw1 v܄sX4?m|]xjvY/[hgXEFY4`o*׍x7t߆ < 6}_J%֋[- Oy4\ +iCtϘљ*zŷ.I]%mk__}±xxJ5 M3ϳm%n.H8&"G3KebV|+״HӠ}B$Jf@EP|Ɉs?('5٧%,>5jZ^ԬWWоu .`e0@Dk>G|Ciς<[K=oHHͩHQ< -4䬯_HZW;oG j$FCrrOw}%ʌ,:JzVr|J[W>t-Mod%ĊsYNp۔k;O7xƷ^Ow6Ve<ηq?mxtg@8q\\~+{Z ^ei] ko\qJȐX%k_u;y[cMKњ5}nɬ| b1ymq?|\S]/4[obԭ%_T6 56k)Hmoݍďc\N:C4>#mʽF'U O Zw|BеYc:Hl-N{1ܝrTSo^g6;NKi =w% 2@dAYGQ\fᯅ#zͮ˦˪'U*6`;g5Oq_X'mRMZKt.̀[ RI.$a!s^>(|9mK{PۮrǟH>iSkk]J_xI7pfNTW/kĽ/—mD]K:iPK{֚.wFcIX,aVݮ̋W?A5 u+[. 27щ07mNC\YgGt&w MF{SeM:liJ3qQ%3>|H.|0!O ·6Ι o."-NΜ@c$08guS]vŶ>>j6f> '`_NsT|!/8eƿjWvQ[Cq-qL |r xx}rOּ:UxmQ{m죕Xn?!sMx>\x{-x +xIe0h A͸՛]g:/o;%o%ٻik}=>V'#(wo%=i$z厥h#or{{e =Ǵ)ݸ`硪pPk>n&uCdMݲ9Ҿ>HOYn6-7ĚU^ G]:X{"|%p8_xŝWxOf.#I$ g߅~7ѼG-/Pi~{e?)Eanw u׿>i~&ׅV J[R F5I5KS.Py'3{x$6+-HԮmo"Kf3Nrw[Hs%[? %hc:&75C1jWvV߇\l3l$\dֱIɧ/D?<+t֮^h[ ԏ2ʫ}cd` RCPk>xXξ%v5s B#%_q"AO.~5w#|Eiwv^)W.fOV,~2Hn!ΧQ/?f9-clGINEȧi:rv>~9:ѼU%u{;[][[Th-<9hHRك⧂ oL{h'm|eS&ۍCDYX  xf*{eiqfWB޽bHZ2`ݵNO=x4hWH[IL 6n|[8wtƕW}w#ݎ/S[w3}+ /K&ڭ%×v08Wt~;=6]*_Zlu&kqsym#O3l2%ZJ)%gs8jE%Q@Q@7z`Q[:~"W5ao Ϋ:t8psH?A;_G$^O%_ʀ3"xv?ƴ~kk< H?A;_G$^O%_ʀ3"xv?ƴ~kk< H?A;_G$^O%_ʀ3"xv?ƴ~kk< H?A;_G$^O%_ʀ3"xv?ƴ~kk< H?A;_G$^O%_ʀ3"xv?ƴ~kk< k,06;Y}5YZmż :F* Y&b%,$$Q@3j] $W3͵,GFVV\pj!IpJC# FB<kc10յ >ᰵҬ~uq=A  } B)u;m~sF:#o~O5> yY߳qv13Z2uS^]}moPZ_cZ ,RV.^H$jzu"cp|W [MҗVt[^5 ]GX_7#yaYcDR@WI=j;[kh-%IH䍃# VG Ox ψ zMm-ӯu$FiQ aQ6g&GP|kuH4>ͨ֞Lҫh7FYSZ%5!-_g/:ƽKDwşHG/hPGưdx m6k.n U_~7xF?o[O gKeq1O%Ak-ȊVΝX#61s\R[Aدhkׅڛ122o1|Ld [_/Bٿ΢4X}}Hc$M5wmdh_ih-5O& #ظێZ]_Gya:\A0ܒFс2W_Ηi~ :e x3,6д/t-fa0a!0@W/x{K|>(/ntFv-ⷊ,I>r8UII-_\ct1yN+}ž/UNjGu|$0 jY]Iz.&Qt"6џ!G>-uZ;M-4"Sc4EDI`pXWFJJ/᭵&]ovn#LW 2@$qKwyinoKx*.`2f ܐ+WGg@յŤl86-J$yMϟq:26}WKqmGĺǏ-~ XkakkO%2n'y$É/o &_bKumo,O2G-2D4\!A$7ljmXMs]xSHXlR+G4KvO^$^-c-&6*+[X]nL0y D#NrR\˙WW [݌N_|mMO&>} < X៍qi?>|}kOwn[[sa%Zmͥ1nE*k':?t5u!,k J),_I8Ո-mh#Bw9f8I䞦Gm:[tK 췓}y^9bUPK})ǚz!JaAd\`Q=*Fn:]>;I$X$$#2 1V# |)|C]S|l%t Yp\8v V;|$|2k3dwq{] v%X\<0WaRc{Y[2s\>/F0(=at[+m;2՜Vrv)-l-.mdX::0ʲG W{kCmwZZ%)kAitx?&56o1E 8Gm`7Yѭt5smwn%:Dkh3Gu |Y]~i_;|9*xdWk|kw{du0!f*8zsVf%;M/B:|[uGXxn((((((((((((((((((((?oȿ׬w5@EP/JXA-moY:=,/cKHVF;y(VU* i<^P RNp3kHmdbԼ=e$~uEkڥDK[% I(e,+"~-u- qcK'[ʼnBF' J@UKG}?:F7M. k8a啑@I g>7}|/-cZvaI0nEBLĂ}_Ѽ1]cSP[趂eIwM3+QGnV$ z~6g=([ !lȸKA{FKyJ!Y X~ ]q|%k qs=ז$C&Դ +m^-JPlE'Fh9;Cƈj mڔާGlAtyiaM,cfen杩(_II?Gm4N38ķ܇Tک01ҾOҺDާxB>.}SLMwm2pHYel:2Cʍx?kS]ψ>xNqEf`G[tQdֲm2'˾ڶJW}mq? ZS̗7lS$H"+6H?(KO HAiig;YI@JA|IhxǶz ۝2;Ěj߻+D\HSs|}S>49# o;qamWOG6)TmshVzτzgx#CTm ё,z־Fu^,qgqk15#T 0+n95-I>#[}+FnN(mDD,m`p,z߆_yf/Zi\ydW͍#=.Ciuwomh1Zܻ:݄] $F$l+)el&{Ooz-﷦* č匥l#}Vo{>>}[fjݝ6m>[9''Qo0Վh֦b6O /̑q}M|k uL{xoi,Ojs&[ o_~;|N~!^Qƍ-2Q3-N녆iI!roeEIz:Ƨoj Hif8*;FYD`1Vu? _;FwFo 6 BiF:W^=χ|=5c{]%B & 2.  q ~9|@ |>ZoAPG3%ٔY-b2 aNvZ<~_ZcLij$pWip9Pqҩi ~ Z};[GxnICs2 }9k]jz楪?DtZO ~]5rSSK7`K**sT?WښukopXGY!DY$2K 55\I/qwݾh]Cյ?iyi"Qew4qj']2zH89_? 5 ΙInOզ*Ia 8q^'/N+o 7 k6Z7IQeV q|NP45hlc'Im!⌻CRd*^hmif4"[^T^,ldZKaS1\!ϭ|fį ᶝM2 =wZMxD[ ?5ݙ)d,MvlJ_[†/E#hg\Dl-N"hDcENb"Sžhh-n!żc˽p/3A6~uOv|5nS\X\m,xuY.I6BrӓuTj>Y1jZ޹4# 9GEqFZx*~Ͼ> lЋ?t+.Qcy;H\1w xU،o0ln瞵947{PFZ~4[ $"X;D SM#iCh#-T>H@HPw-]f/ɧ۾m A#x\o̠Hi >h ^}*\ԃ,q)ˉ&TdNkyklX>}j,m<6O(* tgd@.p9O ZXFxaVm*i_H9.F:J(k>75-?>ӵ;]bH潊XUR7]HQUYT( Ծ|%t];ztK'H+9i-w}&&B3޽*VʶGmm *#E 0@8U(؏eg:V|0H2#Xʐ?&ytP7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P?& F[kke Sm2{ΊM_ҭ^L#(Z4PEPfu֗z"xePJ)VGc 4{{xP44{;iE`Y9 mb;2+pFh|:SxBy/ok\yhYUF̎kԳڌϛݥEΟ/xMm.m+e{aH)z|Û{KmP[=:MV&v$g,3H$r~fWQJZO&|? xYޣ/RrX$ bGϔ :Vi_jS/ ]Iqr'[L$n.YIۅ}ϽOUk|75XbѬ.-X릑#yVyaPqT.?dW> ׼S Ν&u }> F&+ iJ˴&YZX[CU{y 1)1D]|`qF*ZLdžx7Wk>##wS\A';P:T[c>X]}Nj|Aak[?NBaZF 9_ϵ|?i gO-R&)'ktcQĄ"aɒn$҇]K>_|WM:VcCrVR(Ȗ)`q?gY}SuMkRMVX6Ma9$E-Bڸ-CGI^DӧuoXo]xV.-6w^VV1*p}KĿ/⫏\Wh-xnʑ%,q/Kr/\(^|?nQg[fd.B*ey$ ~R ⛻]/ly:Vv{\[[& F٤xY/IɲRQEQEQEQEQEQE(h}/$h?aӆjW{m(|W$ }.}EE|ɤ^<=cfѦu6$iu. n\*( *p3r{ Nյ;vK!/# fX\rX/4py?iCH#j7zO.^2yQb`HwMFA5mX|;J$%6n[CN Hߗ+_qT⼯ŝֱOt3$K"_vpqOHxmS-zxt_6}ș'x9nz$ RgXIu[[9o6X,G!xJ0*njARxtNM kj$n$K2+[ƇkFo@J~^4]%/`|I{kWuxՄ($AߵK_lf׈j/h76nGgimMqY1*LΛ+ p5f%2(7񏋵8>ڍfi⸵ 7KJTGb`@ 3V>k~&t_ٺ4;]ء,TRp#xy*nEMnzQ\@QEQEQEQEQEQEQEQEQEQEQEQEQEQE+WY$EfYԶȐr¯s|Y໋rѼ1x(d DKf%L7 zo1u?i]zt9Eq'gzPMWܸ$ϱo!lF&ჵa?.RҾhD ^l&//ུ.%o{fT%F@`We x{Ğ-m.t ]i#מsgws1afU#r1+~1M~;K;Yu[=RJԛK{dy =39b)Ip+)e# kf_3F6Fl1J͎T3(I0ggCUd|ke5_xQ丹_I4Jc%XfNм*z^OL x#Jk C^ >1ai%щ%ʅqU=ko<||mῄ /:4][3qy{Yg.]Dae>S 4w͗3xÚΟ"={=F{a*(dI pA:+'2oK O?/SX䷵,X'e 0}ʷL񞉫x]UKOt1!ctcǭuk n<>6;[HX4R\8@C&@6^կh+:6#mgy BD/|-T0O|7٤W0s`}s _ҿ#k6jO";=*OԵdKi5;J8{g9R債}CYţ^%OS7O[iua^wh.4), VK{9Eo.aǖr Pk ݟmzvnsmp7SE|Vc[h®kuȼ5^k+c7R B3o=.[0XY*Ro|*B^?}5Ѯ|7y?>H&W^h~#V7 @n"@?iGPjůǎ-S=_}I}^N8T^]e3.W$2r+WQo?N4}b=j+3[s @`rDh،pG2+/h:o/_k^6 a6~x #"+Xe[ EX%k%tAYCuu.<VhI$e*,qq']kz:_ _9%k/Ժd%j-8eݞH6,}u,|#u뱠6~|m"G@朢x]\$21;e1NҔ{;3TM㠢3>R=ԯ~0k;u{WlWyFl|u'߈~&>'x.D͎rIh'RT+$x-+т2MuW_AN?thx7FO[VCL<*$l225t|"xF 3' ȧcw!X@ћo)RH! ;itE_\xO-~q0mۂRA@5uy| Fz楮LhLVKd$U*nI5 xA%{7%_jr8 Wh!@|i>,nI`zx ֫aeмXlu jYVDԘ"1)';A-i> :l5$\Es3{v F7L$OL?'}gYDUKܯ;7Ǯ)mC^68]9]"FS]ŏ:-WZKƥ᙮&%ZtT?$09J4Y7G%W ?쿭 Ow~"^_h,n,n>Z쭫}y5[ӧYyJҵŸe8ݱ15~զo/a:zʷV G8'~Igt>,n_1_#滏ٯ^k?M5=EՃXw>BiyA5k8mt+YhlKh#ۇ{e J +gY=s?蒽'o0 G4gWUgFۖN=kZ'}gY>,nm)-Q\$O ?}'\$O ?}'CEs>,nIY7@ O?'}gYt4W= >?Igt\$O ?}'CEs>,nIY7@ O?'}gYt4W= >?Igt\$O ?}'CEs>,nIY7@ O?'}gYt4W= >?A-4 ӥ +xM?:5ZKy, E-Q@ k>Sӵ!1Ӯ_˚3S)wtX[$fE$$c>{|M[> wq^}ݪچ$]MvuqU3KqL#7O2&B^R>2կV{^0BԮ d"ir^s=*6`M-O-rqӽ\ȯ~%Y|~Esy,pF #6qW5Kh.5H<0<-3A<)p;g&ں=/l]>;I$02"HHFe@bF:pOGƏݧQ(=K.Zw7Z,sg# '=r: (((0߆?o-/vV۬Ym 2B[ mz};k[khlKx-c8"" ªU)SuC41(ܒ)VSQR`y![[x~D,Od9$q$&*)QExE/KSElqA3𳧹G_4_{DuM/Yld[)eyhdd=FO((((LEmOXX\FAG5V&!cSI -(_4`/t^ 4(KBe{bCbcEn*q%6btWͺ?X>2jZգWN "dMʑ`Fek[xi }lα,2@ߺ2!eQNJ䭸s6/E|࿌:j$t[izFm=jmxn(>3<)]XY^};[kIo6h3%iP>RY]%?b(ؽ6h{s}KH4MfyHi ^[#9_>.> N$:NjbgKr h>Yo0I/_ .}cEZtS<_}-(E3_<@ -)P<_}-(E3_<@ -)P<_}-(E3_<@ -)P<_}-(E3_)$KF:溊> |%xcatIdc I1/u ?:ť̰JG*kAA ì[c16 챪_K}[=:K}rm c!n m'hÿ/(j( ]3'X|;e?V)4ktl6\eYؘب;r}:$Aމi֞N} dss&+{aHݧ|íPocf8i7ZZAslmK8PE!lr5DFr)-/n8=㷇~xzѴ}BXfO&İ4nsiB$d,=kШm,g+c ChZq m*m+7 Cmԯ4;{֞ټ͹F&8ze((((((((((((((M/B:|[ubNGO2GTX"FAA::|QM#ŚjlBE!`Đ=A]=G=I՘ӱ:*񴛭'YCIƖ2ijMfrHn bH" |)H})4/x#WB+3bH'vS=4{Q|?u}r(xG~a5}Avz\H2` 7H%YToÛ_k6$ VNx嶑yn&T![V I$?u}?u}M;N AxI{'zdM4I xYՏDYvN;W=|.ZzZ֟k4[|^A3R8ڗ`_ݿN_ݿBXۢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڢu}?u}ڮWUӾ=oǫļW'u']-,`E{N33S@$oPLX?_*x_|%MIh-JfUEl#Jrh,4k ŴI}v((Q@:՗JHC'EaiҀMݴg9AG|G<0ci {M|Z̰7 t*v9;h7Y7%ݠ?NIKY̟_CJO] }M~џ mع x1O|<x+4oxPRZD}ZAc,sjݦ&ΆxV^֮L'7cuB,TfvI07"]>jmh5G؞/񵭝`qr+C"ȥm #"HlOQ|\?]Ba!uVՐ\M]c$q^i~>,4Z]_4iVQ=ynF\n` vVV+|b5KMQdA7[+KV$d i,m2\^{?K^~M5v^2$҈RF p#a3 WZ^1jz%$> {$fT\!9^~Ϳ^y;R|aO >pXhĬ6.Nqwh}ܮ}G_WßS^/; YfH }v󲦖*^W/\vK[~՟ߍ`y > LKsʏ6hHU yWvt5?JRIR+D)wR>]l|Džtim|liSK7q#gm=ozyFt"b{dTKu̅p$L$gE%_Q=Yx ~&=> dӖ#R 9;$ QpN x׾kK̳kŋKY&hۡihy[?¯׵;\[5C𳗞;J86Rmr g96|qПƑxپcm9%HL>Xxl$!ITߍr"u= n7i¶Wm~ױg̷n'Oe|D^c#G"Kvב*Yq4{X:ެ '%wIy4=/Ğ!};N8ƽstG!i}fimh~>x'Lumm-^%՝P=@&{Kyp<jG:6Bg֥+{;n- $P^&:&!N*MĿi\\a`{n$ՒFA @XsGƺ?;1Rֵ k0m.v$jm9`_uS Z)@n,Yo kijI 2Kq#HV!?"ֻ]<.M`,b /L5^5|g5lu=7PŬ[KjڶCƣOv.l"Y/SɕQtr{ ;O]CL;Mf-$P,^jf/-iߴSĒZq%Bഗ<w^K[OL)`V-V#R(K{ %- E$imF '4\9ukFk:Ӽ/eueҵ FXo!8%›;YQY%F?e{Ioqkkءmo[օvڥtڐ$ren(hk=/mO4osZnNjKku³(xɖ$U Q>~\sj%egoJto 9h= H w7[?iZYH;Ɯif,)_Ꮫǀ3ҹKoq#ꚌxlX~Ǭ_%V2"ʰ'x{'o] 2P<2[։C-3ƖR6Źm-f( [x>FX[qm=̑],@$`+&jw:wZ^{-JAԵoCu2mKvY0MH<џaKm5>]}T7S->l7HQ蟯q-@ ^٨iVфzl3M5mX\8B!W<ᇎgZ:f$EK=h Fϵ^__CX^4vh |G{i'bNKRTgoه_ÏxmN=tfF |%慐 G6Hw#ag;RV> m[i?O'īaaMoپRH.beI@>[;I _gwOu62(V{İ[MAj^x~xkuY\YjR9lw-$4l\ADs^2q՚!߈$"-u{Kʖmn+J&q eb85~Fcgg<iPXx5-u+{[6h]:MVZAimsFP[a95_|8L_wu=G9/t%ZmJOkjщEK#>IR]_$vOj9=&=^+Io{iVX=a|ÿJց!yivEe[$MmEp9>+y1OkW֖>!K[K6 <Ա,WzuFc1]+N\Ŵ)3־%^{ >.ou .Z%<hII&Cns'5| [=qxgVоI>sȓv"hA-@U볷d'}ղ3\ i<D }5n 0q=gbs"*4lp8>~ 'H/| mqn4۽8isq4V2,0\:T@_OZǪxxMwJn'i^N-P-ͬQ*2*3C~~ks?W>DS|Thó}-?(W9 h&5h̏GSNzWO-a#S폆,%,;[im.26.D wX-R_MoxNz\PfEk*މR[̤)?uy$鿋<}?=i,a)(P=pʐCK,X s]1e T#GE| | i_<9&1?9b=\,0OL| WVŶ(Q÷tyV]qk71,PG"#1ѯyU[#?sd̊A^'_׆Oх}a}IԆD!lm7⥇g kZVsmq[(ѷʲ\d|1 m[ڲ>׍_^|e?8iogicDgvU@淴@]X))PýClTNz}wv>3Oiƀ"6:)l_<>ryra^qʚix-4[ [ @X\rm[Tc䥜 >W]i࿎v>~]og2\ً?N)MĬhu*_0!\?%X]DդR▓{Rkv2_&*ȯ³]ix]њF2i~b8g.W\W:?Cx5? .IcZOlsmmk:)]HZ"Uc:'y<˟j lnocZ[݋hͼ󢕗b:p\ҏOK#y5S-g߆jz߇*dk;CHm*{/vWqt6okY#WrGGѬ=C:ȳI$WM"7JF@$B7?a@w]k,/_\PNfBa$22SN][4m"YyOGi6aj7O|OXm$qov4kor HQ"e$[~:F񦍡YMv F#n_f>P Z\r~xKRc5 & $NJ+Hz㊆LGx_ 81e֟71&w<}̱jQœC '^GbVZNqj {m_0 xa{2>xnkiv[վwyKvYf8 qdx`FƚΧ_m͔vd{Wj% nz.)ݦIs?i cE&x~ {P՚;o3M$׺^oބzDŤpP c\ʹn(U03Ͼͅ][NIly2ycV-)f音4CDZ^iBwhih,J3`Uןo '~3}.|=Y,tI5tXO\Wprk3H!t=&᱆8ġAbV"Z EQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ }{FCEa72ƥ@c? .x=)<5ê ܭQ&yu+Dc$4}I~v  [hl:Xռk6kZjDjZR]JJL7V$M"s?|}J={x=m?MnĆK{m9f <V̭wFw{{gD\PUĖ$hF*BѭO`(aP=@yo ekRol8x_'nE'GDLnd~HfA;b\m](_CQJ0+埉K~&}eυ{yt4`fٴRg&}s:BLԴ;/]%b7"i5o)gdpOʢ_`z&ßn95;]nJr.k;;ҝkTF[u 1+P~&Z~K4-VmGsZ%VHتFI.|yUzRM[Hm`, ";gC*20^Oďau }7_Qy-?.\uRrHtc;G .l4ٴ_Ȳ~ P0J.qH!k ]֓8|4񯈼1lSC2OKQ4eLt G+/V >2x]_.]:[V[j|`OȦCĊ#T2`qn7>W7~1ꗟ B񵎿F4m5K9|֚9bX[|&V݁Xr1J>ЧԴѼU躨-u~y&DD 8neKj9++]IӚ7g0<;ᅺ^Z= y9a,kDc1ڹ]h>"|ii~뗺Wjz}]at]!ۺ6!ee r&:\I[i&4 ~;;PืwG,N0"V()8P${M|]ᯍ_|y VK[:]Kc9ӵ9(IPM%H'AW$WGJռu$: ΢֚^ڵf-Mpf[#0GZm>W%Yi%o-ƃohE$XYX$w|?ᨴt!mB`E ".0XltKdg鸢-z=kLz^7ЈZ(AEPEPEPEPEP&m;C1\M#w,;3$ۢ((((~T\zex0$gPXn V$xtCXxxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9ϷxS>?AMƫ9T>5弻Ϗ`cPO+zxm㹁G2V_7G_?PQ@2Qea ׅxwj-]vz.ib,{KHU+{If{x9 F*ި fzacڦ>{p?mmDb~2XşgZ-Y5V[ԯX"3f׼@'oѦRPVMڅMF"] ` Mrw6I+A4M{GZlmMJV$_7lpTl VB G~x7~ |E}Rin4ܸkigO-![+ `[g5kGsj+OP=EY_q[p;DVl.oUgW< U ZRmb}o{psqN|/3RA Hoٻ޶,RBqE}yW66|AoxLw 8$A~:b?DZ}bqis6,%%T뵁\#~7X<pڛmm +-iV"ʤAAڠE%wV9xS@qZNhX{T;eaE<q\ַ-W:ΐ:~zu+2̮orOHdRUe*FWץ@Ykk{h4})_ynnd>kyfa#2> 5!gYo]\"M U;^$Tx82z)Ꮘ: }x۽%$R$r#Uѕ5s_ \6L"k~4M<$YqYV|O%CH)`Pg?ڳ54+E41C &:]* +9Y2Ht{q0PES(((((((((((M/B:|[uGXxn((((((((((((((((((((?oȿ׬w5@EPFu}e^]vgQQ"gTH*8R[{+KυVqykY8Z&7Gƾ>|y_~ЗQXqh#}n^=Z_m韻DtO^.=VאEvl|0~:CZ~񧃼ua&vQHbyYxY0g<f{h_}F3 Ww f9+,QJ: vw}xux>5&i'Wlu&k?8En";H *:oʛ`9⼯_wxB1_qc ,ё34(̂X @eC_ >$x;gXJ}*ss6vŐhp*{f v ~pni/k^x)Z_5^^lنA4<帍Up@Dݧ4Vkk>%;KTO^hKd@%ã9GC[O|eGGWY.n# "fRAx5?+}+-"DТ5ڭܺNnMsvRq_xN"%mZ}2K6& &s´qZ4߯̓j> ^<-kM6mkIydմj/4!ƠI`"B//t LW WZUؠ=c5⦗yhzMᛝvjr]艦ܛ.cWɷ\͎EE vڂ]<[wijV=ExAf+Dw:C(~a8;JK.3᷈,n5]zNek2[M=E<9$*:V-6Fi}2ڙo-nm'{KN+͜+l DI aK6ߴ<'5bKajԏ=SO[iF *f;p4t;7/ǡ?Z'iZ13E[G|h˅LČ 㷄>,Aj dSI ݡ]:Ek+@n |x?z6+ m7(k?-Kdi!iE6/ŽJ:ɡn-l|u-26T^&u +n_ O f r='~",,5H5+ yՖ"4{N|5盛l'N\ tܻ.w lr=Eb⿅<k :}+yZKVw3H7"HwcC5>LnBgF&٭m͂o]m3,Ї`sve/I_CJj^ڏ ; *PSj/ZmKxN/[@$b!1pIkԗ.9y#jdwYU~QUr$ |?*|_]4x6+V54_g+("&MG*ѕpI|>*Ai|9ƾU[n|/my47qϤ 5RC|[Zk~}h>uVwc7$_9/ m{/($ 9񿃴+Z-u~! @I5Z$-nȥC5j0kQx^ZSFOȒ[ŦT$dQ]_+g>VASS{[[' ph0e!y?x5<3]3W]#"p-J"cC_~>/?/Fix,y b̮워{G⯂?&<)&yfեViT?1kM}}_|W[ gWly[K.,A;Bѻ̪WibK` ~4kvO\uD̹xo_'W ^[efTd8`$Iלhq(>c:E} Wf|C.Q笱b=g?&|G;MSNoMƙ%ӛ{qO$ZDZ^F`A84>:,|!ZZ$ޯiɤo'ϗ%Gwre*0gB+J˷_0m?MԴ}9kHpΫ1a_^ /si1v}>P 13 #{aWf O$`<7csjY\K><1*瘖5om[ſx71G?|3kkiӸK,6KjG 1Yk(Uv&EyWEc}?k?_!_?[4P7C?ֿ ~l@gZ+>WEc}?k?_!_?[4P7C?ֿ ~l@gZ+>WEc}?k?_!_?[4P7C?ֿ ~l@gZ+>WEc}?k?_!_?[4P7C?ֿ ~l@gZ+>WE`IgLoDa1[]pSZEmo 8UQGX (ZNd7EI$6aP-S1{o_Tꚟ|+}v˯It[J%peB JvWȮWcQƿ#ߋ? :W"^OjͬsLgE!3%¬Eʗh;k6Oqk?AajS](6AۀY*TCv5j5M.Unxxdxee,u+ź j>-bCj:^ĐI?$$h_ C;K>/l/e2l95fT_#G;^iyj- ?ݎePI^~X-6]xunP}&kmľv7m`+-'ǖڿ}']W_^/,SJE+e"]0.j6⫝ou٬JcKm٢twn.ڤ8]bћ?*|aYo'ntJk3:X궰Z!$I.Sc)ے851'`oF~ x@mD[6GyڙW#$dV xKuV[x-yG4l1یsӚ-֕T~--u+>ݬqK jϔ*F?[Z<_|5 :-͕=͓]ηJ,V2`F}\Dc8DğZܣIx*y5K"L,UU|o&Gk.{/^#WI_ԣH2 f۬sC|c!PmuM/3/7K'!wx { kڒ]Nl<+2F""2Ȯu~5X m֯rmaJuyLlԾ6A{/9~٥&yG6&N%f-"9 Qr_-@_~"x#kW:<'0SYYs}k`ג5֝+ίBឥ:!-|<3_Y]5 LDı|Λv9Eߐ-\u+IkDoa7-E;{)!FIkt>3[^{MN__ xKK ~Eՙk çYȯ=ċJ8D܀ ;HU4h֫aݥ%ȳD`1$[duF Ӵ~:%ݵu[}Э[SXع •PG76h?>-x/:Dau ZBs ]/&,ƩŶA-]O3F_eTv~#O>ty5{#yUD#xo3)Ňo k7 Kr~ŪME=W~d*~e)$zE+՛](+WzOi~Ok_kҼ'kv]j{;<¨6P/ e|/_v ?djPƲZJ֨^-_]/DvKO?I^}z,  st8cb횓X\iZˬ] +5|k;覾eEO] Mi$1yj?yw4Hޱz|uk.{ˍkR46ZutkږB Ʉ I>f7һ23jv4ĩ$49P#@ _ǃ}kO֟nޡjW4&1 JHQ&-4f_%QθY9*&vFP-SMmg]<C%k,RGG AEPѴ-öcM4}6X$0@Osآ(5Au-8JQ}G7#78j~p~ж~:kV }^\!e'6';RIk?V7w_?m?R[6rVeLEn^Ӄko5Ѭ2r۠g]RnA[m%{k r :_<9oǖoZZ[#<2S+}߉۞X" GA#Ԇo9&[p\fT;-KIiu [~ێsb11-.i>o6̰4SדxsOti_-ҵj- o얱g9vn2帛*T /Tҫ[[#'eVF2_Qմ!"V.%H#iX!F˱Tr{W5-|Q4ۦyoyożքK`nI g2]ЭuYb9bm$1Y#t u}/o Ks& ~ҵѶ eH՘c&Q%gb#+EPPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE):sTu=BIӮuK)oi+I cW7G>55-X]-wr\Eq+$ a_-Ǯ>@Olq._-F _dZ[5G }eX$*k?h 6)x'n[PWͳD \F+\ēҐ' 3kn8AkGf-灇79#~wz?ş> ju/^~"16MjKh&% P+%d }ߪiھwނ2v)VAYi~ՖG]hqc2OZ‰_iۍFÖM핽SSHPr xZƨizF6dWh^Jt 'b0x u>h Q%)G./;xOSϋ_->qxOנN۱s}ip2YT|yĺlj1.#׺Uưڂ-O@`1~ WQ(57_>(FFqӱA|%FzZǦjW_>̆Y#eI BѨpGw?╷3bY4kc^fAlռ۷qc|ɥm9MYvKM6_۝BI$7R$wcGEEU*:+Ok>˭/ln]]EqG}}ؗDہ |*ֶK u:n?EyI<3~>+gUk/'S휁.k'}bxo17ZhG,&XXړFYQqBzRC >(Uoǭ)Z((*q4(l]۱1@1[89@st\zB]Bأ˲V>ǵtQ@ Q; / o\jWMMnd>GoƘ3g95P11Q(D@B*j)v.W'/9kέo˦+o>_٦9@Ȯ/Zy0*HޟEP0*X }iP%֟EQEQEMk#O0R}ݻqfv\ךŮ!m 2C* 9~q͜|Ҥ2l$#eق$nj-JyQDJ\ 1\/|<=sm7J5LH!vTa`3Glf _Y߉?_IǨy#A`*t-s_Iǩ ~+?'<şQ~,ğz::+W?O=Gs_Y߉?_IǨ/g~$y~+?'<şQ~,ğz::+W?O=Gs_Y߉?_IǨ/g~$y~+?'<şQ~,ğz::+W?O=Gs_Y߉?_IǨ/g~$y~+?'<şQ~,ğz::+W?O=Ga* '3bt-wp^ÐqQ@-ff9UH Z/-.DsFzf\ami}nAIUM no^ռ_-kLn?k j;t% V[9P3ۨ^ߌ/|A2_ѬS=֋vQIQ dyDuGӭW[QY5~ψbfV0kx=+jqȲ o43!3"m+_o|?=TZ EwtoeL9+4f3 sTb&H-y7x7?}m%mW+y(Tq:%LxeĞs|%7|?Wk*6y` %mi}\}nx/>Pdo\4'H?jx4RE|!B@ګxYf j>%e5gvDkۛ(wn'T&5U)p$2HnM˛6ӔC$#ѭ+Z ]F\W%FP#!y 퐜5!Ӽ}ԭntff6tH\ ~hWd=GzI>Nͳ[;W>cxC ei&K[Hf-wHJm^OWm?hv~%m^Ě.1]ƚcj1/o)2%PA"*ܛwaz~g<w5ލMun}Myc6{i#,BIƿ*>[:Ij2srNdU@2pt=,7QEQEQEQEQEQEQEQEQEQEQEQEQEQEM/B:|[uGXxn(((((((((((((((((((Ş6!m{Tloiݓ.XPp8U$uvWF$YdQx >&[ I AK?MLW ZG!Yj#HN>Q|cBYHo?oȿ׬w5@EPFu}e^]vgQQ"gTH*8R[{+KυVqykY8Z&7Gƾ>|y_~ЗQXqh#}n^=Z_m韻DtO^.=VאEvl|0~:CZ~񧃼ua&vQHbyYxY0g<f{h_}F3 Ww f9+,QJ: vw}xux>5&i'Wlu&k?8En";H *:oʛ?mGjm^_x^Ӵ;i#R($00ep3_O<6]cMѡ/c1F vk|sm>*34(eu=f9ɲ32&|F7n|[6G-^8u[[2 . "4k;)[yo8.Xk~_~/_uY ʖM>+@3&`\%vjo,4mEtR9̃"Bc)?(|kok:7/' ޓg}I\S-ko AJ#1xη O jWMu}c{_mqSg&<xwAԵK [B0jla͂yVN\UmU>(xKBC^V]j6nx@@ml}T r|V][0RoA u_߃wgھ, Ƈ{ M?H cGmg*j4gXX--3`8ο!;O薾mZn=>uq$ u]fWH~ҟ_&]׶Wvqi,bT4m ՇHq~п <3?i ꕕ,/-rGY$<=ySA> ZkږWQ 7K2[50kE@>G㏃~&QҾApm 9t` eX-XFV>a(({)Q9xO 4ߊ9PK2^ܣPop^8 Y10 g~>|85o Kizv1oj~mk![Q6H<z~ l&(R+1DeRJpHZ/k~>+_5 1Ԗ0gy" 11LR6r}wQ]ϰ ׂnQ7-C;te<ypA\r=i)ǂ׍u?@O&.Iev#J5G|K~ /oUSL 'Hm_VyID2+W_ yQy-S[қ@Ѥdіxeo4. n #FDxe$VWvOY];}ox:^{v WA,MQ3?2{UCǾҼGgS:u|py89a#T>5O] P7ڎ='Ak{Z[BJЁ#,JUxgs33gmg񖻧jkwe;d1+J QՋ. |7-!>55iM6}^],[a6"\ tc[8 //_jG#_HmA.ٛn [+&ҹ׌BtKeqI)mS3.?s_DZn{$іeGHL#!*A;y1?:śO/UVKdZh`YCi#TOM֭oa$EVXX"R@tpUU ȯ-? x#ſ_jEօM__=s[_,ġyh@AێAYdesXۖ7ɺ-ظF(OZaj.bq]Ȼ2Ch+^<_]SBҼ9SkBuh!-]6Iy TgIlↃ∼=᩷~ck4HMR[¨$X.?J.0?C>m.}}}cڽqC/,{U4{{xn羂(.bUGgbpxX[}YJ) [J%p!y{w1<n`ob^Ok6rk]jpHFZ&K@^Mv)C2˒'tK;x/. x``"18b{Sz~1]Err':eo*yɩŬ隔t}'OtVz~iڮam:G /KC;>NtZ*_SY+mtk(Ay%b;|yKqWn'eO}sygfGu:xCѮӮ|$̈́Z2˥ynWR>U9W/CIoO5-&5mmм#d3&ލۛP`s%2|/̄Q ";o)]GIK NQy f!HPŦ_L].stً񋶌"<!Kq@kt7H)plJ)) qAʫ /ǺՍIJ-Me[pdĊG WaKLf?2x'eU:0`ɐJSo.?k>zȲִmJOEsR7TG 0֩}-nHy}ة5g]OrLyqآ7pt_ uIm1{fӴ^imdIHV6Cv f>ծWhoot];QeSJ{bjhm|)v]^o|%?xoYK;+K /@6g-K@JE뗟? +˗?{i:WFU65ʙ2D8aZmԷ$BJToL_z?kuix{\OT$O.#Q1_F|e+xOV 4FR[si/bs mY ͨ*I?4{WW_$xs/3PW}cKmo|0е"" Mk=XSM!<{wcz.7 =iut1T[r0852v\A^\{g5F6X1&vPrq83'_ E-JYNc(%d[&Dean˷ wƟ5ŚCx;Ztѧa!Kvsw-Z?~mG~-B[H+G]OѼ:Qh#Uo,mO1m ;xgxG>+_NdVN.UY]*Gt7㏌|i0мqM6Ķwz徝m4BiDF`K{y[ȌB]9oKyN& ` 2p2Iz?^!>Le{X7cIЦ7n.#܁繱$n%'ԑ!HD>g'99vmSj|ϭi84dFWWCEQ@(((((((((((((((((((((((gm3'X|;eOV)tkt[(.2E&DlGv&0v*)ܟZ ]'N/O4$$jY'_\7~>>atuwqwFL$nѿ>h>;-:oAlqYNnb$ow\[<1۴\>umvڞ& [kH.m8g X$1 .X^mGm4>^>4,k-`P$kԎ5)"ʰ"HUAUwJ_Ѽ=" nlSMYmBE_6$[w[(!sUnD~>4OiW>^j &o({_xKڗZ5됥[' 0HܧE+^Mu mY:C2]xc|lӝ277w>Y>𶭷S+co֤]ArbP*yYhonϬ4|>ɠBSS{KmZ.-16 e*p2* |2i|xvCѿ-57-K)6u?5ůK4(`%"DSO?d+@mB2h-m^W|Y^?oMVljVp,L(&V H$ua=}ZxV%5閦d&=͓ ^)/xV<''mع{zgt[Ԋ<‰mcSn*YmA 0Y{HcNv;Mé7T??3q}>ҵ48l`Kq1Ȍa@p=dZ)k&|Am&<7,[i>olv.9>ckcv ^ 5_>̆Y#eI BѨpGw?╷3bYt[c>fAlռ۷qcjjɮ8sYOCw.7-XFm`J>eܸ8b3i~i0i 2K,ƱK;eٝ۫1$k;Ok>"˭/ln]]EqG}}ؗDہ <^tMY<8;|߲ygolukߥ_mxoÚnt;֖p!kFTSfCь_'­Os6c6V}[o9o% OZ9IXouַ=剰(V0aхAÔl)`jU'OE9mbn$t8*vp@#X:ÿxLtNT[n;Ef]HyT;e h3>->idlq-"4p bc$ *5uOx_J*ͧYAhҪĊXĀzdwTQ0kIOImnn]X2IvPFget P~m >wBo?#ocۏjvsŐ:d$ "䳅c y0wkm{:+ KH]aiV!g2;9a19&(o<(xO lp]ƒceVH7!5ݡЬ q1-YDcvA#@frsGg㿆>֍u]Ci-#8#i^O[ذܬ6v d']hz4_h>s{goJ$㏘+ШwGr5QrPagw9旊WsӾJҮ=+Ze}n,-a8&P!5@!@fZo,ex_K mBk(dG9 ܝn2q]pc~"|!c0RS+[sl%V&@W_+N@l4"%kZTb-deRpNp+s><aHUy ?{WRĺ&nqosz%-0Ƴ*.䟦Iɹsu&r|=Hu]3Ěz5qAK1$y/;D?:w?Cc:Ι,L7NI o*lytdz(Ri'iٶ|y'xJ oxxal$[kyLe<MbIgĭ]ѥ՛\}+LmBF2;E-&WO䳒JH?@dQ[n:XA5o־,GѴiOi/,ft$eI3R}~ xz@T֚6FUnI,B4Tw (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*G7OX ?t_P?}b(/¯Q@t_7OX EQ/?}b*((((((((((((((((((((((((((j{[}O[1<rn<N}kha[_zo#eOA?¶sJ_iQGh \5i.VX;n[$wu$Zk})k~(w_^3sH|4Эtdž[+;m,^Q/'ʆ94Z40K* DFLyy8\kbz\G|?@_G#eO4+IE⸚̳C:Yu/-޴bէEK)qϼYS|=Ox?[yԢ;q(ρ1q:钿72G]?-PKtӠKQs+"ġw(0qc<_Lj=.Dv1E&9r:k$ԓԐ 5U'[~E}Vn~d}9VCh?BA<kzv^g}!U[PIRK@9ן ofOfVgǁoLrn*@=rTiB.Kit곂z쯫.xg٠K >_Wcj)k3ɶxl$k!wOWE+8”-!HA1}ź!a2҇9  QfW/cu'V3>_Qh?B[Z4[1CJ-<#hqqW;COI?4]u*B[ؑq8$`8ʛ m˞UENqimvy_٠K >_Wxw&/kXqq-eefLpG\+IUY@n Kog_ϊm%ἱCtel8e'utn%yj.ϯE!h?BA<_ |{꺖eo4m.<n/`FA w WL?gߋȿK1e |yrp|єrDr'%h?BA<GO&hrmL35.$0Cyj þvY6 q6d vCL7\ch)r_̈ȰHp!卻9M8溟^M[%MZX]R|2: =*P7UJ;;kb)e8i}4Jfy/(4!^'Msĺí"9"-L譵H7=A/M17U&Brmn%uyaxrd%եeȌ5(k^Ui5S1`ߚMyO٠K >_W/@>':#4[БXvXˉ `TZ#R4-]-BFdo6E*2zsW, nlo!eP}俐%{ŧObQ ι\$ $ +edB.wNௌ?jw4{-4ۄ-{5/2C퍘VwmQiӯw4!G٠K 3 ZYhMM%% "HK"pA@ז:9xOռ9o;_izmjv- Ŕ!g Ǹy%ďF{L_'Y_ ]|D^Ugcw#KVS ]ux# x';s0Uiz+5:޻ݭ>^iVcxWM3㿇9[HJCgdV69>"ԫeA`gWĚ&ݵn6hfc`%Y< ڧ&DL1G`U];n>ָݚ ~Gزoĺ_5 VidHF^'Ϋ*)nv&=~Q?y`>_WN!m?cs7ϭu S,Vmt bO,\61 \KvYS|`v kK"cI%+7,.'iz(L1X4yRk. Iɻ]~ٖ>h޽;uI$0[Y׍,yl<9'tc[g1UC܈ˌO~_Q?b=ײ^}Žq|ZAec7#"ui-&ɯ< {_ IfKʰ2q Uԁ\`>_V\%QE|[?qYzܼ#/ o׼C‹$rfL-47QX@k^ Լۣw#my|Ƿmm?b=W>9R5koߛoC[7u{;I?X#WVVvf]Ϩ/;Q$oXGgu ܨCJnws 2[8zG&Y\ 3L!I'q9h&7chad,mmod(bTRCD gTRCD aabgP aaggP descDisplaymluc" hrHRkoKR nbNOidhuHUcsCZdaDKukUA2arNitITbroROvesESvheILnlNLfiFIzhTW viVNskSKzhCN ruRU$frFRms.caES@thTH XesXLvdeDEdenUStptBRplPLelGR"svSEtrTRjaJPptPTLCD u boji LCDFarge-LCDLCD WarnaSznes LCDBarevn LCDLCD-farveskrm>;L>@>289 LCD LCD EDHF)LCD coloriLCD color LCD Kleuren-LCDVri-LCD_ir LCDLCD MuFarebn LCD&25B=>9 -48A?;59LCD couleurWarna LCDLCD en colorLCD *5Farb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCD000 LCDLCD a CorestextCopyright Apple Inc., 2016XYZ RXYZ e< XYZ jXYZ &[,curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y vcgtVEA8 0Y  a l5k6@ !# $9%Y&k'm(\)B**+,-k.=/ /012Q3!3456Q7789x:A; ;<=d>,>?@|ABB BCDEFG~HaI?JJKLMNOPRSTUVVWYZ [\ ]]^_`abcdeyfggThBi4j*k!lmnnopqrstuvxyez|}&~:I[u{4㆏P% (>NWZZXVTQNKGB>93-*0KI%1;DMW_hpw~ſ¯fŖwf]WQKGHTqР /CIE8%ޣ߃gUNMOOJ94zHGx^OMjDVE1 6 h *wy25X !"#$%&'(r)>**+,j-5-./0W1123v4=5567O889:[;;<=\>>?@[A&ABCDEeFDMYlѶ޷+;@7!õħśƐDžzrtˉ̼}?Tfրץ-Lpߗ905&V'z; DV.^) % AZ%Ejx B !"K"#$'$%^%&'0'(g))*H*+,(,-c../>?4?@zAABOBChCDEPF FG|H4HIJ\KKLMSNNOP;PQRkS'STU]VVWXHXYZl[$[\]N]^_%_`A`axb"bcdheGf7g9hIiajmkal?mmnomp5pqrsPttuvdw&wxyEyzl{{|}~Ào[E*ԉZ$͎~[:֕Ɯ؝ 3H\o~a7 2P{9pɿG¹Sǖ3̍`IABlndin6XU?& P T9&f : '0:DOZfs1G_w(Ls8vG3:a7   4 =8HtDTO_=}^ v4 r!5!"#$h%<&&'()b*A+&,,-./01{2123454567y8d9L:0;<=.>@+ABCEFGHI$J4KNLqMNOQ#RWSTVWDXY[\b]^`abbcdeghKijl?A3BdCDEFGHIJLM*NVOPQS$T_UVX"YjZ\]W^_`abcdieRfHgYhik lbmopzqsBtvwpxz!{v|~5 {az0ݒv4򙱛u:2'h=9εaQLŸb:!vЊ Ӣ_MlۧP&I2 !+6CP^m} &@]{ 3aeYC=L s  V  q1uYIJQJ7)5_ytbTN M!M"L#M$R%_&s'()*, -7.f/023d457P8:?@BKCDEFGHIKKLN.OQ.RTCUWmY Z\V^` b_d[eg;hikRlnMoq[rt{vwy{}UVfG˔f6͟f8էuȬG/FY׿Yĥ4ǻEx/ב'ڝEފU1k0DWadP"9Wr@S@x!Y1d1esf32 B&lmmod˸" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC  C L ?(HAXikV^^_%.H\.-p;zK.;)^1!wif$d((j'F>%"Cnlځt13mdq ˚SXk/x|siښ4VrE+*mSvL/6_f?E|A*oyxǚ63Iqvb[{cnH'֩msoMbe>!֬Ж񎵫kR^CKyuK+O*"l>0xO]nG}߅m3o ՛;J_ͱ c@6^mkMϻ̠(#i>,W}K/ak&OxC%;b*ҹ¾uOht_ޯkl88˗ͯNW~!4m7o >k}&.ou84uhHU.63 [;|W㿋颾 mi?MxƗ:O04c|U |vf{iokzǧjzkoʷ\Ȇ8\@M[wZ7Տj+|w3k<h)j.-2l0T /Oڣ⦣?}\6>EZ3|$ w&0@/{>L9{*==t?fhnm[M;=CGm1Ki>gM;EnpT\sx; ŷC6ncedT*&W6Tn-6O?J?࠺ |=5czĚwZfֶ cI2b1Xrv㟰> W7|3~&vkK=a$>LyI Hрa`gQ2DQEQEW~"<%?xľM42Y!;dE# ̭cWnEx׉|1s9Cڽ]Wztr$pɀNߊ_kM[?k>C^5~8D~j1 m{HXQm+_/t$r|oK5-#n,"XUrpIjGr DB\Ќi|+P~#ľ/k Pjun"ybQxDfx?x7~~x[jp5+=5mH@_kR @V鹧-ZkT 7Y9,q f1I݉݋ZowkO^:usC=:KjS㵐Z,^|-S09+I$'']ޖ>8k#㟌<sCj驫Tѝce[uQ3Xzgz%rT((Iuu5p;E%w7eSWo<94jZZW#zrDPi3E#.#r((˗G?e(om?O~'-'42% X  V,d|g$@|<_ ._[)UӁFI$Xdbi~HP])/rW3k袊(-Oↅº_|_^iziG"Ưr Z?oiKhjt+]txv}E;d[6tsZ#4DzmiG߇'{'Wo"ňWTƗ70gxTy3Ԯ ,WмH>!ɦj[G%S}8ph#GQ~0.𦬓Y[i #01z03ȪZo^tg}~tt^ >=/X[^m{hP IQ·EA |7Q>*|)Sռ oI5{dI3m-1!y)_妿5k?ݏ p|%xᴺvYH֬m!<(0B`N WgcpZOhͭM}Yiv0*Ybdpchb`Ѿ)>WZj۟w_߉^.?D%e6h\XHv6,ϗ=z/ C:Ņ,?vK-36DnIy]?;ߠ^~9i?|^ _/>0կ寢j1JQnaC@ą 9 q_4vmG2K@) ((ψ6>=Oe=p_BʋӶ}Ȩ5=PQ]NΊj_ZC_&^{}6/tZZomۂ%E!bo=-?̆EO5oaAs;.TW殁R~Ѽ_f-^> ki.^KՂ)B.nK+gĭZ_x/ .1:$^ 7qXq$kLIFQ9隭FԞ_k}\eKS +[o[ݧ u TmC}iS+m3pw@ČϿ=+X:UD^Uխ&),B9}p_oϡO#Z((((((Ocj;b2S\Pk?_2ݏwK𭎏56q\I[۠]bzћ˻ل%$km̪0]Fg8Oh@4Vw??/' Ɖ>:-(К1>a8~/c>fyxoQa{L2@?5̅ ]??/Ҳv{ok~H;om꺪xZkO~5jrhU!3**Yf` & <c/xw^mTS7^V"ne[|BÎ|?OhOz6]>Zֿcj0hh%/m+LGUԿn+gPU] z  xq}4~ycvw2p +/ƫ_{%k?o_qEgk?__I?H+;_I?Oh?eov aq/CPԯe3C湒9BN@b]3z9lj m{ ͅoSZcL#?Tk?__I?+~䕒R>]uSKe{>[׉릅X$I,*${/9?*꺠MM"$ژɑ=__I?OjԚ/Ѣ/ƤfƏ}'`4Egk?__I?hYOh@4Vw??/Ə}'`4^͟ skKuU]V 3F#/m# 3#{w??/j-? I5Sosᶗas#fд VVqu^\Is$S3[2+,lIß/ Y]zy)Y :i^VmQrS ?Ok?__I?TSؖ5/\>x;O[? IcQKJĭ$eu!Ar_x_fw6>z.LH5.rk/Ɵ3}_]/}Ѣ/Ƥfy/>G..uk=bh,Z5s"H d »}'`4k?_m}}>,?euj[7u&b8=VxU ~.aKΥ P\Gm$~QXgR> ??/3πso^4ԞյJ[f}/[RE MJ-yXtpsW[ö/n?=T5;=GeMQ%'ƪqlsk?__I?8_Kel'?<7yiui=ɹ&bW@A$|G(Pyv!'l~\=({VүX2:tQY#gEbPw6~G?/s5w|k^u{xllϴڛoO1Sjdt', $>x6ՆjQL@i5"Qsܫ2M}Ə}'`4Q[+~eRnϕg;+ tNR՛uͻfBHAso;MB{ff.K+I=(ok?__I?8Gm)jhYOhH +;_I?Oh-⇁5 x4zgٕ&T~7Tj>_÷w]j:V:\Ķ<>D #F2 8\G?~wV֩i} w~Ï_f 4#vHY69+\kBk<3qs] {$A{"ңFYsq&G?\|#_/mY5;x~Nӵ;觲 {yp$ a|2U@oCcN*ԭ4[KLm=RKYZۀ'I$@w*?/Ɔ[_v>}k4cXе/g}Q H3B8wsvAu,7Uc:}ƙy2kq&ot򛵺hBؑ#FP@UPRk?__I?9;]6n/_?OixZ?xN,t/K;K2E+D`4{=I'KX,ma]ۢƋp0O<U/Ɵ3ԛ-<4Vw??/Egk?__I?h׉cOC KA3;is[VQʺ( ׭k?__I?&g֟_h#ooO\G&uygnЕIJ68| jgu?GI6wow!C"G?}}>VKKBZm|(*YO7Kcm|ki$QE18vV| }곿/ƪSoq(Egk?__I?H+;_I?OhMKMt#Tnx'RHR;RA<7|8<'6 _OJ%ҭ.\ؒ;m]k?__I?8>eo`z?'03Kĺtݳy/d$g;w}o=힃iq%[9{mK?> ??/~9>m{>Lثx|M8#"=%oaMPH$iM1F_pRBkßo4~>yn,+Թ{щ6Hb.p,?/Ɣ=[!IhYOh@4Vw??/>'>xw=ޣ[xPӵK!mnL lpI1B@|k5Xj;=]"xxУ,Z)_I?Oh}ַrwwgD~i#NR.,K{%3xH9M{߄:/ <+j2]Oqk7ؒvmeV$rrƽ_I?Oikۯ"Z4h}'`4k?_3F_3 \ūo8dE_K+F^Wk=r8EG?>k~ o &j:ˣyjLV\#:~WVR(տb+֫sm\dMRRn&s، 񸖯/ƒK_?W3=韲7>6񑲞o鋤_-)$er]\j~^š5xX/tȴ{ht(YU-,[I ??/Ӓkobw]ck~ <w zMV`Y6J\y|x>%߈/ ?/oootد K:3\E3}X OhR_օIo{7Ϝ~~ʞ e7TC.Iu6OҬ"XII'澟}'`4k?_񪻵lѢ/Ɛ4Vw??/?>όZ+Y$ u`/+#{Puj?5T5/h##"xmAɐ,N6+}'`4k?_N~}[Tyi)n Qao=I 220!%U7_A'">,|C 7M?t+[>yi^5*4bP6k}'`4k?_ ܽ?/}&~_y??f[Ě׆ot:$EFEr$ T2T0ArYtl`,mI%o߼V_I?OiYYW]?C ~!k:}vu[nXDȑ%C<{c.TV )oدGxZ2Eߣ-gT3f9(ŋ??/6ozJE#Ͼ|*ai"kڿu bWnQB,qƊ3D^漂/0"XG]k%Qqp>7fxߖG?/kgQ~g~k6+5='V(;[eWa""*|R" y!x6÷  jO<ϿIBU"Pr9-}'`4k?_W/m|~tmZNoZ\xWWm/㽄^UY>χ@o9o ~'Eyk巆b5wcY%7Nk}'`4k?_~_]_7K x^%M.y{ BYd$q7p>n/ƝsFG?C4h}'`4k?_ ߌ <=|>]w= c"D󭼂Elʥ 0AG?,4]j%|Ix~:{M{]FQ4W[ l*I91{0mZXWskZn}x|M-okp1{d1ɭ3U/,5=G^׼]% M/PJ(bP!719bxǯk?__I?W;w>Ud44h}'`4k?_(Ѣ/ƀ4h}'`4k?_ +;_I?OhFWDY#`A ,.&W%`e9?ٚoC~+^`UjϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kzϤ?7}!kz{ >3-*Is/ɁodIu_ٚoC~(X~ hjΜ]"}lrՀ##__ E۟^+ӯAfҤP#L?3Gj+@U[[ukf>ߵ ?4UMsZ 8V $E kfu#ÚU޻çi1\:Qgv =MMծRZؗ3MHٚoC~+OO|H|ZkҘeQ @m`9Pr8Ω&QGf>ߵ E (fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_3MH^(fi_T4@U@pՏZ>(X>ZJ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@xDjn ,R pkm@G}-mlq=*i[jSsC!90h55߄&Ew"|!Xav:KxFY KmegԸK#|&`խmt˫JWWʋ8D>dv'rJC5eʶ~P7[%?ߪcd־$O&.[Zu,8%O#I =#*6hRaF-[w% *FQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQUnPi^Vf :y `P+?Z.=Gڵ/\vz4(j_jԿs}Тթ"}REoǨBV@QK?ݿ +?Z.=Gڵ/\vz4(j_y4x4AE 2:@Ey- /2i7X@V+ Xom\I , (QEX>ZJ@~^{ĺ:Z]zT*p9;Yl5-F&FO& 5im$O4EU[-6ٯ5 m B` i<xx\_hֺXYS59VY#c X;KZ/9į6684}?K'VٖXcdw(?,cget}YZ W5ŽqOvYa+Qw0E< 89G_ڷ X9M/ݓib{~L8pr>*ޓxRn[{=VN ,<8G 1w:Z_(ƄEMo{~?H(4ϊ_|sI\pVgw14vFhYgL]wE}SZ Y )ѕRkaETQHN8 eiG`3I=mm⼳'tY#6 28 Ak?|WƷf{(v-HX"giݣ9`<7~kVMy|SkuZ'#XDon,c Leu&[Rvݵ] ӵK_˭ʏZz VSҮfV i`da^|BxW6IqM=%ѢcE孟%C9l֊ i?)_s_Y[\[\\Ge¼sS_v|GxN,/Ym:)kE@Tr?T BN;֮u VmMn<%jum./5KIRbi6]1H(]R|\{84|h@<|c?u뿆|Sq{n` {ꚬ6 ,pIѹĀCR x{R־.$3ivFX{#/(5+C}\ַWo)˖ޮK;-BqaqA28uuʒ2#j|Mw4x[:Piv/#7][x<#s#1,XH?x':mX߬ZMa2MqJN0W$`L=)EKI8>>%?'d?Eu8Ib)}MV)J,Q|E.\<77!Yy <$)pNSKKZ\EQEQ_?j~7u>5;Ki[6&3n<Ӝ]xL6W׷W\bF*R~ͤ}QM<6$X'x4Mg⎱?0hS޼;RmDb,.Ç/y;TqSx oRP!FXmr)hcE$a1QYivֲ]R8mxӃMd}{5((+~;x>.4Q8itf#4/1Yw>v}vߠ==9#[QHK#"%TqHUߊ39l|]U|^jɦۥSؾ$p H` ]~w#U)[n<+4V!ؠ=]^×q9#~*Ri[ ?z7d/o/`.aIm}sp)zzߊ|RTcTӣp[ZLgqm#@;VZkYV>*FQEQEQEQEUm%n#KI,4QԳ1s\?r%k针W|o!m.70=?kٟ67߆:f瀼>E$]2dCg=i\9?AYEo?W͝S(#A; T|df{av4}]HgR~+o#u]XxK xQeYEBHjo42 $谱8 *[oR"RII?ItinnkdZVU,ʀ gW+{6?KkxA| 3=iA+ci9]f㯁O Ğ"ƍ/j#{H4hbdYd(fɰj&J&m:m4ܿZž'tƹMKTk4I'f$>6nx/>;Ծ.?/i&εuokgi |~*8 ߵ~=Ʋh <cҵZ!ͯ3%$ogf?xd}TԖi`ݰg޶k+\y+ᆡO>4-Unnd୽meי %>m=[LoS>"h |?OXY90vߖ`QREPE_/ǯxKþus18moJCrA[:N۽|ٕjUxdHcZHAFFGlFk ޻?5SR'›lVutPVk}ǝ6f6{vm<6Ku\@=zf4Wܯ{lp]IEFM{i׽OfKɒb`ԓbJ5~Yjx淵X[B]pNs-OtTŬYMg6>Q6Ml98FQ׽M9^/G岻YzLQ_?x+/|O"6Ǿ:Q2F|,qB q_@כʄdN^M3 dz;?^ (qj:|E%ݪK #Y3 G88nu=BeG8T0YIχ>0O%h?&=_M{yk_\RX+Z%8ɒfT5߯E_VKGuWvrld70~$i zdWᾓ}O⽒FեċOchZ9 vwȬr*+7<}O:NB-[Q Ei8O#qEF!|tQթik|'K8>Nhu~Q_~(']ּy!ߍQ&YE`nEh,ϨV-R܁@goƞ{d4RM3,KyYTSŻWFrVL(aMfTRBx ue?_urBR싧i$(fV]3SʐB󵋒H3j >+u/k* ;t_U{ D*+ݴIq+Icdo}D6ok(k\qڜl)*UZ\pʿ7nvhG%n}E~{h>5g񞝭\6,-6澵$c+y$]*k+|_?4 Oɨi+%տҎi?=o]gͭƽVtiF<=FU?dVp3>W`~%|N? x_iiZSaIl9"R 0(NAm)kn^?ȿ}e5F6XxęYG*냊?_t# >lRFYDD-Cʈ́AOiX~G%V'YXG;9͒+T)8,Rqm>& #~:_?|w-Cfo,%oZ *RFbby98]5=|GK]cVQ8aHa/  z~?ooz(QEQEQE>ZC&(^EY$T'-aZMhZ~;NkMNO+VҡKYD/Ι(<$1ύ1>>‰MY5w)+YnV(V4"kDE|d:>1f]ܼLr`7k`pyX -ЮE=7TGltyX mInpV ?4/ߋ/^ijC 8u[ItE m{]~6?umq~GP@օ_\iֺ]ա(Vjv`ie- Z$Ba}^o%Ƒ{ )$4,$BH  )uko%V PHzż"*am(kcO.fuܦ--*^dc)@At׆! \\c+^72vifF,,ܞ'ѿLk8_zo>[18nڲ˶™R *~k~%^#[iַ_hH%3D]avv19Y2zgZ<DZdMQRPQEQEV}'+>BZG|@EWßCVٽ|E/*Ѿ%xĞ"[si/ |?-REtq[JWB 1BqS_ߌ5Dž5/AՕNYohOJlV,m؋M6ɯ?󱚕̿Z?)arm}C_8x7E;IFL>FM#$ ^$2AWgMbkk֪tsPnpT,xm}C4+D-Rw <"n^)kZ>(X>ZJ@kePҭ 4Oj~hi:C $jY!Uum|9 6Y.3$I4L<,BY*:(Ŀ >xSZtsP#_XAs2s6ϙ\k:=Fޟ0[K.jC F*1^E47}# ̍YMBPmDU^fSxJ&u̷RK,-.I<eܲ$ryhC⺪)thӹE^[X ;v*|nJ8VSQE (8o_,xWJ[HI,'w|^"CZGjV`yx$L#LNF6[8 W @־B]M1M@<(W#4xOԬyRXh$]HcEVl@8U? ]&gx&]S/I5&kn-!Ht-62GjFGv7o`x5Cχs*́th[e„raЁzMJ8=o/ ii𖑥q ka15ͻbL#c*@ o\xWbn{9<7WAo\[e+~T{WbRžt{;KEIa9^'ʠ:P$pFji:AXۂ"a0I$* 2I<h _ ^e۝$o}9@WX0V @;LӴ4Xc,V(cKf!T2ē$֮@Q@Q@Rm7N}B=YbkhreX؂ ][ 2[sfmxmċ Ezptqa, GVfvcnwiQV˹>=( ( <=&}:e\͵. i 7G e8 )44q qhvҡӡKdBlq@Ds2H+]HMFMa,`[Kg(&|n(BX95ESwKCо2:%iQk*R-l n""dž/xVxw\I,zuV#"U @ں) ( ( ( ( (3}IsPjZ}綹fT=UpUUo^ko冴&"& cpWexGA[4Pgiq\$56TNS/+__$ ПT^=-rLdyyki=w8[o <%AysIb eXaсVR8 t߄ tm!?7F帎bMD mb2"K((acsqowqohdt 3V(eIRAPqҭQM6WMF]}Y' w"q}9;=M:]#IoRk($]EvHvĹ8+Fi.]r'nZ>')nF<ܧ㍣jtM[O1]m1&>xY<5ES7kɒl#🃗wֳyzڞ,iby[mg$y #`WlWFE+ImAoQ_KL8+ćs%D9o<3GYA"n+tΊpc LH4M`TE1(NeI exk_^~ΙjG7SXm$%xϓ^Nf(LS3Gŵdv/nm:PbXT!Y" GܮT+1[ i o2KMq*EfVBiHBLI&PO ( ( ( (8[ýV.|/:ϳYA-^Yܛ&t.Iy? k:LtMkL= q9Rc28t4/ \xBHfeY`}v3ӊu//tGoEVddWT0@R>ljM;˲KZV֯kj|AifKvC 8ZJ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@͢_/=\ѯgeX\?x+GB+O!e㛽Uh^8H,.fR7 *)`e?Wדj_|5_eM|G$bu:,ʅ˕P/CO޷oߩqo}iO_ xe<)R귚% ,`Y[$R[ uσwķM.y9,GtG޻KL9\WU7Ώ:? %Ƶ_];Eb;{ĬY r>j|^%Ŭ7 {bcq IĶ7=]zCx9t:zk-Ug[o^ŷ%~mg51|̦`m.0@<=f}6KDqZnsU|vߗx ?xROa[Mb2]=cFVV1c 3]K0-Lxjŗ>l[sme5q*,!njܕߧ%KK?>5kYAaĶ:^?eO/أD0ῼ9Y?2떶Z6/ yJ9b[5mf-riT,aD>Z9iO V/mW6Wv]31 gE c!A"6kY=K'oWne]IOkqIs0Q$p(g#q'Z4QDؒQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEp)it [:@jNBi(Z+~8~oU4rz3̪C2CAޗdm/~#KwWj^x○mY m2PeM+cK@*QR~^ 4Jž.85$lQn짞;a5C4I*,Ypy &5O%O.4}nNӗF/UVk?-[mG'Ҍi:_gaEsrk SS`@ْCFN(kWշ?SZ+9'A%>} գo-n,;I'~`"jk]ѣӵh{,:1GSBCeRZ'TQEHQ@Q@Q@Q@Q@}M6}"㢻b_3[H oIb(/--T"by?zok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/?ƺj(G?p騠gOMzok9=7~4i_񮚊/$ݿ_񮦙'ݠ.=;i_ƻАG 5OkoE5{ ӯ2⟅AmFmPԴFQ岿q$*H 8*2\zmecwtOZmH,?:inw0@`5On|LzޭgkgcbP/H~)kKD@>XWTU)Yl^_O_qg_\W4[ZIa,T:?h :N'Ȭ+OŤ>W4RPEzeNPx!GH\>rh"KKR{_>-tmWDvGu]44|źƇAiڅլW2c=2* %Irzw/ZZG{wX BUwmr#%P:qovQk+w\3]iBd:e{ yy`RG'K#ux/%6s/ڽag{P>祵w~J{[ߗ^mt8xpxz-$4F/!XgԮ]8 u2PgoxfdROwd{,E*|q_k? >$><ӼUͦ=́'X3ò3{U)eԼCtjڐAF[@]C#Lje{q6&$nE-[/ݭRKoz|z?t[yo5 Z[ݮahybyU129U2"|Hy'#l~(қ_N10(m9k_xºƑ<#ӼyxW—NF}sr%ry) TZԅ,[_쯵~֦#p NMKxZ`l]$;}ͤW'}SLW~GHyq9|o*]~Ar@׉#)p01PHsOJ-|pѬخ]Yk)%C(?u-+O~ 9F.źIZOq~V[#ً+]n B4oZoO[{d>d6݌屵Ab ||YR"jXYWn]q5Z׋~Ϣx~W#ޑQSʯlN02H| {gѼ74pZx6X\ik&c%+n4"a愻ccby''5\Gi uh-4ۥȬ92e@$U/jxþ'MR(R-o``+$(ܰF 1 "uqm5V0A+a6K0v)n*%WIs3|7m^\kCThEI1gan8iʖ__ETQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQES$dv9MS a^5OkoE5{ ӯ' Piiq,u-7TثZ[߱9| x|M=į!\ZLd#_} JmmQ׷s~2j\6dmN&P 5 orF$6/owu$Ȗ gi6`? ;-C_MOҬW5Ũ[bc 0A 6i$xG>𥾡j'X֯%K4 \,lIV*ƨVi8Ww@}6Oɳ=S/6 RRP,my_i¦x.񷎯<-hI&w,$2ܴH=_O8}-OĞ LfOKZFchyf}==|d[\^N!]v3$kjn#"c):a[:E=ֺ@E`C0 ڏ?h+h- /m1ֲฎx$IhpјZ3ëWZOٚY_iϊ=pwj"tǴd{ryM"8rqy^ъrO宝w,AӼOy]ZΡK T^KD|5CmQt6 )bEtaٔZQ3 rl(Ȱ((((zA*aoǤ/(j(?(.-0]ēFJ*C) jz(G< :~eZeincXETPU(1m7-^_`.lqJp5EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQES$dv9MS a^5OkoE5{ Ӣ߉􏅾:giejo/.Ao P]݀IMiE|ύZ>'O |\{{ KƿO]|r C4ae(W%\DًP-Ϻ!.7ȏ2.X7fy~d~'}Eqx?^h4`PHČ085CG-^#%}B]> F$`C1&b7F)-oy~l=>->CFvV,J pA'Ky;|?|1/jmµ]Cb΅rKV~gn{sO\^'ߊ4 J/m![Xp *0`CҾ)1l>k:Gy5o4v#mEȌpIR[? ]Z_J%o/ن2&\dd}:O|=?.xųB_ۍ>YIp>YN<ƕ=5?? ] OF"Fձ;xnr2r5bjtދ k@^~*wCAwOYldzLw۴XFd#}dqO;VgA|e<5*_ ~ɨ]K{jq8 0ve@י|Yּ?}:VJY{MFk`e_uu6ያk}w 2}!f,ue)-ͷZ3I7kgzZVۙ7^0O-ExdufU嵼xI\. Yy$0\Z߆ǂ>^0ir5bjtދ k@^k_ &#d'എTP!I O\}hm&9ČQ|6*e{?G-]o?|='u NM`}{k5{biO<kAIicxwĺfkN֒CZ~%4s\^C\Q_|!yX&c\'E5-{;}]1, p{ r! %oWkۙ~koǷį ̗V2_yn=2@־ i(ﵡLl`[-rA`G6:oþ*5iQJY3y3 +<1:Wi7mX|MCS&XcckAK!DKo mJē^oCv2$x |~\N [w %1yh!iS(%EPxx˙s[^(͵ӷ[hv_x.tjJ*++nd$mcf1T6A~ x^8UҗEyonVH4hb2uu$a 3}ZI-,-VQE%Q@Q@Wsx= qw0@QE((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5bjtދ k@EC߆^xiQ\|gN~dxPe0Kq]>뫥lW9nX՛uk((((?ʻ~ ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5bjtދ k@^GT} WE&~]{[̍J/ծ8|QfMuӵn-ַ7GnB"CUoxþ'׉4)w0iwˀޯm ~Uw_o~Ch:_O<|esZLӵ;D-Ls5ݼ. 2 &6(>*O||Jҵk|9smsO7/ L7k6T )2;XQb Y,[i]dxtV# ڥor1{._Gy4nw}OMoo|B4=SYiѯ&CzmM=Qpn }̛ 0^(&շ|=m'ѵ{]:tau*]­ PA >f\HeW;|ӳ~&ZC~Z𯌚FxTeqi+K6`i"ou]́9"F=CžaxoX.k̇Te.fb2  HP w.Z%B,?~2Sr5bjtދ k@\??^e?|Ά- { <9 dq^GT} W E=Lb@[mpI+;zo׶机_oIN q?̧Mi/>?.L>W':6=G5o xgʗ6-ְivi3pC !',... d( d@ P<^E߈[&/f[PS) ޼I٤_B 89>޹x)YЮMĚ鷠WQl$w.Wg ~7>m]񅾋Qom9(ZJ>%`Q] A]༼ԯIk/6 }Jsۤc@RM&M7$S2-^xc?+T]ﴻ7N.M YDKr( B:7$l-gi:ՖŧjF-Z@d;2\,H%b,F'ӊh=܆/r(QEQEx= qw0\7Ws5Q@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5bjtދ k@E/^Z%XX4ghi*@88OZNIj)RvKsr

    /{ѹvyq.vF0-S)X^*i=utLh31 \U.E ( ( ( (8Ǥ/+zA*a&(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5bjt}tkh)b$DYYYVR0Au#ކV(z&MYu-kkMI5H#pMuQ oO/ msb*pe\n Dhi# 5\ho94}-ƢI5m#[6=kM&j:'^Ow̶-Ο?cY -} oO/ mq~*'ky'KM.$'m.7!Vh #*A6X{{ʢo[߫v|kjW (7k'/t5c¾j%mrA7w_r6YWV)Y㥬Fџ$xź⶷.tvAi($H_BcD^e%:K8Pp$^@ϭvG‚A>3 ?cm)m)EP[nwi_/W%:3I5Jv:iW'LJd#/$N7O 2[G;-Ec@.H0s_~#HVZmFE]khV.l8_u{-r5GNG=~tg|Ka;Y8zx BD'E:F GHjw哻FI?A_B<##3:jًz+]Hˈ}?'Uψ4\iV>շ )\2JU\tMB e};5ǚ5 1  RJ??7dx8+NR\^6w劔ッnhjϰtMg{ ak+C.?DsWS_-b[/HHМNlkaiL^Z"+u2u,ǎ*{҃z[箿#WjT.kz>Uim=Oi?VFn~ev$ ٯSѴa֑~Ĺ`ZT;z3('֨2[7qL<&kpѶܼ`d+V ߱7tb M)7w]sچ;&X]!dPA(c=xu99t]V'mxDy9dP0o>6\I)7/8L*;|SztʛI}f|ZKM]n%NPVQ4.X F*}"]^\ 5Eqf( (DC N:g)mq.խԦe,yI+Onj6MoɿCXܣJtu4U8k޾׷K{A ͵Jl)ZP׵$yzA*aoǤ/+C&(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5GNG=~tދ k@_֣ZW>xJ˟%rEB^KْT#EQԳ=xO/'džݖ 'ފ5_~o[WͶ&#܄ջ=:[BxmV@Gk-%.G׷>AZ")Yu7<Q 5rfyD~g ݝ?j(TPS?Q_\/s<1vv *$whn"KwYbC# dG<;__?h>Xڔx5R#?/{Gʟ^kKC#AO{^ԵQGO!h=W!Er5bjtދ k@^1K<+ioyװ4R#T!$wgZk3iWwl΋$np>R)ߕ>m}}-4|o}C t]?M5u4~ӥ/x^89aC^' CU5_z Cop_Z (ڣp* Ӻw|Z.XZiZđL1 \?|՝?>tz5'Ƒy&PՕX62rp,唴^wZ 'ZM˭3ZE}t2Ƕzj^4~YCdJ.\}mυxB.>R_ vkfMNGݨHX0(/OkK]Gll 6Yϴ t~s}WS<RyR2[,KĜ5U0T |,TRĨ^|*kӺ>P־&H6$Og`t䶸,ė%ؖa!1fB/ x^\ӼSŗ:iw:HfSnB2kꩼR\BQTX.Z#4Kw(_Ck?j˯{NSFw["IHIvݖ,rs`jm͂ ״cVwέ%s~9H_ZVoOk'R۪lNyc t|=jIQj+-mi-q%,,.'ߖ#r+_NZĖZ-2;x}҅sߞ{ѥx?Zsh-{y>{{x]q蠶O''ZVs_m}oML\ta-ֻ˖>/hĬV5ٜCܟ$/?g 6 EýR2"C| 1;inkiO̶tjdsޅ߇R4U1ԢD2*N@bMi+h_~}tx]^z\WQi]ŽjZx)㒚ԚMwH|0d1uxo5+( !mnD6%N$h;pi |&q/ˮN3hӝsߝg=8U.?Ctѧ{LomⱖZK]z'yTSZwn#|o[6$]^=Vsk-c>j"\.9ң9k#&u|Sk(?iwhn^.^I,$|lO $Xm.jgA*WštEGKGȱvȅjm&voxN\OϢIEo\ JWxsӊ|-59_xycC% +;+}^Q]GpH?_]?tW UEMEP((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5bjtދ k@POsmjR*쨥(.jrNjzߎxGOm1"kz1ffXm`vPdHqy#knFvmsl fҠEgX @f ԀX{dzgM _t*k jm-zh$~bHĿ:džWUOjp4'Q%GG0Va  Ԋ|.3N5!R6J:4i]_ZB7R,0 ; RI Tj.Ⳅc]Ǡ3_Y>2Hò.2Z!V2f,C8nM{kYolk $" mxv|YdZevnG:PQ>g+r2I-Tsٴ[JiocC EЕ$fKqoFʱdavzp;_k:Z՞xkSm/GtI- w7@r4jH7w6ܒo}aWϛLmFiwn/.фYI%xe\'(+D{y|Vo]}/sLUrZ~{j$ApXBy/}ٷ~lR_|ka܎-KvXIĀn#gh^je쓿|K67l7-Osڠ7Vrm2 Fc,7R0^@'E|cľ/ÝsR 1%c:fL \@ۘa[Լ5♼AJ %M Uȓ;Xp{'%OO׸IL09IKRmz;+=RiXu+ۻ=6)D{! MB]!9l 1m_(4]XʷVqL Ee&=VY3H/+;8pGOںzs=9y-{]ϰ诌tߋ~7ִ5yn~Y:I {H(?tdcRo_<'{_jkk>YX$(.bk\0v-.vd, 9r7MkS1Qiǚ[+]kѮW{Ož8񟁴˛+/C⋻6JmofZu+Z0Yuo~$n7o{k/0'K t=۹J3ږ.3"OyخKr5bjtދ k@U/,,uB;DVTH:8 X##E&4DYTٌ$. ܽGa*OxgL6hF$ ?,z0r3]>7:!Fm.g+x:5ms~z )3NO>+i("+P@t;X@a SP,UI$kwz]_3\[IXQŁ&2y(~\hx ,qxoMM8f6$TrvJJ2U69a`hG-}/wy[v<4\w4,'ҭ[t6kAsFWjO w>Iҋ5{߿'34zt2oJjɍ+00WjϪilhB`,Ghp]R=<=Oּ&,G.Qmo)z(ิt8 =tiEV/%&$XϽu4Rq\qTTݽ_'oq]=~I ;HAl'q膋(-5X?wnwD|*8;FG4 wll< +N6ZđL1HBZz'/ YfwN7mbHc[j2qɭz(8b59}] (8Ǥ/+zA*a&(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Ii>r5bjtދ k@^C; XOyiJ셢$u܄0 ^c6ͤ],&GX0°sC)F&iiUjNHX^hˤ=%[o:2<{r ,*Ηxՠ{-A;lotĞ;dx<̩NHfVVzӾ|=,K4=j?*eiӻɁ0i4@hl$Qsku]\\MYIڙy5%u8٥ӷ۶ U!)T_mwѥ̷wVsW]sa.ѥӵe fm!]kDL߅<7nj-Xj:d:JIf;sI8C < kq9&$3q+na'%O-Yi :zK+HfD0q aqV6mo;Th{['fު;{t?.umЖeɻ<G@HJ<|u]N+}3H $l}UpZ3V݆g׵xcU]6#!jAZ5 {S ~I'y"Kh)"gB̞n \ 8c:W[F\]n|Uu>^cuo{mQ'I.X6Nv+9,@"X7־Tci<۔_$o縚MPyEOKh0nc.(@k6O Om2qm Y-ՙ)q7Q  X\R[ۡC=غ/4߻4Vmn־-|@&6|0nݼHk` Ǿ95-CX"ҡ旬"Hn㼓ʔA,1q1w B<6,QaKyDǗg.6h2g<'W[˧^2[wa'k`qÔ'<<\Vzzk<i~TR|ڵ}<Y{EMf(_E%"E$r/fҥ `ӵ_hNӌWqI4)f[ 96|@y>¤ajجjX(8>}Y_wmuՔQEuQEQEx= qw0\7Ws5Q@((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((IҟH"9}I SQi l{KhE-A rzvx 'I]?O&r~Gw_x~r5dGO4]?O&K??G ?u w߹??.'\/O4d@%~hܟMp?w_x~r5dGO4]?O&K??G ?u w߹??.'\/O4d@%~hܟMp?w_|?O&Ucn3ˆddk]'մvhh46DQ@?.io A@(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((('5T{P{VlT V+Z`PGWҏkQ@a_J>¾Fd}}( V+Z`PGWҏkQ@a_J>¾Fd}}( V,TvWhQ@ajj((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((horizon-13.0.3/doc/source/admin/figures/create_image.png0000664000175000017500000020571413553660754023253 0ustar zuulzuul00000000000000PNG  IHDR0z62xebKGD pHYs  tIME .]tEXtCommentCreated with GIMPW IDATxwxUnvC!z H/** ]* * ޤ7 $$ҳ}#DQx}KIvgϙ9EUU!BF@!`B!$!BHB!!B 0B!`B!F!BB!!BHB!#BLr)&RLtimܾxB/3o^<RH2&oOn0Jb!LMz]j~Ξ=c{Y0 *Ʊ_,kG3]$8ڒOłkr2|DŽB֍e p`HCUs<7z:5Q" g|ZT ȋg2>p")<}p&0gt;:EܾNʌlރuɵ?+e0 i>PG'fӧopJPO?# N}E[-$l˸ۧ0d!,NOZ"eL6*7Z3lGy|vבq-sGq,&>qi=:-vϦ_hOBa^&݃S4x*Y1ٚoy5&⯠/' F(YX+Ֆm tj`NaQUݍj*FoLQ3'WWLVxU!!j&ժASGՓ +W:^bjmS;U61I=5huʹjŀ_b?v^jmԭ3+9P CgjԆf7gYuxj=ԓ߮&NԠW3_xpP}C]JWGcX]-Uij59˫>R_TL).;?T*63Oduj*_bUUU;n*z}jj5:sdJjyU]UUsvj:}>U[ըvTB!sUG)f'\h?AoaHlK$~T˪ZOϑhZ6kgR>GRIBm֬׌Xvё uD|xsTjf}4]GL b"^{Y3K,gwBWyU^_s$#goť C}GY=L$\6#.+YԪ6YR//,3ô/0kJIq7awV^8M >n؏SfƑ̱ye6,IJm2w!.?CHYB<q`#hAWjsQXl$sN*(&N,Bb{ؒ Iђvf gFp߸_c>< +dyנyQ?Eax(`+@ܞ9ȼ\?Tbs\u᎗ۓ[EU)Eƥ2[9*7npv<ٵJTv+QWx Oq*Pj3j0Bj}x1^s|ZIhI@IO'%/H!ċ`4?qr$:lTNgW`.g4: zC-V+#)8y0FXȔOsAn/7\=)P"·jݫz[!`yNi ܵޗғmY;o+z'~AR>z0ZtڿzE<ȞU:w==ps"w#Tljsh4˪ݔM%!vfgr9Jٖ)&+],m_k-94XS\C|V T!SסTxj<1v'Ne-7_لw6ZNP3U:2 80?]{W^1a8y.Gi~ЍZe횹Z'I^= W%_AV_yCyLhe\\$5[/};yB5fkM &޿ŶycxgFM%xgJW~HeubىH,0'd'Xwݏ65|\^xbTI^=S7 )ԬIJunOyOgɢ#X`I fQ~1wV9l a ܟ1E>tׯ1evrwu9aԂ[H~ Rv *u`7?J (YIιѠMcWOdTN-Ĉ1-KOB^s!R&j^cψeQHÊbx£GsJ)[3Bه4wtTnДƍ*qgAo.J XrC'Һh2sudԱd@BtUk3˟W(mfƔ)]25ڳ~2EA_c>ϔ va@3P{,ҭhWdKٛĥZ{Qj-JQ={+1 5[VG^P,ZJ*iـ G $pED%prƧ@5" 0e |q:"5_/b@B!^tj_\|cFzZ:)Q6xqe+QlQB!x)H̿`NNKOFܥk}X5!` I%9*U[b^/6B!!B?%g B!F!BB!!BHB!#B!F!`B!$!BHB!!BbH+! 0fYZA!/yB!^:r B!$!BHB!#B 0B!`B!$!BB!!B 0B!#B!F!BB!$!BHB!LF6tkNbr63>3agaȀnr:FMVB!=?8i UD.9P7NqN,6'פ|ABKqBoZ"/{{bShRo;%ٻ 6ݭ1-ٿb0Vn܋|wm㾾Bpx#t<[];#B,ڱW_p}yth58 ھnj*z@UU.P@UPlI9/9S?NX2u"BBRfܫt] /+;&O6m@>.3|h4C̓gT P% Ӭw||<%!/0 ق@EO׺S!WivH ]l8S L)iĄ`ZjlŊѬ`2NJI !B!D!' !BB!!B 0B!#!@G˩DEEa6ץMNykTEoj=*x; %ēmnNKKݻϟ^/%Ŀl*T;<2vg~V ;b݆t./I㒗3IՓK ( 8\tLc-]qw9K%^&WE‹֣U;q+9CO1f E$u}Q֧KH׵ _mLg%k:6kD&m8q-OޏO߶us*6ӯezB:}XUH{.$G1SsҘLPxVy HkѸfuh3h5&&ݖA#sl#{=ZӨ+4m8JJyO>t۝iԠmaшlJ8+W[[$?F!^< +4?#3SѴlo^c8i۶oa,x=~\t(i^1WAo席xq-es"!80zGqe1aU}u(Ŧ}Ln7a+҇=٭B[7bP3+SuP&i*^.:i!fh1PU 8&ɦr^-DX'?$f/OVQ}K%FPW(\B5nU ץ`C;.rd D\!RVR2HNN&9){7{4ygjz;reTz5]/ ҒY?/yhͯsG.G'B|M\c ;ϧ?rdIqX6]?GPgs>7bSHfňLEOFP"_i(EAcI&.)WZs˹O;1$}m;j jXOΘK{9qK/$ɗNTlMtEo*nLi]5[G>I|G)iFxsX?z5@ xd|&^-ft<(Qfq^N㛑Vf\G84Nx(I7>ᓎE%,LF#&l$hʼnzt &\hЦ;g֡de)G*ZNeT,'ux15lļ!74bԠa+u!b6b8~պVcfˣysy$?ΛKѲyj.sxyU&$ҥKT\o%kΒ>5CcSy,OF1uD/2fQqɕ~[׸!OseySt!$gXQn+\5W.Q+a0O*ϏNQIõwq)H%``cxAl/"!BBl62RS1B"^!g?C_PC 0Br(T:@BBB!!Byy]BݎVBˋDi%!IIIKC!^bp= NNNZBKl6L& B !^he4q8dɾ IDAT !4 ...tr% 0B!/܁4bP]̕k7LJ6'Gr7wCI}Ky.0AR=bmC)ܹ}Q-%)OBbeMՒ@L| {\r+oh/MogwKHBP5#'˖{ٳu9SGgcO&g3]FAe!=L#G)6lXzǼ!#8SHƃ3|= Xte~t_^6LqsLFN]={ٽi)Į&o̥$ۿb[!F`)7lFbJ{B|)otlՊ36`qB-oɶfPL׉fAju쀪Uerz8<K3\<ߏT`RY1NҼCO@!/&}ǩ# 5^>i9wȥϧca׆tj˸ǸC:֒A37o ~DךҬ[|w86k91GgOpw("kkG)->68Ͷz3%E+|Ч:?# ?{((2 ;2V7VqusA vҲEk}Y9t jJ6/ Gh١?~>GOޏƱa }XB3gZ7K,8];W3uG8g}N-:0ee(qޠE6g.n蝜r`w#jݍC86oc+1Yкe3ھ170X;y\~:ɧM(rhd:lNwgq)6 Ss53dz/N$6 Yԡ}ޥs O0(HNU -v-{Ȍ?;ѬU>rCB5[ޙd03,_[Lff,ۼ] S{mcX Ć0p5[o8kEe0Jxߏ&ѴIQl Iz+`t&5>e2wlzRoCŘe m>v?g0pI| $\Z͚Y|Ikv-~%,ۃYe;Y;-]M)GŹ6Ígz}ld+ /oBWW>砦o[|6iD݋Ú{sxcaJeĢɂOgyį̏zlڶ);b9{`/[p`8.įPLg벱"25]O>ub gEoqn;,ާS&Ѷ]/&š %l?a?dB OJl%.bʞ)X{fdzafsĖ:VBNcRenެc%|YFwv/b|ݴ2rw+Dr [f{fX(k.U ̷d~9n{f0CtHVo>`J޲3p\nN9W+I XU߲&8 7᭢g='T2DlΕc[0O/^ڭ! C@kXԿvg܈a;@b2Fx8;}k?/'ů2S\3F8}녻2?ng)vUn.eVN'mkU!.ߒir #2,6LB x.xR-Fo+c֖ce@Ƽӣ2ZCnJȋ͚η9Oru`VߨξШ1z .%ɯ \3f8ΞIȞH;حV0&r5FMһQ]o Uw_E٦{vL}Ý{I0Ez-\ i׾\&ó!3K9.י: Tg)mO~meš8T0^ύQ #&ֳn-jawر3x@Wĩ`{4Ѥ`'OSDNoSmh<ѹQ\JPл@_ FכQݫv !qvN6Bn":p,}yU# `~ ֩:5)lu:}݊r~W9?aT9Asr|4ʺs`5:Uk2j&#ؘU14{9dEP 7NPg}h\ZjP37Lԫa`⿥$әk]#8 1(ZNZ6Vb”aZ14/㌊.alS ~Dh45˗AԄɗyÿ ڬݽ֝\ݑî([8 pj6;db>bx/.k$tIfGjZ̏Uy_WDW5?_ǍESǂosӮ43a@+ߒ`ʶNڡiɐassvc[ 7/]o>< (.͟'Gq7z@n\ :i!QU"˱Վ?<[z M nٌ# fϘaš4M׃hs4`6@!,^mKɤ ?cԒCӟ˥>>ߐz1xd[O& !^t#f 1~+Ix,Ĝ&Q/9q*x2Kg8\sWϴt222rD?[?mGWҷ2)hC%3k[z`Bk`B!F!BB!SŚjØaF}4AG;rSUkŠYrرUtN:TYG(6B 0`I<ˇ|Kon\,6(P =65S0oΰq}f|ǰI( kS!(:ιy]oGzM\%о,Mlњrlٽw-UFp*:ƾWIxSV.adԧ.r삳3v.%ROq-ɃAmu|tj쌫 NY~'Gcs<:Sb ĐqU+/VDrQ;#HBsA*Z<}/@ʵ^ypo'_߃ý,3Ǖs>}_n s9V-Ds~NيddUNxmnQ*Y2uy)W%'|l^ 7tŚxp5Y.n(ؓy2vl2.aȺB!2ht8;{%ry%KQAS \8NsxB*Cԩ<,R{Q%"a76`Ƹ>"ÂR  ~k̮FI`n9PQ<8[xAQPօ\#r@X`k_M`iX KBFevը8[bKԞ\:Qa  Pv}cNFol,CEUq7$ݎƿX&Oh]2w֣hl d>=۴dCED.nzl6=TcȼFF%dO!`sGIBEN^82wX}|Py^ħ`!~ڿ cԫY]j=pP(<nгBcݟYScӥ*kw?9̳æF}e1}l>2oa;7f%|'wnFƝ+0wBwQ۷ŮI#ٞ͜ xnŀ5!"T;g6ΗOc{jvm 5A|.`El\ úal'\\ N0j2PG,9P[K _}d7m[8z*4 Ʀ)kH`y|{)ҰUo9֝F"O@V /?I-O4h։v^5c:4nLJJaFMǃ VpW}HF6̬"@q4Z'Cbϥ{WkYzNȝ_ХYZp ht2wX7Ҙ~'ޔm(ȐPvaAor(@hF>-_ A,<q _^t9dl>给hX9ԑɀS#;9晏=0o?ڔE:o0;Vz ׏u+ 7ݛZV3Ckĉ'1>U+9W4]*( |?i(3a& 4UjRY}fΔyZRk iW6z>!IlkbE'.Zٞ o<1gJ݆RLش>u$>Lfj[SI5¡CGk=%t\:bx}F6plH>˜vR.2w7L\zAN1|`Q޾x1\J{W\PwަbPx, Wѥ^=VƬŒʱmFR %"Õ4.4` Ūl?:d8{ .@ym~ˈr~C[nLͭhM^"훎A|?0dst\wSrvDGbɋ,ha҅kLL$qTܕN<- |~b ukN {3IIeϒ98'gM?ØŖl>{~zfn]Ķ<.Cܶq%$I '<ɦP=?]"z-6OH 6*+]v G&2tT ø-E9x>G<ʼnUmREoafz -+PP!-\S$Sr^b>t/j`\ pL4o_w%-ݧͤ.q(Q'g2?I&Wg:)/wu$njRe1X`Nv՘<˒'wOq!9DX?/T^LFank 54͚Or1jOfD‹i+|xt  :JuzvRK87HH#S8r0lypޟ%n6Q^7a]?Bݾu}r0b!8pq&R ;8~< 񧼉1b4YFq;9} {")T H1S;k eZ*PmLfJ7gˊH6\J;d9%i=dJ}gQqXh|s_ t{BB~n (بRDMFFJL[|;կ`\Կ>)̩;B`6 $'똲,]O*($Y `NlY1$ udGLh Ϟ>d˴1 >࿁#hѽh.bj^s ?5B.KH~iiѥpV!)v?cf>afcG0f(QIm&YqVH(gf dhVߚBiv] 7%ZU%L;2.)+7IܹF`P,61^ڍfE©m.ŲeC̿g@R[Sor{)ܟN*;TFbHJff0OVPu:L(7U#I :JڵD7VWSʄc)P{{.xICKHBᜇ}VClƵv7\$ީ0]:eß2fT5^4N/(VăGH|(J4TV& b˒H=^%gpv>\[G66/9΅(o7Vxǻ;L4oZeˑMD !>xw]);ph,k$:y#̬n'"Q}qՆэ&O\믈O!W }ˡ`7c_|OOOZ'ddd<0Yd`#}D9٥xIzQ>ؕ5xpl#36^Eb ӡ`?QoJN7ꁹw%Jŋ B>|l \̖ŋ,`ddddddddd####GR,0≌zrp޽{ǻw I{BT\2QL?- a{$"b#"ěc>ac DFH1 +yN@tTgu"#1AJb1Iq%PFFFF|1~!_B:Kp|ׁ)sdrNxȢcvNo[űQq"ҳoˤ$\g)XbaIoChr9$x".0oZ^G>fԈfQ.p;Æ%[tl}f]o0d^ƒЧd###Í MnxU[`|϶YsytKӎItjӞ񛮐^[5ҿNԳ^a.xͪs/xuvncnx͍WXr>& (czŻH>N=p#Ӧ]ir7˜s5n Td̝3ucievmKב J 껗]жX>KxB [ RBΰ|*OCc8t;uAz{,كV4{'oSU ];:>x >c0mZw`s$|QH0blx=IMsdhך>+x^fRuљ#;s/e@vXU®1{pWbA/!voʢq}hq ncs~EБnBzoðŇ׃f>Hþ>ٖ6+ rw^'C&%Vm(q\2.m=q_P:ц].4΅SƛA3ƠSL94d"k;Э7ߏ[4pݽ[q\FJ_膷w'fnN"=Jk-8m0hR@RhJk,كVm'5eϤ\;ҥ zH@P':tK&+k4>m:a49uCҪm/V ̿ {c3gqvAh2&2-t}}OfyXm99)&D+օ͓ϰS04†yy0$^!1K7%[!,\0jekҿIq70t=-g"ӑ޳`\" b^^&8ᷧfsj_"V(Xr2֮C{9',>v|t/aa$͘B8r^=3N Abs[ NE IlYn/@ o1zC.\KwyDc?y֬Y? GvK>*D2g$^̆>g~`0.^Iw0t B2t零eڊ>C])k{`6@|L!D6Ѭ}1OY,d_E2&Ue!DȄYz_,BJiTB Sd,iky1*G*M?:*^c6,5ԅik)Wl$*k}[&d| άd3wZDI/:;O> 3_ǚL\3oOgy}mdH`Fμ $ ev$bTjB67.l!|"Ipfur#ش/o gƶXjAf%PFgT=G.&m 4mޔzѯC Fpr I fRԞy4/K}AQ02224Ȣ}_3UdY~*Tѓ}p}ebԭ߂rxTGB\Z[n}X[*nlˮThٖupV&<uv9OP"0 bߊXWadػmͿft&7kƎ= *eKHj4Ε_9Ϣ)Æ|U+SصؓrtaE3WƞwAI1Cm[-ۏ 1g?8]֎({g~/EH|u][BiQǞ~H*Zʬ~e WFYN{#oF" IY?V 1ntgRf&NImEz(fEbu)lD>IӣNd^`mFY1S1&[K,gʬO9e mu ks%#:0`0 xa|t_ &Pi>&OhT F:3JeWBXP[|ק MF>Jl5$Rz"J T, %Rlݒfid3.|?l0o]습~JD!P-Qt(0c6 |C%RE b.P]V->}"vUVkP(h(iïk+#dJKˏC&AGXsJ!H %#s̚^{orr!LЂ&tD % E_R=HJ ?'T JcbSވ$ xbF}]ݾ0uHQoB~DkĔbgF֖&$aJ19:3qw=0222BtAcaS3">!Izj-o^>#xĩP)(3g3aYs-+㨏 bdm]?`5Bc@ 9|0u '嘯jQ-W@ŒAHh. ǒ5roFC?K~Z+v@vľyHNd$0~j%[3W3T1O+РÊS!KQϝdmv;wrĬNw~n6uJEx )Z œ'I0p{8lkF8\Ͼ@= jBw:Q=^to1]'_xjmSy.C=(~t{$꤫Z{=I~qF`6+V)/HטNꢻyWyfPV+ )/w3 fl CZR0/D[ЩCuϟ$0,`ddd}(rc`-cTrynכr2uTòV)m٨Gޜ2AsR;{dwv=%WɘsA<X~ӛQH,:Ncxqk  MLXc̑*fLS),ͥe܌؝ =Xo nU) b9 4B%ʏh,QT[`4ٖ=1YtXk4ȒZZVJHJ ҼKkk-*BnN5i4t'b: aĀZU4KXӸ2pT{Q|m6d@-O*Jl\2M=(W#+eӷ{(K~3{ᘣ!c%0vE:xB2m1o={QP(5XZЫb]/^iI:CZMdDI  :3gXZ[-%(ZZRjjeV|_=1YUtD4wbnTlꃢZWjwDN.0iqM[e<Rݡ:,aFUGʶKăԭX_Ҭ]\.ҝ^ҥJ%I`p2RK:AJUyÝ4B؀MKswuN#(tcciKڃyvh=z-N. u꜎T܌Sztk(D24X)U޾$H,-HX//fLƈ&12?۔Vc4XM3ISh>^/ǤZX`Qݧ ,,mJtc7m@xYq(Ëi\H%K{UĄ$L21#ǰ T_+Cv"͢1k,L)Irҟc>M.s0K:]S|3(0Kh-AQBeݗ`IZE6̿###/\mJFWQXe;,`ddddddddd########Rh$##^G7OĿzȃS-ćk\vC0g}Bx2J)Cyzׯ]ޓ@XOg(s A~x"_/dveHaU<ֹv"WI:TypգJ &~ArcO,M1^7%nWS'P*ܡn6;2 -D(KLabjBQw73lf7o!l@Jf~x:~mnѳxSo5;!aJ|ʴ.c8ڸ.0gT2I]?<#&1{B{[O/!h-+vc8Ec`) '~U\=ymI^ֶV$qb|EZCQK )raq4s{&CbJPR!8 2Jt1x˯3~_CWӛ+L0 Z53O^G5~g0jYj[n[+LaϝoܟYCZMwm`=bb+wBCRUE,t#ףo6 d/'^ 2DSRwBѭ6RDĴ39H٥4"Q7sӌD)9~9wn_|G6Bt}^OEt+1 7>BD5[s/ 9B,_YOD;_Fx IDATWHP0-˴Ŏߋ>LlVKԘW$G_h"v% }qѹB-u+DMw]ŊW((|]|։z;4]tXDF_mʔE+!1ѡ|b"b1}Ya<"ZUGE؍%N"PeEGDI/!% _1NѴ@LτOFb aNz)wl)f܈1WzBJ}k!J7ںh1H4Ċ=:k͢vنb{":U?I*[vnp_1`q%HCDs}:h>"VlQt[Mbb"tQ((Uk9Jl8JI/~ku"@Ql}A.5]%Eal1a0F-Er*Ѽ`kF2e-/ĦĀ-`[|:g4 xbݭ(a $V-'x'6^ƉEͪĩ:>$5l+EmۉE7BH~!Ʒk+6$KDms"6ѻB މ qǃHz.j _v}fIFbܞOѱ0Ŵ.ĚDZ" Qwb")ѽ~;q"0E$=#Wn(+M222PA%Q`g :7*K(RISˆaPyG@zVP绮Vq~#K}}O$rTʆqźyS0f vEg&N\;z( K@Rd/ޜV?7pT,*H(4 $6-]locCG \I+ h$!1.ȹc@Mpڂ%ː-O'&.D| h]ӓL Pw+&OG>\J}J&RDbۏq﹒q-|bl = G<{h BӮ^a7ܶ0'9ܚ1\򿵄$###"0?fl2#_ocW4ֈBXF$Y|\c+.u{>3[3%svJ=iݣ$냴鱂 VRj䔅,YnT|Hj;RgmlxTX  /C)+pvL(\Q' Z- ~ȑiC u/DN'm`6@Lݾb2NJ͗TKlFv}S9zQ8$g~l_2j݇Q3G.M؛#wp${fG $ $$vnfqlp7%$###kqIDΗǑ/I܍Šqp(6Crr(gnufw }8Ku!Jx=y:w*8iX4mWnf k 88<%Jm{N/e|*˹QOnX=3Y/բ-.MnϘ}8eD.g(R8x>f)G< ךL]DE>,}Bnޔϴ#/Ig(`uJ*kL[vx];6v9҄T;?Ma{=ywBY8v<٨^"0" zL[ZTcap)Će vi7{EPC3݈^@TJvS|R(*Inx|o]&(@v^ E(o,Iٝxq@Tˡ$cv*&C-B/9&&<)%wk,0)T)72]n)'U<ŧ"(Qų$U7<2xI2냹!0=".(isx=02222&V8gNI+Y+ +OpxwX~†u'9ra3gICj(1crƜ%aLԞz2v%Zgr 4 \ d>dW@ƽi6mZQcGlsy~tYTG\^ioٸQɱ0vL6=zy{Qй 4`Ok8qC+)_ kEɵ'Ce~䷁)1kxZ3Sd">7W @:=!kq&Y4[-±Rfw({6uwm3{q8&cޅc7ӳ+]k1s^봔nُ&%3c_z>=OXȌsi-U;0i!, 3LfvkMnm&\^(qtuVDRe=GR椋f]Q_ʒ,%%N'Fbb0|"( vHa&))Y~)# 7R/. ո?oy> 5)7nE7껝ջ/{+-'LQCㇲ3n"N?v2k!B%w YD=޻֩2Z{Fvs(z.WXy99a\ʼs3Qriu[CUfZo%5Oܼw<σ(!Ϭʼn?yeNa]_ɭ{*{XaayE::^#3n Aj5fJ]Z,evgєn?a}39i>[/>Gr*B!9'ܡzd6u.LR]8X-*"iN? p|? ,_K g_3Ϣq=ɯ̜ADh7M=c\RrYsx-q:M K+5]X6/T+SY<"3l%,eNbZd0ĽupdR&9DŲ5Y|<gf3l? j_ToG6M_?ûL&$ny#ZzĒgp?yFPlu6.2&㇮Ȁ%,X};p{LÇ2X=Nbw ܪع:UaΪڹQ- c'>90Dp\ԕUsrQ/fg8˟vcdC%ۖ#dU96?b+3/פ^Ѣ48Y8z5ܚ|?aljC WzR1˖I`,؃ucESl{cιKǶOjQ8VyoyY7#{f> ʹ  {*T!SF)õMU;P~ {d"W(ǥ57.[UF7evJ *lCzqQ%5TC;*c8a@PAm`*3fYS7XIyҷV6ϊ1!y:ה*NǛ31DquF%m|(Vl B^?ĎU,}o ,ot ]ҽ6~$Qy\SRQg k٢HN?ʳ8y}%*tLլ IOX| D7"dֆ܉oS#]cJ=2k.1C'^U&2[$0M{„a[?a# ?cp1sVB# =O{ 5'0Tq!}ORZG" -le< I$L&F QܼGh]ӓyl0`|ǃIX$c` ܉7Yk-~𗏸d nJs<0 P(Zo2h5< $CYUs[/ 냗l{ ٳ%M<8m'bW&9_'DW*χذ'-eu]\ %٫3m`s-f "Ȫ9xE6ŻqY5%T2Vu7K-#"G3+۰lzWleлcM9s;°Er?{FjFWhʺhPٶ}?W:EٮcS&e Erl8!{Jf23]^>Q&3`O3FZRoL:u sƬ[2}t#[W`9+ޣ"vNƘ=03kVpVO2jhBq<}O0:Oз:[&)ĪɫҠժl"[cSal$z,dA }qɦ?dUvѤ`9Z[~,T)n$vEߕ6U2v%qz.5])_=?otmUCҾ(vC{kW +"v3iL_*.tEzͦV|POHػ2sf3gfΑqpo>C؆+j؁1EZRUw+Qd:~d+cqKG~mUDZwoq)qQ vyO1QxoKqCbCl4.^@Y6u,+%SL邿g^b36mYSKyFpJ{ALsʊ_vB$gim#ԴL^b1cȟ6^୓hMs-Eq0Xf ½KIN2lޝAEyө7_gNkהрICf! صM/ - gM>j S&8-kŻFBPXSf}LTmFa&N.vsutM(o#;-M`$5و.AP I Z$Hr&ֿ =hJ+ `!5Y/7.XFIjC)ԢE<$#AьmkE^fo>’SJ'vGPHAZ/W5eaZJ:k:Zǎzm:PG2ՀEIg-"b㢄$Z n WMXpP<{ֲ| rܴh)j[:޿}I؅hJE<]>-]M q _OQJ^Z7Z-HKǹ)M 0Gϝ"R$t̪xJ\Ҳɣ֝j9 'թnUviV022 + ?[0TwT ֺa(TXT_>22,9r/v`/N`V_7[H(Yo|rq@-G2|T"*' -E^H&p/Y{'xVҥQRf*z'UT1G<(Z ;Z*Ҵyh)ףtV;!)ږ*폍Z@EiR g9J$db[5񴓝222222ux*fΎ4j4_<˴(ؼ//Gғ0 ј̑qŪ6UpYdEFFFFFFC5`ddddddddJdl qd"55"KKF?RF# CFFോxF#t: Y##$IhoҨqwwz`ݻ3vvv##nļĭ[ @̇mJBL22F, <..;wR^ly|)F3("!#*#QHr`oMAP$QiC<$>J#W~/5Iux;ݡ%RcH8`yn:Hxv?^YɥHхy_})f<ȳeL&FF^<9 8΃yٓH0ZFhX^$ދ懇HZB 1 )%%6Ne FFfQ (U($ т&f<;=m4ȓ8$I 2F$'Z{^VZ-/22!AxGP`61Lq9AAڻ7]5#EF?C~ӆ~yW7Ģ/0w˕w~ٻ7tLoBMbr#i5?PS[km쟱C =rwoYa28wo=Yz:ë IDAT^g~\@ J Q9k6~e5bR:$̡ ޛT:7eEv툉,]Mk3a,=xn^G: [ kXWύأ[E߱#̏z bS.^GB222om OAkmg}[_Pϡ 9;#pI|=s+q,uaX>kՔmÖ 1(wIHKz #* 3:q̖̒i9d=;U[wH_D-hIo<_"UVNKq_]Ьm(G{n=&vz) :QÆҿIY܊fؐaؖvZqq$Z:y',VArW_V*-6ZqٻyC2p^.n'M2*Or{woR!3sCLێF{ٌv!KqYEVǺ/{TZ:*toLvXs%ά?'(9#"6u4Ofs W%}ёU*X?G6Chq0;x̱ BlLba 6s>JdՃ M$APh֢ 7e&tXPOA?#׮ ͔.IO *#ZƾͻP2|4plw8͠1  JPn:QrL*ʴL=A;1kS#h԰) 70j4>kZzM:09>3E2sHd}]\HϧkmԖqoBFCPd&V>|_oBHo9換!###a!I``:3D  6!8y0븚Fx7|8gy.4hInt'fm74Hʡ)[vSi?`5ve(-ێؑY!YHJ%b\.Nhċ+k:΢_2u5}ه 5S$:㒲 K(Yҳ .RK\RvmٟԐ5qT_5qc\Ïkӿc&Oc㫍BIjdaĭ,^{8t1etkw`듙6b*uFvn?-X1 #JY>k{hۄzWlHiVvȾBt.2)q^M[]Çh0˗%dXHHL&ìx ~8GnOSr}-cv{2um'pv wxbY3r$ӆTHR:4]z,Ļ\V Ʊ0K/S¸BGT.W|CBG.ίCHδVsUyAa CwoƳ Lnį;F"+DN0bV1z륜hOmQ_s4[Rk]pzĥY[Ka'f-s:paKՠ"$$d.=.NSQ -D`?:HK] %b0hEFO$/˗ 9;o5f ,K&P&Goۍ0dgL؛b{D}1ʶb·=j _Md+\! 7LE] R=SM&MGWPFi (!I4SD_P%}QF/ye@= 7D4#JK @ D].G xp q_[ZV)IQE t!g#<9l` h-ˆ5"d_F/p$ۏ-Ҋ% axg c_# x>pa89l^JL^-y F'%3jk |x:C>(ͤ(y+IəS(%8@\聭OmB+{0)τ0йxw8%QDOc-@,[qw'Iփq|ĝ36`CNǠ~xF>$Y F BDB)<}A ( ,$ZZ7d6Y@HycAtpےZ Ae]ZT񉢈!#҆Fm[_钠@),-"JZ&ψlww^x;M@@$  $LrbUFxXMX(ohmө +saq.Ӊi}VMŠiV畈=w溬ۛrN"6NϷmqҽRaV#v*.ulի4 DPZC8?%nmR-" eEn`_ †cטNmЂI'ܙi5*~1^)zw8V~{up\նܙՖk)7f ?éZ1kK3AE|iV j2S^vL]KZ '!/-UWWOѶVC^ɡ9~>Oܰ(89m+hFKΎnIZ^|"赑xϝ;GҥqH1Ͽ;b𫤺/oԥ+>:篠_?G9"FQbWSV-Yhuԡf͚ٶ~kN?D|$6Fd;!&6ࣖ-9#8%C[gKXwG^TBz:;k/;wݛy/k`"Dbc"=ܭ/6`D߃ޫ$6&w^/$$$0i$*WLHH#em?qX՜k@wEgC~ԆLAm hQ$w>9Gm*'|R~7CBBTQ̟r%}m>^+JgaSRȁMe>޳VU"%_}&Fy'4as'.Ke<ŀg\ߴiX(oZtCl=aiLYKfq>ͥ0DfՁt>;=q~t?~ʥl'i>t q^ :)}smZR KQ͂"BInO)ΟcFH@F~7Zl&4 f=ztReɾ.J6^d> 6j fEnjw,8:L9quGM(>^e=)V 1"\e\"ī!q̹9s3 R^*^[ ѕ茯UA={dh8i[L*-juIT0p~.$1VQ2c-ggP@իxA.W^爌LƬv]?;n&`6ea ^gQ{$QIZˣTvu 돻4"}r ѨGd6#I*\>eRD\9/ys4 ,3,H)灁Huȋ6E&qDqgyb( gELF *ˆ$WbXd!|)$OGu/7x2D+tBW!dDoi֝O뜼vYĥD-Q+1_`ܷ͢L0%Dbb"I8&NOхCr.]]aúf1Ɠ)Ya%sen"L&22M(2IHx ;̽9C?,-dG44>VYbAI7ь`|w[ĐIy`DB”Nzo?z̢u]F^zŌhz`ȚF&˻Wlԣ7^8HFu5La-b^LHH_jKY"~s[4ff>NDA!Y̒Yӿ:>·n8( Ư?v X/**<Tq͚?ےz>DJqf5WGkPep!lcӱtMGJ< B}~;~b0$,+alyX9ҬS+rNJM'W)eܵ8 7Xn7mZۛ1Q븘l/NyWu=Nl\;N mFC"~YuwT###~?-y9[{c+|?+qŜuf;>!"[+[2;IhPZo>Jǎ"p~4sy þ[E[)#Gg'@gшwԧfn`46_B F͉㙶IuflmpvE%Nlmyܤw|K`B V'\{nV65m˜?_ jiCü÷ĝ"Ѥ-Gﷅ_pGVXQMgf Uz5%?oUZt[RKR4 :{)>.: Wcjd E5ȫ>.P TyևmR?1 ZvZKg1jzi:nFV [dd^o><٫~[ѻMe,s8Ónj>%0lྦ fl <&w>6_¡l~XأȏWq%"ӗ)[JEmbֲ$Цg*V@EX-5>Ir6윽_d1'{]AZɋ1>A7]S͊Z ,Y<vbO1gJӝ 1uO:sf_=}sX~2䓲 ۖgXsYO)KMƭ|7g'4՞FOEl8zuySrNIl;KnjvN(SO1oa}ٶ7 BݨB5[ʩL܋եGFxc\ls(5Ar#wRkx2kRZ7m]LߏȽz6n/&Y?҅K#QaG> *U6Eƥj6pTX {gGOf#P릹:x?- ozNff LBoQ)p2ӵk;C|p/>թhagA燿=b1^nq gƴ[/`ϕ TkMh9ө/IN q>O:ϼ?q1NEtK̍W} rWE!vTdG.=A~q'S#JʞElHGLƜE.=LI}KBf3dE B}a+/ѩܽ~4ңO1ifԣ@FN ]ң|AxZ4c{Qұ['Odڟ|)7ǐR(ݨѦ%j4m)2"725NL5$KRxq*wOW)Sހa3AG~, Ӥ-{ `<='B)Պ43!v`g jLdYjŐ͹r ϦPŅ;w% 癳ρ~2M!f툧ݠo\qь .TY իGL;laԶ?+IIЅi=cs6R"QqF]<|q/ 3Ry|sX6:N\iFvޯa]#s|?c=ʢ){`< 4Z/Z#GoAs1*,)Lĥ@pRGR*SuŠ+Fu?v[[bfX,*& kCFO)D֞ > lnT5?FZgZ2 RYѽ[5$"Xojl7E@mE`]aXWKOF۝8p2UͩQTu6 bOQͅIJUpGnNOʋ!V|r\*CPoU wi|OJ @`H? X,&VS2j%Y<@7DtZ.=aDep3`,dD#~ZcH n4={O3x"u ̆` IDAT'XĤgaҹ[]|(L=8*tT13%]NՖ < >iLAX4erl^L~E-(,h@.;]Xf)9SƧ!)1AP־l7u4ԟ&miTU=6Bp^C3_m ]*=Vp-{^HAx~]2k![y@uxh1|4Q(Q+hrtv;۲e 424lЌ[TꯚBoT*Z~P(Pu?W'-OL BV+~Iͨm+ZKIoAklnl5IPb|Fͣ]yIR.ܘbGyTIZhɑN1Yj[ZXFYV`\1OS Jll<ԛ|Ia<@Yj*2AD"A%jm^o2qt[$ 4j{ ZIEĢ--pt:oKX)<&"U9N%_~Z֮͛kVg@ed# IĄyT[ 6-]ʳʬJ*JK%PދH`Th5xt`w (U)G,X4[pL)m0p@34ׯb}4C8Uޟ71*Q)~uwȉd0od4lM@HҮ1Ve3̱w12u~ɜߔ{l;t{ѽyyHE1d>GPZ937#|)^7 ض` 1 <Q doK+ cJ4 (36?¦ |f3[19 xR~=qY;,f35P|b@:!= ZCi2ăxo;'Q EEY8p٣Ys$". e.Y|3|*#YՂ 4Y^7o^"""HOO228pÇOllK? %K~ S/jӣj FVP%k*żqUګ 9Gkաxvi#]M"-oӵz *Bqh܇2>֡dP ҢuFu!+ϿԮ;jFܨ]mhxC Ǧ) $0ONyFR}kӭZ)}yIcL>֐ ب/xLX3u|E*OJ݈}^Ϯ^/(븠np=nB/x-׶qTۂ_y3AYZ-NGNXׅ)HԩSI-lKco(*u$X&7p`z1VN s3U%wG`/_Ĺ Pe+g5J V{=v!Q/n4zU3Ӳzy>nڗy:0[mTHO/Td'*1)3 k(Y7>.m"?Ӎ*mݡ'4(ٓY 4b_??nNMæ=wXݍ[3ҳz NT)[Pn}ry8ZCѼ/Y|*!_>)ʧ!nHlL*FEADx|=w"VdvސR>'@Wt`em6jFCL&?Y$gJ%...|H%M#"Q_R I-RH<_lhR㻐Pfo3Ͽ93t,MOn݈T8Jު; =LSֳ@GQmf}?FSBΌo,={'78u&󯬳rXP{ f<7z`dddddd(dd j];)uvV>  *#######022222222#########02222a$q0#}$e  >#39ԗSb7e~ D>H~)\ZRIio!5BRLz^[KO\0rlO~I͟9~Yz3m3q$[ʭy)?1a%e:r7Ws~]~dFFF?£#KmL=X}Yu Υs`u޻n1aNAtLdodRQ'WУU#WMѬ؏&=1Caݻ=l 7]Zvu[yFg8vvq.=3G7%tJSR1_=4?[, ռA2'iRO4hW%Ti/b-[ ~=`s;MAZA.gdX_ֲy}֝-tjLi͂hIg8Ociw?qQVXY5+]10"hH`'^dXyy*m7ǔy1Kq1|1s,:4mLH,r3`zrys_[i48fxIi(Vr^V(~[иq+F: J$\HvMnӇR_дFZ`ڨ4nӋ_#X7#AM:פ/1m`qng>2!M6VM&nqzf ?C+0222⻓[/mZb2 &-NL<Z2< ۬;+aN]pnw2j>MZ 9rKg bɺ2eO'R8g%uhQܑ#+ 9\:w)Zq[6 3m< AсUwY4ealJdh2|.^Em|*f K|ÙYG/gh g@oXA㔥 u7=1gؓ[9tD⏯㎺(2Lovs/ҟ3~y87kq?/[I؄UE$ [´hB'ЯXSI >F !þ\Aa ^ͽڱ96QLQD2%qaOW'RNgI<,TLlb]Yv)\jEWwI>0d92l;Xn1Sw0fRcʑ<~ i3`5ـyh])OcpS(ΔI]KDiR/b0{:+N-^H!u|)#f|b=*c;B)jqo cĻ4P뤋"yܹ*z j}\ۋojʣ۳vuJUZU8tc](ݱaħEhg囤%2E37V) J(n%RʁDO\E<92\p/W볂sR{#:ɏ?mjm(ڨ6[eb6kSZT)DU%1D^$ڪ,%+\?Z%g$WQEklATСs)TVqڛ .`ܻ27U :.WlP[$fƨ(Z _.E")p(ȇkO~ ΅ьŬ'q 1bEm鱬yySUYIsJ W=N&̧/ɯw`6Ǐ~v3dpVQp8)=QLuq F"IΡ.rGGV ɘ xy%~LϧN<42k0ssujsy4.jwnžSzWs NeXW?VckCS 7ɝ2з5~?DӘu5>Rv2w֪/]Adr|l~&Xc"ŎFy떑4N?6Y=ɦĤAlMz.H!cUɖ%Q`sل _C2yEb"z;:̮ M\s%g2or YFD[C*>sG3~ B~t8~&#}3]$hQ~c@ѹre>w)zpc;9ƅ΋b䈓E?:X8RӒv?i)m\ykx$uY? חaH*7o~'^O)y,?]¤[؉,N_6 z:r?ڍ ?ng>˸T<(zâ/.OIqK ŻwL^s9?'3%\=8 7)xvr*)^:4x_u'_|ĤǞu11?eVn_0jܯ]wn;6 ǛHpXl$#/򓚖V8ғvNOO ywcr5qs #y~/nDؓy9=9~0~!T5yZH"rhn~Q].}ɋw=nxsRsfOpl^>5J;v>{v{?`}tt`)7_z_h<'\ 蓟?9y酅 JG(_F!}[O̖\}Voe/|DyMWB\̕g@Do_45ރNˌ?25+nahS>.|5fB5` ey%dt78q0X*VH%3bXrG`|Xsj"vfPZܵIu/8g|ĻK;8Dpr/o6_e?\%gi{5MCE(;p_2Տ`vfp6տN^ϞW¨nf7 ϵXd~{.ͶA\V "'xmz $mD]Ώ& `ջ⅏VG&rH:fŇ/2y|c&ɝSb<'l :%M e1<>ua W2Ϲ.[&a:S;lV͖s-Ҫ F|6N?qvaGw׷mNeMx:+'P]%~.u:^q(ow| w<3n,CRo&2q ;rV'Տ=}A^|~R K0c&]z9uzmgue~g_uAfl\VGיO˒x쭕fDZy<;kQh<":\w]fRW<3$~ ]˩ڈd,\CF5gs9lwI#z5.é_dhz=g56~_qfe,شEa;4'w}׌?|X#'ͼf=f'O]Jᨮ{q.Y݇g@O&O:w| ,F Gݤ8ۗ,Mޝ)wdHT6[ay- +0y+{AOn wL:Id VhQ#.qpvbNQ\'ÅtuPF >ΩX|V}20XDq4O?2jL/1;i,KŌW^dS|+-F\3:\lwTn8Fx%I6eq&02>~OyuQ_p,}sn&% L2|VFl̈!x὇N93xV% >H^./$hP\ӊ+`QtetWpXs9W7SسJ#܄-)×ӛn9k!ԅ.Ԗlv=u y+Wag?E˨ IDAT|P̆-޸%#⎉{C786Fr!{tifݺ-4G (ٖu[[F}!cgzibr, "lp֫+ ,BV^6jZ5[7l#.I4WcUҒ$|[>ngy'`ysv*`'do2{e%ޜvU\| 24\DD gyoFJGƙ#K4#"""mb"""#"""#"""%. Q[[K,CeDLt4^Dڇ BlذtnJKFill_SɦM!!!Toȑq^/@D}Vp\9Lrr2* i0 uax4ND (((((((.qz?0 C!"?$&&ҢRWAH`8hf C\\JK P!FDBԥ$rx4%6 pl"'%ZoMMFggxZm\yU7z jGQp29ǓJv"^EbF>=z[Xl%zc%Ru<[h =2\X]z"; yJqŧQܣy|4j3Z)ڑى@?+gek?1 +)""'h8Hkk+`^Lwnұc<ۂt2kd/.TcfSK"/EumDnZj*|9WS-p{2l{w7%Lǘa>*}VxQ2f,wE״)kt$V?[]דnف, =efhwz(ϮLR2{sNG⽊*MUEtIbwLq1bN1յ$""" 0Dμ+cg1L2rX+`80bbLpZ+꛼K!αp>a|j (""r$_^WtAj$Ȳ?e%7W|1E.0/wo 䃷ą2 &j %"""_38N` HԲl WGYhEWN"!  HE8 ۂP8H Զ- E0V}cMxfy epGӪ!E3((A粭p89By^HOOWaH0TUUQPPǣUDW>h[c_b6lP`6oLqq1~ol"rh3 h4mD}ާٳgSZZ#"?b10M 99C$痕֬YCQQ JDڅ&j2FDDDDFDDDDFDDD`DDDD`DDDD]nrHNNuՑa#iwas+?:n8cfɼ1c57:H9O^Vi_mfQ@HrJ1M41 z ׫v;h;qϹ|ƵL~QHJjekYԎz0 9un`sy%Kf~'\`RA]kČ;$XpʪFڱX:>g|+a\2CGܙB fH/"; V Z#(&m [Dr,RLYgEee%wy'_|1[nU`"".B 1*&fK3'OqJ_Lac࢑}tD"MPfO9dFhllrvpS#!L Zci,{9>OG3="z.#gOfPrp̲/XԒNiN*:_]9:dPW]O #P(DII /2^z)hTKȑ`r8W3<ǧ;2𸱌=&*Ɠ ~O~rL Khk|Zߏ__> _$n p=5i!@>DyxK(Aac@D.,|e?s31M|v;,>Ha#m0X)lgѽhb(zAHYfng}{c1K:rD"ٓb 9BLc'd,Zɛ=CUsE /  pTÄz@;8"[)1nj>[pl2}m #3;f,$59!p7/]Ǝh.筗EE !G J$'ǣ""GPi_o˙7_ǐ|4Ubv&9WV~ +v璑I|,V Uw۩6ké;*.Dz̐ap㋈!`O yr0\ {Pn.>a3/y'Qƛړ3;# !t,q6;sr]yNʙgMޛNDkw\¹'gmY`,!v'J(q;F0NGq6ot? Hyl8ajyO84*x 0حeYW ĵsUkƲll4]LǶp01Mgǵmqն/ۊacv=Ŏ4h{eu+<Ʋٱ+r؎uNF9ۅcŰ1p\h"e9`'xN:QFD0li\ٵar`&nFޟrs"nWa ޵{_|N;n9|+""" 0"rxI"Ҟ 0 l4sDwmcY>XEBB~_%"?dddP]]Mvvf9kQVVKs8W^竰D]8UHXm۶UR")4)//{i֭H 0;}Y9L9sV0vN-"rHFsEDDDFDDDDFDDDDFDDD`DDDD`DDDD`DDDDFDDDDFDDDDFDDD`DDDD`DDDD`DDDD7}n@`w`f=5W}~6Fc禫˾^̘|?7 MyרD_Nf}cD}{8^θ[*XΥ)DkƴjwbkO*%~#Nϐ`̷XM )4%gojܩ\qH+>c͘i > 9eSm589̬lӸdfia 'Pe[oА7{{>~$kzPj4v";UDDD!ml-c*LXjj^gS'Eۨ)<+Rb >(+!%ޞcCnQ w]  tӕ𺷸Eլc/PfҔ4lԻGbx- ̺YX;Riyx5gp]O"LhLN4"FƦ|J61s.wbʵlo0Nf1|N+72[>f(D-7ą60?9Klzg t)O‹Qn ߟƢ*^x3?j.20)ERPGi`mgnv4=b\?aʉ]%!>T&BwĂlX2{4aW5K$-1 PKϋs8$@|J\|inGNՊe'{6p1?FS%al`n fceƎD}|a/)%51TDDDmZΜ.N8LJ 1b| LS ;H[.C쟋> m3XF!vŞfw(cƦ#={+_9RчV+UuN۰gNay-^:{&1w K1 i_#+/OEDDr.3ޢ<5~GOOi1N,#d9KN)du:>Xv'۶,-bgwsn3/=Q?dbxyJGq?z"yM_̿ޡ!{$O\R>=:xW9l !eaPs־$2oדsTj!pl,{u鈈{3*!`DDDD`DDDDFDDDDFDDDDFDDD8LCMM UUUD"ZYHx&++mk`oNSS:uDPh*L$33S"";l۶nݺ0M6^/999)H0e&c#aFU".YEED^ "\QQQQQQQQQC$eY*%!xT"LP9ٶͶmHOOWaH`8M:#"--lLS="H{R"""#"""#"""#""" 0"""" 0"""" 0"""#"""#"""#""" 0"""" 0"""" 0"""#"""#"""-rhcisn"cKIq;ԕemXf2JiX1 \N\4my5З)h|pp,<~;tWYQ;apՄc1"l u|X[^妿~Aپv)Q:М6oZA6]˟.,fOr|[O'd3"e37X ;c 3xI%]O ǐW0p=t,>kCQ< =a%k`=ã?դ)\y ۅ _.]y]GX6%'OeN])߽̙C'rxݸ'אl{r6&qƱש| wwk3I<|~׹U_-+""r8۝L4*'j!N8ܛ b+<%? ªWu3x@>uw6PzL?9<&q+Q>?Y CQ0*мkgߧ2u50y^naD#?θ >[l:ҳS PQ@9?䚋ƐI 0 <}=Ϸ?h"CS!}Ѥ:{-Ơu\pS$3~;ƥK5ت""Gqty!*hLHF}&;J8k?<4Ns&1/u9>|N?g)!1nI4CcX'pc5fb ǟi%>%Iqw$t&m%KՐ8FtMƶU_'N{g pRc;o)<ãwOV8F*=`SN˶ek:T#ldV7v9OQ?y^boձ,"" 0Æإ>Ď$ŹC ˁXh08,]c887 bѶ-ЃeCr0Lrl0?\0]xv.hVɉH xfZ=|y QIԝcz7?;Y1l2Ln"X:[x:p 08+ϋh;if2|F8֋5X @4;8==x^pn}ޯDÙIB6ɓG&qvvpqv9{,_&rid'zC-PDS*NݏtvytgÐ8Ԕ' o[ͪJ#RqPjӒ=\LH@1gcھ?[Ҏ0)y 7x{>ٹ1+i2v =IB +7KQd`AHK͈A,_`2˥ckS1 ;Qҵۮp&""r$pz뭷7D[mҭO_Gb5a%eӻFs$wf@t- D3)DnI_r|!j[|8iV7ڻ7#Yu;&[I-EIluJ#p'1Oٿ@a_>zvpMTՙx#@mG 3iϏ Mg&c6B~ 焨 ٹ7={oQlӱq\0qqztzQ&53܌e_}0s9fHO=>Läc.t40jlHIc[؆쮥4<ttc˟7rRc#+ςiSO'=i_)s S A q4z?Z1̕)%47af?'^7o/Bt{'~ CO(7os[{Uq/~D} MrbQb1`EDD`DDDD?$""" 0"""" 0""""_ry`bP۶p#vU"L44wM/"GqmB<M("{ iÆ $$$iIHe6 D" U "?wx>wNn'"#"˥#Hwof[ GDI9Mo;mSy1]&DУ#@,aժc`DDFD0m g˯Lfy8C׮]4]$" 0"a8qgwD#vhaiQv`vr\|Xp</eh=$QvIJ-l5i`q(+/#3#S%"W'"5k`=0 3$"BB!~JJ4pom_@ϧBvဗaJJ (//'''G!"-[DTZ"G(K^^* 94H{DDDDFDDDDFDDDDFDDD`DDDD`DDDD`DDDDFDDDDFDDDDFDDD`DDDD`DDDD`DDDDFDDD=sO9}Χ>'5A'Mbf9b8>CsKêO^ũ;qo.g'~sSòc ?N حZe@?=˖'~ cWoaS,Sn) :Rv O?csE;/:\9;j+s~s >OOa8g$&W͖s9N_i"[v!m-x]O\=P5ZDDBr0-fqu QJkk.HMc`3 Afh Fp'-%+HcC#-454" @cs@ X(@cC3Qf[hhh"jmۛMi v Ibp3o,װbU nb[[hml[9 e>ZZWzyn$ڰU5@Eln JNJT`3.i1>{oLh>,1ꀵw\|'W#cx|Lɢͪ""rD8hJ eZR : 'JCM5kØDֽ<>6nId('dy*^翙F]7Кڋ]Z33N+*㑿q(_B9?{McSV^K^i)ޖ*6>/@aW;R;c'0$߉Xh4bѴ=~"; (>JZ^;!Pħ39ܮ`%R^TX7>cږ}!Ţ:ϱI?N0ks#gH_x펗hJNK/R/sƮ&.H uM,^Lf Sֺ:_t.p61vx|L}>rǝR")˅o:N#գJ-"" 0`xrKO]@;t2\DR\Myu/eozeQZ@.|.&.ci{- oB(fK"6`'?7şΠӕ>,{2qٝld̘2`R7:gc`6Fqq=.6`܉=0BQ+J,w4G&jXܓϣPuZDDcG D,8)))lݺU*"?V).\xa]`ͺ "DC[Gaz+ض]Bk֬>hQ/M||< r "}b lRi_ 4M,]̇Mn[vXD`D͜g3Yz1S?{wee^bohb&j,I4|SԴ!~<M15!@@P@:R[fNJB]ILpWc̽ssΜHpSI"'hxlܸ\.%\0B ٸi=exHo Gu]FDDFDz] 3NzqQ"#"G5F:е^<ϣ‚Bu|i+. /7h4zXMffiyi`)ɟ40qp&kS[9CXʃ,bӮz̼sŧY>&}ۼ #e dt3\-y0vGdPGmG^Rg]JQBi) \?ٷB,w˦aֲ}W- q1`wJ2qNo`rCoNS} q'Iײy2~svff$""rXgϞmu@?& `GU,-[{yrifѓ(8j]ϲ c̠0F( x |fMo,ij7KH}$6so=??Cֈ1D)veTN:bg22 Օ U»am"Ƃ0yMk_2n3dHu+xWHo]F'wO/e _cRf c̚Ƕ% Y^ŌihTIDDT`0er y rC6uʷ/Nh]qz` {g%Y4m^?$ Ρ C>ϬH0ͪ8v+{L/-zyƆ?,_xi~~غoNeR:t;S#'H~N-+iu#l8Tn,ylf]?AL6o\YLU"""~1  n;3cDlcuٳ{c7?4'=ۼ7"$0y!HV6Fr7)%3wr@ֵ1jRWH,c$c Nt Mkbj|ٙ4$ΠN:XlsɌ|$cnsާfFyCh#o\0mt:Mhsjna/$3ô|Fs9,x6\ ټG˦T6Q;Ʀ=U 賈7ME+@ ^!g@rƌ&UYSvzw"G=Ͼrj\s|ӧm{8x\jfq_s|d`;1L溝,'lKye$""*0aRWĎa틆S] σhn>euy3/iw$̀qICHm̼'OEq މe,{W̯xWV.3<_q]D>S6\RXdcDN`+rr׏NryyKî≒oq }c`P2Rǩ# 00?nUq4]w.u`: `Z븝&<7zAvn);/\w1#>)=itU10>z㦨߱o8%>iu?cϻw1޿5s/FƢXEw"snm)|V]X,/W..cѽ9ZR ]<H W8LRgD!DNKcܳx3c,2ٷr1^W˳ӹ3;p}_T3K<㽜xzM0p4){'!Ց=?i^5zB2L@.;mL!u2isclh:ԥ- LZ^qܦ50mp"D:/ZFDDBv""""((((((|=5zx]勈E1JK1 C)"""~qi֬[ ggX<Zkvmw- ɧlP}Ғ|{+)+b-GSd.u6ADDD<< 9䧰0p=ٳbA2A /G$}X/-҇fRs.yջ4=B P0m,c! nv7*Čv>0/3,^Xɩ˶<˯X̚,6a؛fWswZY)igSf;;,FY!r5&""g=& ~!?&Aw_o=GΓkz7wr4g(ZN,uߤ`#ܿ`YCϸr|#O[F陟bFs%e1sh?[Yq?9W K;R4YDD#R  aہ3`je*h9;$af:gz&NcNgS<)f-('5fK};L&peC;AH'mlqL,\)ǦUTR~wZOr꫷ahwY0dN5gL?0=\}{g[cKÃP??b a?E.?yiDv4 )1aeT gI\>} lKyl-]ogp@V-[BmkK:8xi::A2yeM+{W-'`nif3; G1η;< BlxQ^ؓ|D*?Nes/<+,#""gXgϞm0 :$ \%J|'Jk0L3s1 54g0?4*LQfaϳ΅E eMŒiX4'^ZKԋ|J1d'g.)dd,{i%ccT/yVb:O(%F'9w|@"J1"""}y^?'MK9zfӲ'EDDF((((((k IDAT(((((|X|S 2q\U]M#k˲ys`t 0"""@DDD`DDDD`DDDD`DDDe"""t=\4 >\ĔL:෰*0""ҫ5߸BN:ϿWOU?'jL4:eߙKmޅ#K_ȍƩ-~7 pPFDDzW~ǵ߻7q/_=ե16 0qr*Vj&meFrNd|pblZc9fx?wr M>\@UG:mpY(=Z &MW.GvUdSl20fMM% oͺ$η4[vP gbΦ% 0iifMAo=3i;EyԢzo 5 &D-jciH 3Hش;ƞ 2yPt- XU-PatA#`ݮVS`~~fϞ=[DDDHbl~o: v6_߲0'#eӿ7 +ϵYlv0-G41=?,Gs)F[r ֽ]ƨSh13N,~s'??[ܷxAݻj?? 9>ʹ`i*[&02vglNFDDz@!Mw+ÿ& %Q}aĵIwy2rHNm ;z+*)ɒyf`$Fp7G>N%<0yJ{PZZ 'dr߫πX}-̝<ƍ-njam` <;8S4dz[ydwaãsH#{pz?|fm-aN2>j/6 -,lG/4`dgrZYfGFDD>%&^ ~໛w# 1@.>+nHJ[%L7ןdihkm{lm!1:Ki5.a^0;]T#Hǧw! TL1 1h-V#"""G(ŀ)c !u4];d#HFDD>l~>T e8NW)I0p4#""h4J]]}:رɷI}St:7_ԑHe\oT4q3I;.׃ ?t,Ya|4hIDDD`DDDD`DDDD`DDD"%۠aIENDB`horizon-13.0.3/doc/source/admin/figures/create_flavor.png0000664000175000017500000016352713553660754023467 0ustar zuulzuul00000000000000JFIFHH8Photoshop 3.08BIM8BIM%ُ B~(ICC_PROFILEapplmntrRGB XYZ  /8acspAPPL-appldescPbdscmcprt#wtptrXYZgXYZbXYZ0rTRCD aargP vcgtpndin>chad,mmod(bTRCD gTRCD aabgP aaggP descDisplaymluc" hrHRkoKR nbNOidhuHUcsCZdaDKukUA2arNitITbroROvesESvheILnlNLfiFIzhTW viVNskSKzhCN ruRU$frFRms.caES@thTH XesXLvdeDEdenUStptBRplPLelGR"svSEtrTRjaJPptPTLCD u boji LCDFarge-LCDLCD WarnaSznes LCDBarevn LCDLCD-farveskrm>;L>@>289 LCD LCD EDHF)LCD coloriLCD color LCD Kleuren-LCDVri-LCD_ir LCDLCD MuFarebn LCD&25B=>9 -48A?;59LCD couleurWarna LCDLCD en colorLCD *5Farb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCD000 LCDLCD a CorestextCopyright Apple Inc., 2016XYZ RXYZ e< XYZ jXYZ &[,curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y vcgtVEA8 0Y  a l5k6@ !# $9%Y&k'm(\)B**+,-k.=/ /012Q3!3456Q7789x:A; ;<=d>,>?@|ABB BCDEFG~HaI?JJKLMNOPRSTUVVWYZ [\ ]]^_`abcdeyfggThBi4j*k!lmnnopqrstuvxyez|}&~:I[u{4㆏P% (>NWZZXVTQNKGB>93-*0KI%1;DMW_hpw~ſ¯fŖwf]WQKGHTqР /CIE8%ޣ߃gUNMOOJ94zHGx^OMjDVE1 6 h *wy25X !"#$%&'(r)>**+,j-5-./0W1123v4=5567O889:[;;<=\>>?@[A&ABCDEeFDMYlѶ޷+;@7!õħśƐDžzrtˉ̼}?Tfրץ-Lpߗ905&V'z; DV.^) % AZ%Ejx B !"K"#$'$%^%&'0'(g))*H*+,(,-c../>?4?@zAABOBChCDEPF FG|H4HIJ\KKLMSNNOP;PQRkS'STU]VVWXHXYZl[$[\]N]^_%_`A`axb"bcdheGf7g9hIiajmkal?mmnomp5pqrsPttuvdw&wxyEyzl{{|}~Ào[E*ԉZ$͎~[:֕Ɯ؝ 3H\o~a7 2P{9pɿG¹Sǖ3̍`IABlndin6XU?& P T9&f : '0:DOZfs1G_w(Ls8vG3:a7   4 =8HtDTO_=}^ v4 r!5!"#$h%<&&'()b*A+&,,-./01{2123454567y8d9L:0;<=.>@+ABCEFGHI$J4KNLqMNOQ#RWSTVWDXY[\b]^`abbcdeghKijl?A3BdCDEFGHIJLM*NVOPQS$T_UVX"YjZ\]W^_`abcdieRfHgYhik lbmopzqsBtvwpxz!{v|~5 {az0ݒv4򙱛u:2'h=9εaQLŸb:!vЊ Ӣ_MlۧP&I2 !+6CP^m} &@]{ 3aeYC=L s  V  q1uYIJQJ7)5_ytbTN M!M"L#M$R%_&s'()*, -7.f/023d457P8:?@BKCDEFGHIKKLN.OQ.RTCUWmY Z\V^` b_d[eg;hikRlnMoq[rt{vwy{}UVfG˔f6͟f8էuȬG/FY׿Yĥ4ǻEx/ב'ڝEފU1k0DWadP"9Wr@S@x!Y1d1esf32 B&lmmod˸" }!1AQa"q2#BR$3br %&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz w!1AQaq"2B #3Rbr $4%&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzC  C - ?_xg׼Sm[ɒ$Td2kj"*W_y4^ybX+GyG!{DaVlIJ[Qr{#? xTmZge^Xa0 Jt+R om0r$/davY*bx*?l/>"wt~7>YΩ6$B3[(n_O~ֽ-Mm-E~hIb~7ھs? /gZ7!42!)C?A/ ٚeU2"W> ך5?' e.yanMZ@-]ë+#'iukTkg{[Ϸgj;jt_|=ӚF[ m³#H+zn Mxgǟ65]KM3q6=@шH (Wiz_oޛnd߭/}yE|_<$_G㽎k{RY9ep|UU=Ux| dOgxT, VCn׭{Z+'}:~ڵVV6Q;qE`$_hwciG/?-I%~&~β=^ 2?<럴:<0?D?s9沌)GOe6eݵ$~2|! hR ie2jvʕ ,$ĒRXnr++;㎃GGg[խ[ȋ5&[fy8dr~Mok![BSԗUbohT.Ya6oDMI~G4W?mk >(kyaMSLYkwxtU")K/0z?l]'Hi\ m:kNU cN"`v3JNM5-bcxBOx u,m/-qE#T5U`Zk?|b W |`+>Ap2]dpYJ0k}۽/7K'>,< uq\xW_x&nrnK[{o yDUmT=囃nQE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@ѴY[}^EyoP+ܯZLLpQ*{_ڶmw}\ I1Cs^ڀ= mɣu$=;|e8Xr@Xp}:ɿƿ\n\k5YlNL1K9 2ːk Jq~Y߶HA"m}j n/FlYtDjcu 5G/[V47Ei|1'$uaeosS'fִV?//oO|ki)dd (f amѮ[}UiWŏOI^"V#P2p:2l$6E~%R<‰$}[{ӡɂ%?^}lkۼ_qw],f{IXCOڦ=not$=Ms e2<wGj?L/5NJuN_"ՌR`άHYB"bu 0T=1M hVѬH:*ZkzAUs+>'5y$ OI_'= s_5-oúuܗzi-!F reI8j R}_sU4cͿX?H_hQYY#5$=¢I'|+~/1ÿ<] 鲷M476".|۷ȡB܊[[i$9R SOJޒտ;y%9? 1-MӬ|[ FqcCs9?6Iiƺ-φ|fRDz>O{_РYcQ3 M[vg%gxFS(wA]?m/E?4WSCo9G-?91}BsPG^} oOz/ mQ >C~/Ey-?91SCo9G+Ϸ0z Q oOz/ mP>?4WSCo9G-?91}BsPG^} oOz/ mQ >C~/Eye '%4MSĚeMhM=t 1\jׂ;{m=G?fb(LB*b鶒 8Z) PHjFX3JRI]VKjO,1\*A$m$|AP]NQ]+:׍t77fF(sRTd#[^DO{|?f8a!$<`Sڼ@ Wwm>GRK}b׵R/^As+HzPr3 2ms_*5?[WsG џbQEs&g|.k䰴yw A@PI'~JMF*ퟸJ)GEEpZ W{ 1JTnQEf jP7R-?= ZWMyQYCT4MY6i_He.k:uη6wʜrUm W徥(((((((((((((((((((((((((((((((_U|gaGJdܪ5%WJ7/_?#kꏊ:ׁ,t?Se&pDᶷ'q_+_c0 )6okMfV'=h*Ӽ#e^CKZFAZȧORmƟ\K6жyn /<#K;w_4[+4cU[G<wg(jI]@a{ŜcԞMxᚊMwm^t7~ˊ)8: {7+u/i O 8wZK!9#@qO&KC]"f2Z۬S$H+?(ORMxF;3g],үR]5ݽޮ>hpc3R;>MWG5;~֠wq}ƚꦝQ=Lxݸ:s;7m'86-ʧ{-%WB}nկiy?-~n|C¾#Ú-k`*2 *p1Oǡ 'kKQgm..%Ph^HY3$Axp?2ՙX2s;Ź'?gyTvUmtN7t}5]W{+z6}{{]&[߆|ALqiqk I, UG9`I# n o_wmAZ۽3_ #gtlT8s^unoKiWN$JiW^E%i:/O\B ;-Jc!#VUQpq4Q_GN0n$MUR%kR?bO%ZkjԏؓIVaھ;?T}wz3J/I[+gH܉0m=Xյ~ى)Jtޏj<}Gþ  ~LrH6pgD׼YysMN|7qj0Zj)3Guwah#tE F Ҁ$)[iY.{u3Y<߾{N[K-O.|7-cnuSOʲHBW1^'k>~O iv]apqg݄/ H0\-aW3P=]\",Q"I#X̍3)^K` S?n'nu 􉮦/&6 FǒeH; rGޥT=)H5K=wKmS&$R~Z|[i[<1-,gAqq}}y%t-1c(+ Jⷾ xPZM޿q9.X) wcq;Iv5wcZIS7On]ooPMR3S;w׮趲Z(p((((((((((((((((((((((((((((O=CJ5?X]n!k22:ʱ F"No#ϫJ6wVwh񳬊>Z2ǟ?v E}?DwC?p??'2ǟ?v EA"~;+yc'lX??_4Q+$p??'2ǟ?v EA"~;+yc'lX??_4Q+$p??'2ǟ?v EA"~;+yc'lX??_4Q+$p??'2ǟ?v EA"~;_ ;gǫKuc*yHI{2Fw,M"$a $2H׻^Fu8ucUE+DV[&.ݭ_AEWRQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE]ͅ2ω$V-#zUD*?|8ó(>i\<譌qԝߣ_)F~XviKDl2[W>xkþ _NMе RXrYogT3M}"x!F9zo>xZxL6rdLdێ$ֵkY0Gnk;[WE(޿?"jE+w{C>|5g?Fa=#}jE(ԋW }Gj?]Ϩ6G{lGz3ԋQ/ g»Qle ؏giR/Q\_+w{C>|5??AR/QG^Ww3?͟#Q }Gj=~~b?^HEqg?F g{,?Ϸ;OHEڑz]Ϩ6G+X?mo_vڑz?"»QlWw3?͟#Q>޿?"jE+w{C>|5g?Fa=#}jE(ԋW }Gj?]Ϩ6G{lGz3ԋQ/ g»Qle ؏giR/Q\_+w{C>|5??AR/QG^Ww3?͟#Q }Gj=~~b?^HEqg?F g{,?Ϸ;OHEڑz]Ϩ6G+X?mo_vڑz?"»QlW4j?͟#Q}?㿍#ށOGs~tOq$)oHQm9z+˟:0sXG1Rrpl7(hI Vh|;:H#-2 σtoM8j?z޶n5K!",p\It^8$W_?Ao/X̞dD܅Yw)#*A5SX? ~[::I;| E7/qL[~%Mö4VH#E<ڹػĶuܬB[kxQ7|3ծ<3nbѮl>gc$z, ,PJ2Ժ_> ykEΗg=1c~׊h J7ţG|dQ"Z'@hli{>缿/Jٯq4C,AR ͭwŸx<_^Omsuew="[([i=&?*8=W7}}4W_g4O[km<|;{/\^j,֖|K,ɆH^Y_z q1$)τ9|P`TV)׼W"ӵMv-6X ;n]@!b㿆iek9'm2qui=WVm+i"2T\Ey'k?xkO'.5 Yԯ⾂mt[r<ċn2D.VSZ?J)yWm5~='V{[-uGnRn!YC1bgE!g^$!ܮ:]mM=:B_TɭKn⾂q4?kʳg`w6>Zxz8,FӨ:m.b$IҸo$΍x~m#u/tp&ڶ !PvKgZmEۦ;Kr+.f+%/Ci<6KW׮5Vci-MPte jv?/&AwI{K /qhs`k5,^j9`sgNMS[u$CwtQ̥dť6;lʱ$ēᧃ($COzf?tDWdvQWsO$)]4dm_x;9CoCj-YZ=UCHe+?"".ѧ'@{ƼbUF^I$!;G2?L/E|uGb1b&/nQ-| BK4k M1\լجPGo ={qrĖ$zZZW'^ճm">;dӼ.^Oo\6mW )-nyT2k%7o&Yi Χequ:o("2Zjqyllkdz̟X|i.^Fbv>oZ"lx//χ`[{1=6P B0)ҲIK&-?;|OAgC^E?Cs]~HiYedG{o1O/uoVP`6B,L9PS 8FxÏD|G%PAQ楁v+S̏ @#3^Kdie-Ynno.漴IgF8Dw(@^M*i+'kmzNoo5u{mN]Ly>v!WTXiLxV<6IYȻnqɯAG'u jҴ:vnBl ]̟&9?%4Z4. |NQ[ $W-߄?텚` c$Y ZGk im|$&+ qHs04R2&&d^}ͯѕJQOWW=^@񅭅j/ +5@7N$ʸay|d<+/-5$xKqs[?@gSlG F~NheL7 GOH.5=Bnq^myQ c۳rs:?]n}cL-%t;e|9LCO07qi]m_'_|5{OFφu5KAZxG iv"fA$AT'`~վ<|mtWO : ڬRI3 N4:|"'pL"dR+ݵosv_ڟVKơ|צQ-gNjHY}NތR #[즼Cs-J-d6"8V-&U >GC+_$D/][=3Nҧ h$;b.wOӾ?ao C[= |@[Fҭ\ɨpesteLfx"H4nc_cxsÚ/xtݤh,+Y]0+ʹ&񶕡u(.' & 'C+['0W^mv?g_|.u?(-V`5n,\I)XǷp…ۅ9|9^ok쨬.fo`XwQ=6jYM}-j=4LJV m)k #8umqҼKZ^>gX&:_]ߕm]M#A>V"X+UH&i^6o;ŭ|5>&λz'ٛ; 5@\ HwȥJAS_oVæhzɾ!-3fM_N迳mGv....Yl3),r63krxZZmF[iUnȃo%՜rŵvTg 0鴤I۶g~ZǍxĭW寃 jZr3IhU ޥmo24r2A]y_,;7[Ǟ8k Ju-jyCq{M6po,bZUφbsM,w6"ygUe2eyw?qii/! -XCݕTvР ߧ~*G?~;w72bxl-`6qrgQ(vHcMe|iˠkIԆjh ҝr-<+RxnӼkxE5ez-\Ciqe15R&6`trH?dt+]G0ZWwV)%'LQBQ1df2xrqk8ߓ]o|_Mm|޺mWWG5gYQ:=#Ԭm1I%WR@((;j_?ïZֻIs{zb 8DV`rI8q>YEo5 :JKe֓IwAiZ#sYɚ |6-.} BlS5N. K#M HF_*_o6rim/,5N#]G[,iEqc&Hcy1V>sc:N%͔oGl)Hfo#yџÑx[{nDC^h6ɱU2Z,o3a6xV|J񥮿[jVz,rhv)ah_LypHfc8}YBZֵnv}cJ){~6?)ix(mRLHeG*D#́ǼdcԾ#hgZ2[pLڃ_e$`X %3~Qxb- U[ YtXeKtܻH*Ե?|!]`7qYV1 0f#v:ľ=?7 _]5hf?I+{~ZխtItcmo-/k`MĐ2ʋ ">M'ˏjZ:y-[(ifql#I hٵ X#{o{K-li:iViisfc8]Q'V%>$nz;W=ǏJ5f[ZŜiU̎,`88kK_??OJkQ]-fp.'l3Y?;=^>$nft4ƒ-Vsj<pѬάAK%_$&5B;_mD!,B7_7}S+S:h:^Y HGRe"dQJ:dcߍ[ 9m?K{~ItK2}*kh&3kFkoSd?4ziwz}q"u 4L#t9WdmCA'$iguh:XhM*9t+K$!v}C_3klO%z:?cme}{n8i+6#o|[[>pIPcJҭ"ڥjzΡE8%xWYZPYS1 <iZjV#'D-+[젴fa#J"^bɸW~̾/ҼOǪ#IdMI2nyńw@;poLY Z..흦8(o&7#tq4܆*[im[yrZk^^O > ςFk׺mޢU]-HŸHb2qT+xql5Ct?/yuy>Oy⹏߳v^Q}O^j:km.f[iw T2 gN4'[+%fYϮy/j9*Z;zD?O v>LjO$5H[HYٳ2}Q$(Ve`^Vc W]wOR7tgE67e+h uɊDhсpzcgymIj5Σh1h!xy$)So:\Dq:ٻ/_roúzr ^6I-RA\A+"EB D.~Tvo+~5<5RIx?cRM'ֶћJG%HS2bbd%YAk]@5O^XbӶOXLȑ3(byeb?~Mti< F'lesV~fWETQE/+mԑzw~A]"E*k|gqu%/qi4{o0\BNN8NHJ7mS;mzsUV 5{z;mWHӵT"{{]̸dHձH*3;=G!MA-SwzԨ[}?\;=G!MA-SwzԨ}rEy!MA-SwzBZ R??!z7O? o j3}J?+ϿW?g{Ǩ)%Q*?\>Q^GSKT;=GԨ}rEy!MA-SwzBZ R??!z7O? o j3}J?+ϿW?g{Ǩ)%Q*?\>Q^GSKT;=GԨ}rEy!MA-SwzBZ R??!z7O? o j3}J?+ϿW?g{Ǩ)%Q*?\>Q^GSKT;=GԨ}rEy!MA-SwzBZ R??!z7O? o j3}J?+ϿW?g{Ǩ)%Q*?\>Q^GSKT;=GԨ}rEy!MA-SwzBZ R??!z7O? o j3}J?+Ͽ|E xjzt*I{խ".Esck)]^\\e9 Yr A(ӄ)*Pwս 0Ih~ˣKEW3?4YxFMBQقX!-ك\&qJ~;i3~ Jx˘LySJ926; l1#ּ -ψe'[Zm%{ᶎUy푊R/޳I_]4_ag^,|/sZjO3]xn쉹_@1OBw1IXxvqLn.{xJG+#IPϣx/^uڧ 𭯂|Ckw:neQ PK)ae[Y䉤؀ȼW~0źk[JY.Ӧ?7؋Hz֮WFnZ_~v/=>u赹8c:]YY"?o/~{\|-.Rxrض<+۽ʅ"W u>O޳smk-A7&;cc4Wz}א!= M?v |S;ocyӳi>~-~J?SxT6&Ek5>[;!*yO±#623Wlj| -~O,ׅo،45x+-đ~$YR33ubڇu|#PX- C[.uGi4adT^@d 8_[tk'Wo]WuÉ7 >2YkK+u{ WlWyF8p<ƿCqc5υ5?Cúu p^Z˧\kg%̌۲-uI񽟟Kkߍվ?woi=|CZOao zEјGs-arQ5ͽtw3doiXoM:Mő[vim ᓬrr+WO::(((((((((((((((^o|^m5xz Wd+p aO,z'yk\7~(=$3^f'Ċ⍆ۓ4:O-U,A^Y+dQ+9@03ɪu寥ZW)Cǃu8;X!@];0b?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(ab?-}(Ҥf#ҏ-}*J(aa:)\(EP.đZYG},";gC*20Zڢ`+ZMcvdȧ#r89<3?Ol:NX-]f.ԖbY$I$GE(((((((((((((((((((ʏS\phjWVis{?So H>.3`Hۤ֯{;YZD+ eYt< ǝ|v^trMI]'\ DQ6BܤsάHqP*O?Z]Fw:DsMv^H;#1La02UR~7x]? Ron.nj,hvొوs/!q 0MM7៍!g%1Xx+ |7%4赻awݬg\5R7}ij䢺z(>si 4jz͌zYyZJEn c<-ckŠMho-6_;:Dd8pU.20sҼ ρusSĚO]:H7Ae>0j x⦵㏅J|=OԚyt9e|#,odH sr*_n+W@ՓOmcs=xJWȲH#a F3]ߍuؙNԷrm*66~cjR^}kr[Ư{4$m,p'F̐q>!Nj~ '/ ho$`m.ٝ˫IUrx'iO__!5|&!zn4i|yaT,)Lؾ`rPe3^X:,Fxnlte,}>ROԵGBtSKV]tqVYnMrz> (NТ((((((((((((((((((((((((((((((((((((((((lt?XM Jn'|"P ۿ4Q\"uO_:'PAEss?E|9@@+[ߤ ?tW??o~(WßIQ\"uO_:'PAEss?E|9@@+[ߤ ?tW??o~(WßIQ\"uO_:'PAEss?E|9@@+[ߤ ?tW??o~(WßIQ\"uO_:'PAEss?E|9@@+[ߤ ?tW??o~(WßIQ\"uO_:'PAEss?E|9@@+[ߤ ?tW??o~(WßIQ\nzfuksiʏ9Adr:]QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEOǔyVbx<(6袊((((((((((((((((((5[]eԧ8^9E yĜ)7PcLҦk ebŸ>Ts~tƯiprM$cIjVH.?eW2|E=_H{{[iI]AVˑbؠ &wTi[uouO@5@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@WO)nl;%@,"-FCuTP7$~O?R )kh5EcG Z?#-l@{?ƏH=AK_[4P7$~O?R )kh5EcG Z?#-l@{?ƏH=AK_[4P7$~O?R )kh5EcG Z?#-l@{?ƏH=AK_[4P7$~O?R )kh5EcG Z?#-l@{?ƏH=AK_[4P7$~O?R )kh5EcG Z?#-l@{?ƏH=AK_[4P%j^]uWw7Q< }o{=J% n'NJLj|/D׶m7ye$dgo_¾o\YIyx%$Tu $:Uz=ɺ7K_}%Ex%ǽu+{ ./nc %Ȣ8\)Iتswh(#s"O'$2MK\lAER((((((((((((((((((((((((((((((((((((((ok*SZ֚Kס]*k;!.X[88ܖUm0N2 5uPt^Cnd $T$} 2z5o?Hz>2$+gw J>~h6@Xa~_5xbIn?X00(Ht\e@B#-{?ƮNoĭ-oS῅&5ac"U[yR G>`;I7cw͊mHSľִK0J ȊY҃rFH5'$~O׼1)C.f3s4gk *_51|Wv&AF[3<mi4;['[oċ=cچWgam2{hHٖ9C8U8F+??R )khU;z$JVl3^o/?v֥.'r o.IW2A,FN+2|1}F۷3=_H=AK_T奿 kv6F )kh5#6hoH=AK_G$~O +?R )khf4G Z٢#-{?ƀ6hoH=AK_G$~O +?R )khf4G Z٢#-{?ƀ6hoH=AK_G$~O +?R )khf4G Z٢#-{?ƀ6hoH=AK_G$~O +?R )khf4G Z٢mQz*J5@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@sCHPurR#GMECh?(45٠pTP?fASQ@ >MECh?(45٠pTP?fASQ@ >MECh?(45٠pTP?fASQ@ >MECh?(45٠pTP?fASQ@ >MECh?(45FVӠuw6'Zh?+5Oyt'mM/-)7 0V-4݇cRc@(m`Io7).7kn ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?OǔyVs6wh~gTxeẽհFqҀ:j+zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy] +zy]zy]1w}'Z<5ڼ7 8c x>/N ;D?uwG%w `m3g)EJ}9+HYVb0KRZzߡQwO?~L_ ~ڟ}Fú^!M TVMČ & `o ᮣa'uVh6q0GEI*wyLz גZ~͞/V5{+M|EJU!݂č0+߳Ľ&h7cuzDY)`3tŒT/ʿS2.KF:hs %mY[- 9_hKxĶQ^~-deږ([ktlDJqScm΁iVe>P ~W*@l|O) ?JIcf,F'`sӑ*ۙ[m_U˽~)3]l/溷Yc(̞E%± Iwsɼ1UxR𾹩,w5mgzQǽqA#xnm|^GÖYtMPZ$R p+ehZ??E4}:.Z$L 8p_N];s{i__3ϮxS~SүYam9fyckYᗌ5^hGOHf~u`062ΑajVXxѢZ̎)mLWՇUNT즗7jspTUSvߐQEuQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@wMwy] `׬46'1@$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?btP$g\?$=.?m?0Km㊺7P)t"1,#iU2,JI7:3[uh{Rm)o+B[QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEOǔyVbx<(6袊((((((((((((((((((]gFJĴ 7*3CO%U@"zJI{*Lqm_M,u94{pE*AK8ai8rYfoĒh]?obxES mEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPf#wOJ2Ls݂:WMEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uErڭuƙcKv/o,J T`=u4Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@?_|s._d KwڥkocO\"Hm&a$mlO=+qYt5|A^x73?ѱ]8rrv˟]p|ODۧ_g-Ok~":Kjzuohdr$85پ?N x{R[}68n$GpJ=rv˟]pQo/Rwg*.|K][\o±zj4aȶKY`ÿ#:O4}VS^O/v}3+ܹErv/'K[$>ResO?JO>ak ^BH9?ƭ&=Mޙ nVE(˓޽rv˟]proB_lb=$ 'v:Bl?gRP lװ:uńW] .2S Xy>s@;G.8?).Rꏘt~0״_ N/lhmM&PF%ݴ0lˀ@#dV3?g-`.|R"e1iOۗ?n\"Wn]zdo7g'L@lo~6v+4$b>UL9'+kx-b8QQGo˟]p?.wi'ޮ#˟]p?.wiQ\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\.8?ۗ?Q\$ )dY@O;] QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE?,=zܬOǔyPOÿ Uҽ)c8x1HS;Ep }۩G4772B'Wr:)q el$yO袊QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEg˨H gc>2jR&wSkiO8krmSۣZ)!́RVorEg[~Ӽ#xOGu- }KWouMB%KR3-k.w7o꺿|&ua{g#mwl:da>ڟ-)u+};O5+EԖ{آU04_>r Nx'? 4~<7)y,W7Ry7G‚8IvݯOo|e>鿴%/z]ꬶB.Ͷc 3v@ϭZkWMa}5ߊoԓTH--)m3?s͈ѕ K]#|=ғOgFfDZUqF:NNr =xiZJu.UkkުF' 0#0-|?QooOS|]K6QEhKqs]lEap'u&GNM7V/>ȳ UUq@$rx kuȷwve툎y#q*ߒevd*] , UsjWwRXMqv%E"@Z4H9)Eo7O4?~LZn.̑6d.I7|(0+&uO%Z){PHQ==p5Y&跫g:4 `vFr1] O4Źt=RD6غ?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K(#m#m?K>?K}ַ1['0F$biTi8q± k[\Gu Ovcդ֣wg3w!g r>|.;Vՙhu#J+A)_i S_ꭣ4(o'b^[^O+[H4D<ܻorY 93wv?ns\ߊ~/d~Q_~q%[[[{I,HKt `j$Dm~Ymc-I&w@e@1wag?>ᢾ !+DZxk4O+pg0rvj)j> qY\Zj^X=ݬ7ڋ4 $$9⫳{? nϱ?h?RxK&t9.㴒c{jn )+QW_|Fhg; ;Nӵff3Gt$9<|_-oR\v*FQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE>>ӵzxKidĆ4.5W6֡,Ϧ GQghث07_ZM!|5Gᇄu+]_Cv]YZ= 5ЊF.Ѣ$l3a 3τ40=d)dƒꁜv]wy~(O?U~.)'|-wMvXr [ P0Pu=&oF7o1M;~8/iQ?6~z(bi 3{u i q+wX/5g#a'Zm-KYbki2 H3+oG;<ͧ븖c/nMc;+ 幷ur$,f\("'KZ`^s&#X X;Qs$Q?6~/i?OO=-@F|8|&'s[#Q><gÖct{ɱI-c=Iy~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@~(O?GwQ\wy~(O?@]\z,qn} cZ((((((((((((((((((((((((OǔyVbx<(6(c""8R88pAǽV,Tn֞kawesRqʰUCxg ÐxD{_=x|#x:MŤ\Y vb&#rMA~qJc.3?J.7kn_FmuȚ!g^Y|goEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP(((((((((((((((((((((((((((((((((((((((((((((}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E]}俔E((((((((((((((((((((((((((((((((( ԯuJ,"˾ohf˾oh˾ohf˾oh˾ohf˾oh˾ohfĶ{MMt뛃w<6U`vR~_ø Roci8|rdxf"a q'F$W5_l?j?6= q!$~_?mz?>a5<>ȇDlO_E~8Q|Ck #"??G} GG 6GC\wF$W5_l?j?6= q!$~_ ~}:otd\Q"yRFVD`|Acz )VmsJX*+(((((((((((((((((((((((((((((((((KcI᭚XxkVd2.FG^jdMگ͍/^5߉"ݴh.#pHcIƙ"h`NA޾GQm>j4hF].i-Em>*.,wu+ooz]=@/_GⅧMc0%AHZp 6i^;/FQ-}>ٽH[4+O;B>%k>)P&[Œܳ[ClFa3egi _ xѼ.b/a7W}O yP/ApcO6]hɿ[W4p{tOiZcյ+=*- J"pq=5G>9)Ol좚;;˫$[R#Ig*?UXr9uk_޽U//^40׭|p-?;]WSe^_o$DIu^ ƇqOz gYG4=?Y>a3N;#>w_AxVdeznڒ\-wpØ䐄2FT?fxӥmRnײoWm+­^Yv_jy SN Mms$R eIr3ؖ#xK^|GgX"[2E`Ho*#nx:Εse&bVf-dYeVM;X+BW6[5=Qv+4uieiQou  $ IR-apT+Yŝ7"wd_WiSR FcqU1P[+U˻ԯemb&I7">,l6T+<ꎝュk[.KJ):߷MV׻w^O~ jwznuieI/g_&fgcj5:k6LQsz.Z5UAF fO@~$'?oj,&׌DdwܣA @7׈j/RtxvєUMEY 3JRI`6F9^?{wy\圯&䓿mz+4 N%la%1g$c!G9%oa+Wzq/_u{>kmW>%[_J׵];]nr.Em8)4@;u|3,O++ny f''k (r}4Zv}{#2xw|޺z5ӻ>j_ҫJN؛J`+*oD~?.^(((((((((((((((((((((((((((((((((K _z [UuY^J.%WY@۾)&+LlcUl\٢l\?l\٢l\?l\٢l\?l\I>"9$8*?,H r _^Yv[|#t3Y&ew1uHdA`e 5T]Es jP#E~(|ea(|eaDO_GCwE~(|ea(|eaJ$CwE~(|ea(|eaJ$CwE~(|ea(|eaJ$CwE~(|ea(|eaJ$CwE~(|ea(|eaJ$CwJ 8\*M>'¿|3/i &d.p 'ˉxcn4ҲZW e3a 7vS(((((((((((((((((((((((((((((((((((((((((((((horizon-13.0.3/doc/source/admin/customize-configure.rst0000664000175000017500000003546213553660754023230 0ustar zuulzuul00000000000000===================================== Customize and configure the Dashboard ===================================== Once you have the Dashboard installed, you can customize the way it looks and feels to suit the needs of your environment, your project, or your business. You can also configure the Dashboard for a secure HTTPS deployment, or an HTTP deployment. The standard OpenStack installation uses a non-encrypted HTTP channel, but you can enable SSL support for the Dashboard. For information on configuring HTTPS or HTTP, see :ref:`configure_dashboard`. .. This content is out of date as of the Mitaka release, and needs an .. update to reflect the most recent work on themeing - JR -. Customize the Dashboard ~~~~~~~~~~~~~~~~~~~~~~~ The OpenStack Dashboard on Ubuntu installs the ``openstack-dashboard-ubuntu-theme`` package by default. If you do not want to use this theme, remove it and its dependencies: .. code-block:: console # apt-get remove --auto-remove openstack-dashboard-ubuntu-theme .. note:: This guide focuses on the ``local_settings.py`` file. The following Dashboard content can be customized to suit your needs: * Logo * Site colors * HTML title * Logo link * Help URL Logo and site colors -------------------- #. Create two PNG logo files with transparent backgrounds using the following sizes: - Login screen: 365 x 50 - Logged in banner: 216 x 35 #. Upload your new images to ``/usr/share/openstack-dashboard/openstack_dashboard/static/dashboard/img/``. #. Create a CSS style sheet in ``/usr/share/openstack-dashboard/openstack_dashboard/static/dashboard/scss/``. #. Change the colors and image file names as appropriate. Ensure the relative directory paths are the same. The following example file shows you how to customize your CSS file: .. code-block:: css /* * New theme colors for dashboard that override the defaults: * dark blue: #355796 / rgb(53, 87, 150) * light blue: #BAD3E1 / rgb(186, 211, 225) * * By Preston Lee */ h1.brand { background: #355796 repeat-x top left; border-bottom: 2px solid #BAD3E1; } h1.brand a { background: url(../img/my_cloud_logo_small.png) top left no-repeat; } #splash .login { background: #355796 url(../img/my_cloud_logo_medium.png) no-repeat center 35px; } #splash .login .modal-header { border-top: 1px solid #BAD3E1; } .btn-primary { background-image: none !important; background-color: #355796 !important; border: none !important; box-shadow: none; } .btn-primary:hover, .btn-primary:active { border: none; box-shadow: none; background-color: #BAD3E1 !important; text-decoration: none; } #. Open the following HTML template in an editor of your choice: .. code-block:: console /usr/share/openstack-dashboard/openstack_dashboard/templates/_stylesheets.html #. Add a line to include your newly created style sheet. For example, ``custom.css`` file: .. code-block:: html #. Restart the Apache service. #. To view your changes, reload your Dashboard. If necessary, go back and modify your CSS file as appropriate. HTML title ---------- #. Set the HTML title, which appears at the top of the browser window, by adding the following line to ``local_settings.py``: .. code-block:: python SITE_BRANDING = "Example, Inc. Cloud" #. Restart Apache for this change to take effect. Logo link --------- #. The logo also acts as a hyperlink. The default behavior is to redirect to ``horizon:user_home``. To change this, add the following attribute to ``local_settings.py``: .. code-block:: python SITE_BRANDING_LINK = "http://example.com" #. Restart Apache for this change to take effect. Help URL -------- #. By default, the help URL points to https://docs.openstack.org. To change this, edit the following attribute in ``local_settings.py``: .. code-block:: python HORIZON_CONFIG["help_url"] = "http://openstack.mycompany.org" #. Restart Apache for this change to take effect. .. _configure_dashboard: Configure the Dashboard ~~~~~~~~~~~~~~~~~~~~~~~ The following section on configuring the Dashboard for a secure HTTPS deployment, or a HTTP deployment, uses concrete examples to ensure the procedure is clear. The file path varies by distribution, however. If needed, you can also configure the VNC window size in the Dashboard. Configure the Dashboard for HTTP -------------------------------- You can configure the Dashboard for a simple HTTP deployment. The standard installation uses a non-encrypted HTTP channel. #. Specify the host for your Identity service endpoint in the ``local_settings.py`` file with the ``OPENSTACK_HOST`` setting. The following example shows this setting: .. code-block:: python import os from django.utils.translation import ugettext_lazy as _ DEBUG = False TEMPLATE_DEBUG = DEBUG PROD = True USE_SSL = False SITE_BRANDING = 'OpenStack Dashboard' # Ubuntu-specific: Enables an extra panel in the 'Settings' section # that easily generates a Juju environments.yaml for download, # preconfigured with endpoints and credentials required for bootstrap # and service deployment. ENABLE_JUJU_PANEL = True # Note: You should change this value SECRET_KEY = 'elj1IWiLoWHgryYxFT6j7cM5fGOOxWY0' # Specify a regular expression to validate user passwords. # HORIZON_CONFIG = { # "password_validator": { # "regex": '.*', # "help_text": _("Your password does not meet the requirements.") # } # } LOCAL_PATH = os.path.dirname(os.path.abspath(__file__)) CACHES = { 'default': { 'BACKEND' : 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION' : '127.0.0.1:11211' } } # Send email to the console by default EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Or send them to /dev/null #EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' # Configure these for your outgoing email host # EMAIL_HOST = 'smtp.my-company.com' # EMAIL_PORT = 25 # EMAIL_HOST_USER = 'djangomail' # EMAIL_HOST_PASSWORD = 'top-secret!' # For multiple regions uncomment this configuration, and add (endpoint, title). # AVAILABLE_REGIONS = [ # ('http://cluster1.example.com:5000/v3', 'cluster1'), # ('http://cluster2.example.com:5000/v3', 'cluster2'), # ] OPENSTACK_HOST = "127.0.0.1" OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member" # The OPENSTACK_KEYSTONE_BACKEND settings can be used to identify the # capabilities of the auth backend for Keystone. # If Keystone has been configured to use LDAP as the auth backend then set # can_edit_user to False and name to 'ldap'. # # TODO(tres): Remove these once Keystone has an API to identify auth backend. OPENSTACK_KEYSTONE_BACKEND = { 'name': 'native', 'can_edit_user': True } # OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints # in the Keystone service catalog. Use this setting when Horizon is running # external to the OpenStack environment. The default is 'internalURL'. #OPENSTACK_ENDPOINT_TYPE = "publicURL" # The number of Swift containers and objects to display on a single page before # providing a paging element (a "more" link) to paginate results. API_RESULT_LIMIT = 1000 # If you have external monitoring links, eg: # EXTERNAL_MONITORING = [ # ['Nagios','http://foo.com'], # ['Ganglia','http://bar.com'], # ] LOGGING = { 'version': 1, # When set to True this will disable all logging except # for loggers specified in this configuration dictionary. Note that # if nothing is specified here and disable_existing_loggers is True, # django.db.backends will still log unless it is disabled explicitly. 'disable_existing_loggers': False, 'handlers': { 'null': { 'level': 'DEBUG', 'class': 'logging.NullHandler', }, 'console': { # Set the level to "DEBUG" for verbose output logging. 'level': 'INFO', 'class': 'logging.StreamHandler', }, }, 'loggers': { # Logging from django.db.backends is VERY verbose, send to null # by default. 'django.db.backends': { 'handlers': ['null'], 'propagate': False, }, 'horizon': { 'handlers': ['console'], 'propagate': False, }, 'novaclient': { 'handlers': ['console'], 'propagate': False, }, 'keystoneclient': { 'handlers': ['console'], 'propagate': False, }, 'nose.plugins.manager': { 'handlers': ['console'], 'propagate': False, } } } The service catalog configuration in the Identity service determines whether a service appears in the Dashboard. For the full listing, see :ref:`install-settings`. #. Restart the Apache HTTP Server. #. Restart ``memcached``. Configure the Dashboard for HTTPS --------------------------------- You can configure the Dashboard for a secured HTTPS deployment. While the standard installation uses a non-encrypted HTTP channel, you can enable SSL support for the Dashboard. This example uses the ``http://openstack.example.com`` domain. Use a domain that fits your current setup. #. In the ``local_settings.py`` file, update the following options: .. code-block:: python USE_SSL = True CSRF_COOKIE_SECURE = True SESSION_COOKIE_SECURE = True SESSION_COOKIE_HTTPONLY = True To enable HTTPS, the ``USE_SSL = True`` option is required. The other options require that HTTPS is enabled; these options defend against cross-site scripting. #. Edit the ``openstack-dashboard.conf`` file as shown in the **Example After**: **Example Before** .. code-block:: apacheconf WSGIScriptAlias / /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi WSGIDaemonProcess horizon user=www-data group=www-data processes=3 threads=10 Alias /static /usr/share/openstack-dashboard/openstack_dashboard/static/ # For Apache http server 2.2 and earlier: Order allow,deny Allow from all # For Apache http server 2.4 and later: # Require all granted **Example After** .. code-block:: none ServerName openstack.example.com RewriteEngine On RewriteCond %{HTTPS} off RewriteRule (.*) https://%{HTTP_HOST}%{REQUEST_URI} RedirectPermanent / https://openstack.example.com ServerName openstack.example.com SSLEngine On # Remember to replace certificates and keys with valid paths in your environment SSLCertificateFile /etc/apache2/SSL/openstack.example.com.crt SSLCACertificateFile /etc/apache2/SSL/openstack.example.com.crt SSLCertificateKeyFile /etc/apache2/SSL/openstack.example.com.key SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown # HTTP Strict Transport Security (HSTS) enforces that all communications # with a server go over SSL. This mitigates the threat from attacks such # as SSL-Strip which replaces links on the wire, stripping away https prefixes # and potentially allowing an attacker to view confidential information on the # wire Header add Strict-Transport-Security "max-age=15768000" WSGIScriptAlias / /usr/share/openstack-dashboard/openstack_dashboard/wsgi/django.wsgi WSGIDaemonProcess horizon user=www-data group=www-data processes=3 threads=10 Alias /static /usr/share/openstack-dashboard/openstack_dashboard/static/ # For Apache http server 2.2 and earlier: Order allow,deny Allow from all # For Apache http server 2.4 and later: =2.4> #The following two lines have been added by bms for error "AH01630: client denied #by server configuration: #/usr/share/openstack-dashboard/openstack_dashboard/static/dashboard/cssa" Options All AllowOverride All Require all granted =2.4> Options All AllowOverride All Require all granted In this configuration, the Apache HTTP Server listens on port 443 and redirects all non-secure requests to the HTTPS protocol. The secured section defines the private key, public key, and certificate to use. #. Restart the Apache HTTP Server. #. Restart ``memcached``. If you try to access the Dashboard through HTTP, the browser redirects you to the HTTPS page. .. note:: Configuring the Dashboard for HTTPS also requires enabling SSL for the noVNC proxy service. On the controller node, add the following additional options to the ``[DEFAULT]`` section of the ``/etc/nova/nova.conf`` file: .. code-block:: ini [DEFAULT] # ... ssl_only = true cert = /etc/apache2/SSL/openstack.example.com.crt key = /etc/apache2/SSL/openstack.example.com.key On the compute nodes, ensure the ``nonvncproxy_base_url`` option points to a URL with an HTTPS scheme: .. code-block:: ini [DEFAULT] # ... novncproxy_base_url = https://controller:6080/vnc_auto.html horizon-13.0.3/doc/source/admin/manage-images.rst0000664000175000017500000001256413553660754021720 0ustar zuulzuul00000000000000======================== Create and manage images ======================== As an administrative user, you can create and manage images for the projects to which you belong. You can also create and manage images for users in all projects to which you have access. To create and manage images in specified projects as an end user, see the :doc:`upload and manage images with Dashboard in OpenStack End User Guide ` and `manage images with CLI in OpenStack End User Guide `_. To create and manage images as an administrator for other users, use the following procedures. Create images ~~~~~~~~~~~~~ For details about image creation, see the `Virtual Machine Image Guide `_. #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Images` category. The images that you can administer for cloud users appear on this page. #. Click :guilabel:`Create Image`, which opens the :guilabel:`Create An Image` window. .. figure:: figures/create_image.png **Figure Dashboard — Create Image** #. In the :guilabel:`Create An Image` window, enter or select the following values: +-------------------------------+---------------------------------+ | :guilabel:`Name` | Enter a name for the image. | +-------------------------------+---------------------------------+ | :guilabel:`Description` | Enter a brief description of | | | the image. | +-------------------------------+---------------------------------+ | :guilabel:`Image Source` | Choose the image source from | | | the dropdown list. Your choices | | | are :guilabel:`Image Location` | | | and :guilabel:`Image File`. | +-------------------------------+---------------------------------+ | :guilabel:`Image File` or | Based on your selection, there | | :guilabel:`Image Location` | is an :guilabel:`Image File` or | | | :guilabel:`Image Location` | | | field. You can include the | | | location URL or browse for the | | | image file on your file system | | | and add it. | +-------------------------------+---------------------------------+ | :guilabel:`Format` | Select the image format. | +-------------------------------+---------------------------------+ | :guilabel:`Architecture` | Specify the architecture. For | | | example, ``i386`` for a 32-bit | | | architecture or ``x86_64`` for | | | a 64-bit architecture. | +-------------------------------+---------------------------------+ | :guilabel:`Minimum Disk (GB)` | Leave this field empty. | +-------------------------------+---------------------------------+ | :guilabel:`Minimum RAM (MB)` | Leave this field empty. | +-------------------------------+---------------------------------+ | :guilabel:`Copy Data` | Specify this option to copy | | | image data to the Image service.| +-------------------------------+---------------------------------+ | :guilabel:`Public` | Select this option to make the | | | image public to all users. | +-------------------------------+---------------------------------+ | :guilabel:`Protected` | Select this option to ensure | | | that only users with | | | permissions can delete it. | +-------------------------------+---------------------------------+ #. Click :guilabel:`Create Image`. The image is queued to be uploaded. It might take several minutes before the status changes from ``Queued`` to ``Active``. Update images ~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Images` category. #. Select the images that you want to edit. Click :guilabel:`Edit Image`. #. In the :guilabel:`Edit Image` window, you can change the image name. Select the :guilabel:`Public` check box to make the image public. Clear this check box to make the image private. You cannot change the :guilabel:`Kernel ID`, :guilabel:`Ramdisk ID`, or :guilabel:`Architecture` attributes for an image. #. Click :guilabel:`Edit Image`. Delete images ~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin tab`, open the :guilabel:`Compute` tab and click the :guilabel:`Images` category. #. Select the images that you want to delete. #. Click :guilabel:`Delete Images`. #. In the :guilabel:`Confirm Delete Images` window, click :guilabel:`Delete Images` to confirm the deletion. You cannot undo this action. horizon-13.0.3/doc/source/admin/index.rst0000664000175000017500000000174613553660754020334 0ustar zuulzuul00000000000000==================== Administration Guide ==================== The OpenStack Dashboard is a web-based interface that allows you to manage OpenStack resources and services. The Dashboard allows you to interact with the OpenStack Compute cloud controller using the OpenStack APIs. For more information about installing and configuring the Dashboard, see the :doc:`/install/index` for your operating system. .. toctree:: :maxdepth: 2 customize-configure.rst sessions.rst manage-images.rst admin-manage-roles.rst manage-projects-and-users.rst manage-instances.rst manage-flavors.rst manage-volumes.rst set-quotas.rst manage-services.rst manage-host-aggregates.rst - To deploy the dashboard, see the :doc:`/install/index`. - To launch instances with the dashboard as an end user, see the :doc:`/user/launch-instances` in the OpenStack End User Guide. - To create and manage ports, see the :doc:`/user/create-networks` section of the OpenStack End User Guide. horizon-13.0.3/doc/source/admin/manage-flavors.rst0000664000175000017500000002022713553660754022122 0ustar zuulzuul00000000000000============== Manage flavors ============== In OpenStack, a flavor defines the compute, memory, and storage capacity of a virtual server, also known as an instance. As an administrative user, you can create, edit, and delete flavors. As of Newton, there are no default flavors. The following table lists the default flavors for Mitaka and earlier. ============ ========= =============== ============= Flavor VCPUs Disk (in GB) RAM (in MB) ============ ========= =============== ============= m1.tiny 1 1 512 m1.small 1 20 2048 m1.medium 2 40 4096 m1.large 4 80 8192 m1.xlarge 8 160 16384 ============ ========= =============== ============= Create flavors ~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. In the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Flavors` category. #. Click :guilabel:`Create Flavor`. #. In the :guilabel:`Create Flavor` window, enter or select the parameters for the flavor in the :guilabel:`Flavor Information` tab. .. figure:: figures/create_flavor.png **Dashboard — Create Flavor** ========================= ======================================= **Name** Enter the flavor name. **ID** Unique ID (integer or UUID) for the new flavor. If specifying 'auto', a UUID will be automatically generated. **VCPUs** Enter the number of virtual CPUs to use. **RAM (MB)** Enter the amount of RAM to use, in megabytes. **Root Disk (GB)** Enter the amount of disk space in gigabytes to use for the root (/) partition. **Ephemeral Disk (GB)** Enter the amount of disk space in gigabytes to use for the ephemeral partition. If unspecified, the value is 0 by default. Ephemeral disks offer machine local disk storage linked to the lifecycle of a VM instance. When a VM is terminated, all data on the ephemeral disk is lost. Ephemeral disks are not included in any snapshots. **Swap Disk (MB)** Enter the amount of swap space (in megabytes) to use. If unspecified, the default is 0. **RX/TX Factor** Optional property allows servers with a different bandwidth to be created with the RX/TX Factor. The default value is 1. That is, the new bandwidth is the same as that of the attached network. ========================= ======================================= #. In the :guilabel:`Flavor Access` tab, you can control access to the flavor by moving projects from the :guilabel:`All Projects` column to the :guilabel:`Selected Projects` column. Only projects in the :guilabel:`Selected Projects` column can use the flavor. If there are no projects in the right column, all projects can use the flavor. #. Click :guilabel:`Create Flavor`. Update flavors ~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. In the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Flavors` category. #. Select the flavor that you want to edit. Click :guilabel:`Edit Flavor`. #. In the :guilabel:`Edit Flavor` window, you can change the flavor name, VCPUs, RAM, root disk, ephemeral disk, and swap disk values. #. Click :guilabel:`Save`. Update Metadata ~~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. In the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Flavors` category. #. Select the flavor that you want to update. In the drop-down list, click :guilabel:`Update Metadata` or click :guilabel:`No` or :guilabel:`Yes` in the :guilabel:`Metadata` column. #. In the :guilabel:`Update Flavor Metadata` window, you can customize some metadata keys, then add it to this flavor and set them values. #. Click :guilabel:`Save`. **Optional metadata keys** +-------------------------------+-------------------------------+ | | quota:cpu_shares | | +-------------------------------+ | **CPU limits** | quota:cpu_period | | +-------------------------------+ | | quota:cpu_limit | | +-------------------------------+ | | quota:cpu_reservation | | +-------------------------------+ | | quota:cpu_quota | +-------------------------------+-------------------------------+ | | quota:disk_read_bytes_sec | | +-------------------------------+ | **Disk tuning** | quota:disk_read_iops_sec | | +-------------------------------+ | | quota:disk_write_bytes_sec | | +-------------------------------+ | | quota:disk_write_iops_sec | | +-------------------------------+ | | quota:disk_total_bytes_sec | | +-------------------------------+ | | quota:disk_total_iops_sec | +-------------------------------+-------------------------------+ | | quota:vif_inbound_average | | +-------------------------------+ | **Bandwidth I/O** | quota:vif_inbound_burst | | +-------------------------------+ | | quota:vif_inbound_peak | | +-------------------------------+ | | quota:vif_outbound_average | | +-------------------------------+ | | quota:vif_outbound_burst | | +-------------------------------+ | | quota:vif_outbound_peak | +-------------------------------+-------------------------------+ | **Watchdog behavior** | hw:watchdog_action | +-------------------------------+-------------------------------+ | | hw_rng:allowed | | +-------------------------------+ | **Random-number generator** | hw_rng:rate_bytes | | +-------------------------------+ | | hw_rng:rate_period | +-------------------------------+-------------------------------+ For information about supporting metadata keys, see the the Compute service documentation. Delete flavors ~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. In the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Flavors` category. #. Select the flavors that you want to delete. #. Click :guilabel:`Delete Flavors`. #. In the :guilabel:`Confirm Delete Flavors` window, click :guilabel:`Delete Flavors` to confirm the deletion. You cannot undo this action. horizon-13.0.3/doc/source/admin/manage-host-aggregates.rst0000664000175000017500000000565513553660754023542 0ustar zuulzuul00000000000000================================= Create and manage host aggregates ================================= Host aggregates enable administrative users to assign key-value pairs to groups of machines. Each node can have multiple aggregates and each aggregate can have multiple key-value pairs. You can assign the same key-value pair to multiple aggregates. The scheduler uses this information to make scheduling decisions. For information, see `Scheduling `__. To create a host aggregate ~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Host Aggregates` category. #. Click :guilabel:`Create Host Aggregate`. #. In the :guilabel:`Create Host Aggregate` dialog box, enter or select the following values on the :guilabel:`Host Aggregate Information` tab: - :guilabel:`Name`: The host aggregate name. - :guilabel:`Availability Zone`: The cloud provider defines the default availability zone, such as ``us-west``, ``apac-south``, or ``nova``. You can target the host aggregate, as follows: - When the host aggregate is exposed as an availability zone, select the availability zone when you launch an instance. - When the host aggregate is not exposed as an availability zone, select a flavor and its extra specs to target the host aggregate. #. Assign hosts to the aggregate using the :guilabel:`Manage Hosts within Aggregate` tab in the same dialog box. To assign a host to the aggregate, click **+** for the host. The host moves from the :guilabel:`All available hosts` list to the :guilabel:`Selected hosts` list. You can add one host to one or more aggregates. To add a host to an existing aggregate, edit the aggregate. To manage host aggregates ~~~~~~~~~~~~~~~~~~~~~~~~~ #. Select the :guilabel:`admin` project from the drop-down list at the top of the page. #. On the :guilabel:`Admin` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Host Aggregates` category. - To edit host aggregates, select the host aggregate that you want to edit. Click :guilabel:`Edit Host Aggregate`. In the :guilabel:`Edit Host Aggregate` dialog box, you can change the name and availability zone for the aggregate. - To manage hosts, locate the host aggregate that you want to edit in the table. Click :guilabel:`More` and select :guilabel:`Manage Hosts`. In the :guilabel:`Add/Remove Hosts to Aggregate` dialog box, click **+** to assign a host to an aggregate. Click **-** to remove a host that is assigned to an aggregate. - To delete host aggregates, locate the host aggregate that you want to edit in the table. Click :guilabel:`More` and select :guilabel:`Delete Host Aggregate`. horizon-13.0.3/doc/source/admin/manage-volumes.rst0000664000175000017500000001621113553660754022136 0ustar zuulzuul00000000000000=============================== Manage volumes and volume types =============================== Volumes are the Block Storage devices that you attach to instances to enable persistent storage. Users can attach a volume to a running instance or detach a volume and attach it to another instance at any time. For information about using the dashboard to create and manage volumes as an end user, see the :doc:`OpenStack End User Guide `. As an administrative user, you can manage volumes and volume types for users in various projects. You can create and delete volume types, and you can view and delete volumes. Note that a volume can be encrypted by using the steps outlined below. .. _create-a-volume-type: Create a volume type ~~~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Volume` tab. #. Click the :guilabel:`Volume Types` tab, and click :guilabel:`Create Volume Type` button. In the :guilabel:`Create Volume Type` window, enter a name for the volume type. #. Click :guilabel:`Create Volume Type` button to confirm your changes. .. note:: A message indicates whether the action succeeded. Create an encrypted volume type ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Create a volume type using the steps above for :ref:`create-a-volume-type`. #. Click :guilabel:`Create Encryption` in the Actions column of the newly created volume type. #. Configure the encrypted volume by setting the parameters below from available options (see table): Provider Specifies the class responsible for configuring the encryption. Control Location Specifies whether the encryption is from the front end (nova) or the back end (cinder). Cipher Specifies the encryption algorithm. Key Size (bits) Specifies the encryption key size. #. Click :guilabel:`Create Volume Type Encryption`. .. figure:: figures/create_volume_type_encryption.png **Encryption Options** The table below provides a few alternatives available for creating encrypted volumes. +--------------------+-----------------------+----------------------------+ | Encryption | Parameter | Comments | | parameters | options | | +====================+=======================+============================+ | Provider |nova.volume.encryptors.|Allows easier import and | | |luks.LuksEncryptor |migration of imported | | |(Recommended) |encrypted volumes, and | | | |allows access key to be | | | |changed without | | | |re-encrypting the volume | + +-----------------------+----------------------------+ | |nova.volume.encryptors.|Less disk overhead than | | |cryptsetup. |LUKS | | |CryptsetupEncryptor | | +--------------------+-----------------------+----------------------------+ | Control Location | front-end |The encryption occurs within| | | (Recommended) |nova so that the data | | | |transmitted over the network| | | |is encrypted | | | | | + +-----------------------+----------------------------+ | | back-end |This could be selected if a | | | |cinder plug-in supporting | | | |an encrypted back-end block | | | |storage device becomes | | | |available in the future. | | | |TLS or other network | | | |encryption would also be | | | |needed to protect data as it| | | |traverses the network | +--------------------+-----------------------+----------------------------+ | Cipher | aes-xts-plain64 |See NIST reference below | | | (Recommended) |to see advantages* | + +-----------------------+----------------------------+ | | aes-cbc-essiv |Note: On the command line, | | | |type 'cryptsetup benchmark' | | | |for additional options | +--------------------+-----------------------+----------------------------+ | Key Size (bits)| 512 (Recommended for |Using this selection for | | | aes-xts-plain64. 256 |aes-xts, the underlying key | | | should be used for |size would only be 256-bits*| | | aes-cbc-essiv) | | + +-----------------------+----------------------------+ | | 256 |Using this selection for | | | |aes-xts, the underlying key | | | |size would only be 128-bits*| +--------------------+-----------------------+----------------------------+ `*` Source `NIST SP 800-38E `_ .. note:: To see further information and CLI instructions, see `Create an encrypted volume type `__ in the OpenStack Configuration Reference. Delete volume types ~~~~~~~~~~~~~~~~~~~ When you delete a volume type, volumes of that type are not deleted. #. Log in to the dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Volume` tab. #. Click the :guilabel:`Volume Types` tab, select the volume type or types that you want to delete. #. Click :guilabel:`Delete Volume Types` button. #. In the :guilabel:`Confirm Delete Volume Types` window, click the :guilabel:`Delete Volume Types` button to confirm the action. .. note:: A message indicates whether the action succeeded. Delete volumes ~~~~~~~~~~~~~~ When you delete an instance, the data of its attached volumes is not destroyed. #. Log in to the dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`Volume` tab. #. Click the :guilabel:`Volumes` tab, Select the volume or volumes that you want to delete. #. Click :guilabel:`Delete Volumes` button. #. In the :guilabel:`Confirm Delete Volumes` window, click the :guilabel:`Delete Volumes` button to confirm the action. .. note:: A message indicates whether the action succeeded. horizon-13.0.3/doc/source/admin/manage-services.rst0000664000175000017500000000221613553660754022267 0ustar zuulzuul00000000000000========================= View services information ========================= As an administrative user, you can view information for OpenStack services. #. Log in to the Dashboard and select the :guilabel:`admin` project from the drop-down list. #. On the :guilabel:`Admin` tab, open the :guilabel:`System` tab and click the :guilabel:`System Information` category. View the following information on these tabs: * :guilabel:`Services`: Displays the internal name and the public OpenStack name for each service, the host on which the service runs, and whether or not the service is enabled. * :guilabel:`Compute Services`: Displays information specific to the Compute service. Both host and zone are listed for each service, as well as its activation status. * :guilabel:`Block Storage Services`: Displays information specific to the Block Storage service. Both host and zone are listed for each service, as well as its activation status. * :guilabel:`Network Agents`: Displays the network agents active within the cluster, such as L3 and DHCP agents, and the status of each agent. horizon-13.0.3/doc/source/install/0000775000175000017500000000000013553661042017030 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/install/from-source.rst0000664000175000017500000001564513553660754022047 0ustar zuulzuul00000000000000=================== Manual installation =================== This page covers the basic installation of horizon in a production environment. If you are looking for a developer environment, see :ref:`quickstart`. For the system dependencies, see :doc:`system-requirements`. Installation ============ .. note:: In the commands below, substitute "" for your version of choice, such as "pike" or "queens". #. Clone Horizon .. code-block:: console $ git clone https://git.openstack.org/openstack/horizon -b stable/ --depth=1 $ cd horizon #. Install the horizon python module into your system .. code-block:: console $ sudo pip install -c http://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/ . Configuration ============= This section contains a small summary of the critical settings required to run horizon. For more details, please refer to :ref:`install-settings`. Settings -------- Create ``openstack_dashboard/local/local_settings.py``. It is usually a good idea to copy ``openstack_dashboard/local/local_settings.py.example`` and edit it. As a minimum, the follow settings will need to be modified: ``DEBUG`` Set to ``False`` ``ALLOWED_HOSTS`` Set to your domain name(s) ``OPENSTACK_HOST`` Set to the IP of your Keystone endpoint. You may also need to alter ``OPENSTACK_KEYSTONE_URL`` .. note:: The following steps in the "Configuration" section are optional, but highly recommended in production. Translations ------------ Compile translation message catalogs for internationalization. This step is not required if you do not need to support languages other than US English. GNU ``gettext`` tool is required to compile message catalogs. .. code-block:: console $ sudo apt-get install gettext $ ./manage.py compilemessages Static Assets ------------- Compress your static files by adding ``COMPRESS_OFFLINE = True`` to your ``local_settings.py``, then run the following commands .. code-block:: console $ ./manage.py collectstatic $ ./manage.py compress Logging ------- Horizons uses Django's logging configuration mechanism, which can be customized by altering the ``LOGGING`` dictionary in ``local_settings.py``. By default, Horizon's logging example sets the log level to ``INFO``. Horizon also uses a number of 3rd-party clients which log separately. The log level for these can still be controlled through Horizon's ``LOGGING`` config, however behaviors may vary beyond Horizon's control. For more information regarding configuring logging in Horizon, please read the `Django logging directive`_ and the `Python logging directive`_ documentation. Horizon is built on Python and Django. .. _Django logging directive: https://docs.djangoproject.com/en/dev/topics/logging .. _Python logging directive: http://docs.python.org/2/library/logging.html Session Storage --------------- Horizon uses `Django's sessions framework`_ for handling session data. There are numerous session backends available, which are selected through the ``SESSION_ENGINE`` setting in your ``local_settings.py`` file. .. _Django's sessions framework: https://docs.djangoproject.com/en/dev/topics/http/sessions/ Memcached ~~~~~~~~~ .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache' 'LOCATION': 'my_memcached_host:11211', } External caching using an application such as memcached offers persistence and shared storage, and can be very useful for small-scale deployment and/or development. However, for distributed and high-availability scenarios memcached has inherent problems which are beyond the scope of this documentation. Requirements: * Memcached service running and accessible * Python memcached module installed Database ~~~~~~~~ .. code-block:: python SESSION_ENGINE = 'django.core.cache.backends.db.DatabaseCache' DATABASES = { 'default': { # Database configuration here } } Database-backed sessions are scalable (using an appropriate database strategy), persistent, and can be made high-concurrency and highly-available. The downside to this approach is that database-backed sessions are one of the slower session storages, and incur a high overhead under heavy usage. Proper configuration of your database deployment can also be a substantial undertaking and is far beyond the scope of this documentation. Cached Database ~~~~~~~~~~~~~~~ To mitigate the performance issues of database queries, you can also consider using Django's ``cached_db`` session backend which utilizes both your database and caching infrastructure to perform write-through caching and efficient retrieval. You can enable this hybrid setting by configuring both your database and cache as discussed above and then using .. code-block:: python SESSION_ENGINE = "django.contrib.sessions.backends.cached_db" Deployment ========== #. Set up a web server with WSGI support. For example, install Apache web server on Ubuntu .. code-block:: console $ sudo apt-get install apache2 libapache2-mod-wsgi You can either use the provided ``openstack_dashboard/wsgi/django.wsgi`` or generate a ``openstack_dashboard/wsgi/horizon.wsgi`` file with the following command (which detects if you use a virtual environment or not to automatically build an adapted WSGI file) .. code-block:: console $ ./manage.py make_web_conf --wsgi Then configure the web server to host OpenStack dashboard via WSGI. For apache2 web server, you may need to create ``/etc/apache2/sites-available/horizon.conf``. The template in DevStack is a good example of the file. http://git.openstack.org/cgit/openstack-dev/devstack/tree/files/apache-horizon.template Or, if you previously generated an ``openstack_dashboard/wsgi/horizon.wsgi`` you can automatically generate an apache configuration file .. code-block:: console $ ./manage.py make_web_conf --apache > /etc/apache2/sites-available/horizon.conf Same as above but if you want SSL support .. code-block:: console $ ./manage.py make_web_conf --apache --ssl --sslkey=/path/to/ssl/key --sslcert=/path/to/ssl/cert > /etc/apache2/sites-available/horizon.conf By default the apache configuration will launch a number of apache processes equal to the number of CPUs + 1 of the machine on which you launch the ``make_web_conf`` command. If the target machine is not the same or if you want to specify the number of processes, add the ``--processes`` option .. code-block:: console $ ./manage.py make_web_conf --apache --processes 10 > /etc/apache2/sites-available/horizon.conf #. Enable the above configuration and restart the web server .. code-block:: console $ sudo a2ensite horizon $ sudo service apache2 restart Next Steps ========== * :ref:`install-settings` lists the available settings for horizon. * :ref:`install-customizing` describes how to customize horizon. horizon-13.0.3/doc/source/install/system-requirements.rst0000664000175000017500000000202713553660754023641 0ustar zuulzuul00000000000000=================== System Requirements =================== The Queens release of horizon has the following dependencies. * Python 2.7 * Django 1.11 * Django 1.8 to 1.10 are also supported. Their support will be dropped in the Rocky release. * An accessible `keystone `_ endpoint * All other services are optional. Horizon supports the following services as of the Queens release. If the keystone endpoint for a service is configured, horizon detects it and enables its support automatically. * `cinder `_: Block Storage * `glance `_: Image Management * `neutron `_: Networking * `nova `_: Compute * `swift `_: Object Storage * Horizon also supports many other OpenStack services via plugins. For more information, see the :ref:`install-plugin-registry`. horizon-13.0.3/doc/source/install/verify-ubuntu.rst0000664000175000017500000000042713553660754022422 0ustar zuulzuul00000000000000=========================== Verify operation for Ubuntu =========================== Verify operation of the dashboard. Access the dashboard using a web browser at ``http://controller/horizon``. Authenticate using ``admin`` or ``demo`` user and ``default`` domain credentials. horizon-13.0.3/doc/source/install/install-obs.rst0000664000175000017500000001224613553660754022027 0ustar zuulzuul00000000000000============================================================ Install and configure for openSUSE and SUSE Linux Enterprise ============================================================ This section describes how to install and configure the dashboard on the controller node. The only core service required by the dashboard is the Identity service. You can use the dashboard in combination with other services, such as Image service, Compute, and Networking. You can also use the dashboard in environments with stand-alone services such as Object Storage. .. note:: This section assumes proper installation, configuration, and operation of the Identity service using the Apache HTTP server and Memcached service. Install and configure components -------------------------------- .. include:: note_configuration_vary_by_distribution.txt 1. Install the packages: .. code-block:: console # zypper install openstack-dashboard .. end 2. Configure the web server: .. code-block:: console # cp /etc/apache2/conf.d/openstack-dashboard.conf.sample \ /etc/apache2/conf.d/openstack-dashboard.conf # a2enmod rewrite .. end 3. Edit the ``/srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py`` file and complete the following actions: * Configure the dashboard to use OpenStack services on the ``controller`` node: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_HOST = "controller" .. end * Allow your hosts to access the dashboard: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python ALLOWED_HOSTS = ['one.example.com', 'two.example.com'] .. end .. note:: ``ALLOWED_HOSTS`` can also be ``['*']`` to accept all hosts. This may be useful for development work, but is potentially insecure and should not be used in production. See `Django documentation `_ for further information. * Configure the ``memcached`` session storage service: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'controller:11211', } } .. end .. note:: Comment out any other session storage configuration. * Enable the Identity API version 3: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST .. end * Enable support for domains: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True .. end * Configure API versions: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_API_VERSIONS = { "identity": 3, "image": 2, "volume": 2, } .. end * Configure ``Default`` as the default domain for users that you create via the dashboard: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "Default" .. end * Configure ``user`` as the default role for users that you create via the dashboard: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_ROLE = "user" .. end * If you chose networking option 1, disable support for layer-3 networking services: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python OPENSTACK_NEUTRON_NETWORK = { ... 'enable_router': False, 'enable_quotas': False, 'enable_distributed_router': False, 'enable_ha_router': False, 'enable_lb': False, 'enable_firewall': False, 'enable_vpn': False, 'enable_fip_topology_check': False, } .. end * Optionally, configure the time zone: .. path /srv/www/openstack-dashboard/openstack_dashboard/local/local_settings.py .. code-block:: python TIME_ZONE = "TIME_ZONE" .. end Replace ``TIME_ZONE`` with an appropriate time zone identifier. For more information, see the `list of time zones `__. Finalize installation --------------------- * Restart the web server and session storage service: .. code-block:: console # systemctl restart apache2.service memcached.service .. end .. note:: The ``systemctl restart`` command starts each service if not currently running. horizon-13.0.3/doc/source/install/verify-obs.rst0000664000175000017500000000054413553660754021663 0ustar zuulzuul00000000000000======================================================= Verify operation for openSUSE and SUSE Linux Enterprise ======================================================= Verify operation of the dashboard. Access the dashboard using a web browser at ``http://controller/``. Authenticate using ``admin`` or ``demo`` user and ``default`` domain credentials. horizon-13.0.3/doc/source/install/verify-debian.rst0000664000175000017500000000042013553660754022313 0ustar zuulzuul00000000000000=========================== Verify operation for Debian =========================== Verify operation of the dashboard. Access the dashboard using a web browser at ``http://controller/``. Authenticate using ``admin`` or ``demo`` user and ``default`` domain credentials. horizon-13.0.3/doc/source/install/note_configuration_vary_by_distribution.txt0000664000175000017500000000046613553660754030036 0ustar zuulzuul00000000000000.. note:: Default configuration files vary by distribution. You might need to add these sections and options rather than modifying existing sections and options. Also, an ellipsis (``...``) in the configuration snippets indicates potential default configuration options that you should retain. horizon-13.0.3/doc/source/install/verify-rdo.rst0000664000175000017500000000056013553660754021662 0ustar zuulzuul00000000000000======================================================== Verify operation for Red Hat Enterprise Linux and CentOS ======================================================== Verify operation of the dashboard. Access the dashboard using a web browser at ``http://controller/dashboard``. Authenticate using ``admin`` or ``demo`` user and ``default`` domain credentials. horizon-13.0.3/doc/source/install/plugin-registry.rst0000664000175000017500000001044513553660754022743 0ustar zuulzuul00000000000000.. _install-plugin-registry: =============== Plugin Registry =============== .. note:: Currently, Horizon plugins are responsible for their own compatibility. Check the individual repos for information on support. .. list-table:: :header-rows: 1 :widths: 20 40 40 * - Plugin - URL - Launchpad * - Astara Dashboard - https://github.com/openstack/astara-horizon - https://launchpad.net/astara * - BGPVPN Dashboard - https://github.com/openstack/networking-bgpvpn - https://launchpad.net/bgpvpn * - Blazar Dashboard - https://github.com/openstack/blazar-dashboard - https://launchpad.net/blazar * - Cerberus Dashboard - https://github.com/openstack/cerberus-dashboard - https://launchpad.net/cerberus * - Cisco UI - http://github.com/openstack/horizon-cisco-ui - https://launchpad.net/horizon-cisco-ui * - Cloudkitty Dashboard - https://github.com/openstack/cloudkitty-dashboard - https://launchpad.net/cloudkitty * - Congress Dashboard - https://github.com/openstack/congress-dashboard - https://launchpad.net/congress * - Cue Dashboard - https://github.com/openstack/cue-dashboard - https://launchpad.net/cue-dashboard * - Designate Dashboard - https://github.com/openstack/designate-dashboard - https://launchpad.net/designate-dashboard * - Group Based Policy UI - https://github.com/openstack/group-based-policy-ui - https://launchpad.net/group-based-policy-ui * - Freezer Web UI - https://github.com/openstack/freezer-web-ui - https://launchpad.net/freezer * - Heat Dashboard - https://github.com/openstack/heat-dashboard - https://launchpad.net/heat-dashboard * - Ironic UI - https://github.com/openstack/ironic-ui - https://launchpad.net/ironic-ui * - Karbor Dashboard - https://github.com/openstack/karbor-dashboard - https://launchpad.net/karbor-dashboard * - Magnum UI - http://github.com/openstack/magnum-ui - https://launchpad.net/magnum-ui * - Manila UI - http://github.com/openstack/manila-ui - https://launchpad.net/manila-ui * - Mistral Dashboard - https://github.com/openstack/mistral-dashboard - https://launchpad.net/mistral * - Monasca UI - https://github.com/openstack/monasca-ui - https://launchpad.net/monasca * - Murano Dashboard - http://github.com/openstack/murano-dashboard - http://launchpad.net/murano * - Neutron FWaaS Dashboard - https://github.com/openstack/neutron-fwaas-dashboard - https://launchpad.net/neutron-fwaas-dashboard * - Neutron LBaaS Dashboard - https://github.com/openstack/neutron-lbaas-dashboard - http://launchpad.net/octavia * - Neutron VPNaaS Dashboard - https://github.com/openstack/neutron-vpnaas-dashboard - https://launchpad.net/neutron-vpnaas-dashboard * - Octavia Dashboard - https://github.com/openstack/octavia-dashboard - https://launchpad.net/octavia * - Sahara Dashboard - https://github.com/openstack/sahara-dashboard - https://launchpad.net/sahara * - Searchlight UI - https://github.com/openstack/searchlight-ui - https://launchpad.net/searchlight * - Senlin Dashboard - https://github.com/openstack/senlin-dashboard - http://launchpad.net/senlin-dashboard * - Solum Dashboard - https://github.com/openstack/solum-dashboard - https://launchpad.net/solum * - Sticks Dashboard - https://github.com/openstack/sticks-dashboard - https://wiki.openstack.org/wiki/Sticks * - Tacker UI - https://github.com/openstack/tacker-horizon - https://launchpad.net/tacker * - TripleO UI - https://github.com/openstack/tripleo-ui/ - https://launchpad.net/tripleo * - Trove Dashboard - https://github.com/openstack/trove-dashboard - https://launchpad.net/trove-dashboard * - Vitrage Dashboard - http://github.com/openstack/vitrage-dashboard - https://launchpad.net/vitrage-dashboard * - Watcher Dashboard - http://github.com/openstack/watcher-dashboard - https://launchpad.net/watcher-dashboard * - Zaqar UI - http://github.com/openstack/zaqar-ui - https://launchpad.net/zaqar-ui * - Zun UI - https://github.com/openstack/zun-ui - https://launchpad.net/zun-ui horizon-13.0.3/doc/source/install/install-rdo.rst0000664000175000017500000001134113553660754022023 0ustar zuulzuul00000000000000============================================================= Install and configure for Red Hat Enterprise Linux and CentOS ============================================================= This section describes how to install and configure the dashboard on the controller node. The only core service required by the dashboard is the Identity service. You can use the dashboard in combination with other services, such as Image service, Compute, and Networking. You can also use the dashboard in environments with stand-alone services such as Object Storage. .. note:: This section assumes proper installation, configuration, and operation of the Identity service using the Apache HTTP server and Memcached service. Install and configure components -------------------------------- .. include:: note_configuration_vary_by_distribution.txt 1. Install the packages: .. code-block:: console # yum install openstack-dashboard .. end 2. Edit the ``/etc/openstack-dashboard/local_settings`` file and complete the following actions: * Configure the dashboard to use OpenStack services on the ``controller`` node: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_HOST = "controller" .. end * Allow your hosts to access the dashboard: .. path /etc/openstack-dashboard/local_settings .. code-block:: python ALLOWED_HOSTS = ['one.example.com', 'two.example.com'] .. end .. note:: ALLOWED_HOSTS can also be ['*'] to accept all hosts. This may be useful for development work, but is potentially insecure and should not be used in production. See https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts for further information. * Configure the ``memcached`` session storage service: .. path /etc/openstack-dashboard/local_settings .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'controller:11211', } } .. end .. note:: Comment out any other session storage configuration. * Enable the Identity API version 3: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST .. end * Enable support for domains: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True .. end * Configure API versions: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_API_VERSIONS = { "identity": 3, "image": 2, "volume": 2, } .. end * Configure ``Default`` as the default domain for users that you create via the dashboard: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "Default" .. end * Configure ``user`` as the default role for users that you create via the dashboard: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_ROLE = "user" .. end * If you chose networking option 1, disable support for layer-3 networking services: .. path /etc/openstack-dashboard/local_settings .. code-block:: python OPENSTACK_NEUTRON_NETWORK = { ... 'enable_router': False, 'enable_quotas': False, 'enable_distributed_router': False, 'enable_ha_router': False, 'enable_lb': False, 'enable_firewall': False, 'enable_vpn': False, 'enable_fip_topology_check': False, } .. end * Optionally, configure the time zone: .. path /etc/openstack-dashboard/local_settings .. code-block:: python TIME_ZONE = "TIME_ZONE" .. end Replace ``TIME_ZONE`` with an appropriate time zone identifier. For more information, see the `list of time zones `__. 3. Add the following line to ``/etc/httpd/conf.d/openstack-dashboard.conf`` if not included. .. code-block:: none WSGIApplicationGroup %{GLOBAL} Finalize installation --------------------- * Restart the web server and session storage service: .. code-block:: console # systemctl restart httpd.service memcached.service .. end .. note:: The ``systemctl restart`` command starts each service if not currently running. horizon-13.0.3/doc/source/install/next-steps.rst0000664000175000017500000000157313553660754021713 0ustar zuulzuul00000000000000========== Next steps ========== Your OpenStack environment now includes the dashboard. After you install and configure the dashboard, you can complete the following tasks: * Provide users with a public IP address, a username, and a password so they can access the dashboard through a web browser. In case of any SSL certificate connection problems, point the server IP address to a domain name, and give users access. * Customize your dashboard. For details, see :doc:`/admin/customize-configure`. * Set up session storage. For derails, see :doc:`/admin/sessions`. * To use the VNC client with the dashboard, the browser must support HTML5 Canvas and HTML5 WebSockets. For details about browsers that support noVNC, see `README `__ and `browser support `__. horizon-13.0.3/doc/source/install/index.rst0000664000175000017500000000221013553660754020675 0ustar zuulzuul00000000000000================== Installation Guide ================== This section describes how to install and configure the dashboard on the controller node. The only core service required by the dashboard is the Identity service. You can use the dashboard in combination with other services, such as Image service, Compute, and Networking. You can also use the dashboard in environments with stand-alone services such as Object Storage. .. note:: This section assumes proper installation, configuration, and operation of the Identity service using the Apache HTTP server and Memcached service. System Requirements =================== .. toctree:: :maxdepth: 1 system-requirements Installing from Packages ======================== .. toctree:: :maxdepth: 1 :glob: install-* verify-* next-steps Installing from Source ====================== .. toctree:: :maxdepth: 2 from-source.rst Horizon plugins =============== There are a number of horizon plugins for various useful features. You can get dashboard supports for them by installing corresponding horizon plugins. .. toctree:: :maxdepth: 1 plugin-registry.rst horizon-13.0.3/doc/source/install/install-ubuntu.rst0000664000175000017500000001141113553660754022557 0ustar zuulzuul00000000000000================================ Install and configure for Ubuntu ================================ This section describes how to install and configure the dashboard on the controller node. The only core service required by the dashboard is the Identity service. You can use the dashboard in combination with other services, such as Image service, Compute, and Networking. You can also use the dashboard in environments with stand-alone services such as Object Storage. .. note:: This section assumes proper installation, configuration, and operation of the Identity service using the Apache HTTP server and Memcached service. Install and configure components -------------------------------- .. include:: note_configuration_vary_by_distribution.txt 1. Install the packages: .. code-block:: console # apt install openstack-dashboard .. end 2. Edit the ``/etc/openstack-dashboard/local_settings.py`` file and complete the following actions: * Configure the dashboard to use OpenStack services on the ``controller`` node: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_HOST = "controller" .. end * In the Dashboard configuration section, allow your hosts to access Dashboard: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python ALLOWED_HOSTS = ['one.example.com', 'two.example.com'] .. end .. note:: - Do not edit the ``ALLOWED_HOSTS`` parameter under the Ubuntu configuration section. - ``ALLOWED_HOSTS`` can also be ``['*']`` to accept all hosts. This may be useful for development work, but is potentially insecure and should not be used in production. See the `Django documentation `_ for further information. * Configure the ``memcached`` session storage service: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'controller:11211', } } .. end .. note:: Comment out any other session storage configuration. * Enable the Identity API version 3: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST .. end * Enable support for domains: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True .. end * Configure API versions: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_API_VERSIONS = { "identity": 3, "image": 2, "volume": 2, } .. end * Configure ``Default`` as the default domain for users that you create via the dashboard: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "Default" .. end * Configure ``user`` as the default role for users that you create via the dashboard: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_ROLE = "user" .. end * If you chose networking option 1, disable support for layer-3 networking services: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_NEUTRON_NETWORK = { ... 'enable_router': False, 'enable_quotas': False, 'enable_ipv6': False, 'enable_distributed_router': False, 'enable_ha_router': False, 'enable_lb': False, 'enable_firewall': False, 'enable_vpn': False, 'enable_fip_topology_check': False, } .. end * Optionally, configure the time zone: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python TIME_ZONE = "TIME_ZONE" .. end Replace ``TIME_ZONE`` with an appropriate time zone identifier. For more information, see the `list of time zones `__. 3. Add the following line to ``/etc/apache2/conf-available/openstack-dashboard.conf`` if not included. .. code-block:: none WSGIApplicationGroup %{GLOBAL} Finalize installation --------------------- * Reload the web server configuration: .. code-block:: console # service apache2 reload .. end horizon-13.0.3/doc/source/install/install-debian.rst0000664000175000017500000001253213553660754022464 0ustar zuulzuul00000000000000================================ Install and configure for Debian ================================ This section describes how to install and configure the dashboard on the controller node. The only core service required by the dashboard is the Identity service. You can use the dashboard in combination with other services, such as Image service, Compute, and Networking. You can also use the dashboard in environments with stand-alone services such as Object Storage. .. note:: This section assumes proper installation, configuration, and operation of the Identity service using the Apache HTTP server and Memcached service. Install and configure components -------------------------------- .. include:: note_configuration_vary_by_distribution.txt 1. Install the packages: .. code-block:: console # apt install openstack-dashboard-apache .. end 2. Respond to prompts for web server configuration. .. note:: The automatic configuration process generates a self-signed SSL certificate. Consider obtaining an official certificate for production environments. .. note:: There are two modes of installation. One using ``/horizon`` as the URL, keeping your default vhost and only adding an Alias directive: this is the default. The other mode will remove the default Apache vhost and install the dashboard on the webroot. It was the only available option before the Liberty release. If you prefer to set the Apache configuration manually, install the ``openstack-dashboard`` package instead of ``openstack-dashboard-apache``. 3. Edit the ``/etc/openstack-dashboard/local_settings.py`` file and complete the following actions: * Configure the dashboard to use OpenStack services on the ``controller`` node: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_HOST = "controller" .. end * In the Dashboard configuration section, allow your hosts to access Dashboard: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python ALLOWED_HOSTS = ['one.example.com', 'two.example.com'] .. end .. note:: - Do not edit the ``ALLOWED_HOSTS`` parameter under the Ubuntu configuration section. - ``ALLOWED_HOSTS`` can also be ``['*']`` to accept all hosts. This may be useful for development work, but is potentially insecure and should not be used in production. See the `Django documentation `_ for further information. * Configure the ``memcached`` session storage service: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python SESSION_ENGINE = 'django.contrib.sessions.backends.cache' CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', 'LOCATION': 'controller:11211', } } .. end .. note:: Comment out any other session storage configuration. * Enable the Identity API version 3: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST .. end * Enable support for domains: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True .. end * Configure API versions: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_API_VERSIONS = { "identity": 3, "image": 2, "volume": 2, } .. end * Configure ``Default`` as the default domain for users that you create via the dashboard: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = "Default" .. end * Configure ``user`` as the default role for users that you create via the dashboard: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_KEYSTONE_DEFAULT_ROLE = "user" .. end * If you chose networking option 1, disable support for layer-3 networking services: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python OPENSTACK_NEUTRON_NETWORK = { ... 'enable_router': False, 'enable_quotas': False, 'enable_ipv6': False, 'enable_distributed_router': False, 'enable_ha_router': False, 'enable_lb': False, 'enable_firewall': False, 'enable_vpn': False, 'enable_fip_topology_check': False, } .. end * Optionally, configure the time zone: .. path /etc/openstack-dashboard/local_settings.py .. code-block:: python TIME_ZONE = "TIME_ZONE" .. end Replace ``TIME_ZONE`` with an appropriate time zone identifier. For more information, see the `list of time zones `__. Finalize installation --------------------- * Reload the web server configuration: .. code-block:: console # service apache2 reload .. end horizon-13.0.3/doc/source/user/0000775000175000017500000000000013553661042016340 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/user/browser_support.rst0000664000175000017500000001323213553660754022363 0ustar zuulzuul00000000000000================== Supported Browsers ================== Horizon is primarily tested and supported on the latest version of Firefox, the latest version of Chrome, and IE9+. Issues related to Safari and Opera will also be considered. This page aims to informally document what that means for different releases, everyone is warmly encouraged to update this page based on the versions they've tested with. Legend: - Very good: Very well tested, should work as expected - Good: Moderately tested, should look nice and work fine, maybe a few visual hiccups - Poor: Doesn't look good - Broken: Essential functionality not working (link to bug in the notes) - No: Not supported Kilo ==== +--------------------+--------------------+------------------------------+ | | Status | Notes | +====================+====================+==============================+ |Firefox |Very good |31+. (Earlier versions?) | +--------------------+--------------------+------------------------------+ |Firefox ESR |Very good |31+ | +--------------------+--------------------+------------------------------+ |Chrome |Very good |43.0.2357.81 | +--------------------+--------------------+------------------------------+ |IE 11 |Good? | | +--------------------+--------------------+------------------------------+ |IE 10 |Good? | | +--------------------+--------------------+------------------------------+ |IE 9 |? | | +--------------------+--------------------+------------------------------+ |IE 8 and below |Not supported. | | +--------------------+--------------------+------------------------------+ |Safari |? | | +--------------------+--------------------+------------------------------+ |Opera |? | | +--------------------+--------------------+------------------------------+ Juno ==== +--------------------+--------------------+------------------------------+ | | Status | Notes | +====================+====================+==============================+ |Firefox |Very good |31+. (Earlier versions?) | +--------------------+--------------------+------------------------------+ |Firefox ESR |? | | +--------------------+--------------------+------------------------------+ |Chrome |Very good |Versions? | +--------------------+--------------------+------------------------------+ |IE 11 |Good? |`Open IE Bugs`_. | +--------------------+--------------------+------------------------------+ |IE 10 |Good? |`Open IE Bugs`_. | +--------------------+--------------------+------------------------------+ |IE 9 |? |`Open IE Bugs`_. | +--------------------+--------------------+------------------------------+ |IE 8 and below |Not supported. |No. | +--------------------+--------------------+------------------------------+ |Safari |? | | +--------------------+--------------------+------------------------------+ |Opera |? | | +--------------------+--------------------+------------------------------+ .. _Open IE Bugs: https://bugs.launchpad.net/horizon/+bugs?field.tag=ie Icehouse ======== +--------------------+-----------------+--------------------------------------+ | | Status | Notes | +====================+=================+======================================+ |Firefox |Very good |Versions? | +--------------------+-----------------+--------------------------------------+ |Firefox ESR |Very good |Windows 24.7.0 ESR | +--------------------+-----------------+--------------------------------------+ |Chrome |Very good |Windows Version 36, RHEL version 27.0 | +--------------------+-----------------+--------------------------------------+ |Chromium |Very good |version 31.0 | +--------------------+-----------------+--------------------------------------+ |IE 11 |? | | +--------------------+-----------------+--------------------------------------+ |IE 10 |? | | +--------------------+-----------------+--------------------------------------+ |IE 9 |? | | +--------------------+-----------------+--------------------------------------+ |IE 8 and below |? | | +--------------------+-----------------+--------------------------------------+ |Safari |? | | +--------------------+-----------------+--------------------------------------+ |Opera |? | | +--------------------+-----------------+--------------------------------------+ horizon-13.0.3/doc/source/user/log-in.rst0000664000175000017500000001652713553660754020303 0ustar zuulzuul00000000000000======================= Log in to the dashboard ======================= The dashboard is generally installed on the controller node. #. Ask the cloud operator for the host name or public IP address from which you can access the dashboard, and for your user name and password. If the cloud supports multi-domain model, you also need to ask for your domain name. #. Open a web browser that has JavaScript and cookies enabled. .. note:: To use the Virtual Network Computing (VNC) client for the dashboard, your browser must support HTML5 Canvas and HTML5 WebSockets. The VNC client is based on noVNC. For details, see `noVNC: HTML5 VNC Client `__. For a list of supported browsers, see `Browser support `__. #. In the address bar, enter the host name or IP address for the dashboard, for example, ``https://ipAddressOrHostName/``. .. note:: If a certificate warning appears when you try to access the URL for the first time, a self-signed certificate is in use, which is not considered trustworthy by default. Verify the certificate or add an exception in the browser to bypass the warning. #. On the :guilabel:`Log In` page, enter your user name and password, and click :guilabel:`Sign In`. If the cloud supports multi-domain model, you also need to enter your domain name. The top of the window displays your user name. You can also access the :guilabel:`Settings` tab (:ref:`dashboard-settings-tab`) or sign out of the dashboard. The visible tabs and functions in the dashboard depend on the access permissions, or roles, of the user you are logged in as. * If you are logged in as an end user, the :guilabel:`Project` tab (:ref:`dashboard-project-tab`) and :guilabel:`Identity` tab (:ref:`dashboard-identity-tab`) are displayed. * If you are logged in as an administrator, the :guilabel:`Project` tab (:ref:`dashboard-project-tab`) and :guilabel:`Admin` tab (:ref:`dashboard-admin-tab`) and :guilabel:`Identity` tab (:ref:`dashboard-identity-tab`) are displayed. .. _dashboard-project-tab: OpenStack dashboard — Project tab ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Projects are organizational units in the cloud and are also known as tenants or accounts. Each user is a member of one or more projects. Within a project, a user creates and manages instances. From the :guilabel:`Project` tab, you can view and manage the resources in a selected project, including instances and images. You can select the project from the drop-down menu at the top left. If the cloud supports multi-domain model, you can also select the domain from this menu. .. figure:: figures/dashboard_project_tab.png :width: 100% **Figure: Project tab** From the :guilabel:`Project` tab, you can access the following categories: Compute tab ----------- * :guilabel:`Overview`: View reports for the project. * :guilabel:`Instances`: View, launch, create a snapshot from, stop, pause, or reboot instances, or connect to them through VNC. * :guilabel:`Images`: View images and instance snapshots created by project users, plus any images that are publicly available. Create, edit, and delete images, and launch instances from images and snapshots. * :guilabel:`Key Pairs`: View, create, edit, import, and delete key pairs. Volume tab ----------- * :guilabel:`Volumes`: View, create, edit, and delete volumes. * :guilabel:`Backups`: View, create, edit, and delete backups. * :guilabel:`Snapshots`: View, create, edit, and delete volume snapshots. * :guilabel:`Consistency Groups`: View, create, edit, and delete consistency groups. * :guilabel:`Consistency Group Snapshots`: View, create, edit, and delete consistency group snapshots. Network tab ----------- * :guilabel:`Network Topology`: View the network topology. * :guilabel:`Networks`: Create and manage public and private networks. * :guilabel:`Routers`: Create and manage routers. * :guilabel:`Security Groups`: View, create, edit, and delete security groups and security group rules.. * :guilabel:`Floating IPs`: Allocate an IP address to or release it from a project. Object Store tab ---------------- * :guilabel:`Containers`: Create and manage containers and objects. .. _dashboard-admin-tab: OpenStack dashboard — Admin tab ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Administrative users can use the :guilabel:`Admin` tab to view usage and to manage instances, volumes, flavors, images, networks, and so on. .. figure:: figures/dashboard_admin_tab.png :width: 100% **Figure: Admin tab** From the :guilabel:`Admin` tab, you can access the following category to complete these tasks: Overview tab ------------ * :guilabel:`Overview`: View basic reports. Compute tab ----------- * :guilabel:`Hypervisors`: View the hypervisor summary. * :guilabel:`Host Aggregates`: View, create, and edit host aggregates. View the list of availability zones. * :guilabel:`Instances`: View, pause, resume, suspend, migrate, soft or hard reboot, and delete running instances that belong to users of some, but not all, projects. Also, view the log for an instance or access an instance through VNC. * :guilabel:`Flavors`: View, create, edit, view extra specifications for, and delete flavors. A flavor is the size of an instance. * :guilabel:`Images`: View, create, edit properties for, and delete custom images. Volume tab ---------- * :guilabel:`Volumes`: View, create, manage, and delete volumes. * :guilabel:`Snapshots`: View, manage, and delete volume snapshots. * :guilabel:`Volume Types`: View, create, manage, and delete volume types. Network tab ----------- * :guilabel:`Networks`: View, create, edit properties for, and delete networks. * :guilabel:`Routers`: View, create, edit properties for, and delete routers. * :guilabel:`Floating IPs`: Allocate an IP address for a project or release it. System tab ---------- * :guilabel:`Defaults`: View default quota values. Quotas are hard-coded in OpenStack Compute and define the maximum allowable size and number of resources. * :guilabel:`Metadata Definitions`: Import namespace and view the metadata information. * :guilabel:`System Information`: Use the following tabs to view the service information: * :guilabel:`Services`: View a list of the services. * :guilabel:`Compute Services`: View a list of all Compute services. * :guilabel:`Block Storage Services`: View a list of all Block Storage services. * :guilabel:`Network Agents`: View the network agents. .. _dashboard-identity-tab: OpenStack dashboard — Identity tab ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. figure:: figures/dashboard_identity_tab.png :width: 100% **Figure:Identity tab** * :guilabel:`Projects`: View, create, assign users to, remove users from, and delete projects. * :guilabel:`Users`: View, create, enable, disable, and delete users. .. _dashboard-settings-tab: OpenStack dashboard — Settings tab ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. figure:: figures/dashboard_settings_tab.png :width: 100% **Figure:Settings tab** Click the :guilabel:`Settings` button from the user drop down menu at the top right of any page, you will see the :guilabel:`Settings` tab. * :guilabel:`User Settings`: View and manage dashboard settings. * :guilabel:`Change Password`: Change the password of the user. horizon-13.0.3/doc/source/user/figures/0000775000175000017500000000000013553661042020004 5ustar zuulzuul00000000000000horizon-13.0.3/doc/source/user/figures/dashboard_settings_tab.png0000664000175000017500000023211713553660754025226 0ustar zuulzuul00000000000000JFIFHH@ExifMM*ij8Photoshop 3.08BIM8BIM%ُ B~DICC_PROFILE4applmntrRGB XYZ  acspAPPLAPPL-appldescPbdscm6cprt#wtptrXYZ$gXYZ8bXYZLrTRC` aargl vcgtndin>chad,mmod (bTRC` gTRC` aabgl aaggl descDisplaymluc# hrHRkoKR nbNOidhuHUcsCZ daDK"ukUA>arZzhTW nroROznlNLheILesESzfiFIitITviVNskSKzhCN nruRU$ms$frFR6hiINLthTH ^caESjesXLzdeDEenUSptBRplPLelGR"svSEtrTRjaJP ptPT LCD u boji LCDFarge-LCDLCD WarnaSznes LCDBarevn LCDLCD-farveskrm>;L>@>289 LCD LCD EDHF)_ir LCDLCD colorKleuren-LCD LCD Vri-LCDLCD coloriLCD MuFarebn LCD&25B=>9 -48A?;59Warna LCDLCD couleur 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCD000LCDLCD a CorestextCopyright Apple Inc., 2018XYZ RXYZ c7 XYZ m MXYZ %^curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtV.-Fn L )   !"#$%&'() * +",$-&.'/,0013263:4?5E6I7I8F9F:F;C:?4@.A&BCD DEFGHIJKLMjNXOEP5Q'RSTUVWXY Z[\$]-^)_$`abcdefgh"i,j7kFlVmin|opqrstuvwxyz{}~,_ۃ"o#2BSfzڒ-JfҜ*?w@`AJB2CDDEFGHsITJ6KKLMNOP{QfRSSAT1U!VWWXYZ[\]^_`|arbicadZeTfQgNhKiKjHk;l.m!no ppqrsuvw#x:yTzs{|} -=Nbwڊ2Qnȓ5PkО)=Pcu̫3SqĴܵ*:JXft…ÆćňƊnjȍɍʍˍ̌͋ΉχЄс}vqopt{مړۢܳ"1>Ib5{;vKRVEA/S( R  m9JOFn#>H H !":"#}$ $%^%&'4'(e()*+*+S+,-%-./B/01[2 23p4!4562678=89:I:;Y??@cAABpC CDE4EFGOHHIqJ+JKLZMMNORPPQR^S$STUzVEWWXYsZB[ [\]h^2^_`a\b)bcdeaf3gghijTk,llmnowpWq:rsstuvwxyzw{k|d}]~XTRQQRTY]eksÑђޓ)\Ś.bʠ2eϧt??@@ABCDEFGHIJKLMOPQ&R8SLTcU|VWXZ[A\p]^` aAb{cdf8g{hj kUlmoHpqs>?@ABCDEFGHIJKLMNPQR'S9TMUdV}WXYZ\"]J^t_`bc6dnefh&ijjkmGnoq%Uz޲TYKj6g3 (6FXm"Ju DQJ rTNc 9 @ { w*b){[@+  !"#$.%=&R'g()*+,./20W1~2346)7Y89;@=ABD3EFH. a5"ԼYRXB2{ k3j..M6}M¾fѭK:brr^J5sފ>#BaW^#fTAP;0H@p7;Q9, [_XwюRLĹ[ a k4hЪ+⏉ׅ|E?M#6&Py$|͵KHU_r^x$bo|L| @BBl:Oȉ30@Ul;* my>ᢾ<ǯO? kZbY5KNF[H]"%;- Z-?jjQwŦxj:dn%a #^͟kQ_UB>0O<4vWaq!#Baܕrg*OP+~+~:~ijHO76BȎB, :u$OhٳeE,I=meI7#W_6ǐn 6ЭuYG"ʻ&ۺNX@ψ~ZOC4[?A<ӑy{{hѳv[_j3վix_Kwy_ n6 Lwy<Wh_k F[ iLm^] =gr<0HRO@5xG~ ~;bxI(]8a~5u-CS~1IӮn#4ʠ]|9b2'_?e,)5kML\<*69bB d'Pj=x*oxbǐ[F-DL܈Ի1bpNq+ Š({C.ahfyAI,+rl%gL FN t?jω>?x?ſ ?ӵ&zܬSk=#">[PWxFOe&[\Gu 9frgPAOִmsPb]xZm5{mbh!rȋ$dβ4-cu|6{([_`C#^Юu>fQ%w"T`@#O/VzM*館w9H9r_;??c?ZZ>ˋ6ʋ*yNTɒq5ï /uF}5Ǘ,21 *Ncc84W3诟(?>kqxoǞ#[MVEGkh`H~( @"ž,lS Gqٝ7ƟYkV2iz]^@S.cXUm; bM|_Ϭ1[~Y!W&Msi# УiiIp/+@ApŔw%IpEz_F?y~oy~^7on9qi z;j~Ԯӵ N(wc*'h\^:WiIЇZ mnmWWH&Cps/Oȼ T"Q.<i [ۯDnHFSj?xݝt{7W[{u2LaX'rCH*q ~Zxoߵ_mM $RGp1%PN8ɯ̱V=ruc\Ak2/޷.],ӻwR]V>*|iO –-.`%K*@Hqs^!U *ՙ_E/F!SQqq2{hc&9&iAfC^_?h >|%mvkmQ/bESvw;q ^-ojg8#ьy6s^7c\^%P<57B?-?|S>YONZ$wdɻ ǐ}kßGo|.V⻋n|#H0Dϝn$邤~uF֭Xm@}Bʬ➾hIYFd-y[vc?47R] -FrFvrpvg9xnKI ,k`ȾQ08 b^𦳤C_ER;IjPnS>|YiA s١yHUOi{#u8Kqέ1 4+m#}:-/a -9+偷dSxvG|!]#Zյۛbakʳ.8 _ZugmskSy-QXͪ,$V]cfl9^CmWvkw8Ok@qq6g{yOvؗH+K@go).ѯE|>~.|G>'5LJw[oq"%,n!G|iz4{~P7v׌d]0#h쬼WD}T;_'gRo/[R𽕿">KguI೦rok'΃k:}[I+X&R]Rݲzw+2͹of\ǺGx>"BlrNc +CfN KN24CM [f}kRdkI\|l#( qU@@QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEE9`4I@8f=g߶}. xORo x'VJ:E'@R!K#>^ R8^EJvV4_K>YQ AW7ɱr=1~mX71,žB?4dxxC#?kk8O_3GG獯??{e], ~CK?ľ+m9(Υ\[jZyjw moyjeHL2OZk2Ek]PS㿒ˉ /;S7zOWO-}eivz|vO-ի@3`R*ːONM魿Ϸ]VvQI/]n.}e cRAo`%y(c$+)[/Ǐn\iz~vRj%?/$fA@i$y!.4K[M4mu[yk_*ɯMx@%OƲu4$|x%[K6Y-m9{F]kyzQ$[~$k?'uk45žh4kxd ,r*$5_hۂ}᷂/z=ƨ֥}k"HZ)q)WS 赊_:W_@)QEQEQEQEQEQEW 4xX\l7D\HW! |wu|M>??W%Ë/$yw@.ns@]>B-N1jTĭpA ʳ}_9aOuˣ^̺).w4Q3|0ܒyr760FPEPQyEGxH|{:χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((χz/((dc}stT(((((((((((((({?X4?#f{;d\&Ms0W) FMQx?w[Z(ͅztiF$ KEx];m3[Hfyw+pXC:f7d˒0~EeoQ~o_L;s%W^CeE婊?4,nBj[J[=wşo ÑkT:[O.- ݷۥAKnt,5"7EAHI-Ϩ{n.5~3cjy:nVe+>kȚ +q~*o _jI.s3spNK+ ~F̆Z6wS~R?Ng>Ov﴿^Us<354kghaڍӤw2Đ< \IP /'=QmnoKkh$)"*3# yᇀ]jjkH_U1Sx+n,'McNy$u-!]J-&/gT#wtKѿ|ʒIl R_}o^NI4(F9(p[Ս{~04}KWl&00 I)9-?|@&-IW#h ls#UXr.&OAơm?xYx[oibKy66%WI#0[Z_٦z_~Z6IZ i[1`;W]|Do_&^}ejRDm?(3_OM[JҤs^$%E*HqO}REA&᫛)dgMekt}ZƲVoe}/ķxj&H9`O*a :F̍AS]x'3~s{Moe6iz"xܓ4ox&ӵbRÜ+Y$3}B(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?tuA?󮎀?O=]uB[B҅+*wh=Rq: r9Uoa'į/Ş $sq6 O:+ccb W r|Rwe-ڤruqs#85_];5߆:^mvexXH򍬧w8eدǟ $vfO{g:Y4O;*}$d`gHim7-,B*i?+tIUPI'ۿo+j .c׆# 3U`qws*;HyHq_E"ak&'GO_]e}Bqhݴ$MKmd(ͻE6پ]/뷙NCYEFK9fs *N+c^KG8FW䝷o XL* Ѧ0;6WJj2F3_T~ʚOgų)K? I4P1b2g.ZNRUgaQEi~]W}X>ڇnju^兗%"˶TtUQWx}/o{W_G_/oWI_`ڏO??q9i|_q'&ɶ??'U~<]ci^8|Cuuiq^\On&61 BpGZxđ/|7k,݋Χ2&ǎ:QRK[J+]<*1rI?,e-~ wUҼwo6[Y~|졋 a@-/ /Q|1S_ߋw7mu$QI#KƝtҋvo$Kf-7NZ4-uӾ^*q]/G Kב_^~$G7~$оx}Gon2Fn̪3nU֟~'ɖz._j< kKm.Y6OKK{u;?@A8_#?y$Z:My,v˲IAwa6Bvi^,5EI-Zm,zHd[fS'#jSqE'=Bi/Ww?{-+~|!hFRBPg18Jqi; ]\((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((-xo_=:?5+h$)zlpJ-c.iRK2=+ G3($R]/I~-SWOOG>0CeOXZm376? I羰˖9Vnl˩l #cA|\Yt)KI{Jt?6G$WL0u<(*E{>%i.N67oniT Bo'7t |54+5M9ځ4<\LIXlNMhS=}9!? ~w~>yRҬuMv:]oyݒAXFFYg`5<17=տn7O )K=& K)GBTk <F$մyu;mNy"_ZDC2I ѲFvT$7tKOӌ匉sr\{%O}( qvLv&p 2jM!Mq{=E6h7Imc9?BW#qu+-oQoQTԤmq+Fr,yjʹ=o%{Š(QEQEQEQEQEQEW; #ǂԮtBcVRX!HîF%r3+]cQ:աMݡTG=U#*Hh/G|[{%^0iw?$fS ?2mWᧄxJ V.Y nVO@0 QE52==w=7/_oHn4XO( k[26mQ_еxů&xf RvCv%'$ G( $#$51rER=?)T9wO揕iOۓ6z7~x{J6vz^X!.d1G""o$_U9|IΏ'ƾ7WQyBS<_Tžtl9WU>[K{왥̶Wܬr:%|AeciQEv0yWwjz73꭯ͫ-Ĝ  @Q&M[ZQIӺC b‡|=xƲӮf{EiT`#ƥ [bv)9< ]ZLcU`CC_R9;\dӑw#Z~mԮmtcQڂG1? ׭}wsce{e.yoI 7FB*G8*iScihjs{+_s3'^(ƣƚ=ѻHUdU!p*|i}m KT.uix&\fE $~af OZQY5Y<3xmဖ9;cI@A詴W}ெ>t{}I`\]sܖ8M99/m%uEw?ecj|a> kv7n"5Eav1Ub3u=b|(/ |G:͆A ,w4N!@` Vt(Ƌ[ӵu3Y9T+#OSW(~ƞ1>k֚7:\RIi(G!}*XW3SxgbӤC~ɿ4_]?? 1^^ٛgGLjƼ+q碾>? mxx?ʴZڮiYL(Ns3tQݿ&Kyw&^l/c11/#V_>7~SO?liHmn$]]yY݉f$by?χ^>'7&׾/nu/~Y^%"PcN3J٩/Βq[?bǿ 5#$]$hṱyE&t˷ Ł|d :~,wȌ4(8,U]$Rp|^Y;ivFXtWI^e?f-ڴbB0䍻-xo|2?g[[^5 &BHK®坸s7*Ч}^;wo#v:{-~Zt}ߎ?=cſ1"ޗpo$6jAspKr&sw[_~$EG-_nZFcQ~j~vo #Gf )Aу`Mxf.LNbP,0 G]ROy6[7˧s*!ioׯ7 jѵOM4WWv7K-/ ; ހ(>ת^hzmCaaek1D  WQJW_>4cH(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((+WдM~zo[>]I2gT:|7^zM s%}HT[tPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP->^W`%v4jݯ+h|k.t; R Hep紸Upe6HocpJl~[ #fe2*G"Kᯆ o<1dL,%/Y]ᡲS{{Y-/S& \ ϮxX?Uk{Hnm @%  Wf_0|tf\n$_MqwjnLPHb7b@bpH WW1c'aUIzE2Ggj;3S'AE‹;MӗWVq %"HD?( ӳitm|oR̯TW~/]x_G^ {?i4}VP]FOgxY|;exJ'I6c/442n<mH%N*"oO\Ifߣ=>( ((((((o|Zׅta'#h#36v}AE|Iğ ⅖ ˨2v~@Q@ uNp} ?eX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX ѩ #SE[_Z ou)+m'u4Rh.qÏƉg[]ml沆KxTLGvox6ALm$kXC#!*UpWME0Z}iEkgL"5AqPf1\bMs>i^:nlIQemuԢE{ q2RRN<%?52Đ_>8ih `TDTpRI'ش3MѬ-++DQFTET U)-QE ( ( ( ( ( ( B2Z(/F4oimp[FěmD IsZQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@袊?Š((((((+<kt ~KXU+R+:'fwqW9 +5j%ְ]/pʧ x{Q SaZj(H|mijZEZ}G&qVtdi_Tˎ3`f>*UmVIƌ[~GQNedb x )x+G>'ľ]e7'~LWNWaMn+'w>g 2a<@La8ekQN*Qpq| (~ZO4)xssMҘ g_~Go/(\(<[ |)wcH'bKʃm+#cєj3xğx+ƞ/~X,SqH]vD*Dua--{8u v;#P4$6go6cD,8-+1s}/E}36;_?lr98 01c€'Z:R.дWÿ:Tz_Y1H@+˹&-߈|k-jfH%-F+7qv]I> {ZN2 }>iC' @<qK࿊>&Yޏ}36J+ S,yS4_~o(' OnMݼmcm)kd*E;*"$xi7YO?4B/epghR'\7HSq\nf+[=?)F|oj jRy"@nvno?n ~Ԓ&4( m`mQE=M3<'@?)P*T[boN]TWޓpj~k]U^A,sa~] |'k_>(N%B\-;_j|Jw#snovTfTҷo](k_٫MӮ#hkG0rI)G?ez~"-gh%ʑ *sJ %jΊ_NJ䰷> W:Z[[I` 61I\5sǿ:x{^5ӴR=eHd=2*A _cۨw¾-t;ox;UִWV>ӵe$eH!PAf*m5w(C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?袊?Š((((((Ҽ7_χ2XnoGYSE#8|Epf)VQp,ѭWgc K>wx+LO6/ .dKByw奰64Y\doڒ|ͽ ۱9pp_&_aܥ'+'߿[ZZu=ǘlmvNZ'{]Ko ?|Qg5ލ}h&[Nsz^& ~"4KHګI$JBm`qt)dHݏ5O^?MxK]g2G"d1J`X QӖk]i5qsKdչZ7g znη"<*gFc$LTwO33#gwcO$'C^A+[y/qom{Y/E?%k?:σVa7Jc:ş?gĿ +Ss_OٛJƥn 'ɮ;.~6:]w*1[yWk  ol;+7Q..xU 2  W >PgFc2!EpzׁK _t"h߈SovK?~95??𵟏4'Z?~\yYRX&⤜{/;u+c9ǨoHՠYc#Lck(iE)nO?d)yWkj$ksomdu)8܇;XeO ;], EK>_u/cxOTѵh76kr*C+)V1ZY~Jy rϻݾL8~/?u iw?[϶A.&hA$R7C,~п>7^ioE-@n q2ܪFG227ZMWe^KE{[ nCmfd}5u,|6>b𪷜0[BrK?xZ$՟kťM.:5Tߪy~b}AI~/jݵ"DYk+Q(w >{v×f.6ٵ m%f)[n'O BxQx_o]zZedqG• "3ߌ_ߊ:Zd l5vHб]␂2 NxƐRړ~W}_7/%I?6||3s'^ M[z;͌;[6,n'95>/ۓ&ҫ 6Nޫh>$HQnYw#r#me}jK/᭄_QՈi&M!'>Nfog{?bזkXU;~g?5Pf)u&7h72.1 JnF9&jG?d|M۟Zpm+cf<6ʓHnFWسK "wAy{V1۽|y],Ѽ%oAWS/%Q"l`1 tPIB7?5&M9}57AS,}u{<}/&B#i"[i^&UX.91|ŗǟ87+㕦;jF\[,HX85*X ep< xi>Gűi!xJm,2ɈAc(@wfM;kᏌ<|JT^~徧%G}O~-𿁼!w]CCqW7_,w2'$R|AR־i- J'κ `FQUG$ O֕dV-ӍQEaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPd+; =ez7"c>K?4WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WOd+; =ez?*?t WO_''[~E',dPΎf,pI++8ks'm/}Wp  6ﭿD(>((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((7㛟XZ.HI.eH 2E݂3^\F~qS!,N9c' -ҿd۫NP:cݾ: igM$!n\2;9ci//֠5W{+xB2I#ex|*'~ym A5G5\0|ĈiZEhb j/k>R[X<3 , A|1_gŞ>b.|D5[",d &$iD|(_OQ\Zuvyltdv~HjBkѓ+|V_;%}}.K~?/ w7f?t=OD/4%uw]Gg ^ )DhiluGxcI^U9t!fH 8 |m>%t{b [ʄE#p8=|?]g6JҤqt.Qny1tS'YK*/[~6s߾SMl_rW,4ɯm,-ldH~8ٺG,~6a?|%mi=ַ+[i7đ_Zdiby *R6 aZE%] DY'3M$Dۍ؃Xq&˫Hm6is y-Zw 8 [V7Um?;.׾;[?cC?Q$ӝU;KTPc;@F, kOmkVZzT9I5IǗDF줟 Wgtb72ë\k闭n$]<[;5ω`̔mܙqΛ/SZ>%m\I +KX-a!/]˳cF_Wk7-=}l/ksmjuxR]wMS,_C a)}ȍ$~hCVFme14-ObMYnCIk:%[Ԑ 2骘:~6KQ@ Ъ\d~`ҪP/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}P/?Q}PEX19ptQ@((((((((((((((('+Zk-kVWPr25??M;ŧ}0P{n4k>9Q-ݣ"Ǝ둴sQwO-5͟Wτ'|IF't6rLѴ{=VmJxk!9"ba* ^'ƞ)}}[/m&)s4IpEx&r|e˪|1e'0x״3Z:cs ڍMi,dh#EI!@IԞM>5`(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP((((((((((((((()%HX⨾kʳc\7/?a9W;L-,RLrV2pNi|:2\. zpQ#*j+V2XӜF){kfvڶy⦓g9iQYZ]MgukueIGFpqJ _Q_羣+?M7c?DYh~x⿺knnǙ<;m$A:kW _QW$.loᏀ>(^i:ZTeip-UkgF P+/Vei..噙31$_羣+?G* }GW~ +)K_9^ֵֵW _QlK_s?NZZ+_羣+?G* }GW~`_ 䃟"vֿֿq_5=^?Uk{?2[W$ֵֵW _Q? ,'ȝG\W* }GW~U~ 4>7c?D?:??:Uk{?2«e{H9 'kkk _Q_羣+?E/AύXO;_k_Ώk_θU~ 5=^-i+|okZ|~tkZ|~u«e{WlK_s?NZZ+_羣+?G* }GW~`_ 䃟"vֿֿq_5=^?Uk{?2[W$ֵֵW _Q? ,'ȝG\W* }GW~U~ 4>7c?D?:??:Uk{?2«e{H9 'kkk _Q_羣+?E/AύXO;_k_Ώk_θU~ 5=^-i+|okZ|~tkZ|~u«e{WlK_s?NZZ+_羣+?G* }GW~`_ 䃟"vֿֿq_5=^?Uk{?2[W$ֵֵW _Q? ,'ȝG\W* }GW~U~ 4>7c?D?:??:Uk{?2«e{H9 'kkk _Q_羣+?E/AύXO;_k_Ώk_θU~ 5=^-i+|okZ|~tkZ|~u«e{WlK_s?NZZ+_羣+?G* }GW~`_ 䃟"vֿֿq_5=^?Uk{?2[W$ֵֵW _Q? ,'ȝG\W* }GW~U~ 4>7c?D?:??:Uk{?2«e{H9 'kkJp p5=^/ {}&Ң{{c8wFY U:ZPފ[J1qs#ewRdKYZUW=jחRǫ +Hďo$>Xv7OqwchCE^cx\[)y4ڒ[?7$=T_o,R+`ڦs $r|ṞntV6Y)>Y;;_ES\WWoC:qkmwZU mԃʬ6rIapR@cf|.2A%t;-.-ܕ*6 wX`Yyv޿4Wlxz]%ťոHԹ))l>p @ujwÿD۲j 6΅Ur~jrVA-OК+E;[GDu1<1kser#ydYо2 nJ_*~П./>$+74sNm*MI<dCs"ۂ3r9|oU4n?[!&z߈˫+ycS<Bxrncjރ Y}s5/!&ʃ"62)(X$ӻ^q*'ߎWoK|F-C3_ԢM<^O2oOYHTO$|[w MkK摯M&;]F-T䪢NcouM蚿╿q_zn#7X QxSbץ <6F2p {7'[iڇ|khך}ƒN%+:2P}=mq=/>$Wi:mo־TcTi>eeGYIR`TCJx+?|k inZKai!&9]W8NI^>) (((((((((((((((((((((((((((((((((((((((((ҚʼC#o!Ǿ "x+f' [':l3@ g }+Q5Mb u#ѡI&%c1,jeI4aF|ߋo;ASg4we(Sk}}ѬV $ݸR4D u\C B|7u6v BVy ͽͫǩLcRhmTBx#646y}O ͺ@4"Vo'|qwʨ`WOGk>3oGimrh+k,Z\K:E,Fȡ,cU .kF_3K:Y촙%IJsfc088cJ*m?sGfz]cRѢy#xai($eB̠㎄q^vú.R^!RJJB'd1^GW{~oF*z͢iMjk] +$3FtR0L%sǏ ɡxAg6u'y\:2xW'WB}=${:zϝ%>Doy6s"Ʋ ( wK>o[h2+B qvbmEG BR9P~%&(څQHaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPz7_j^2[O 4LxsgWz!Exaq$MWmB]kvK{{ctaIv\9 Lu{Ami_iW[ $ 2P7ztٗS<ާ]yҺ=i{Ă[տJiΕE{ey<H :0*A:惏sNI})FT4n{$[;,醊CH?{7k?>>xOB<+2L6Kqu-JTi4fC0C".NCWg!ᇅ4[5^aacçiZۨHqU@{ZԴiy]ZwƝVM?>v߳ޕڧ᱂VV;̒nd9}Qd HQz/CFc^j7z6stu"M g(Ќ=WW]w_Z׀+bY]ϦI;:;!,9=+'--!?[*Z; NTV\qjr7[}w25? W>࿅>-[|pKaw Iȼ񻋆KE%Mvwxv|IJ[)st[ i((bde2k ~;{xOJJi,,E!]Uf$Vw> ||AަC\ixPYII-IGtV~:.~$Ⱦ fᮏxy}ʻ-&Z?N>[[Y~Ξ`YGh"]UW>?xM|S;XI--!J#GʌH@ *o39f&JhK< VT\kV7SJ)1zk|%jw׉(OĚQOkIeb0NRrdg|I]BF㕿H,-bVGڠSxv ~]4q☼s}]*ph9,`{(6[`*8nJ< k? hV%qqV4i$XC;c@e (ju:N}%+䲋?0<~տ׊cI3FUִM>DTqiq~0mV$0Dž']GXΆu}"s!}A^WN#Gm: Xm$?Ǝ0:Ox?\ִkZˋX-+xiڪ/luR$_{8ONە"~zxUPtO|w_Y(⇌c?$ZτMZ7H%"C7ܟzk;U = ]ԥ5R]۫g+̦DqVOCԵ-gӴ}CX2y>Um,I'5WOw_M˚nkWܬ]QTHQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((go&;!Qef=$GꕉZդW,0 WR#AWS@^ЂqV[Jj3SRwEyg+K=G+K=SڞEyg+K=G+K=G?CX/:+?_/u__?_/u__>Zϥ^Y +Q +QKOSWUWUx!G_jz / /Sb?S,|i}Ǩ|i}Ǩ~}k>ڞEyg+K=G+K=G?CX/:+?_/u__?_/u__>Zϥ^Y +Q +QKOSWUWUx!G_jz / /Sb?S,|i}Ǩ|i}Ǩ~}k>ڞEyg+K=G+K=G?CX/:+?_/u__?_/u__>Zϥ^Y +Q +QKOSWUWUx!G_jz / /Sb?S,|i}Ǩ|i}Ǩ~}k>ڞEyg+K=G+K=G?CX/:+?_/u__?_/u__>Zϥ^Y +Q +QKOS\D@i.!A&C(f`OW /[Eu ܷWowwJYp(Rҩѻ;ݾf5b*TWVBh+v%jgJH(O| K=:Wᓀzfm+B7iE~kY9gI!=~ {x3*Ũ̇i3@'}Ѻyn_xڏ^}ch` :ʌ~bye[\zGԴW4[[xZ[/ 4o 0k0Y9-de?A1*55?&67`\K[B}x,]#-qR?ɚr;#JOj"&S!iod[m;dgApǐQ㻽 gÚh$WQ^,EfbmC+eV?1Xʍn >__:JIGW_;kc+wś?e7:"-*5w(wdbk^oCCJlˬiق5oζW4q_#WgǿZw}'PEsUQ[+^Y-Ai s2Gd` IkT>/t4F;5/{gnv")p F 2˫B"kWڟoN!KMǤ}Ti HvB@m̪˅ַ1׵_RtjK$2ڴS,[QW5~Jt}E|k7We{e/{5tV `Y,CĞ[|o ܵעT#ѬX/mf2 1w Wo_Cj+~ھ? Oa--u5kأO^#Le@NWjh4ZVz-ińZכ%U|N͌b۷F?5owٿU{W:wk'¾׉sZIpk"i)l ;.i]_YR+/^i '2qk0mWsyvIIQfYQ_7dN;U;=aLLi c2+K2d+пj|CѼ!?lkfuq }Esr"Q L(d\t˗t7WU~[Jk쵩<4~Ivb 4;2t BO8њ #9O_7sTk\$޴m"L8Q@#v%tr[~:7 ;L+ۯڿA%xAWK5=^9ƆQ,37.HU"~)lwVt-XéXo76wog:< ~T=ecHn_}][Ϭ(>$~Ef7:V9.fnmaDQJ=g6xM3xFX/VZ]Ŵ7>jYu)|匡\MxS{n7m?J׿DkE|Oj:;]mjzӴt{%02 HQkoퟣxj&?{u -v K(<Φh)2FWnPZW\4U-AŧoGoj{/<'቏l Z#VqmiBPJ`)Ŷе鸻I' (aEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP(((((((((((((((_ĭ_W{l󥸏M}1 N[ּC?G鷚jq %_=-ȇb1U ӄ\ZWg4P( ((>%W7|bUua@W$ F&ISxE_C-B8WOlU}f} V:$cʎ Z4m N48ǮR}kWu-S*Kʭ NBeHḐ-n~'{ߖ͗'ssoYh^ğp\fJ+f*I^,]tsH:RKIfקks叕]>s{-_KyW{C]v7[vwY9)cs]*<*fxPxSP,'KY"[ifO1S"$FYrI?GNi~_jW5ۭkFOOM֋d\<6y?$P) V&C][:Zn%մ[ja<#>y2qV[姠=_7_7m`(S.LL]7+)--n^ah|q1xrյ~*}FS]~Ym9l"x#k5yG|#1ᆧgO xPug[a5&<.58!˂2A)UU$B jM_/ 78W|?xzhPtI.-Nh刢Ej78B ~ANJ]6-6x լqY- 8*"#qhVOCEQs'z60CMcg_k.aՎtȣ#1C qJM:ZۮYEZ]e[oJtnkxT~U\ۻ$Jz[y9Kg{+[Q_5k!eHX1KqZ $uզ{)樂kyI6-rNbIhk_+Zp&sM#QᮇxY- G,,ghrY!K)xeNžѼeai:.a eRfӥYVܭ,0$t#vV^7["*/+|ol𕞹ov޳'fm<dè5TB. ,fXZswVo9fKFHᕥ`wURrǢ+ݹz )xqSZb>&MNH%P&Z_beCk)Դ!nUȓ%Fݚ4 WҔPKom>o/v>pӿfC^x_V=ZEP; 8-!wQ,i4[]*Hӯm+nmn)$~trr #+++G5JYwM35iM O|Um-y9a{VDxG.T<#6'OLӮY ygǛo",it!UQ}ibW}nygBΠt cVN隄 .VۙbQ0vg}zSm (Š(((((((((((((((((((((((((((((((((((cp:U>&:tYx{WT:4.PE3B@|9Պ*˖J^qs~;jο][\~$!u.eQ|Ox kM.3YDk4f#^NQ.B.߇/#3Jy~x?%< v]O_Nc?Yd{Eu m w~#yw$/u⿅nmJkw)6Y!T$#6aWVMxMGGѿh3U^D.L?smDs]!ἆ 5yOht'W}> (â:~ۣ_n=²H ~N7{%"V/+}?)O>> 1_j;&Fv#'b0ݜ3O oك᷇|K+> L~J D(:C  >h8q)TqIn4kjJԥ_wp+= ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\28G1S8m7G#;15"^a RI*ƭ?}'>%kZ:K2e XagBTp@\8xoߵ ?k:othiku-SOIJ.S;WY4Kuƙ+-N䦑2ę툀'8.KwkW0qݗ}'_/ >^?kѾ%[kVw:-blJvK$Ms]un[ Isxoߵ ?kR3KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@xoߵ s/ >G"^|?º:(KQ_𮎊?k%~+9D7@P4M;F5]&0v?߄<.u`'gkk L3A'bqu&}[:EΉ9ba:Gt cd A^(((((((((((((((((((((((((((((((((((((((((((((((((((((VGt/xwTΤ]m5kY%1# 1RAZWO^K;6@'a$vx{l=M)E5f4x3xe+EU=#Y0Ih:G Z?#-96xU?OSu:h [[v B]jeY@5ucG Z?#-7&EcG Z?#-!4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4EcG Z?#-lX{?ƏH=AK_@4V7$~O?R )kh4Oj:?u{X)YZв̜R2Xr9<%mw INs&Yo.ni k#-f:^sm,$smI4EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE(((((((((((((((((((((m;i%EPJ!`qҸ&) ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((?i#OT$E#asq'%y>GҸ~povkWhZ~]h[|SYS1R"" _fKkϥEq#TЯt_*-,[,UB72q(s=/^?mZ,eƾ?ay mZP-ycq+}<rq^B~?"15l/+9'լ$KX"$!ymΜcVPo{/vRgo]m}OHA}8,/Y][\ & ;ߺTgdUmC 8>k&t?j*-Ɵȓu|J+a|4ax/PlnJ5rn1tB MJ{===M[ҺIuE妧Q|_=ő[hU]`Vg$Krd` UT[~_.+x[6O ̦)7c1\*@|sU_&I$m.XP/,fYjgߊ_xYVg/yLI"f_ԗV?C ďS?' ^tWӼM8"C=|h$;FkB(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ݽ su 5I<=I=J4Y7OT=֑rvr=q~5P%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?#Zt~T F?HguGG`Qz?(=gYoyiQG@?i,nJ4Y7[Zt~TyiP%G ?G.>}|'+Oʱ5EUtr/??^Ӳ@%ѠLQatѴKs-lI4ԫԂA j^|SIGȵ3Q{kXB-b6ƹ `88 :+߂7/" g敭I\j ^R [Y1`]~I}6=U+&18Hěz =29 Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(((((((((((((((Xo+ci89b3܍|q';7[TP7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7YWi%~p0|q]uc<~ũizu-5O5˩PK:*3Mu4PEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP(((((((((((((((i(xm{d-_N"ilj>)[7[2Qlb&"^^Gc:u$ 뗝z]zM>_Z滧Z&ዋ[Kf$q@!1劊|zw^t_D<*Q5FwHI#*AYRW=5g_1Ο<[/Xg!x%Cm69;X=k]@Hj4/kVk.Q{_,\8"rUXd)4f{Lz+/uׄ3mK5Zh6"MMObFhEL0I?sw5%&ULJ>&>xwִ( 'dȐC$ r̝3wɭo~g ?¨9?xJ|;7-4JaXVW u|WZmwXcdmo!*P7ǻҦ"Z_8GJ+3~پ4nz^ҼA>[BIٌ#; 6:C]oFtm?[m^]h U[g"r՘A A` EuN=W5+o+_}YE|_ھR/|[ ҥX! ){y PEد5'G gE״VѯtlKfpM*8pnH>h.?ދkVW{Lފ&/ጞn}~ZBO3j'Q4gzqԂTs Eږqk?mD3 bugq)@ȧoW/O}_x MB;BL ^?5|8_> {,n4R'wC"1%F뎄;WǐX[;,{u_6Mb$BsBycNz5:)G<K5џW_0/OOMP3\Q˯VBۥ mlH5K -hi"CM mODJ3[w,MkF*W `HrDZ.GKԭ52W̵9oG*S*AaEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP(((((((((((((((Ŀhj tW13zW߳xV~37kKx +6Ro'?h\eZ' spGٷiyUQf=dA?/?*z6|zw>,x/Z?^6-o.C1&hݙ$q=f~#&)4}elg:I$i\$j;  ?v7{uZ|V|{FwZΟݥ$}<ٸ\.@PqKODx+6MM}>^H?'GOH'EAA?/?7{[?y>=͟kwY4}R{]^K&K9{w$oC&z4:eՍb{Zg[vk)9#.X37O@h^;YB*0T_nm˟~Cu/紎 ]:j1}w|>̋sI8|u mxa]>(IW\*U>E^;G$" کIՓv>o j&Ғ)qbK(,v+wO$ Lʃ9p+ߋi1'Á j'M>A?/? y:_pq| cJ>$|?yǑiՏ;7ywaۿŦ5K+-?FIA&iu̓[dpBI$" ? sm>;Z+/D($xH'gat?YGOLYWvB(cvIS߱Ŗ(%[7|YD{h"-2qbe=ٴSDp,H1$c8;~ڷVOeꗺk:\ZbDK$dv"VD+q~^;G$" KEe$$]ͿKq_>[gਯ"&IgMYmRV(pXk_Jԣd1%L9($FqYO@h^;W%{ܞE0,ƯO4k1*bbu)9GU8<x&8t''5ox'|e.ce9hb@'985 ?vH'EAA [(T""U@*Z?  ?v::+^;G$" s yO@hH'EAA?/??  ?v::+^;G$" s yO@hH'EAA?/??  ?v::+^;G$" s yO@hH'EAA?/??  ?v::+^;G$" s yO@hH'EAA?/??  ?v::+^;G$" s yO@hH'EAA?/??  ?v::+^;G$" s y0X`m*)!KXDO>Wry߀A+[׵ fzzh`iz՛MEʈfs!e)ՁRqi?\N@8{ZQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEV!_IK=崺Tln[l[{hSS'h7 xkak;Cme4̓<˕w{&jZ|ƗP2v:0Ai~/k'" ˣ_q&'H1ms>O5rEjg%ewp71$rqny!_ 1Uo!5+I7ueWl)8+~پ3ߏ5 x+IӾã\d4K5`" Hrcf__u3N5RM_W|Oye*N#@n.n_ oh? +4?~13A[K!i?xJ?X(ˏ_!_ź>6 gα24ʤi^zduz1Yt).koC0ߗ{oQE(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Wo{Uk[eXa7; rOrk%[ZE햝چ+Esn;Xw`6Fs\aJ(;_HAK8u>1Ox~ĩh:[ߍ.Ig$DȘgNJW~k;w.ou->K[ݟ+QQt^Yh < g'č#@5bS]?J&;[Α}DpGD C3d|~qJ&l=]5i <J=vVVj~R_>o S|4ev49/g+IWd12ߴֱk-.Ƶth)-.wo2EI"fUqGظŦoGklO?ࢊ+C(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Wo{UkG߈^&]{~eK,f\1H$__ ;?DlhB3rM$G83Wm|+KKOugȔ |)?%} sN+KKOugȔ |)y_y}4io_}|ثZk3]꺰e[{ɠ0;!X>}w_qEah^q9tk_p۸   %pur]Tʶm>-(Y◿e὜ @@֬YC{],rxx;SL\;u5\Tk  'u-[hf䵔ړދ,[Ph>զ)  }L>N4r-ڥICU9g阚;6=c=[LRZfSZIˬE-HJZCȬɪn?|t-溨 A|O&ym:ҩ-6ƱW-<C-uyZ@@xgaD(v {KA@OޫQ"@%  '}(@@]^D~>s  .xv "?@ {u9JhW@@/E}/%@[Ώ   &f5`Yb=x޷,hW   {^oX  ,xfA@@ºr+/_>}4Toh4T) 4dxm۶n:덺W^=y=c+uС'|Fm @@9}Y ,W\-\k^?ɛDW~wsĽR=  tFe ^~N%_:tA@!\.)s]L°8b0@VmxV  8/(dS`Nd女Yo,..}NLLm^@h!>12ֶJ{y*@@ iy~ym~4yثI`v}yawi  g"$/퐫 %,Æx/lľoZn&ZdpIۜEsru\,~'ڑZ;ޭu=q]鏟y3g.H-٤%뉔8x.÷mK9JUډAI@[OMye=i]#KܴP17\[N[N,-G}X~\kDפKox"6pŬ W0MmK=oxp0ЃWQdu`us֩싧XW\owsƔ.S<*%s|^G%,)Fs/\FydƴE6݉ȌʶƧ~N'%9=^!JX?|Ɓח&]G8l|Gt&T>:p@|L Vs 4 -Rr~[SMZtmNG53Y88'*ŹQLND _ᯑF@ t(/?0η IE"ll7<֠5h4oݭ+j2o[@{jo{#63G]Zvr`sy=82IķٕH4*+O_xTfP3rfRYwXJtL.@8 t""R_,u;߹)4Qt(4Grmy_Ęw J[<'?8՝W죫hkjs0Jo"3U4Q1p+OՕb7O_0ᑖ]wc-W,Ԗr#ٸ*+edjX`DJڣ.H! @W֯_uÝ[ou'k2a{AߑR+$kz?_|ĝtGEMFUH~Z 0WY$G25s?T$&6=yfƧ!hʈF6ĽV"I_Dm7Y6*Z´st܉٫6h)@W w}NϚ*K_R4~zSH'ދHwx|_ȍAؗvؘo7d#&R_zj2 թy#rF*z/s]fϬ=#W<*UJLQ2Tsj?ϙH"48X*bFM̭H @=܂мW])̢xK[s/wS[hDNMSt0u3Pc3y,w?A|nN ֨QYLn=acn|J1IV tK V P&=d_կ-) !ЋmbD^cssӗ߬,^aDKwjOH=a]F>SU91Pao>¿)9|}y,9ƳG [Zy2odXIŁ9Iq8iœRi&Vh8#;|S)pt,7;|a8Y. b+++TvHEe-!_"Qwm B/{o/g |s]<  x/} _A _p1>ؼb\e)z)ŚCUW F@@ {`49x>>'|Ha4@:@ 49r@-@Fs @? N&m}]=_aoCzSE\?oT|{֫6GvI7@NR[b4/zfo,o1qF PKg B_zjXK] q7"zcMys6A:p<0kn (U|7LR.RIOfH3E:b'ui"RvV]?S%e ĸr(LQJ*TQZU+jšnZB$f4xNr\{~ĥ1V!\;DSt ;ApꙢh(jMJS%m$QrSe8 !eG \8=`/Qo :kQ2${Qihf>S6eСH=Ɗ6Pc)j y", Od2rKXLӫ|Ԩ'*-g"N]L 4K 6\.J6L6 ͞O*4ĥؼ}h0Fk\sAnW{KP=4%1tTjԇ#2$0@k>Hat=[͎랬{P8= Qan֕oƷx{Ɓtwދ;k'tHqPɴvT X\^8~WsrPw7u6;1"YzWmk<g~~'///)~tG9˦_ Uҍ-3N#wK[~;[W:x+?ڝ>7ϔ_N Պ2sfIHw[w;W^}{4]6={O<}M'D-˫WY$lxsTvQE6Rd$_akxHPꢚ_"_3$뙴̅U eո.*k' 㴻\4M{as]Nۇ1/sZzCZ/,Sns*a s0Cj-. OEUhjj>>#b#kN9߼qKr_N=0kk^1OыnѡQOǾI%g#)ojba>)Ws U9]\XX˨2\ˠ4KbjiVK3KoD[n}xԲ(6G]Z!g@5!t@i{T&?!~yx?]BV%r_gBc9Nh_۵V/! |&_4mϱ|JwU"ClR Ox"OoJ?nZAh隉wCy_9 ~տk )_K?4fg x4d&nPId"⦤Xb\Lӯ:[e_H B3 b||FX t˩56Kzч قէea>.5QS x;Ey/{]?qcI 9e^w`|JyrZ:A89 `4[V+)4xQ-'0hRl#h:ϕvb/m`)@Sb鞸07*iO`)E4h0%Cb8$&Si䎧1Ge8vG%m(гDdӯ:/A}K.g"䃁4`Q#)&z/fB46=J{Õ>}Dt)n*`GfIJyz< Z5 Ԇh-/`/(BfCQ)T|Y_lkabP(겾+2hz8DvE@@ @Z:j#@`*@0 {`57xn@I+fzuu՜+p$IoD.-Xsnr'N.] vM`}lذappp e˵ϳ gNǏ_nݖ-[aao [0.\8y;Z0]h.Do}Ns-E聯hr^>u1 n.F t:5a8~E˗/>zNj.f !t:S`jʎhϴcՂbDo{2'^Nd  _^mX  Dˉ A@K˿m@@{9A: {m`x/'2H/x/ ,p"0XO;A:_ < IDAT k.g^lp@@2.}h7߅,D@@'ri9^6@'zvH pWmQ>C?`40ݙ=G>9-t9gH.稔1x:ppwm;v`"CCCsZ3]5n.nNsNzi= foAD[g] Hd=\WPZvz[J !2YV!Q%i$ 7fhA9Z(Y@A#h'#4dG<تRL"z\a)J6ŘZ٨2'ٖnh·+~!0x~*Ze|fIEBVJPYZdYe*BsX2U'!DuYW>"E*.RI&--i '<MEYEʋ8b,eXINQ/Ctu(:@@K^^ .>yթEAN`܅On#3-Sp  ~'U~o!  PK`pD u8[^X  k^n  `K A@|M@@l 6l-~P0Yl (mUrm6(@&uIsuVjgs*|'?,;`v GґL˽qyJWrH:%Ѱjs$'D)BffI&: }H+F+SKuxidJJФgprG>&g8Ipl30LJz$M*6MBr̔mbI!,ȥ9D.\hZQ!juY@@&C…>n28[:릪 R$!=Bj<"uvlp^jf="5io¢|!) Cx|&=*?;xK/XDFd=M i }I`>ˊr݃1-|ظ-ȠBjcjmf=QzrYo7hB6͞ @ [ r-ų8b4_$>0ϦR2Uoғ|FLfD|dK:YeҴDai^P09/.Hnz3jMe@ R 5Z#-hߝWT  L+++d'&&H ^nXA9b=X9g44"Ո7OI$\@buv   +^"rA@Z':;x^G  !租~z? Ƽ{](R-[,s^6nܸ}NW7ߤ"m6 NGWqե%x.G ,^j/uuuiYfPZJNs7o;vC@-@p {z`9r`}ul^׮]NY(@ Xe0%齆c_|!R@DWR՝CYUrT\ @G ,hDޫ!x/ ,hDޫޗ;.cx؄Y;v/kTˡJj2ƌ8w/NR#m YN;h];ygvz*wj >A@#-dC@hcyۼGOp)P z9 }7`e&[/,,ovtF64 0@KkR!U$=*ӱ>Brs6i @Urem/S񼒔_f1< [[7nzo|  Ɔ ȡVx/pC %Ȁր!d@@E_Ѭ5~Վj;祂裏FGA!uTw r]%N@@@z*XFqt|;uA) A'0x뭷@@`ޫZ0 C+  oQ_x0" FޫZ0 C+  oQ_x0" FޫZ0 C+  oQ_x0" FޫZ0 C+  oQ_x0" FޫZ0C%PXӧ۴&˯F. @VͿg\K\kn|Jo8ת  {9 Lre&T%z\OAb>\+GteWJB}>w;gk\ȁ;rXb|=w"$RhTZ'VwTli!"T\-Dn?RyY>)2I17|LaS("Ѩo$&~#3DZ1ԦtӦ̃"٥iCdWHIQ GtI{[qOװ6/&se/59hKQ&UMLO>|F!M[_Cl6%Ua]%vԆŽ@ d5a:#|RymYFQ=SOq8!~%k>Dzt_U!Hf ;jCN\WZ9ZhbK'>KZfrb:mE w}m7a~s! 'OsYeyhN>HzWm>\.Ũ3@Dv>WfQ 8;$o+jM4-TJ&>/UD(/Jd2Pwt337:&< %+yY=Z#wB݉xD8MQ_TV'.hqGFuV Q)}>   =Z-h/P 8S+++TE7 1^nXA@34( [= E}DޫUn͘-i `FȐ@@z0@@i^M#Cyx`Z?G;X6y'K.&[6l088}v2/\tܹ6k6Ko}dddƍ.!C^>l&L:~ul2 &rB/ PEy'Oرm۶s]Nyԩ={`,冕oe|4 ?rnzУqHcm(d E];w.GlACF^MGCv(:}t;z@˗/kϑ!g?zNˎghhhڵ{uׯwB3tW[gPk J>FÝzzܸqXC~!Щ/^G_b=2bo^ٺѨG\a߻猵DE~?5k7of/ZGX,vT@Q0㩵GK'2744EUZb& ,#JuMQ_mUB+ ՚lӺg.2zX\=rFg {-gɩW*S6Jjƽr~^| mj\u}l+.9kΦǹZ z7b76nZ 6Ǐxaʕuleڦ{ֿo[W/&?Z ?έۺc.k]l]wX5b4kIM *;r3 k5x==UrMZn~}TxS2.@ ZG(*1=#v={رiE5Y~V_?~u3zr711ѳf%Mzx5YS17 әII}.6s=U8y>{HГ/*qLCp+ԂZˍ$ mL+kxxctc>Ǟz%DW{C{,ڻE >GD@:메 Kfʳ.طoÇ^57ܽ{wI[ʎw[xe7z,=o'?yo²5|Ʃ2H JϽ̊ C$%bAu}sgh=/E4H 3$^ \)WMax*/i͡"@W6H[9Mu)^)|+ny//xˋR0Et`3AQD *ަ&>|p7(0g'\W=:g&b恫 jng^;WP[v@?GAi}-2tNVov'A${`3o hQ{J!52b?OJZ {y A #5DB#i(   ` eS HCLx/ W@ f{`@@ P0@@@  !  ^8x402)@@{`&NA@B+ 3A@  0p   ^i(   ` eS0ӿi6m򯙰 @ T{9Y_?DD .A@}gؗ{o%;['|7{ŇL%T@[{utʩ LCŨjS4|@ >sK-LǼW[2ʗ3s"}wwy|Q쁇ïiL9tbNPNl,1ԔH*#ElXQsht$ɵPB@ Yyj\['k'zWUs,c$.Hk]L%~g.Cr1gy.&F1 #9AQ/  !J?i|$1Hk*eE!h$坱c*"uh"JDįzd^qV?:ݴ)H  1.*6{J/\SalӶ0!Mq8kHfqgz?͂ IDATδH[7?|^='tbNjX,SFܼ$0!~!$xO{(G +a FEV)5#}w}m?7p  %Y=NSVCL ZbѴtsYғŒtS;-F>#0o߾ÇYQ]XYY!k411Abܰ {=` ^n(A@@_@@ x/7   /^j֬Y]]m-#r!>lPVfy'K.&[6l088}v[eL֭۲eˀ8Y4 " pɓ'w*f1DQwM/\Em(dt.GQh7ѣf(+Ǣvx&zgzO{  i^&  )4tVmt0@ |߽xb,{{l  %4/ %nz@`{\Z/%˰B6^DaVUfRO|CRDOT))ŝTFN(]-qar)Ԙ\9_6Qt礟쪏n$gA<D Dc)E)6 ^fueb/0GQV^:K>'DR!ɁH:GiyT˙Iuag*3B"NLZJ >u P dZfN$9'LD )Mi%ϵD:^c*&I*Y9WeEb"baIsҰ̪,,;bK.Ѩb~Uo<8fg6%kls΂;  >U->Fޅ_52D]-t4,~ιScEG i"z  /H!YU/^-}u%i m.C2~vU^3Q3P19@ {uk8| ECaLN!d>DVj+5(rشa98>OBOBIέ-gtIE匤sPWj,ҧjA @`sl};:EE}sxPR<ϓ~#JrcS)yr3HkCx3-0 x#dhd%-6&rsԟsP $V2H*ӊ%#Ss@ ۷#@+?Utu^)XYY!>11Ab9t*2!A>^P7*!!Ð4dՈMl8/{yA1:hWl  T9 jng^;WP[v@?GA@ གr@W?> A%Ԗ  Qw*x~&֧N ;,4\zV7W3PH~.:mVGeJs@Z_ Q3˭2s'CTCT/hڝS٠S+֟1AO8 {]G#k/vj5-mfF\JFLT8qjNVN?e65LI3{wy_/6MS I[ e>gy.SΧ $i>׽\Y9j\'cHP^hktWzmt'QcJ'.ާ \e%MJ wyVBFm8^h䥃DfշPg=S6%3J5#4!-n˨sJ*-boUp }OR"cժɎ(;}c )`oڡP&Jo5eEZ {u6y* cy4qD8h!:Է!9ǷqR pi|BVJa9͗M9'O+ mpJXY}PYUm9 iZDQa5uKe*"GCIExM^$W7_? 0=c7X[Ѡ-glCID 1f9SuC̲Uᣑ{'̙qr^ajHFi4%_\C.r0/71r0D]PJ$'1Ɨeκ"ZbrP%l-,t%7.M``߾}w%Q;?XYY!911Abܰ {=` ^n(A@@_e+U*l߼:00Wca_~" UK3r]7WWo/n~q#Ef7A:Hˋx/?o6ۍ7ݡ]C?Z-o׾Xr]K b 2vurbjV?cû^ Z͛'N/]kCaolذapppSFQEkO^}|[><<؆ lhh`uh؋ȋ1x`Ǐ[n˖-{! v[vz pɓ'wLcQ?s8}-{z,)k?\_5)S`Rj`S聯h6Dn366F!;4m{р!w} ].X[k} uC^G˗9BGG$ =\04{@ fjӨP* o z3챆A=@ }57:ްgd@Eu,@LL8p$oW!  A#tڄVBF{GܑjtT0yڲ~Zq^zIz)z_2Łu8U\ t9x/w6{Xd?4>/H´fCKX#Iv^\*y(G쫣ŢI#EPj.s#;}Zjr.㡇/~o}+CS{\NIq}^ٯmogM~ꧦ&T4&Wf(Y41A)Ed1˲픯UCy̙ޒmԏ *{K_,-_Ρ r=ȶQϡCR8C cFWG{X興l-"o^Qz4vjH΋<|xOHr1^tt= }*D =l$ßhGahy:="7P`XN/SսژRlm[]΂+r9 jpe7lwv`f5?Y&2<\w/*<=J\:Bwc^VdH\,^oὺE'[bQN%)[sU>W=Ϣta)˅iHL SW\@9saieBKMN95c't9tnu:|pC9x7ww=6Gf=g}q]r_v @] ?y˱B)tˉ Ja+D&@O͠$ICR* 9 0vJj+u-Ymm]u W@ po?6IHNs>lMqi\//?Z4.5Cx{[0vȥ;[ןY(^Ti;7chrSYۆ9!ދ2 v7;wҫ ^ݐ]BQՁlhAګɣ.VEu9+0\E^^%ZoG=nXi\ WZ>rCԋ7 ޫA x4X+> e\ ^M`qw POCDTtY亼:nK4,zߙ==0`{^>WcI:5f [3{E`@c^A@@o"@@1xƌ   7^~k  ИWcFx/HK,Tq"5Se4T"PΚZfJl/ Φ&wk[Nь$\ֿmdL2BVv.5#58UkVJסOxrH$ܗ酅ݪ̤e;i媫7GVEw[V쌮.G;igJɪQvxFw*Ʉ M.G'tDD9~5)Sj,9߱1IxFY(7Lr49 ck;(JG+Ws6Kw%UϙVrSlĊRr1/ -doN}6$n&Db hPX/tJ۵B|yWJmYMv*42.m0,6 [3rIЋv* 09ӓJ{ Q8/S/KrΉ[zkIlR烏|4 & xoPK^ޏuJѨNsQg1w]~֋W+ bBV$u3=W'LS:/9ew[7kr'bLRYѨnS bNf;I.hiܰ]t+)RGe6ê {N_A/:w⍟3CK{9 Fҩ-9e|Ԫˆik}hde"YdgmӔz\%Fﲶf[Q$zBSNAiT2$g gƢK,@[L<;~U$'"ݿx4?g:Z) }>lI%t ĉ Cd@@E_k@@rC 2  "5  n{W{fjk aáUT͛'N/]?FM!aÆ۷A^-xoǏ[n˖-hW^t\pɓ;vhKY|Xl@@?#$[4~טOf83[FA$6^Pr@)^5H@!nz&ai/ PNZ6!|9|/NSP8Ē-7{/Ytl֓6u,][bE_\5l 4D_z\|\^" Ѐ!9$R6j ?Mr6X5[( MT.f2pˉL`[}0smۈ;֟nִδOd|8S T\OZ@UBS9WDnQ*OqX'~jmMKgH33 zf6Wl @!joQCzfn^pt_s7nӇ-7vS'#%` Knc%_NF_i;||e+ľJdK,+n V>FnZlz. {mKmF#jDA@Gm@@jDA@Gm@}ozReOx\ΫPjMױON[?1ط9}!jz-7+hӢ.>C , u}rW, Q ٟܨΩ{9 F:N ;vҥKV^sZ0D}!*ս]=uv^uAܹ:b?LlP76DGmgkmW~F-: Uu1@} =zmV태;b/4,LxGm2;Ju d^PC߲?z?/Go bi՘$@\8W={!.8Hu/HqA;n$2nAKeVޫUr `$@!. KP5b^5H H Z^C>#-<_U !=ј5fd/e M R<׹/MGqpk}!޿s\Q~K>4KʎQqD@\x^r%!p3pyC   #^>j   KP[] QEo9ch8wB=P8-ܨSN@vCvv$}ˍ&:kTTpAo7DMbRIDAT69J{9A:#vJ}Ó*{ߜlPlX# @@|@0@@ $$y<^6P  s^>o   `C @@|N @@l{@A {` x/(H9x/7!eI  >'y  6l @@|@0@@ $yũLTGWf“-S妳!@z"@N&bB?T|fnaZ^Sflz@;@ {}y6U,qE=<qВ.*5hw J|U$=a V Z0J'a-';=fMQg^lw_J.1G.$/\0+*3|$q8=dy<, jɝJI. HOuo(d2D^ҤR\9Nfr4.;%r>klcFW6aP,aL9Dtir,BD]O9."\.̗3wO H\<Z:kv֋2g@t@ n011Ab9t*2!A>l_j 01=6=k0>MAj B6c^}jUZg@`X &C_7%e  &q  l @@|<0@@-$ƁłD_u8[^XH;;l;ϙCEI|+RHtkRzlSޭ׹]x^n.cgvF 15{W̤#;˰=\㊷ZHWI퍼\.x+ 䋲5/!. ()oj T@yn~p^*02v;%SS/<;ȷ  zTrfJa:" 2t|@Ɇ͒uru<0K󩺃|C Q'wIȥE]#@9:cD6OwȁO:B'gJԺHuKI_px7t_|dz%7b;W9=VUY,aUZAK1ZI:RdDBTHөG;m^'"1.ބx6!Qhqf!%+ R&%މ.ų4'_Je:Mh!͡der,<2Z⡸+Z2WHˬ^2L0 fV%XK1TCoVŧbC% d *}Jq;lނ xc i=_%J.Mof͵\/=΂H#Al5‹TKKe*#xV r0bWT(,튭7O@tJ9bl= CCTt/\D1I${y_o>χ saC^s,Uh譕G^ {QDt)++bc!t)Q#-l|). Yϳ֢%ScJϤ$fdc󹪒Kc˕J)> dhyaw>9ґy :@ |qqM'&&H V! f(`Yl gitUUsW\a{9` +^0A@@WjWѴ1.\XZZ:w\9;u~###7nl]EOϟ?w{׹"FGGǷlҹ":Mu n۶m@-Uq:uHKW;%۷M^^ز^zv4oe|4 kΝ \eMqtF7Z):}7U E]]p]5k or]c< %"+;44vZ&$6I$Y,nK&"JyYXUbm,rUfܬS.nu@lz.S-V\NvBil،-[/y"-e 0rI=}!FÆΓ_Ofİ潙'ӯ̮ZW]P뙢< p{BRA%ßrKeq&t:"W." h-iJITH$I1@Tq9YyK0FVYZAb_2ڡ'3E1D'޼V {yo^ mk)I-L Jȋ8h)LR&RYWP%X"R(1'JͳM+=j΋gΨр~ Y\1+_XMVuF0yqŇEJU"}2$rILW|Dpmv}p / , "~l޹Fqq|}`_`"'(VQ *Cb )RYHu"k /b<8) RdYhҤD&mb0+Ϟxݝ12gΜo~{sf6uҌͣ/qzGpxEe寔A {e̠͡iH}P~$I].XX*wuBZ,p)GM"!HYp9qH@ב/^gڷa2wg{"uRٜ?'LKK3=@;'''k5'ueW}/{l1({7ȡ" (E/ " 0 @2C$@$@%@腡[$@$@^pxHH^0azꡡ-*+/gw5Ck1foJ|Yw'fgA{EjltvvǮZº ƴz˜6@WOOO O_DH;Q2N!@ hRۢevI@G ۷cy3]^^zlHH'qY9^2 IHO@#TpHCu$@$@\iKE  *]eKEWHHd e^2(IHAeD/IHdT/$@$@ @u$@$@2Li  {z:K  ~9i  8E֭[gq/ 8r(`HH^NHH@&@i0M$@$`T/{\'zI$@$ z4& =$  PdL ؃@X{Kz;f $oz|[ ePV6溺,{(+++11-$# }6^ %%%^Z(~fvA7&f'3')LHUız]z/vږ-[B2 @.Fnnn`v>3T10kl-]80 `> o)邇G-N癆t 3gQ)<3Ō+!!a||ܠ],p.v~@ XdPA̰gJs pzK @, PbIm CeGZ! %W,i-  sPH+$@$@$^&qote Fu&juT[;  hXN[.?~݅aVY' RzTxqoL(Q-ahbj/G#||3gcy. tcY ,D T_Qk=$D58*Q=1'%ԣGg4 1?pj4V+"1ػ-ox>Wv).Xᗷ]k"`q#U//jPr2|qkYJk[jT|ʆ:\r܎ q5|Sc75G*F$ ׳ ?L fggO:uݔ7|@Fv9kPK>WCdG0!wJԹx6Js^{8)5SvRb8{W췹 uU]Gk#J W'ᒲu%msr\MJ&>4Dek)'brUaIMXd^^T>8x~&C>pކ#|6l؀ ,AAlΆڟN|W}WKE-iEN֔{a*](c#3s0769sbP oNǕѼ\Af7]oBK髨. <,_V\)rӊ 2 UWzVoGLIӑCҦFR ^ GJk/π; b^lmIy[-*&+-b~aWc_0E$-kjjرcmvDFfggqEh[oo/T U d,##g1uEª}Hinpg,߃U0SDQH/\.Km#v 5˅Jߘ_Vjފw'dH-ׄB/$h]5{{gEZDajuy{[TtP狿.Mp@!la@cD~yZQ%s3KІIB͛P#U۱O?ŋ(S-^+K5( {zzviZ^ZUQn*?pCEudH([իɅғs줤Ԥ]բAk3 %`juo֕tE'[HT D*bb6NneS~ԜY-F^025àYmRZeRV*x`뜷_aMwUf x xFTΜ9#\1Z(P/g_u5&|C0%AC Ç +ȱb*;}az/t0Bm]&PaVHCDVb<};0/zaS}Mpv"Ƃ)-¦֮K !`XrR o-DLJ|Su)CDq0H lǎ0S_rk7ABba0D Hڻヒ0Q?5rΝyKfWkzlǠa- K63 %](qw=X!&$':گ S/8^I"+Т(Lz4DVdŹɄA9Su2䒑^Kv5V7unq(aaҊHy|qC_r9.l4̛P+21t#6پRsPLwݻa߼y3~F^zI^s(PA۞={[jjsI iؔ5}`<| Iagoc*j7[A*؈gZD^S>%罰B.ɿ O1l 'j CTXwz.737kUdaۻGj'(L0[Kؿss&Wݽ|SO=4<¦ \~}ӦMVZNx&Sݍ4R 0A$@$@:brNL0K}^MW6 W$XHHx(͸RFjc)m. ,kiXHH  K]m,p.&9XL^g(Wꣂ3&hƘzajbb"56,< ĒƓXlqѶ䧛EyQ)38.z, =9G^d\WWʿܣ1a] M `VulހW9Ib _d+Q }|=:>=Q^-L+WAwo4;Q/>6]yטH@ E[¼z +"t}=_N "oSB]P KB5|   W\Q  %z- + ą+.( A%ce  Hs㉮FIHH ۷gim۶jժL Ēĉ o^ x=ochad,mmod (bTRC` gTRC` aabgl aaggl descDisplaymluc# hrHRkoKR nbNOidhuHUcsCZ daDK"ukUA>arZzhTW nroROznlNLheILesESzfiFIitITviVNskSKzhCN nruRU$ms$frFR6hiINLthTH ^caESjesXLzdeDEenUSptBRplPLelGR"svSEtrTRjaJP ptPT LCD u boji LCDFarge-LCDLCD WarnaSznes LCDBarevn LCDLCD-farveskrm>;L>@>289 LCD LCD EDHF)_ir LCDLCD colorKleuren-LCD LCD Vri-LCDLCD coloriLCD MuFarebn LCD&25B=>9 -48A?;59Warna LCDLCD couleur 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCD000LCDLCD a CorestextCopyright Apple Inc., 2018XYZ RXYZ c7 XYZ m MXYZ %^curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtV.-Fn L )   !"#$%&'() * +",$-&.'/,0013263:4?5E6I7I8F9F:F;C:?4@.A&BCD DEFGHIJKLMjNXOEP5Q'RSTUVWXY Z[\$]-^)_$`abcdefgh"i,j7kFlVmin|opqrstuvwxyz{}~,_ۃ"o#2BSfzڒ-JfҜ*?w@`AJB2CDDEFGHsITJ6KKLMNOP{QfRSSAT1U!VWWXYZ[\]^_`|arbicadZeTfQgNhKiKjHk;l.m!no ppqrsuvw#x:yTzs{|} -=Nbwڊ2Qnȓ5PkО)=Pcu̫3SqĴܵ*:JXft…ÆćňƊnjȍɍʍˍ̌͋ΉχЄс}vqopt{مړۢܳ"1>Ib5{;vKRVEA/S( R  m9JOFn#>H H !":"#}$ $%^%&'4'(e()*+*+S+,-%-./B/01[2 23p4!4562678=89:I:;Y??@cAABpC CDE4EFGOHHIqJ+JKLZMMNORPPQR^S$STUzVEWWXYsZB[ [\]h^2^_`a\b)bcdeaf3gghijTk,llmnowpWq:rsstuvwxyzw{k|d}]~XTRQQRTY]eksÑђޓ)\Ś.bʠ2eϧt??@@ABCDEFGHIJKLMOPQ&R8SLTcU|VWXZ[A\p]^` aAb{cdf8g{hj kUlmoHpqs>?@ABCDEFGHIJKLMNPQR'S9TMUdV}WXYZ\"]J^t_`bc6dnefh&ijjkmGnoq%Uz޲TYKj6g3 (6FXm"Ju DQJ rTNc 9 @ { w*b){[@+  !"#$.%=&R'g()*+,./20W1~2346)7Y89;@=ABD3EFHiZҔ qq_rE4áx}c$ ovt2}>hhWLjLQY n٤LAC& @3~m~ɞ c&~ m.iͿl$TBci3(.\`\x^*9nMx))die욳InF-kK_(ư3`_> > m?|^xcwzk9 G;TY1@u< q<ƅ7Q6WuC/5)&\E}C3_ď^[}HxCnp8=qֻ?>jr]?ia{Es*JnE| /O!}]7Rv]b|[Ip*Unմ#pfXTcԚII;tZ+ğ[ٿ6zΥ6Uy/*u(܎ RkWě,BPSNM\el6/Қ+ᇃ++4]ZyR!c2G$r+1|H~|y>%>(Wv<w͍JI$8m8r%E~UѾ>}𧊥⮥äjsEmpdH_ŝ[1׼yap(/%U\2k4q͂$R7H.9oxƚּ5Dк,{V&al/jw9ƚgj=2O$x_FgVC@;p5~.i.OLB[MG"8O҇Qdkv~_ |~A{O7H|i^kXZiv WGDulsE'>j?R#Ak|Cu_"hd}Y`R7|~\WPO։fQE1u6Ǒx?GtK;ˈy4~vX2skCڞRܚ]ޡ位r?2@8=Lפ𿆵'vqI JRFE0MŔ5Sey6*n{gow%eb@9"Floҿu-Vyk R|cԞ`4tjX^qn92Cd;xƾ' wUϩ$1&7H!'aD[!m/F%~Qϭh?j/ubt׼AȎ82&8-¨R[hGɨS-xK\=ܨeMF`qp[ >Ţ/ŻRfl.[MGIgq3@B9'Oio3~?9 %V ŽO8 !O$r:EI=v_tO> L7sy[""s⻪Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@WË hq5ƢCyzddfB!EyŸxῆU}=ao%᱙'[B#̧dc'Ŷm[Ko  (dMIGcmkTShG<4=V~[{fèֺ}ԉYд"̱]"Cjg]ɪںr-Ǜ]}?4OmĿiմ[ocgi|˾fvZ+AP.}aIs47y2M+":H؂o=-wW~0Ǘ u}e'[z, '/,%^L핤YF@r+??7 JUtk7k7d+37~\%Y.a –~ܢUJO/~>oNw(RynR$hO*|T,Xk GëifkPӷ4eeq僀6 Õ >$7u5_F.#Y7kRjѤ#qtXe TM96v <]]g>!?x6 vTbٰN2Pk|wOſ m'É5 {{)~5{q2P Pp -ox?&$D2|G+[[im9EE' 0+_dx8x–_ |_OXGjzMi*@tI !Q"̖)ݵi-*ww".v=u%ڒ9,nMЉ"(ֿgSԧ]d+إQ4KrNv(  tR;tV+]#Z4K+V%<9mƱ]%Ӽ$LѨoެkVZMiHºOnCI< i"Ip`6]O~ >|!Xx;xj×dAhM)YA_~j)S|q?z?=m3.|?i6k[{}"!c/%]?[>!|F=bY[CHH>]P ~QH2ϓXiM6[#9Yf8.X6vLN>\4^9f? |DMQZYjq<{k($ٍK<Ʋ#a:EUWg?PӼSdq y;>n}ͧWJQMYV"cFg?]r>϶y[+vߟ3Ox֋/@ σ?^Ø6~͝{(ܯ䬓/CҭOLO m;gq7x: +lG%wW,ٻqߟ'~p0qQCwmݯO9{GT>MGGtVFFydz(o>rW3}kSO?U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƨU'uAƫ9_EWw_jEWw_j(U'uAƫҿ3?y]:{m??i:7#ѢO.{ yd˨k3x+85owođ?W;Xg T$eVU-%kwž恠j'Dm4}P,,\ݘE")96ӷ_|d˧h,dOùVEwA s= E ȲwÚ7,m͵.LOi* 'l#"-wokz//n[]IjAmREvL *ZyUZo_^·v\w:RCk%=pKgvy chd{,t3喗.b)["I4BcVU(͚=JE&\kwW:t:{MzpV gQ8}>|$Ӵ,km5-iywi3[\6h%I&Iq+6%$oS3~ooUu7Gm~mvO:$oRInc䩆o3sq$J"9/σ4OA}N DFq e@ޞ/?'[%QHaEPEPEPEPEPEPMe I##ΝM`YJW#E~v^*hE >?;~j7KJ勱uy2G|тY[R# 5.5k-֫~CFF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF5~Chчѿ!5Elhߐ?aocQ@?07?Ə}XP>FF55 ~z##b8?GT-5WW^O((((((((((((((^#r8 :%^5;/ C7e0UG7J(e*z?f :MQrM7F@{ [>yH˾aX]b$Zw2W:G+GCk4ryL ŕzQ5rOωi-PR {w*p&ɐ 0vW_|#uÆ;OZj$JeQLi[HoĖ;y/c^/w>,RԵ_XYLQ,y͞Mrydە>?D&!x54Ic'FbA #,+. ޖ zKjEo5o}Pۓ|ݒ1ZCʍ9|u:6]#F'7wu˽B4Kc5ͭ ͬwW$Q7p,1G8s 5GĶǍnd[鰖cv߸OU$ukHMgBЖy,RK6܅%Jn@`U+Lt=MCia pBQ)[tbddyGuԯxzW9Q ̑(]0Z$޽:+E->577l- UwuʮǴmm|k,u5ͽŋD+O͎U gK5 JRfMZ{ڂKhۭ)U#J@9VpM6,ʖww sڏG O %VidJ`.LkFzLӾ7]B(5+H?w2j)3E{382EBX#q?W꺲6]K!cXTHj*NKV |9:ܤb]N.?,d}0_?7 *˫dZZơXB>GKweBP a֮:曦I^MSM𾝧]WV1*OeU,[ ,"|9k/aCymije)"T pXᜳ z^|07t5XY[qg|F}߇E;;Zg2Z/m9#1C!K񊨚MuoȵM_>/|-𯋵Vӭ2 G`NN;tGV\4QEIAEPEPEPEPEPEPEUu(*k+Js\GuwM8uuʒ2#j~gx KsmZƗYKbWIT2:"&́H1(FF*WMErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQErWQEspMtq)XOP(=k#?k~> J_O'J/lḺәY#WJ;D%H$+B|)}vi.5PkgwVkYHgRLl@,t |4s~͡xWJӢam:ً&1dnI kى'90YIW3Cʝ>iM~eظ?(t gI;+Hxeyv#*N*?w !5^"meyLa(IHEb2+ c?u9Oɩ\20Eqbew8T*˭'V7MԒ{_Gx*qk0!HIJҥu~ǟt>/v,Z+Nxp7Ų1jq=5tQ҆Oe|e㿈~ e/}A*.. T\$)]d]=CE:wP-W`nGuCe p@~mbJi7^(8u,egY1Yj؜dk?c'C 5}wŶ]K!a5Ja$\~W KUH?ǕkZM^G>~O_,MȖȉIbvnv!smWB) ok}7Ho>&B1HZKUN GJUc~_|5/˩ܲ 7PXsR3uJǀKW?ßٻM'F|U >i+pB! b\2Gd&'> ~3яY7q{v}xm"E&ppT = |e憗z5-Ž{5dF")Q K/o/xA}FkukM罵7w,*:֕#뭬M9)*RZFNޚ^m_.Y.8u#ma2Gp5~ýz^li- c|"(>#AsG/4VD4zQ-s8wr; 6/z&-WǂK[U;}4mn>M"op>rz v7/ݔ֭^+.ERQEQEQEQEQEQEWdgy 'ScZ%&fH[z#/#rbUJ͵O|o4Zt WUF(V%?0lV Fb_YMq%ܐF%*.Bp@*P rzEP_A%hgy8`};qSf vqwjKpkI+~)_jg>z)l/vCⶉ Zfߋ(uQ|}2O=:F< HHJI_2Z-?>+b?Sk>T2$|D$ERʓ0W?ѻ<-m'?i#HH$QGo + `?rD1ȡ# qUm4>wmbߌht+8rFKo4;ߴ;ςO |;Ԥj6׬r q4uTgp_qh$:?ؤ1.AE}UE:bۿT&ns "|P|tqq(I*@k(U/V_iX~?Vޗ:~.ǿMQ.TYs0OiX؞CfiIh|ıB%.!&FBFJ'+7Bȁ}p6RZ-{b6ELzmTwohW7ĭ{ž[dTYVo!RJd:o):$Hnt?c4Amm * {KW[S\O$zcR)3~>o4Ҭ|ͭ-'z2H#  >w[O O_ tNd` [ mw_ZCpL$UUU pis[5媰Ǔ;ETQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQErzw cUz7+~>cco~mVr[ vem+r!6KG?{?xo%C_?[/E|G?[ vem(mVr(!6KA_>c_?[?o%C(MWϘmVr[ vem+r?a?{?xo%C_?[/E|G?[ vem(mVr(!6KA_>c_?[?o%C(MWϘmVr[ vem+r?a?{?xo%C_?[/E|G?[ vem(mVr(!6KA_>c_?[?o%C(MWϘmVr[ vem+r?a?{?xo%CQPK[Yb&ܮ  +?I|`=7I. RRG.gml}q\έJu)z-UZtߖ(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l}햟?4bO>i=@(l} )<(kzW1_6OQw|S:C袊?Š(((((((( "꺵73`|f?g=usž9eKk7ufA̎tM|ỻ{iwomu6 ڈ8}u㏃/W]ԧ[Ҡ#1/<A%3gOg؜T*Eaܾ5h9]r;;'{}>AԄA;˕>ngUoxEė(Bk*0ø'l§9_\2Hʾ>0x[Q m{iwmmQvٝ2<r{I_vNRIW-X=yy s6sI$ z,Jn?\h-ej<(tֿdaJɹc_[[J[kzTZ%V0@ 6Qyw.&ʌ@xoë-?Ys~&KXncƧ$T7ǘNpõz;|-ݶRgy|iJoZ۞Jʯ՝>%99H|/Iۢ-Nf7ф$`)wgwMc\۹I"J::elA_>#Mӯ|SWVQhIUT&T H !9=>Iׯ{u{—S^W>XBn3{9Vo\R[Kw]n\ G ks6֗WIk{j,?I|`=7I?$?:GxQ[+=((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((\^v5u8_)~C诳>-[C?G0ů hr"'ωQ_g |Za_#QeSO# +a_#Q 1kj?,"vi>$|aE} 1kj?>-[C?GDCN?ď(>-[C?G0ů h\٧0ů hAm5WA;4>0Am5-Fr"!fGW-Fc?sY_D?|HŠ?c?s |Z+ωQ_g |Za_#QeSO# +a_#Q 1kj?,"vi>$|aE} 1kj?>-[C?GDCN?ď(>-[C?G0ů h\٧~KM~u |ZK]_Π%֑ZYci-Xء`*q; Č J8jM7{zqM4S+((((((((((((((((((((((((((((((((((((mSR..͜6l% aZhs+Z5GVAj::+ֿ57Qjo? M~Tek_ƨkS߸?Zps+Z5GVAj::+ֿ57Qjo? M~Tek_ƨkS߸?Zps+Z5GVAj::+ֿ57Qjo? M~Tek_ƨ4z$i"՞GPHY#a>j)ЊuytHԄ~Z;T 8POJޢ|碾>W(>W(ݡ ^Ϲ识?~!Ϧ~'?~!Ϧ~'?hyB״~z+i_߉"i_߉"a5cWi_HWi_Hv.{G>碾>W(>W(ݡ ^Ϲ识?~!Ϧ~'?~!Ϧ~'?hyB״~z+̾B:!I$7+3 8=8uwP,1-۷!^*T>*fiQYV?0߆yZ~և!EgZ~jo;@VV?0߆EgZ~jo;@VV?0߆EgZ~jo;@Vk.]eR9gvjso}U\gцhj4W[_OzNW~oߡ(((((((((#/E ú|AhWw[4'-hFoH_Q77O诌 t#ZxMbQe.bheǹA,7qxW^ѼW_htMqm̑Π? &ҏ7M!SNR4f[^ǣieƤڵť7NLc2SjwWDdžm57N7_ ZLި'WIԫ#(`FQwB^%;vm*m#Gԣk{&{hʇ{[mGH?_jڏMxY->yInt羱Lv;VUirv3rr//࿂ j,fim]RM: NLѣ !¹?1oxgƥ|5vy n,ƹ#=$|P[uU~nvgֹáCk4ַWkV $ȓ;kj(W=/~,|?>uΡ5U-neeڞ_23SboYKcVM֧NJo}k;HV2[JU`0O.0yv+-kG%y|m`wU_?]EW>#rg~UKQ[ηt;fݎs;Ձ^ee7ͶM)iZ?H>E1sZU'¿xnRXw,rG,%U89ҩ{tV6n k#tC}*W|\U}5 H1Oq؁i/@M_O#[7ܞOc^l#S.9+`/2WVwǛ:DŽF5SX2kw五 3=+AorRjV:m} (aiYsYڷ<77YJhF,͹-&7Ϳy[ i?R^X?u:q0w)ݴ=xBPWMﵽvK)[Z<9+zm%k}Ν7Lݣ>t+ }xtcz>Hu94;چ0ATkQ_sC!Ř}[ݾwɪOmM&MsRL Vw.dDTǦqp+*Ќ6:_I4/-*I`2oYխU&xy呵0zV2E/r8VRkKuv*_]|IWzRj`bq[\E`v?¼uNռmh)<{+ Ssj-{?1=Mp! 淖[9onycBgWӮ%, F]qc+ O~ xH\"N$n̤c]Z_xs÷ǥxwVmWė2Gt rRpQ_j6>izg~ݧ6qoRMbDI$#mָ2f9.'m0Y}c<u}#JU*t{il 4͈/4$Բ#\"XtjEwmfċ c Fq QMBK fۣ,pH#|x7[Ƭ<-k49yHGL2*-eESn*Mhj ?4MB2Y%җmFU%9P`IZ_$ckޅgN&屲Y6jh-FLx0Nm]4]7CGo.,o-t˫?s{7mI<Y)v&CX&b;9Ve#PIV`'Deᡦ\j3Y.3ƬN\;zg "+?HMpA_-k8y_ Ʈ(((((((((NOix FNLX/ahf+vрcoɎX?NJ<[Kqi^iW{{ޥwʁ +O Zt6O4R5[GXC !*( մT+8]ڒU`pZ56UEm5Kfg(/2 5>~?>mjTڪrhF4iFpP%h3'%q{ܹv3m+~G/{+4)_q}^-hn 1Km$RDd%Wto :Ïi~ iL^\FV#e;ff,p]u1v\oQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQECYxn4-]\g,1l!Ȯ2=@jXw-w-lXw-w-lXw-w-lXw-w-lXw-w-lXw-w-lXw-w-lXw-w-lXw-w-lXw-w-lXw-w-ko:AG-k$cGu_o(cGu_o(vC"vSgĔW~-~-x߈N,oOQ_m1#1#a;~!;)wJ+f?WUbf?WUb+"((((((((((lYFTlEq ֿ둸I& &tTV~eiՎ wvg2EhU4ӳaER((Z_+'nej1^>4%[أ@dpwdPӼCڎi^hHrK,K2#E47gflEc ]~ GvZEy-?KyB=p?4آ#ψEgY[Q|msld1 HpTJ`뱵EPEyƏg<[s=zmmX䱊6I  +{X2^4vq4~bk$'Æд[א^/B'GLv W㿈YX|H-K%IS2$J"d,_eU/%GHtjݎzg+z瓓-vC%+~h5׉4,S݋5lPnW{*+opHl < 2)hs%EEVeQ@|RWߊ {u--=lQP:aeX>Q1?CϋtJz֍at.+o-%G p2W ;? 2*T};~v֜V]@{Z[_|)ɼ)y,sK5n9IΡjrJ  Vpĕi- \o=^_f^ WEYm=Ģ"+3PG!y4ͲLgQ؝:b"Yi${ZEu%0'+ ʦ!#{$B^xk4#xebvB糖ޝ/5OV$^{{-FhnRx# V_62&W9t!+OZ&_{~ .wg5xR ޟ3jh]>+yEK=E$$W1+5S (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( <zo{WF//(((((((((hHBI=M:(!x&E**e{;n5k|e_0%6ldK/puܨ&Q䬒(;@ߒ SmTҵGgyE_E$SnRBAtJfVuO日C=jsj6~̶z&IaUOm{/Rh>![AkiHUi MU Rt~JvBN߷vz΍ᦿ R^mB-/ɚ/'Vx IcުJLφ^] 705Hvv1ȅv-xƯw⧈u˿Zh'MOFӡv6O Mv`YL}BAڃdF^#FGTXmʖ9 Dʲydm(_z^ݿ{)='EFgd..A{Hk#ffFdg4MkJghWI{$OI+k?\Du^TCKk/mԭʤ fYg sxž; Pk:ͅsF B`IKݻO_m=?*J (<ψ5_ -p>s?}߿M*h%eI.{|S|tɿ?v?SĵۮD𪿈4oxqZ+ISV5RT!߳ft`K;I!Ee +ֿe_jvkW>"=gqwvWQG nL,CVI3I?Q5e{R宮&G (8Q*o|[/+~*6r'yER$((((((+WSQ}6lI8Yx 矍_~7k>=ln Qxd̍WA4[JOݒc/?|iOi5Kx/,P\JqB5B#To9`WNP #u D֟[k$Y}^^?%gpTױY~Kw_C\^5mh.WH;~M3 e4-i57vLF6b6=_9oo]'o#u=oJU| h41e:qiGO8CnORxS/}Z_M}FK˨,d m9 jd̀o|6Ҵ o@\-Fgf&#p%۴m[=ÏٻwEF\訷4wwby%76I`$?~[eeߧ޷oyOx/ x3þ^LjL'Tm?식N PoګqO KaxX:]g2&t]+ 係8dUlaxYYG@;5~ȿ/DwI=ΥmޢtRN$U\W䃞W|ޚ]I9vӷzk#ž~Ժ|['֯<[Th g@6 !xQ~|:6;Ri&0Duahv~?t7D#um#W!yŽ !I69ix^fM.JYd&K%7"neQe~Eͽ/k[Uq瓎׷Kt|cᆁ3NM\4xh]t;[#x^{ᶅ:Wï Oss*%#Y^fѤj~g aGסWMg7ɵ9Vl(̠(((((((((((((((((w,ג!MWZL^ MaFHo HAV duoS A}tŧEgJSˈF Y)|DxSMM(F;#Kw;ai񑚉d||9?oU,.B}(iyE̠,T P]ۯJVgg+xImywcR.t6-6@[QĿ/2x uî|/4?jS_a-dEg L`}Ÿg^lk[ÚW1>jQf_79$nko5-5 [-]̑DC0>лXar*7Rmz5Vܾo/}O mk_~L}n \ZKeYjC$hQqWM<5x3) |S5ơA8Xd Uw0\yrTl3RUšm|Q+_ǩCY\\Z5Zog-2HOg>^>Aq,, {0}0ɞj˶R̯w_oqsTcm[O..4GF,$Y\}E|]>0xº7m[‹×vKpn7trH#(%LK}v!?gii$^6Jy2|u~!< xPe)^k4SoHb D&Wu~g։$յ]ZZ֏qw02$rۼ**d_E# a׾.WxX6I\,~hV2D ۈ(=Z͟O}o._ kv-޷g Mw Ě4Xrv·$uG쟠k?؇Vm5Ukk"Qo?5g2 L"0vX6T?  sKR丼y;V"K1fL<9$9^ϗv-_^fPӵ|#)&ׇOҮKRԭu ׏K$ G1+:|qϏ Z.jZ5WRMKF+y$=>u[vW}}d#H9j1X̯!Aʝ0P_h>5Ş$tt:Z 2@P: ~ONZN-.Z??-QXQ@ֱx{FM[Xc.OPM~\_7OcY.oh1&8sNIHXϊߊ~k7ךm&xZD@p 6PIk/d=Sú >C6omk%y~T^xN$Nf7Ro{5ԹY/;ֲM'o 'Q] ]B-[}{ {P_ #o3ڭZXIkAؤ \ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ G?Gf>ߵ WDY#`A 7Ϥ?ºRUU%ϗJ:Wp~w((((((((((3.|` 麂-k6ns1̥dby_-Y<'+Q/c_jٹZ[/ѲXJ;9ܓ~zZ֫] Ov񝯄-Ki_ȵi+Hq$i5foů2a'3*=cK|6څ&4>D05˥._7c.9+O~*_D`fWk)[3EG`FC0 ˥?먯*F[@H=|M9-UeɋΙ(˅ V`X A CG٧\HKhQRnQKm$_"=<WO,ms}kicc>vq,%b%$+nkݹ o̡rAzոY̶:Z(aEPEPEPEPEPE_|O>I Uu-Vkqu%Kh_K2nTPc EnM-WZ'6;隽A$ĖQ}`] a"6_KWӼCw#%.o{Ap +n>i.k h9]zoW=NsI-cHִ !Y+B/-+[r&3Gȉ0dܟ5zω0A[>ռ;sKŪA #Mo2>\PIWv4JwW_uEy$?<#.kVuFݤ"/lXd* q==c@晫T.U/mRA052˲ )pg /jSM;uoX:ڃZ](R}2&uGh$;IgYVIڌ]\mdShNx? Z[ um})"ӯYaIu2\EE]|[TWx,~k h|?xQ4vTX4K5İ[yU lyLO絲χXiq9OBҙf e1]|6}/u'1ދ~>VNhzG|G3SN&chX5թ7;Fެ2Ēbתx▕g|Mmy~/dys|a"ܥ @h=K_$W7i:ŕ.-ͨڂHeY. LHx*Hl$_߄?垐igjSCiq"L9YaHI/*޻=?>g4?L6ualZi7Hܬ+$$J(y f(f s τgŐ>vU>vuGYddo̧u붏ڳ}E|1[>>^m4j~Ē޾#hY%ކO%O0<?z^W[M @3nVx #ãdFowWiM@=2X.,Um+E$dgBPEXYԂ1m <fʹvXx [< |U|Suu| [p9Y\s{%:nRVh=ThIs>tgЎ jqZ²ݵAI˔F́5zWk,C=Ͷk<`uKp_9'><:n2qa QRvW̷'5)la/`]^@ Vy8r\vTB:3%glD#Ȅ0Rwk_+yKϬwzlWwVwѬw6VXFFH[I=}Kf"Hyϔ!V.6@*? |pOO_kEZ,5z7{=;yZbiZu32YF-+E'v%tQ_)xO: Ğ9mZ @=K)o?Ҧ)b6 |e h|MM3@ج-uKdAw`'K01<,- [[g?tOu_vnQ^  |Eu]i:UݮjCuC3dYI{(H̾VGot}qE|io [ݷ.{*KLk95YfmYTL0 `+5G??4=K.]sW4t 4Idon!Dg(PG/, s^}/[+_ˡZgEՕ5a u+nb_)] cyz埁[^Ֆ^aڐi6"i&I kaU5*JWT/]oЪ+O_| `{S[xaYdyX,yW|N~ўT]sZӅԵ-=O7Ь \,0+ Jj\vm렺]_z>5Ƽ;i0t[_ky@1gph>#x{LK>׮=khA` !URع~ܸn\m{ wMx>H.,BF\I7Z|R>_@𮵢xjՓir[quž&y D_V%v朮v>/;+wZ>uZ[76y\Oun~3 9I.2~RxkTbúΦi6j.@OY#+oekvZ_?꿤}CE|O|oG7QN/g_Cg?~4dG%oǟ_xzGOykjWg{5<`nʴFb ;_m_E|O';|\\xgp:|pa\Ioa/v(xƞ#t]w:g"OA{?i` 9AsmoOWgmk(0(((((>-NFsx3n6q>euI=Aour[K5\tXOHK^i.% fuKugVeʮ_K W^kylm511K!;%{.Yi)(((d-D Aa,1c GVx?J#0swrGt\]> ~9č{v4tSKӼCճx9"YUivG9Y.0Gj?Otu[-ƐڜDtv@ɧU9XÔ#25n9+o[&LGt\]7?q+ż3Y?mwA5Y㳿 CQu#S ʜ1OZ΃xsl}LӥӧK9Kk4- +@;\]]ߥ >U%oGt\]7?q+j 2Vt]wDJ}r*iF$|2FLRysu&<Xjkϣ>.}핎-͔+ jMYI;-!#-:W~^m]]UGt\]7?q+|aJhMrŴ}SGYMԵ tۍc'[T T܂tF+RFR1_j1(x)ffebAs4_%[SswrGt\]0̧G;z?޹9_=1^!UO[ko,.Gpt/xW4ؒ)՘%6Ȳ/;2[0(OŦv%ˣ &#o.Q ԟ t>=OO}NtHZܴrHe dIGʯ[Wu/L$ KK}M!~~Xgda% *4֗ ++[~zv5ݼ),6NpʄZ޹9Oڧz٩ 㕝i6vwYpZkrXH8"b/~YN%Hmmv%kG"^cVAYU }_|~jkޞOC(((((((((( ?j! todxV%Wjq򁞧'ߢVmOAMB{6Y-[FzWou`x߈ >2G¿ K?Z\r&nR6^zo [,"vW8(qmY--Þ 7/-kDhxg&ms3wl8x?ueg/O Ze.QY{Q}&G+7ҋ~%EMݾͶ6xg /:kBZ'eϟ;w <><,GyJ߳s:n8Rm!$QE# ( ( ( ( ( / 5OE׃5犴綊[oYimYдh*20$Jڧ__7 j=)QKHΣoh-mvYJg3efy9,a{O3] x~I&G]Yx$r:4RZ=?Wt4%m]ɣx?LӼN߇cK8iZ;Hfx_+Fጛ~7mK_՞N--K!6ǚ{˿@msr!%ʣ_~>.[!Mķ^%Kki%wKϴdI.D۰ r8_X_xt˝>Y4*9S),M 5%`}EBV\Sm/~-~ʖL77%ZzA徏nc.>Q9dwiJ( 1~Η*o۽z#ԭ54K@Ob6!HdmI؞wVkg~ \|X4-J=GNHxkgJ]fCu[<Р!i¯Mƙq5̾b-1A3v݄*6Fm_4wS_e)kHً:gu_h4K-'Vw#ebmX\`Ѽ1*j~;Ş"} *y!Ԭ5 QyI#[Gis{07$q}Ek)9Ou/|-e՟|m _}\D46vlDxRy237 *%Oi|?g^xᎹ|=wiCbARA ',FHſ5ď]iϥ&8}r4⸴I:\Knџ'''-_?G𷋿cקM+zd}{{؍.(c [1G;`?xUlnĭ}6 /mI#g)w!8`kj)_K[57#xfּO|m7B5"kmLA[m43kCpeHvp_M; Z k7 Ψ <LplEl*3jJKtc|_(>7}XiB{$O)tQ%7#K_g}ZzL tOMqƬ 9~I$hsm=ѳ5smbu/^y8g9۝8f A7m\!u}Y jk[f7U(|K`qTE[E|?KwKGl$>jɵ /EX{hE]-nb72yw5Cݫiu k<[3÷hᄀM\QpvGTQeWRRKoU8xYx-+'5+gҾrtvQe1)wVL:ow\\lymF3zM>W+G& ^_ǯƷk~_o16gW{E=%dSݾwQEQEQEQEQEQE~ 'su{<)YWH[ K*Q%l<>xG^*&QFE$rZhh1L$#v=Ί:[JM^ɶŏ%xkZmi`\,~uiypj䴫$rvbWٳQ𭮋x7Ķg4ɯM]N8iO ͹LJYՔQI|z%nߧNʺG7\ i}m_P}B樂[Gq"\H$ev0|SxmS˩t/ngr8~u|EkG`ޥܓq w24 &0!Ps%|O-ݭ.g~6׾m㟈:]{{!i5ً:O>xa$ 1W^7^!Ҵ(м?qos8'Y'[)s;uuz"6w:5keI㰎'Xgyó]NcPt/m< =,w4cURhuW2[yS$sF柙(y _~zDIⱭG/Gq:؋e{)LR&N_Pp_cu5Άt8k9$th+rG"A!I]%߂-ܺ.gχXPhWy&- ;fܫLy?w7zs%{ Enu6Yfc=0F]i8LUoW]>=Ϙ64X$xL7v}7~A` g QOѿe?^Ɖͨh}f][$ιtbTf7gW#tjk*It+~E7wwkMhu5/Z[jw=i{򬺃FD\ĬT›CA|:\s}/ nNE3 7Mѹ̩qdm(8 *>͍ɵo#\pt}ŶZzƕ,tKi-좘K,rE,`T.fjgѭm=K0v77J\h#[j06~֊$/%m?l|?fov <"dr ۷H%-7? q臺ԣ&OZ'J?>w1٘ZQ fq+el1Wx_ɧb^"Ҵk[{+ 7M:T"[Y,f{EWXȪXq?"snH%qNo߮ڵ׷Lu#~;Me]%ũ)IH&SA"<WkPϓ u{8]"snH%%&LtW/mRRirFh![{r/pm"GSKeSg:Gw}wp<3?gm7Ev.?iݹK7D&di44ܷtgAmxkw76bK?Yݘ[{X$ In!Vb+',|ie37wZK[If,7KtL`\oWM-7? qM\񺄬oܟ~_ֈ?{+t[:7^^]]k&/HAU21$of9|Ih> NH._hDyR̒Om*e%dH$Zon ۟tkmGI*]U_L8ii\_\jw?o-8,?*Zl3i'E^߇.~☵ˉ QΟy.HՖvX0'Y}'-7? qM\ioK~_ea[ڧzٮ?[֬kKx^YDee%1ֵ?"snuuT|L]xEj>XA;7~ V߈^??"snmZQ8k^$ o2.]JY(<j~V?k317u}O Fo?((((((((((((((gOMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?Ə=7~5Q@?p?/MEs?zoh]5i_OMtP3?ƺ^ee /|X:Ыɴ?}G |S丧]C(((((((((((((((ψ5_ ,奌u}rOk(O+ mw,1P2wBjSywGr:+ ~o|?NeKM+:wI0Jvf !`ۺ sZ/qUӵOhVgoi}[]Eͼ,H.YF20n|nޗf[~QE ( ( ( ( ( ( (k?k:Ucqv̗Z0HRf/r⛒vM*+G[ J_C_ kZvԱ[.FQal=GRh2X2W Jm>敿 T_gtW>0xx2}VzfpImͰFIc"H{Mt?QhZ|Z6>f"o

    #6W>5ML>01 &|CXy7Iq(d#; 净rX9T2mt/Koh;Ń~ <7.gĶv#Om$se 3cĮǍꅀ֯m?moi~wjoiheo-پ.єlp$N+>ulOAnO+-~ƊF(-3tBm(OG=>W[W0|E>i} ƑhKj Ta]g3"@;tqm>~-v4}E|oV/4O hw*Ե]Vq_jo}. nLVʄe67=oe?4חuퟜ. >Ki& D(W*A*@9]zOA-?_gE((((((((((((((((|yźw5<{z\:1—fQo֟+m7gOgև_i^ HK 0P i;~6L1Ϫ_k:|YߥKG K{S$UU(7ߖmY>oϩhϯ!ݟx5i>tK<~TwZ&?nuw4y&],wW u_ZF[HuKX)H &ؿJ.h)%.YrDS)(((((((((((((((((C㜾;|?e3<_)jn. =<E 9)%Ԩŷe+?ڇVе8|q5G o|9kz:gXmX%9w^߼E+h,_SHOCqgXZ^Ե{m*[86[*!Knqm.箟f%_kQREPEPEP6Op@Hm`)$Ȍ3OG<=@_Knj_gc9߄ ?|c_O ZLjuz|ϡAAnh(oi#sR0[ڗoxO]U4v`MJ`lvƔb7!Kk{QTmϬ?x{v?¾|>'|Eccn-fQfM?!H`v?{h?x?>2Nj77PPǧ\Km^9M˙&省ߐ[e{?O?x{v?¾_I6{|=%f״h#K{y!I-45ڋ| IR*CE[4sQi#šĖ[S3/ 0qwֶɋWӟx{v?G<=@__x>ao^Zkg%i{.k$m֝[,x ?(qQ7ߴNo57ך_V;Sh"&EY.Ys"mry2N.V_v.'Q燿k~+> ~0>( <)uB./+Y]$ǹ#NvUE-ǚou=B4#->"d)#X,DXtgge8m>s 9߄ Ij_]ះ]hZe業wEk\Z][s,aԑrY5uo{>] <)a4Y]dݴ*[CoFb!T8(ӷ~Ӌ{Mɦ_SG<=@_G#]O-lF񮫡>\hڝ֋z_#[Cw\FcH,>[_;žOi:jUlBt-]ҋו/wBW  Q6$KiYa@AH 燿k~(ڧz٩.'Vf:E핌I,rE?' | H;?9'?oc7ak/Ƚ׬ ( 6?+?9_N(((((((((((((((Dža/ p֟ojN3E*0xxm<`?o'񯇒hQXu aGP74;? x{K֜Xi6ZC弸F=fh2=|k:-j-|;ᗷGnmуۙ2Ur*HEYaENu7ĺui5(M7Bfi$!s崬e0yIǎ+ f_ ۾qkޓk)D[km[i6b_Uy85=R][Mwn=}u3Z?3.d"xv񭧲Yo^g)Y ^!29~Igƭ\j]o[i6QXBc@hbYBQT{Ӻu!MYO@*FQEQEQEQEQEQEE;“; ̩&+pAӳqSxG=ygΔ4v@q3M.ǝn~4=/(Io64 õ4r[c 8^Iiom//_ tzNaq}h7_麆*wiq aʒwDutbK_ٛöW+ \>!]{f`{UF9] HP _覛Wk܂[og9t5[V֥XinAJʊ a$c\`W W-x\APOfE\A$GYG̣.(loOi7ͶFڵ] EYy6o_p=w_heŖOmmut T}^@펗os!r7 drSqGѦ z;]&9,,+,(dxDH(_JSʹy:W;M%X JT&nU]`y@A!qO5NSmM?.Š((((((((((((((((üy+IoMe]o·ƞ^<1-l5M?2@c+0|7>cj \[iG[G((e0ӌ.ߣhu7ww]~Uյ='Sln^ݤ]>3A,l!T,Ă1~z&ij줽[%2ѡhXъU*(i/]ͺCЦumF Y&$[mvNo0\t&suyB ⌜qE[SMۗ6KW$QE ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( jmޕtXCy S "8'<^pRN/fTdӺ<_@<ck><cYjv2[l F o40AW\\r5ٓú̾-$:ՄvboQty G,Kw GtU|^8jFTz0IE7}DAERQEQEnj_gfj]YHeU'2,s;qPj_&to_|S?[~l,YIY3ĪG 6X| tl t%d"M:KP<>Q ̡%;·-VԾ=oǨԾ=oǨkt6v96՟}.G> [T5@eQC G ]0)>[kY}vp2}+M8lPFd;$)9_KvzKvzI}?I[߇/k?ُo%]w^H}?t,rv;ta4EվgX5⿊ˠ &4,pb_3vrv=G=T_Rc|Ϩ~Ȟԡθ_ ~`ح\Ex{ܰr3@\7i-⦱#Ǒw}Eg7أX#oj_j_'S~xvKGxoz,Q4.MI I#`Ufhg ZYi!֬Ooj 5)X'D=F$iy5J^}@{Q}@{T%o?gς|9=(״ۉI.'3\OtcYnvll@;bl5{EJA-F`w1L`RCDAeԾ=oǨԾ=oǪծwn wROI'+ٷ%.9.Wm4,JMR$>Y;^ umFE0Id*:y 1.j￵/u}?/u}KMmYZ_M?((((((((((((((?_/Zޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf@_z_&?_/YPޟ/oOKm~%he5Eim߉?YĿMf3*)w!UFI<fnh4m߉?YĿMs?ۚ'-ۚ'-tz_&?_/\A GA @7ޟ/oOK3BBM~%he5nh4nh4m߉?YĿMs?ۚ'-ۚ'-tz_&?_/\A GA @7ޟ/oOK3BBM~%he5nh4nh4m߉?YĿMs?ۚ'->-_IE$v8 )$he5cȮj8?GT-?|_Ŏ-W58?|˿((((((((((((((^i_ u.KKHYb'"fG*\aʜȬ-_ǂ-ۛGช9S\ }m5uK8|Ht7;6˕|8`/y>]к+?gcGM9ƙFXq-Ve"^@]OZ+_&9x'Pt]WV_hK^Ed3`#5ӂ,pz}$[Rk-Ey4}WE|ߵtOmoGsimţk6w yYb+lh~4PѼx~^5NK-KNk{(-R;+  D>j۷;Ok[>٢>2O|I-MuoHvc88ҾHgĿ? YkoQi ׵H-tgYL'yw.yJ-/v_xo_i35mnIMWM};6{kg=K$@7JcJf% /dE%î=r) OYt 2tχ'U7ڀGnWT#Js (E>yᾅ84oOmO(/ΉOxg&gmv(IJvqvs_C(4QEIAEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE|'_хޙm\uMQ)kg=Y1gſ~M׼#m-N47=eIQ8QF7i- wekk~;jTfWs+{O5Z?o/ˡOo~pcxty*]ޥ[-;o. ZHѵU;[\Mmcr81i66rEy_M}_*ܥʻutW¾uφvN- j/\M.-HErD%"@ {oz/7RxMvW0Gg췑ZU@UAIR&(7PTͯ__Ҋ D}/oe'y -]g >$~|9m9dԵ}EB_Z[K#4Equ0 n& k$~+M}5*qqO]?+/˧?+Ouޝx[ >ou$RBH"]b;S]_㥼7^f~hSk:˝KPM0x$*n%@YՅSZ[_UAv>_Q_0~(xX-Z$ȷ8'dGLM B.x?h]#ZVifl?!asxg"pQ&ҽ_$u7E|wů3OuQ_>,#hd;Ydkh%y,^K0ԴOF3q-An LY(yc<,Dee IǷ;DFJɾs+[/ >(C^SXx(nn`-;<>[m4iȑ L(|4__YxS"ѓx`no.oe.$acwȬ*RO_OcIE6~[Wſs<5xVS7g$Q3oɘ5c)Uߎïk]i,:ְLe xXK4.d [׿ܽ|o<Ϡ?Qw>2隥ާ[CHEֿм! Vbt[1Qvaum'6sź<υWĺe嗊!{H--#?6 %Id2n{I}려W/A Ɲo]U!֒͒o3ʓϒ6iV|Ե='Ljo5kkRmJ6x x? d(`gs}Kӯ}Tf ^{iQ_Eռӵ][>$֠KI;_j-;G h" Qw Թeѵ?ZxNZ:TRB bl FWɹm[z OKau><ùMCC=߆7ؾsj^|)JoȲ*Ŷybɤ|$uF7+ZHI Zm6qy3%٣GW_՚O5WO,85GڮoW7`tus~ /1C1,R1O$+/Lr/KxKҬCntxX ,ڜugݩ=n=~(~<ύS/R@TIoԤ.d(Ȥ.6t2I(l0 J$)QEQEQEQEQExOxv8M[2gB.lERQW|W*rv[gQ_Z_ſ6++p|CON\ɧjz㰲L/%@A|T+&5om&M>нN]C-ň&ݖE`U kEiVmuז/(F AAT0(((?Se^H=i}⸍GU}Zހ +Ğ4Om}}j& )ŲDiଛr3J +?_LJbӾԛPE73,ZU(z}:ݫS.mףIWɞ?ĩeoxz~"Mץ9- q:~gfތ+){ǓMԟ/tkn#մOTRIilF/"8?)YsJ9F.*-gWZǯ> ҖV{w4vpo-Wl`V=1`ߺڹCAe᫛%١[5 E !OZ3u[;}E~yx$ҶY跖>)M6Tky4kI捗ZmH `hobר_)i'/"|QnXnR֎y'"%2?ݐprGk*WgהW?~3x_]~}R'M6[ -g8Zq#,R)cqnE|Ko>x?^$ikkiou%쟿C鷪u7{_z;[_>կX_Vx7Ioseү,xXGqFtb!c((((((((((((((^ |[_xoV[ncm!ꮍрe=ᇉ%JHVwǦ+GV :5P +L4=LAga vF:$Q(DQ@Q@M(ܒ=Aןo:3ha %y%;+VX]]J>n$Dn_i ZijLy7A{xgv"Czm?v\Y^J]X1{{pe XSy|wơ{{KntS$Y8>N~i)ri>]q-ms@(C6׾QPjMlx_dҵ &}&u7kF=-lbw,L~\#s \Y_cEyq7W =\K4;ƋVbB^EW1ypu{kΑt ݛ鈉q Qbeq9w[߳ \uR\\nlߌo sAbo. n[[M2<@P6=Šqml'<wOŧƷCPHwrs]!e#XS ~0|O mz]G]\e ehlVY0gHl*)l"#oZ^&mK TD O h~Uѭ&x{rL1;rxsE5ޟ+G?eΒ˦ijU7r$471ȳ~RA/MOz6Q K#݋3k¿\[k\2Cij[s :S.|;}m{}ǒ _x+ ]\e-ݰlQk$х]UǪQT۽Wڗ¿'|9i+>⫣{뒱X>|ʘe nk?>fDq}2^I@XLnĨ" IRNy~}VBzqc]Oὶ5[ygy^C;&y/Ӟ+xψV({Ky,{K۫ l-XHXLnJg5Q-wlx9|Vâ4m-嵷K8%K{@U( @JOi>No#Z5[)/RK%ZDp@9zmݎXYgR<\,h׉ho%-f#q_-;>xGú%ƷsYwFP(s v刑(Eڠ.LOIE.?}˰y !:z4!mWqH%;h+ WCu7&үlc!VVwK!Hf>l՞|vwG~ϟ m-SGf{{o.ok0=6+dʏ2p1FjIv ٗdAffGOCM P;%El0 [SXo|arOxH{˘Gwso V^( T`ӊ)hoVQE ( ( ( ( yioZOavd(ȹ#r8#{U)J)1Ӻ[$1f۟[k{84$ 6$w($sTU7}_]RKoUTP8 Z(0(((?zWo\mqXWGEM8p+Y_oWC_ִ>ho^H lx>P[*|c'WƧHeV+-.Rů EXѧD,q AƷ>bo|-) F]̽'ៃ4O Ӭ][{e?nϞ$fq!"*I*& |[SoKҦfw[H0BoG:Ǵ0Woυ?c[  1E:Xs={#G}hy^\I m|o6!ڹUiJZ||C=tiFl| Qυ?~O[<\%u%s6]D j,w?iIϽ"URH8ʩsא_[\iԵK=jy 6Cl,?(2u5lkoO(?'Is+_ 49$%ingcndh a\Zxb Kk{5n-n`V[vʬVG2uXw[S?56_ցr?^4DK[,FMydf.XOj?jkD6zthNYU& w ˎ$c[  1GƷ>bo}No_rqZGz5Ž:)7\$i ̳5ԲxQa$cR#%N xjk;+KED}3 Io- U`?5lkoO)_n/ESg l^kzua;xŻ.vRǒ3o|-):ڧzυ?zg>-GrW8S[?s?G kӗ}+.*_Qo/"?((((((((((((((Wf>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e٨ό_]uTP+P憎6j/]U?_/.}EUEr5f>2QuQ@C|e륃p0UdTP_N_ŏ^/ ?(((((((((((((((Ա<[Gi߆[ͥy:MEm-4qb81VFx٤mSP-u}0mkXɓ`,c"ӯ?ejCW/x 뺅{ݴ=qua/,[^Ija\()i ʮ%٫X'|9^("RԵ!m--,e"6Uqw{{-uaj=Etp )9#gT-̛wg -uKOIM}V<_A>Kuozu:0D y*nC&@D[;>7<[oxe|5F ;} :I(IBP}Bg,*]_K DXcJ26{,^_ NXIpIiʪوUUª4.?}y\^zk~N($(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((+(-8}~_~C(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((('Wu_/{|C((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((B?hh澟>_>((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((wbmqz~oOW((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((PϨ7oʾ?>=?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((fy*+be~(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((('7b!oO:?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((z??Wp~?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((6}OWU<4O*<((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((!=5WM?_SO/n%_horizon-13.0.3/doc/source/user/figures/dashboard_project_tab.png0000664000175000017500000034424413553660754025041 0ustar zuulzuul00000000000000JFIFHH@ExifMM*i8Photoshop 3.08BIM8BIM%ُ B~DICC_PROFILE4applmntrRGB XYZ  acspAPPLAPPL-appldescPbdscm6cprt#wtptrXYZ$gXYZ8bXYZLrTRC` aargl vcgtndin>chad,mmod (bTRC` gTRC` aabgl aaggl descDisplaymluc# hrHRkoKR nbNOidhuHUcsCZ daDK"ukUA>arZzhTW nroROznlNLheILesESzfiFIitITviVNskSKzhCN nruRU$ms$frFR6hiINLthTH ^caESjesXLzdeDEenUSptBRplPLelGR"svSEtrTRjaJP ptPT LCD u boji LCDFarge-LCDLCD WarnaSznes LCDBarevn LCDLCD-farveskrm>;L>@>289 LCD LCD EDHF)_ir LCDLCD colorKleuren-LCD LCD Vri-LCDLCD coloriLCD MuFarebn LCD&25B=>9 -48A?;59Warna LCDLCD couleur 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCD000LCDLCD a CorestextCopyright Apple Inc., 2018XYZ RXYZ c7 XYZ m MXYZ %^curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtV.-Fn L )   !"#$%&'() * +",$-&.'/,0013263:4?5E6I7I8F9F:F;C:?4@.A&BCD DEFGHIJKLMjNXOEP5Q'RSTUVWXY Z[\$]-^)_$`abcdefgh"i,j7kFlVmin|opqrstuvwxyz{}~,_ۃ"o#2BSfzڒ-JfҜ*?w@`AJB2CDDEFGHsITJ6KKLMNOP{QfRSSAT1U!VWWXYZ[\]^_`|arbicadZeTfQgNhKiKjHk;l.m!no ppqrsuvw#x:yTzs{|} -=Nbwڊ2Qnȓ5PkО)=Pcu̫3SqĴܵ*:JXft…ÆćňƊnjȍɍʍˍ̌͋ΉχЄс}vqopt{مړۢܳ"1>Ib5{;vKRVEA/S( R  m9JOFn#>H H !":"#}$ $%^%&'4'(e()*+*+S+,-%-./B/01[2 23p4!4562678=89:I:;Y??@cAABpC CDE4EFGOHHIqJ+JKLZMMNORPPQR^S$STUzVEWWXYsZB[ [\]h^2^_`a\b)bcdeaf3gghijTk,llmnowpWq:rsstuvwxyzw{k|d}]~XTRQQRTY]eksÑђޓ)\Ś.bʠ2eϧt??@@ABCDEFGHIJKLMOPQ&R8SLTcU|VWXZ[A\p]^` aAb{cdf8g{hj kUlmoHpqs>?@ABCDEFGHIJKLMNPQR'S9TMUdV}WXYZ\"]J^t_`bc6dnefh&ijjkmGnoq%Uz޲TYKj6g3 (6FXm"Ju DQJ rTNc 9 @ { w*b){[@+  !"#$.%=&R'g()*+,./20W1~2346)7Y89;@=ABD3EFHV~+敠xӮZ_:YQu'p rqڻ3NTJ+~g> c\I9(ь\T[Wn['vU;-O!kOK-O!kOKO~!OD7vy[ms,|Q[Z(+:XsH<`|Ele~dPQuܒٽ^O|1g~7>x]v̛%Hث$v$ 5W˿dOm ][M/nFt{Jqڸ~П[ \;xV]QOPԤdTY"rX;vX*Nҿy^ Ⱈ7J{}E|O~ڮǺ&㿇ѓ{N9ݰ:Cm [X))enj#hSƲXc`)n/GG{6~_&x_|9Ij>+Y^How7e퍔0|9Оk?^iw$X' $b̪J2F!{6}E| !yᧂbOL1r"\Ĝ ~w^%oxB׾4[1*IF/݃*6}Uky>^z? 1~Ҟ7Þ)&/I*m؃,$56?֧wN|A7 vߒWc"MxdsbjCjWh.mᇂtbK%c d8!GZD'CZQ7bkec3ܥI݁\'?<% ꐏoL1_hO;፝炌%B8c4gAqrmӵfH5ikH|}8!yu}Ͽ(|]ƍCZTz5 &yVO_i#xcU}#V!hŠ( k~6xE߇tx"O^Lż6oc>r:s9`9Oi""kTI<(([“Eƞn]}HmEф9݀3s:GƝZ={@|y 鷭skyk%ɉ4FE%2[ (Z/}W~qTWz?5w.u/.5.B"lE}bsY[^'u'w)̲U UMʲ*PvvooɎJc^/dSTӦK{f@ @s; lH0ώhO={C:麿/5{;h% ۴-2CHUm)wa=tW߈> a]V-#J*4oݏeU'`~џ6ug^[%&)-XxIUK.q 6 E`x^vukzeO_\Gme\xY4_]_^ g]`N3Z_<->}xOQ$e录V<ΐ@ɸ!UK 8ɹtW!iZ.s0w3$B_pAkkU״= #[md1@.FqHfk&KjD@v% O@JZQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQ_x1H| \Ot+-O,wFE <wI10D@YI=\f4{AFi^~um{["9Tw5h~ƭgaXɥv?ډyLo乍cIUSlr3Bc(NXm4;Ōpj=ߖH!Uɓi6χ⿃l(jw\.J\51e1GRv6IL!en#'Ǘv6,gJ;;]"'v8x&tQEgŷ;gBֿmŞK=ܻO ;wnl‚{b`hҼ!+MOL;:鶰 :27 q4*Pܹ[z_M_ c0]8XhkˮK_~>Ǟ՜'Ѽ1[dk7/mc)aԌ+3gU/jO ]"Qjgp+xѕ/a-O/zݶzŭ?^?d Gσ6$z[ۧV;\[>P쑝mu#Ŀ.6ofF,A(U^w.w cti77?gm8Sʱ;O +x7/(|[XkqsZr%VWVZ˞&W%UTNkKzimĿ;h=-4)^3Z#8?vϑ\|P4|E,>&􇲏LhdY.Lџʎ\9߉ƿ I]JD{adL` tC1x+žVk,kHehWnE,F v(;|S>߳gjj6v&sKKbh#b>Is^ѥ蟳g0xWyʼnʲ~%B r<U*F AEr [1;Gϛ-6&NNTX^ca|+~׎-ށo7aX,/u&cI$~Xx{@=3Mu‘;dsNX?þno42}M˹!#{Ҳ\y>Ca:^|s+YQo{|٢He#sY6@.  +|{/ 0`_\h ddF~ӭ}%e.-m!WE zw5s[𧅼L7k-Tڒx1ԧi/f?h~x|R^co. <,-%ҀN9&A$"Gt|CᏆ7<6k[/(V*b-}wDvCգ9nDxzxw͆km\FIq(P&5{YTRO(ђI$e=&Y.7m6(H#AI? MɯKY>23ʴwS?wmmmgZDC"(UQ Obz(>,h~)66:ٽʏ=sbJIQ *OVhn~' @/GPHV+V^'_]j| Yk^ ?B`B+ּ=SOF˨5IeX#Y Bk4v!h1f̀'E]G{' [/"oxǟ|;x:}R÷s}ilI6N0r#*A #5]/ljiѻ&9LI v~QY|Þ8u j~m~gM](^Ck'fU2}ݼn|&LO|0_4?};YzG3iwe<+zVѯɲޟ[gW?7Xk>]fZOl|ǶQϴ[@$h }!܎_Tk+GQ䙓q"濥  }^{4ml.vN+e[!^ς9x.3fKkNX#*Sy ~-|9࿁ _B=w9ieaՏaF Z(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPQNfX$ku (RP1->h;Y}ˏj>4"Ƥ\uAx5>RA,~!vN-kv8,oyoh`e!3{2EB\3Zou}_ׅ9ssG5Ķ!,oEUox)y<;:߄n{J'H&=TފQe:6GRԆVYZ?n$?:},MkCdc;Iq K0/+-fqBfKb`0(VƢItO__7%Kkz xS>2U-ome):&*YqJJ?uyoo\!<#sʟ 8[j*+ YX(4dxxC#?kk8O_3GG獯??rHmngm} t[-to# Ԣ2iqiJQyյ R$ɩMy[eQl#xF,`'m+m{OovO(((((((+l|[ZծnEkfT!T~b$u4Xd4џ T_~ӏ?Vg+p0 Z( cI;>n 3ɟ<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(<b(r#>5TtTQE((((((((((((((rtuOO}sō~*tM žյ+)7wiojΙ[fBpzd_dWii]Wj_w,wW\[$28V)=ho~/ltO2Uӯ|Ihy[ؖ xeDGfws\ "i|%9|9?u{9=VLK[{9^% e/, %yޡC0soYxŶ1yeI\ȩFLUc`1^[_(>"hk5th.l:zb3] a-2ni=QHRjS|eJ\__Fz'!$bҭ$&)!{(kA%q x2]973x_xROr}If7)2a{61왓FW~>24/=z{ )>jƯ$vu K>|opxx{LY4;YJK,v,kݖ,jdն{[MLk~Z붟Ӗp\G} $R  ޹&~i 8O$/>Hl5-RH̐1U1>,\ ۯ,m6֑QF*G =k7]-?+{Í:Ğ!$u1_D5+%)5Zk_?&womk`&XZkGQ#2sZ|Im 䱓[ѭI⑔Or2$*[9[~.M}ZQ35ZEkqf )eK`:eFj">Ow4i7}~oC|M Cu tcK[7.-Zb7 :|?# Ȩ3׊/-lt?ah\]G g4Bi"Xx7y܃Ѽ=G|J/ڊI-ռw˺y-hn wG4/u/7wo%{Ҟ% -c*|0p7[IO|'jt{Am2y>'w˾&WMo:yו޾sE|$lgYkC{KKZ;lI/H>R=Y!E.N($LW~-~"Pui&MYMi[Xax>T+k}[:O)cY-U.6ʻS訾.^vyke/= ZxQ-SigAYgo͌ +B0${M&[ ;Oԭ?/ȓ̍gwsuhQ_-†#gn6,WN( <.ngPn 圖 Am3AQi+ >:I'ȗnKc/okgkkoz+r 6bG%6f[Dv5 %9<->Ym"줆~f[/ٟQGgo1[it}n!#=uKk䟑ro/3h'a$*RA5-S~DP) (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((*X?uK΀::(?((((((((cGǧVV^CjrJ6.Ʉq c7-{u˱=|Sϊ?ҵ/xL\Zq=mqwf [$e 3&G৉> (~!O(anmDb+ۖ cwػ0P_gW>=?/B*?WKTYOyvL#gپPylW{$լ!u$wy7Z9!YP4睁y& k&[O=E nv׭1A]Y.@gI.d[s$N'Bc,lTY%ÎZmޥ[iha%V @pNO2wC:+$ >apV+sgw F68|UnCq 1xP+]w*U@xfUeIt(/5(t'/خwTD&)GqJc ą]7|t=_jW7E ]6$aGDUtIhJN((C ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?((((xC×wu.cb5vh(((((((((lڿM%h4XOkA˟&5.6Z( 2__ ?>oscȃ9$i]z ޥAΥ:Iۙشa:|*xGÿS/:-^_>HX\nF |!)09q~ VowiOs,PGIQMm$4 O`?`@+' H\]i @&@OR#elϭ9T䝽ٯ_ԘRsMֽ<W=/_/jtmn%[i5(L\F־~9[wV]u?eo@\[/ڤA…29Uu=').iE=.km MǽGeW:ώ"Ln#^/*[;eB+ xG_~2ğu"m[cDJfyB~Q&Yԏ,FR`ӌ_j/kM~=_o|g Qi놂(Q0!C!@~|ylMV4wO%]F#ܯ;/!E7.Xk},Y)Gzj^jߝ? A"}wQZU}J_MG<,PNWw|iUQֈV+%NOD| ;sϥ-~x&cl2j2( Ϛ&Y:T J۶W er kŚ:ϊ|/k;8gUNTK`3j\wTm |OOFwG?uB>߄'yխ"Y tD"0ked X3]7 'RK0M\O*^K9݀}/Z\xúv-~49; r@+FMA/4IV/FD6>f]VN7&|Q/< Cr MgÖ^XZ#[u""DUZhP8g#>)x@KıvV>'%$' CO6syHb(hPFI߼}?Mf jOʱqN'; ~'dm+Ҽ=Ywm"]/eP['9m{_.Q6uuQE1Q@Q@r8^ ^izW3|?6᭾-b›J]~v-~ɍv3U-7lkxNcM~t[ۘ#`>bgUѫmG?}_y^f7l<֭QEs|TEkHD,2ﶒlH=2I˨?CToNHo/chgeXaVR8 CO3+8fϐ~<5_RH4AR.>e:y$c*ۯ㯅~|AҙH>{*6 vXS KP78:\{J({p foڏ^#:jq1--ŴrKpXi$F@XAz_>3~D4sD5!3HP@RUIOWBi{%h֟'|ώ>uOVl^šc,6h#G#(:Xp{xM'|eF'UvWzwucۇW3P z}ΓZ{cy<H6:0*A#|s=dCSS<kha/#,rp7=HVpMswU$Pk>7oxk/>|4Oi.#y<)Q$;g+3 vI'=K4/^ǂῇt;}7r-[9ԬHr 9氾|[;M>DԦY&IvIX`vlְ,8RmHƢri7/ʟR#+<p\].{(Rs?ψk#.kv:o";$fp@x&gGHRowe+ba|RO7I|d]Š D(}Fs[?#Mkiql‰>kyb$683߽//~RR[t;~gM߆1xmE 0_[C"_V? š[ =/H%p@#@X8PI$9QJmz-sуl (4 ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (??oZ\ů\k!6#Gg_z?x[v?es(kmsW?r؏E|?ޏ:+sů\?oZ\!6#Ga_>ekmsW?r[v?es(MWϙ}㢿?oZ\ů\?b?~_z?x[v?es(kmsW?r؏E|?ޏ:+sů\?oZ\!6#Ga_>ekmsW?r[v?es(MWϙ}㢿?oZ\ů\?b?~_z?x[v?es(kmsW?r؏E|?ޏ:+sů\?oZ\!6#Ga_>ekmsW?r[v?es(MWϙ}㢿?oZ\ů\?b?~_z?x[v?es+d5}mv}FyJo&UK(dKr:IhfC8kfUBZWOy+Ϸ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?袊?Š(((((((( -#G5NFmdq0ĥ~z~ |P5RYo4,39$0'ݎx]˩Xi wqX_זm $`Tzzw|3/>_NKm.!PhMCSYÈbt*IuVwN(y6=[-OʲZU*RwrMZw;|YE~ZxΗNWO"b ı[6y`uP+S𿀴6lu7D֮Nuh^9yb 3 To;M>zTxfMwF ,@rI=g~ּ#xwVQ!fB Aىýk.[66qۍbYbH=@#5 > ֯⵼4[knmd9`3P1ݜx2TSj8{^uaxMV'{uwV?(m/O&u߄ʉ\]^Ɩ.s.K>4񅯂4[kx4K Sw癱bq^U1/xK͸^xJ.&ӿ:_tx~~@<-o[=~0$x"_LFҢ}Phؖ%ҿbGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( eeX+/W4}/W5bX_?X_?Պ(bGbV( e(H`_YZ(EAks?nTI?[sG?#;>3ΨEAks?nTI?[s?_s<_TI?[sAks?n?ރ;>3ΨEAks?nTI?[s?_s<_TI?[sAks?n?ރ;>3ΨEAks?nTI?[s?_s<_TI?[sAks?n?ރ;>3ΨEAks?nTI?[s?_s<_TI?[sAks?n?ރ;>3ΪPmQFza]*_$-t BN۟7KC ?#Y~'}gT\]Iҹnv,p8JP|Z'\můu?p?zO:#G)FnVA+Aks?nTI?[sy~G@G:ůu?P|Z'\mgb?ܾy~~@<-o[=~S BN۟7__&<73}̂&Ty3.peH##APҩBiu_#o pi#?4QE~~QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE(((((((((^0֋{iڵ-zl 6UT1p3Yg {V/m|$vgk b1=#EP鶆W{Ek0> ?:-_2-I9a)'{Ex?~Ok K[l+ɡ,d@Y6w(b8RGOw$>"཰x^6(Ձ )ٓtttQE!WZƟv΁RX=07c̊p$1qW7  k~-/֊%P,VIǗ$;O' :W~0~ Z/y)c\m3uO=;.nV#϶fON<j灿iD74[XPm-D@$ezsDuE||r6W knq*h|v07m <H`y>"Җ7ë]K_"_h{7O.D);G)GP1]JO.gִW ᎟aoqrEj[;ͱVa7 iƑD> xr=rot{KrL)A R<{d`6 0w;_OA(n\aG|#ֿ[8.l{^-fEtڧh*<떖 ֕IUn.sH6>֎-;yNW ?g 5Z"}:. +$΄!=|H'n~i I[m] )t PH:T M3J+͵_  6|[YZuK3i8V=xJ4ڛuiyolY$R-y&|DT%XzCZKUt{~Q&׀|caj1y)x [^5ƛjڴI,(# c%H?>8 "`4W]"7Y"jb3j]f07nJFEqOi-s HR;6y˂pT6@on ]{'Qk?m4 y*++8E-Պ]MnIe#9/x3R^5ƣ{-@\KykitVjRk(bN:^E8ɥ?aET+Ŀ ;G:_ h/3յ-DiòNmm1͏jQ4/ԭ%7H7^\.SW{1u^55^,wW6 D/#d,0 dG{KN &꭬G:[}] cHx](97Q$ no-~g߶^>x{-5=^=.F HT31|998|<'O zEʓ[$7,aTWv2F$gfn⋻i|~#mI!v5'>?|X֯/ 5VD'Wq֝)5Z3oE>ocV)єݿx{+!o.. 5pBlQ| 2s矱/^(>/xľuh-bfl#! &"9mC?R> 7W>=:IrM〉ff\)<ᯉ~xbRv;')aFs2@yk;⇉$-'~-׈mEwo38ž5c&ؕ۽-|cR-w0:$ʍHO6Ƚ럝h"._6GԒLLQsY ce/~-[:j<Onnx Us!y->mLk Vfۍ"#wVIJ7喑&OcG/V$E#I1s~_7~xSZ.,/6)q b[y ᠺU 6Rd~ ŸOc&xC)ÏOF(h4QEQEQEQEQEQEQER3*f8O@)ke0ofyHhffؤCuo,rM%QEU; zt?QުQ@Ǡx]"f_ x7^БxzVS<_C +C.k˧߸pO%ŻOϯ]QE9QEQEQEQEQEQEQEU[{ vm>ެ Opտ5jcRb샿ǽsQ@Q@l: 0?tj(RUٔSkF3Gfo3<:yJf$#$4%Q@Q@Q@Q@(((((((((((((((((/ 7?_WxBڤqHMqQEQEQEQE{'o󗀘r{|z%QEQEQEQEQEQEQEAu:Krv%,^ q<3q)ʼn5+ǡ\cWa^3@Q@Q@Q@v^ 0jMh_yyk"Ci *:z( ( ( ( ( ( ( (<7tr17ּ/dn?ڣQ\QEQEQE($GWh׿e̸o*W;:( ( ( ( ( ( ( oˋʸ_ąt(+$IJ((((B5>yF=v5QEQEQEQEQEQEQEWjskײʾ}99,IQEOk"_RھM!B0#I#T+jgqG4Wc'ltæ۾?{z|f Z?ijLVC} 鶿v-T_𼳃3U)4EI 4fbd{ x C|eiMiC&˦d_ ˁ}3*t{{7`Mґ#eޔaM7.?H3a^oz-;G䏡9 ]bLkcp(((((((U$pzu<o+(((SMK796yev~$}#@EPEPEPEP(((((((((7 >sPLwD7tOj_xׅ󯈼~iM֑jV|jv3Aqκyq,3ˆkr_^o?NOjŎk55Td} C?t[-<+c>1ZCkguq<ܘo.Wڏu ~> Zqjmr" 4Z2>'3O,v?/?Y崎O1)*]Pn*H8P5-<2(HZyIE6R۝*ǀkms/m/5ozχ4{k Wmlk+xfv1HbNG̓_0>U}i+gGHYyK'YRc$T8*nT{??#6{O/?]+?aº׉u}#WmVw3ڪf%iذޭPEPEPEPEPEPEPEPEPGm6K.:)\{坽ܮ{z kzD2[ } ( ( k.pZ^L Wxia\r{(Gfcǩ((((((((/Z 3ۂp:ZhGZF{1rǗw_YY ((+;DU\3ݿ ry)HG*_A^PF@ EPEPEPEPEPEPEPEP]DhnV{^%iRi7 ޫ"M2U6XuS((]WGf1.PuYTQEQE^Ӯ)=eXO]i'׹Z[Egm# ?Dѭ'3շ@Q@Q@Q@Q@((((O7?ō&6{;> .O_[Q_3h_߁æC-jqtYQ}s䤂$6 'smwÚAckwi1jPvv<ŵu-/3h.ƏZ Wޝ4.#vg_[f1'$bibX(r$6bn=AHK7<3[v5‡x,TK.z|C ^Ěl 3A5D\$r9RTjob[kERQEQEQEQ_/_T_4q{΁tomʟ #JOp7A@Q@Q@Q@Q@Q@WuʇҬQ@-d%f0Xn_XOYO?шb^1 +M=pGoiEG1A 0*J((((((((((F sdMtW/OyF 'W@6Љ.Iq]b"FBEQEQEQEQEQEQEQEQEQEDJ:V2 :udlM)>LѸAUtPuv8f~?Ƥ5cOWQ@=>?*!GS=jPEPEPEPEPEPEPEPEPEPXk'׆5Eyρ&6w*c8x֯Ye9lۖ~ׯ5K}|a? }<}ergAik:ͅ7vNsAq)6>uݕbrt&_m?D:F΅aNM rhMWIW϶1okkKm5ֶVZ->0_?]ju*5[H .#X,pR[}۷vx+¶CǾ+4 N2N!_>65^4OQLM!/%6 =47q۫o[3PG!eIOҮtX/!Id\plD(} WO~i>E= Ρ{s+y.Z$j9PKHFJV4.=oB >-#t#a"% ;k=?_ET((((((((ku(r88<ȯtM7@(׊|!jgWe֑[FUc#}n4IT+{7ċ.8Kgwz o rLhTPą\Dv?;=Tˌ[lז('o/}n]׎}wq%rj:r配q;KEDvoA/zk?Z?ߋ^\xu]jx"3_HvD0ƫfH+4v}{w&ky!,{֐ɖb8$Mz9WM:Fm[F)ד,~ydvp38]Km65WMož)_|A.x55m{s'ڎ~yywo9M:rc"|j_Eԭmf;"L[߸{>%2 81S$KGYo..hfX儴l#eVQ.U%[$rnI|ڿ6KRO`7kɫEiy$4Ack7ݒ튂Jo-ںPǚmqUM>gh.6I"Dǘaq}rb%菠KoaZ mB;C$3ڿy,nmϝIm> |;!7qג+I(KY]b**20.”_4Wdw⮱Tv/ >9񎑣)ѵMCLif#cY76&L^34zFu%+ #<1o;{_Ffhe5{omB<.FRs\w߀)x'%͝~aCqcw.vЙFMZ6bq G%޴}a9g&ףmG_Uf/sJA,z*0)@]m~git1(bP=+Bӓq (((((((~3YxT['6?M dAC'տx,6v-3Tc~7޳7uuQZs+e<"t](l@$U; k-ͤvPh Y*[0<:7t];kvLuuM_oc}|ck izơfMB[=јD|X~b:?swhѳ<;'OM G.Z\CnZG4׮|WJI5 <=Bgs{mjeKaoyT&}E|9k_j=䚇OkRS1#S-JơkĿ[.xk ѵDKg{(/Q/J1FUdמ4$$3@u u w ?hswi ?hX8F%$k]5u'JM/;~6>?y}c˝RUؼ2vca9 )|J?Y߄-n Ŀ/dcm̟gFGU`MRq4)IuMڟsQ^/ƾ"}!ִGS. ɊM2[_=clDaʃIWT;ti(C ( ( ( ( ( ( (u_,fO|D@jVHYkH@Lz[#Wk)~nߟoQ_$6s|a~Ϡ%u=ϩèGHYm~;^s}4u6?S#I-;x[/BW]{3+'')wi ̷Wjq픍#d`m!_όTƢ(ǿ/ II.o>䢾u^m<]qi)-Viog,is=22DŽ\/̜W'+|zԡӡ'ë? 隕᥂e1QF6ك^hwϰ)wwnX-WPn4ymKM}}qr{דMh}ER((((( K>!EwCƧouoyio$q0SH$oE|sYxZe%:M^AֺL߹kƉ|\U85ۋ᳻"Y~%αcowzH`P4iYvU-{u>梾&JuxZ{O /o ҿYI"x`B^&?t_#ݯQKgTW֣CY񦩤_hA]5DC$QBnu > oZdw𞝯_7S)mb]ʖ;N} r|mg^o>袾 |u}7V x/hjO=N#BCq+7~z#huI%QkvKK4^]Md%Y!W^K)L]ҷ[~)?}F~Q^A[w: 7JI"Ie1E$(y/ fP[_3k|0|-OJbu7F(oK Hm?ѫFMXZrEyk'Q_)~Ѿ7oZg?VNRծ*Rxbblg.!#5O N{׃|MDž|Iaӱ&KFrIʮqq&ʒ޺<XSi؍g?/|a]M4=Tt &g+k|-o 5kIKkXmckF ʸ5[=|kE -WL4<*mJp1ZO9~I(_!E|} I\MnnZx *6x)fD@~D`?|Q}a,x*kJY)fyt* 6mdTe[YVzF`^t%U-~zz9h}?'Mv>ݢ(_xQ3Y0: kA3hn"hP,,"1c co+g?R|oZ߆φ:~&%{]zU>5*]C##rGjĝGů /o%}uu;{kbL/]N gOKZ˿o]n~QHF}ihQEQEQEQEQEQEQEx~О)5Noo˛}w *6'*o~K=пl}[xK\|Ay-k$vZoo$ `R"& T_& |?9 >l4m"#2X=&TYF(Z*%OߞՋ|?hx4/ O+$%W-mow,K,+$,Ŗ6P2|yC ^ElaQ%Zu$`z@ZH ,wk-ݿK_kS:+>!|Q 6׋l㿊>'a޹-/J&% ]^8*Q 䀣.k⾋_xG֭|/..m#1j fnf6)ل_qN䢊)QEQEQEQEQEQEQEQ^Ix_~ >Z 4!{o@rG #EqhQxcޝk<3iYy%%|%j*_7"i~Wij DVV,#I[| U4o5e8mMg1S_%zZE3wcY5qo}E|{c?&]x{02\ gig5||}O5L_e4jk8h"FJ (ٴy}E|mUޥ=oķwou)7i3F/k A".d=Vx.O ؍OK:αlv7HI ] >Wk]w>^yCI'Fݛ3NsڏZy|z퓶 OdVDK(o1b $w|> jKXuky[9`o+?h cd۔An!];X_jY%!86 m̡c_,7Waϗ^Du;Oy f*<6…?-onvl7kzt:_T_i7zBtE ϜWd?v79?^ZZj:h{ǚxJ'$bwJ55 ["Ѿ(Ϙ>Iu6Gy|[Gs65kF[1"՚S,b{u nP_K_ZuWYs[O=ףKuhk}[OLOsi0r["cg H.ILWif 6@Ѿ!An4Iͼ7 K[;6D6bf|n!o?xgk|t𿈮 x{ZRѧ \1''*'L~5aO5~:\[_÷|=۱{iYzqPc Z( ( ( ( ~&7ŏx'q E*Og){yXgP#C)` QIcٟBj0.[[ZI}:GjjeLl2t?emvz.mIS/-ūh@V_~bws_QU~&'^84xHHDԯ--$?j{&`Y B2싄ڣσ^ n/!Ӵ{l cL1r&VͤHYo_smQ]{<yyO+RS%V62yij5XU3|F6kƝko{shbl\DDhQBK|oɴ]I~eh=loEk9D s{W7(~GjSxe5[idF%đe=2AQU996W"0Q\i|?оx7OO^{K1ry7O#M4IIdvv wQI(EPEPEPEPEPEP_,߲_}OU5v ڨֵEwҮoLgfVTUʥ Io?zx/x\ֵ[ }TPhW1M|b1ntrX@DŽf)& 3W~m1; i֌"q Q29"ݟ裧/O~ZzܹO?xZϊZ[ "%m uIc'#Hx|sKmov,]}{ġ<1H :)Mk[C#~"x aѵ\ gXYBHH>ᄚf RURp0}6KEeoo}%.ͺצxk׏x|Gj6&{&|PyjoʜMh4jڢi6~iܤMi` >U^FE8Y.oOIQHAEPEPEPEPEP^;/Γ[^[uhtMoiy,‹w 1Ee!C(d`sgYG ~Zzi|]ϙكV<$ZhZYiCcqkl.l ֏WtRkz_6߫n|c>"3Xx_IMƷiwqAc};e`𼱴ʑa4웠[]xw~*wfC]B]a,h+u䒻xՔQyzihJ{Z?-Nw^<}+VmEE PYf8rO&*(n섒VAERQEQEQEQE/xoڵޟɦ[Y,SX“JK2D&}@rOK"|3xXoKk-j_Kg vhU. UkzmW폚ne\k.C^jVCK,yș6KG,)e81e=4: ]iԑYDҔW6vePqh$%em2qw_?cgsaxz7#H 9,U"WW ?xQ5}>}Z+aUӑ#Ȓ:Ƃan@eA.ԓwo=}u'[%oCkOs–(OkZ.7l?[2 Rmvf–m Eu_>{⦥o\k:5(.[:Mk))FJu< ׷YYG+~Zzi𧻓Wws|]s-ERiSXH"ӱi`wFz# .Z떚x}FhZb? F! 6I]~zk/vI /wKY8DT⾑y[Z>Dt;_~ '[>[\Ȧb[y4E+Fo-'η.x[ۛKV)n?*/ jlff$*m{EYG>meh:ìk:6yyiz6_K ,+1+嬒]'b}+ῂ4?r>ZEe,3G S#" rB]1VV_Շ-e<xlm>i톣 --ܷ~dNb1Zg@ۏ5|6u0x{F.//d42*)wc*wRV_m_݅QLAEPEPEP((((((((((((((((((GVw Rz(VW|AS+.&r$|sp䭄 ycpXOvCrGTJ?뱟QyW^uE;U;>urWhͷ$çƀ6((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((ֵ%{]ʶ0K9AasQ5Ң COH_#O~͞O}W1ǚL>&-,ּ5hzՔuln9d CryHP7;:}[?4[OZfKoaew٫EpJn T˽Ktv3Vm~}AE|q?ޑ}]쉬B͸[Ilmu.ً'!_c+Pr"A[_uӨQE!Q@Q@𗊾.|@3IMH.]U\h1yI4h _z݊Cá((((jrK1=Oa^O&F8րh"I9I`}I\ Ɣm؁Z((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((G< :g }HuC|;Cv#wQ~>u~Yꗖs$$YO\G,Uh"V +}l4oxH!O`Kyi q I# S[[[˪XX\6ײ[Ef&6"芒$z4QNXoQE((BdϪc]64 Ven>d$2<×a ((((([WU?ԚBWRlp>⨢(((/#B5 UkШ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Yj #\r9H 2;ԆnRɥAp,e@f ԀXzGOE^ [-IZB0b[pA@((((((((((7\CiqZILcClpXpAV((((G:yEc/lwO멮"28ܬ >yAɤ]6cooC+ ( ( (j"ѡ9tOZ|7`t&(bI>v[EQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEդ0[i]náW#͓q2Z&Þ nNy#7mmlP[q]$],o.%E$H_qYZMɓok~zk"=u#U4.ܭܐ$`"*@\+֪Qi sK$,1a晋#.K3I$jWE3KQE (((((((usCӥ pַiLՋD<ȅW61RHJ|W/x~y{o!8R21Py>ʡ}J ( ( ( ( (*Y۵oF)%ir&ti"]˼!oE~};"tOZg!,ҍB37b \7Ǟ)t}f-fn|;od[I4I ] O P j>4[4{ׅdu$0Ob{,^#d`a)E5$FnVStWN|TÍ?FxRAyrJGWOtpSϏyaa^(]ֵ[O.Φvf LRח6V&@!#Sv^O?WWi xIoW,_A<¿gd'͍JbԼ(fRT);# ^׬?EĞ,Q,w#yZd ެ1iד@Id9(cS֙4i:0MMpت6~~:uσ[M\ۤ--BdPH~uXQz.KW ie;~QE((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((xFwݥbdɝN2{xW6 P9?`o heӍ:ֵxwH׵"i8e'V~l|t.=ŋRѼCk"Zz,W2Ckuoh?wr,ҝ0c\%Z=7]%KBg٣ g471;=ɓr6I1./Uuy. /Ѻ?O3͕Σe05*72>ƾ7%Ҽk^/&#QDXbۣG*[?6:?I׵9.;MF{Bȁ\+|#z ̧ Mv?Cr9#5&+) #4&^7_ =/➓c"&f%1h0~$0B-fq Y2y$'o8Y&/œQ-QYQ@Q@Q@Q@Q@Q@Q@qj^Qg5Pfd2*uU;o5n Nry.?뷝vN8-Q@Q@Q@Q@!KE (: fMvq0FE-W5 >3[$9!q" ܣYB&{m5ˣaavCxѿZNJ_AVtաk[.t%x [[I癕Ꮕ? >Ǫ'v6[ @IDh?+dr}MW?Lthvikp덭,+G#:P[O{_mI]׷|9_iM3᾵˧M><:eƠK;{I+nK-hb͐'=M\M/_]WWNZIkt޴|ğ^}xwQ `lm-/!Mt Q q8'-G-@^~!Y_[Igk;+pK [.KOx7sЬ6q[6\*2lcņ=| 9޿?Wk7mYe,8s ]n[h=VQ$0[ 1FFp+; /|9$5!k"Ln|^|ϟ;~ך֬Z];y'4+]}V|N5Y&sA|>./KfM/R+yR5ZV2n\m r_ 0ծ/ ,1{FĆggʝ!VW^4 k~5 Iln,-嵶6&BH@㎕Q=__ӼWsMִ e}52][*@VR$c'ܼ6'{z. jӦ[/c_[O_V^M:.haҥJymHGm 3W׌> x s'|9} ߊ5i._6TO:¿>(bG|#j ;ۻ'U\Q,\2qY:][E1K>i,n"Ef p0gk립oƥ;|W|%!Mea%=)1Q(D@UFֵsϻ9C*=( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((Hn_Ict57;\;~jQ7k~-/z{/ omR[o" =òmEdS3\fş2*Ю+XHQ!\d.3sJ2M7RN+{-xÿ,!=sO[iCy-eS8/Ɖ 'FAm<Ug{u'ϣh'|A'gn{B,HY$*Hןo<]>gzmkl.U.d* F{Q۲=ڊ~ |$NDo+Ā(8BAywO[ZZU|IVh Hʑx#4zhΎS>|1׼C|,-.@hWgy70A; m?6?(OXRX|GXMi`nI(!Gs`LRfN'khEwj :1xGO#caz;4f&f2[ʟJo!xKQm-Ki2PG7ZJ-;|v4WkOᴝZDOmpėYhLQ<5X~o5?zHVͶ1O)CH%jjдW_xsC1|+ҼiPfocԍfYpTUT7O:^g"*5$bF/`AC |C7Cߨ7< J\iXO0{VyYY)nUXv襎hhrH)H ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((:kճI[S,O+neo6- +c;t$gN¥)6Fp[ziUMgK=%k^O *SV> x"㗄%|/k/6}sOҵ{k'8v1{Mv)߈Cz?x3-.a5 Euq,3$P.\Lm?!h˨ưmOQծ!,Yc _p]ŕK5ޯjD 'ج5g,Q, `Ss6^6UnBI>eݿ%?;X?SiOO[.U;m v̲nj?iO|*'>:&ZIYFF/"1Ug̠2FzV1?Ci5<>gpd+)a)HP(m3Gs |?KԵ0t81(Fv.K.]xjxs)?Fﯧ}q R _vzi^:I|qmh?KMbkF#/,‹ 87m* Cx'}F&tV-Hi7IѡU`[p=U?k:|K|;˭Z#PaPXz5\&q-܊0W  $byp}#om:b=;uw/CÿmM ~?Xj2Xxa. 12K,[@Wv<_ů:O)Ɠ~<`ֳ9fSn'O< bV{%i*%p0AR8e# |=/%__ɨ^}H˖]X|ڟ1mް_s?ԟTOo&W}>3>*&Z|BWFjhu3(sđ6$ c_ <0NVIz^W׭xwM?жGm]*; @Nz c/6?%ß->VK$C$ls$m.B K'k GHk?kFJ䆷*7,~/|O|:Zmt! ]YBa~f힇8Uj5|EGDS^5JWɿɟGę?m~^?( #Z 'H:_?H_h2~7g,#d :62$v 7XD| @EY|hGWm]~n((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((~!; 'o׆RK](xQu"Fd`N@aWc\Gom I*H|q3[n"??^|`.F%yjU*bB+g?<)τ~&/xrkRC&?>!*矔_$/LjMK 4[4zƯ4z*qs{l]Z, m<3h[W> {K<:dGFky!a !Cj3kFE+vaF+)ۭGw4 h~&aᨵxfo"KP9]݇+ռOG>!ukM;J ng}O53ÿ־5j>S2x~~ -bNbp͆*]wgzF 7xu=cΙs<S,P5A{&&|n\vzs+jN97~w^Kg?V'П |0uŮh9v=T}ͯDSi+nVP_Mq2Iw+oBʞ0rU٤zeVg}Rm:oz~*LjtH ^SҰvoF),|<5Ƨ6%݆bƯ%9;ck ƚ7'^OٗFZm䶴ekֲ T2O w7%YBt{k_-=QxC 4h$m;%j(ǚpOKu{>磲uV\g~kڟ}󥵵YX""(ff!Luh"|QZf3usk{q$Tۈ?o/RSɬ? i^$o|Okg. s>B "Fx 9] | Jן-|im4"KyaeQLA>Scw5ӛh]=MyRq[~2B_~z#,_QkȌjP|۲2nYҞ &|?$JFk vK#Xם˞46V[#㻝ue@$8+? Tx1;=#^z #ŧWt薚2;kR8N"XpNG /|MT^$xHu=NqJF@`!rI욯deOkr~&~>o%gwv|ChW^o$8_8t7VVL[I$"f<$~!`]A?K-˭n\N0q*h`=ʬG.6=~~͞ncjZ앤%i]i/ڥm bI T6/}qFdG~k:ލ6mgfl7Kqu*C `eP3kKn]%/kw[C*hכr9Z<_~ Yk<%N.6—rn!f~JY|ss )}-xqu8rZ:owP)9Uf0|M?MB&RUO"y4s,Rm>ZWֶVvۻ|߇ݔֺm6׹˭|A$/LHn!1#q}yn٭Um/\qiS5} ROǃyM|O6 ; 1>,11hG!a9~ÿ7- (eTaf#qѷKp|>TN򾿆}dO~|U7~)4XA{o--41V,m9^'/*χcwvάY ñP"AO۴8ϗ%_|Xֽ/VW#УӴkۉ͍j-a!M̻r$pUɨ[%KH(v'k{&5=Vwik{Ď]q#u [JoaS%&i1$p7;vQ_?h>Ҽ}u?wZ4X[ۋ'=ۂ|= a+羏@gÿ_+i·3EK{H `zrHHRQz7ok>NRWDҼM^0,aKB\cG/~|KXkeK+bh$9P91_X[|{鷚[ify>۬]K4/'p-{s࿎>RZq6-G;KF VxGDHۛn]y"Kyoj?[x\&i7|˛ap#dr0Nk\k []l`ԶY+E,"ySHDdg-I=i]-5=UQB xZDwOsm"_+b}e6 I8D6GR ehRVw+m  WqE|'޼Wí[ᧉ.lcedYș']DukxUŬ8lZ#&ʘd#~ujzvh1{YӋ1ݺi'w9ߨ[>}iqP98ɯ2V4i|חo(u~oq1[~\k@&+;*!b0q:gXdƼ?[t[ƃm[ ѿnf3k2E-OM5Ǘ:O$~HH6OQүP֯~[K2=efY i,HldS[4/.jм!/tkhla$`@ 1%dIUe\ơQpks~AՉgr%5::V璸'jtk2m Kj3 |1,I'5EB9^KFv<3+[r*rB5oP𞁪<[nyVuwWFR1NrM 5̤v#$$rdVKI/-Zo zƺ)tau(GIY}ܗfE gP{27e1fÂxQE!w-!ič0cĭqmZTKo2YR8ubF{mqל5~t#.cKXՖ WQ4>e&$|,1r@P)9csj>H$ps}(l5oE<|HU#8=z ;Eht/XnnGjH<)g0LiZܖcIs998h ZvgY~T*-ı=Ij;й+9Fry]yx —S]YO6@fc?M[{iwЇ} A!H#E8 +Z5GVAjsY:E3$"^[I5:A8ӧğhZjq]k I,$0,%C,IV l?țv5 M~Tek_ƫ4x']<~k3kmv%Ũc4PӴkzu'gK[[{X^wqƀp7>[VZp? M~Uvc3KW[+=-xn-d wĆTl\qIzr,rCLVAjkS߸?qxQxsM=gu-=|$<ヂkG?/LivkYc4@'#'kޚ3ֿ57Qjo? M~Tek_ƫ9kS߸?Zps+Z5GVAj:(ֿ57Qjo5k]wO&f U$gug|M"^j>ѬV5G5 |Aƫξ#H~i:)ԧ[kHܲF+A15 ZxT$#d3Z8<BNݿ"(ϴk?U7R"-mn7ETVVTgUAgwoi\`*pyo!+=>&|Fo<]8M yCƒL2%xե mO61h_H_*>f&mеT: ZDgUFbjE.́s}ܐ@~o- b%RUwm'@g#h6WwګHv]MYcǵ qmek_ƨֿ57WjiZNa%޲ʹEKs2v46[v vkDP;%J@# wmgek_ƨֿ57WGE 9kS߸?Zps+Z5GVAj:(ֿ57Qjo? M~Tek_ƫ<]rT2QHr?E'5 |Aƪ}[F+(FAY?h+?.jχvZ9G.#K= 7rpz'/ĵ iTf6Y l9=o:mRl?HuچndG-`J+ e_>J? Xǂ;:5̶O-sM$[F)b |āy7/?h? uu*7iƥii/H!`F 3 s_VE=j83Ej엩7/?h? utrcf_^Y~5]iumY: "Vuseє\aV89 ] j~66MƷ FV+c݈'ڌ?iʭ:Qq[_19wFjjI7oCOn_ @n.C,gogx$x^U1RT]9'٬d*>@n.n_ k 4(nnO-PHĪpZ1¹DF.ƕx7Nu~/JkuGž+ӭ5+Kc{BDHRDՒG)0wA< s~O_X?U~W%VGGYbkiNAX YH#{^Qg曥Aj}y  +U͆۸{}I٧~frվ;#^*𻥾ejwWW7^xy_ f,Kp&Ok nϋ-4=Jc:i:T7V%0$(Ge,PnPI_LQIhrw{73㛯xĭZr^g=7Ѥq9e1onGz*I6dQD.-fy .#\(m1_hjzyogZHXE?ʧqb@G>x_jkm𾈞4_jZku+&Y䱶^qo:\!* 7|7iumoJѴ:5ɪ\D80H?%r685[Vח۵' NӼZjq}G캎e'{$I'asi#albrx'>|kO3Ii"Y}ݮXI9>P[ӟg[ou? xOPѾ$|Fn=H[;쨦%e񗂼yx]6F(kSqya-mS" U)_#N?t-F*|OX6Ap.gno!uSU|G!o|4İ?^1PX.  !~Ps+:Gm"J{QEQEQEQEQEpZWoPTWoPP^-+iҹ{M, =rVQYxbk}j-8˿ϧIy$efc$$־ wI1ooJA#۪HʨA SA:_o{Uk 98g%O?K.? ij${ks4W:WHe[0?o^_ ;Ş+X6Ki_am\I'gi;;'N-[]5/%{%ݩE]$}{~h4m+CM{0008Y ~',< /[j:Faypp=tQeYY5{jb4V;eYt?Jm%m''3LmhԣqoFOCWWdPs;Zk;ϧRoe}6KEkϲ?a,znҿX+ǩJ+3F(_0+(((((((((((((((((((((((((((((((((((((((((((((((((((((((wP!`v r˾oi1u|[٢11u|[?1u|[٢11u|[?1u|[٢11u|[?1u|[ٯ<R(uWUPt=T^j2B$(Q_1$+r*<֧_w-w-|No<5ڪEo>x{;XKFy㵸SkvUV۸;~˧0^u藞lPBoaX wP;G>Q$uѿ˷].3G].3^ګ᷆u}W@[MWWtBJ :\=ڄ68 b+9ELmKFO|i|3SKk"ӣ@VYk܎UA.}븩ǚJ+>DZeb4eb5&MXԤh{\XêGw2#Galt߆#-jj2<e^Ey+CB"o5rVUOO]7%mU_C߿1u|[?1u|[#o_u_-GUO7^bسy,Ds#!MY>X^ n i`M$udVlm6SN>˾oh˾ok/ ;0jդV:i\ivڽ<`UiX* ȺD4Zٲ2ZK8 e |X+2ZЋ___MO˾oh˾ok _&'-uM{WtEmV"eW#,cVKa%i/V77+U'uAƨU'uAƫϾxz|OvVqj-v[Ffygdc"PBHk䣕U'uAƪ݇nT!wR*H&0(wȚI'{~[)cD|^Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5Er_Ϲku;P IV8,i?_ ~PꨠWUU?CԮ#f+;{ XWlQ NO$ITP?dMw⻹&˅%YY cnw}~9ndY&FZFrN2N=+cj*:'՞KMR(:B((((((((((((((((((((((((((((((((((((((((((((((((((((((((((( Νuo-ܱ:C;'HB9M˸)+gGZWD| m?5?xgRR_/|P wR~ֽ+_On|+zt~{/>WH%٫,˯1ҶkCO.gml|w4eY\LYEXˢ+߃Zv}MY'tOKM>kOd#WuM)/dA uc4?!#A=9p,o= 6$vVR0*o,BIhm>>1]OWWpXipbbIDeR Ʈ#gn;{H|!x_Wu贍/\Xڤڡ') G¶p͕k㢚^wcw^~E$z]vq$I$,QU\쭂_n?zn2voefX~_dc?DMf=jX1G%Fc9^FKM1UKlRxRX; [Aj`%Vkhch,Z[OۛgTV'O" Bb# 3XGgfBwWAERQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((ys;IF.%#ڋݏ@;M]+"kKX5O~7a#i4puLA8{$~]>߮Ai[IA BXX|Uu\5VѾɅ~+n GX.4K0 ?nɿ&񷜁sWN|?j8*Tg=FAx⿁jwƩo-:+>1E &dxљA  PHrq]Eqk1h~i sY]]C( |vnMr~98q=]jXX:h$xDq.9+;>=~/dѮ5oY啾}ծ ]im `΄;'~Oi^($wZg}c;s|#fg@λœZmկ~7y'ƿ^*?Z׉|3O O EϓpV0I@l2^_o¹|){[B~ WD. )u Xˌ]^:9 x/ vjWj5[l-"iI`|a~9ďfVo ECk9K̪*Y7z)@g}NO:r[뫷kTeozh⿆߲6_Ʒ/%ҧ}Una׮u'{ۼ0e;~>W?v1Ѭ0tpYNAAA%Qkj9l~}yI Zj'Hlol+(N^XNY<&MYIE>wcկo-48c7,HXse܃A 1œVR?x|\[xƶkOHf;Ib&J[Ef$qnWikb'v?!׼{}NmcKMRt!yY#4XldC/_Okڬ$";*26r~ԭ~ѬuEw$X-n0PU}ۯZ57m8i+Dӵ8xrb,si4RF lnؒ)"eP<e^xN/`OiCo Ʋى҇YE*,޾EּQ~񖀾.݋y-o"hpC":jUrc4|ߛMGJ1K|8ԥI73d«334,*8(m(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE(((((((((((((((~%Ѿ m+ǚ>k55~s @sH{?dimi=–峀k<2JD$3/WgڥkZM2\O&mDݶ,p8Vsik|eMy9+-o+/M/ŭ㮙?f_j֙iMj$5+kFi /&Hx-}h͜ڦ.t.zJ3FIl&@u_0k7OexO4Pp4@mmbN02p+ߵx_ lvi.݌"SÀVҍoV9-ZI}?Sk?sg×m-uck{%ͽR\I4LOh|P3~>!}Ω5\+Jм;wqqou1JE<&Fg*3;ל$WBudv^!'-_EOj/iwp~]-2*`rF1Ӌ^oIFMe䏄<}3|Yύӯ{{-+}ewww}){e8Qn~(м]Ixvv\YtSU?$8ʒ%*_RH4..n$q!FIYU%HYZ&$ts*Ү>xCmk^ SJ:;HXޣߊ|5OxUxCÚqj6z׺EIly02{?j?6|COx.,+jڐ{k'S3}hgo!98i_5F∵UJ[<I񋘢ޣW~0Լ]]Z ǔwo_  x% k2լK0†HP$:>#\ϯ_K^]w ޏXjjgKyeI3$h6u=I-[ KMNRExI˱䌃"r:2!yO|;uRT᥾aqi5M58%vMXX>b{].Qvs]zWWROzշQ*֚oiisuwygInd$yJBɯk67MڦtGΌa7znq]EʳA/d'PF++meeW;D*QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE(((((((((((((((x^ TcK '&ʥ=`xe]RW+uK]Fv3LU _`zjώo<&?kBhKǥ˷PuѥGx~'o: .+}rnOd"a(\u޿XjmmjSgk$ȓ܈4LdR h98jӢ_%f[6F_m?*!h_Zk55]jZ}i[|Y\+iP:`nk>*|1Vsk.3Y_%de"N k먾+-pxF\Y.nnvf0WWSt{+1?A&ӗwe;~5dxVkNmq-Sxu.C۰Ky1FWj4X`J^2yqk卍ݝ{+M_YX JUjQwcݢec7Frhqx/Aկ--u۸o2}KQܸY-ݥQ7` o`ך%uYlB͡bi'%˳?y՞(PZVspH/yUԑ28h/Zh\11^%1<;\\z&MF4Vw~YxWk?u3&úπ+H e}d(X|4yw " bzw-n4 隍MeMIUY{Ald궩kz뎑i(cb[anBdua[XIdlӍb4A)F[StKJ䟙5}/'^8KMg5 '^riIiZF7) Ǘ8pJ=K0uĚwe δJ6M#  { M4QԭuX,{ ɍi6/A9؏E i;eT"TG$*9fZEe~{Ge3ݭs?wx^g}wTM4v{Kws%tI`W뇆$ޒ[Ķ?iEl|8|wf(ͥbַK .M>6Kdf;XQ"5 !C,x{ s)Wx7ɧia|ɤ"*AfbA$ ٩viӺQ{fx?񮯭^]+^(x%m*&;8g$nopI&"~WLJ_ 4x/ &ͧ@v[jڬw:|ыuy|V]?"`eQ吭_p7^/k [9-x)RXd\RDV\ngMn`m^i[xMdC$ȉ=a4%OZh$A}᎕-S7>'Z=bI.1Y'K4*KBv uxO>$Y^xB+f{K:mR):Ye;w5R2QE# ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?(((((((((((((((*[>,e"Y(mHH\,+1 _O6~_uK[SM/,\3g kt7-QT$oǡ~$XkR֫+In:pi0i%ڍ$1orz@ _G><|M} UߊZsMq|-ṂF#t/CH^~D/V],M_m5owo?#>xƻEφ.,$VoWv~D|мo!|Aּqk_m5+׶M{C Ѷ32qj3|QYGxPajiVw[Ogdqy3Y7ɒ8|?S 0 =3]焭o[zz(^ ]#PSɷ:{jV[U"Lnn9u|_ɠxv_7(֯SiQXDl2A yio ј˂ 1Erҧʒ{ks/]|jTm{Lj|}=7Le= ˢpeg!N [ƣ?ͯ7hgV҄`┹~~>GO~*M&8SAYBۊ\$:@ېW_Ǿn_^Xiw!WqʖͥМ9%iXA Ȭ{ܩJ./˛OO3_G7w/>.e} GtMjSbL/(qpq3 P 14_\[Yϝ{-e)0Y_+!IRFJZj}k;߇\_I/+i^źg(V__}ɧ A,mŢO* m?z[1{M-V9'ڒ03+mʌ袪M=( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((N/%/L\pJhw&,oXRl*1ǭoXEz(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((:V򳼸ݲ(V z#V SUM/]_ϰ&zy]zy]:|_nX9H.[ʍg|]=coX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+toX+uEcoX+u$̈́%2E$(!@UaZǜ-n0}3:(j(((((((((((((((}^G^^Gx66iu2GOn)e|3 ٧l;hMjb_ikzU kKu1R 2;XW(Ǐa5^]F Yzk9%)OAۊY_hC&ufy<%UPTUFxu)h y q1)a_AlK;#xTG RCc; J в:jڢ2R.<+%Ÿx_kmyI|˙$hC`(J+.$-7¿>4ʾ2\zjrCsm<JH%Y]ɔ`Jkִo?u ?tX=kDڶq;Z\1ɘ4aHТ6VG*s~ x+RҵmCK BQbڻTEyq4pT5Qxѱ^+ 4rr1 7Ew㮔&׵[&]SH5;t}oc%1Y|aGs?p7۵O7(ݬCeFPa-N43ͷM:t[ W6CjSIl[q螟쟫K*c{k>6Pּ-\#H!)$viUj*"`-E~?.t{m*ĖYST=ƏvGr,VH>l7Y ]$/8.-on.ԝx'E\!pן';KWæHM++%Y< GB]033͜_"e;iwyo.6<0v[Glo XjZN\\jCmXY4W}&9JǙtezt|!ߋ 5χi< 7 Oy#PKy^e3*GyQ3>s ~1 TVvkO.2URx@X5o?~ kV%Z͔ k}w]s@K c1Pr@*7.Vվi_+2Z-NTx/ 7.M6CfЦoujigKL"rXm$>կxJ>?7ڊ]Gv; ˕DaՊ}!}8Ѵo,t.-mVxbKyywPҬlI~|.4 'jz,7 KY(74q( NN>zN+MvZo||wVMFxbME"ԛO6 #D9Gݧ\|f|x<5weh6mma5%{i0ǖ- _T҄jda¸^*mCᇁuQ0K }Gwyf|EfIV.}w2{7}>g}7 ^#Ziڛ>ZKfSO_2-ZƬ,IoH| Ֆx3^X{/eCq coc}Oٳ!\MI{u]OP[{{$ m#d>H`A Ecxoޅm> -eۻ<! `9|/}5K͚J㭿E=#g|K߇]axgrd'I"N`]61՟G[;V>1՟GU6m-vW٢*J?((((((((((((((((('4\E{|%gIt><34r-<6qy8+ ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (0ӯ!uL,d# A=/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}?/u}٢1/u}pچa62ZDE+'7|Tqk (?(((((((((((((((ڦ2۟ &öztz}_Xno1 ⺗Zihڵ&{bj/y~%Ui|YQI*'\̻'9+6?{_|qOG}FJqs7- Y7Vѥ$;IE<h:U8Sy J!PA[jK~ɟLQ_$Gch^o\YjxMаN$b_ BXoxSWn+K-fDkAwZ5F{"30t` #inI4^Ϫ(>~):UϬ[K{jeb$ͷ0P7bEF(CFqj|,|$ [ń\q4FE۰$@+z&e/3+⟆֯'.5h5{kM`%cTaSϠW[tkSx-E,,4>ɥ[^! $1LwSVOOW)Z_v>C7zΉkzMm.u=Ji>#5)6`0T=/گ?ZxGJiot{R Dٶf H`/_s]_}/ETQEQEQEQEQEQEQEQEQEQEQEQEQEQE|E|RS׉SQ5x:C|68#XNeGեy(i}Ѿɿ\nW[w7%73/"[ۙNp^B{x-8[~SӼC 6o>5\nepp2%W9TJkߓ_}-Sn\{+E~QgoxwNƾ~*ƩnBE,FC$W>7fk0ִ{A 9B'E;@[N.Ϻ_64NMM ڊß7|Y:]6.OMB$iX}+PR(((((((((ˈ6~ׇ*|O~ᧈ5{ol]eK㰞7\)$s}v/ºL^6NHvrf}ۘ,_ɓT,9ۂ }?Obnz%QRPQEQEQEQEQEQ_ ?gÞ ne?|Giַ^gVdFT1;:3VNJOSzL$$>MaH%$ڌ˱NI]qzyv_wPok>CKxOα61\tP]ԼMsm |QYӭ`KBKkEwf%Zd8 ޜI'_=k0m_kT~^M> 񷈝T4[!T Tt< ŸBjIIu ((((((|cE9'H|_L[K|wxޡНx]GGĚK|5ov>S~UtG?v2_r]~zp_^~_ 㫦/ZIaMo{pA&7HE|_`%g4 BVHWX/hfe|eϗ$UKwr?|![B5ux:m$\QĠ't*\cYRmOúv H4b/':= =l,,0u@<,ʮ>϶GF`Cb&Yg[-b;X4k\كj,IJ QF<2A{oo_zqW֭~>b ~iSzwZNݤ\JI3Ϙ[  -iAGm?R[QL((((((((((((((((((((((((((((((((~ɣ]ߍk O Xۛfyy[ڻ=x]fi)O_跭kwAم- 'r͝_)Ё<([+)su6|#: {>FO]ť#[K$Om"([ OrierO?$ɡ#j }QWS u(PUBN2R[M4|P y |G'{ {6|c8߹x#D׼+Y|>}}JMR+m Th5Lwq~ґʦ%c_mQR%nG6׿ͿI m~xLޝC[Okte|5ċٕ}$H'@<[v5'q6FN?>0(`єQ7kt6Dt_?;2m>EZyZmP-Ń]*;1r'G| u/E~j.-5ly'CqwVKk|k~m~x/YtGpuu&~-fjm>_27q"ve|yIJ(|}^RNQQQR ((((((((((((((L_c-jThe]vPJ\׫S8)+2't|kkk.-u ]a[D"VIU`@.Was  Kƞ0^gNJEhRx~tȬ,-`I8e ,%77[4Nݵ~=uש 7o&-& H!ϳ',Af=OZ{_w?޷Zf[>i z"!b2  *F=޿vMm۰R'O~w;:ht4h-uv!t!.ɕ-޾ܢ-d (Q@Q@Q@Q@Q@Q@Q@2~hnxW5M"FIb[I( 9L+'i}? W,ӡ` ) KdE´lxݿ5eK_1ɷ{44/3EI MO^dkarbp6`pMyxUS4/QUHYReX2'q-}E oVm~=w> `oVQViRR6ʤ\]XȡNq0[_K-`"󢔕T*J:Z?}> ~gv ~O ]^ۻS^ERn1I$(PQEQEQEQEQE܋ke%M;<}?G~ct׏uƯ_cI-ulS{KKlo -xJzvm;._ij7) IʆFy t/^=öVf%<-wx]i?f{{s} -8$Núֽ$I6)o#-i̎!d V@Krx甴|Kx.׾!Zh:ei[$. 񬲭^Tj@$}ET~non~Ј{q[|j3_R4KPO+ZK d3g$(?f/E?j}? xRVKUiou*Ow i+_`M)u_Q(+|>=dM uR3'Quke}iGGuT oz[:+Mu_M]?vDYi#\E!A,n۴ QI;mZߒw~N_Oռ-M/?P6\xvQ{`cC>'Imh|QaѤiqr. fɹ)H'q^~ꢊ j[aES(((((((((((((((((((((((((((((((JD2,y䓓޺ʓc}M`7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5Ea7Ͽ>G#z/5x⧃<' : :he>The+h?x9;k~ 4!Vjfv\BWWs}?p '|;;]6z֝,-fY J$66 ;p(J/v_3o}?(}?~)Osf{95cH;Eݻ4-]+'#z/47Ͽ>[-wO?jŖIr]P!ʨ䝪pZMʄV}?/Z[Ĵl#̸a4)0h]rRrWCU($чދ>H<]x3VQl"1Բбh_#ǭt\0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0}ܢ0?CsAՔn9Vբ(((((((((((((((?7֭eIEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPE[➁jv1XGhE@ 2[9pD, -KZԵo .QIlt{u8-4Hཊ%]Y5i3DG E(h_׽wdS_kg_i$piwn5<;j*\ZL+C/ gDeTaG^S*L4 j=5I])Sd%va*~QNJo)~?$t%":vj #vԼ<\l4J7ugv;bBn{mO>DoÆv_Pm=&/q a&BaC=iCz? 𮭠0j뻭{U7חMݵVW֢u <[{gB ]?s|t/eSfek0ڻm*3\vBp+z+WdCXjR{~ב/ _<1--[Y&kse㘾βe\F\l'2W/AxNQ>*<xUZm[!m-jos"/EUeϿ}o/f/J]oi:c:dhdǠ[B~צqU-N0t <)f{:]MSjٹ/:,b! )PWvLJA (֢mdrXRig+t].}Lߏtj_jڝ߄ &Ws̖wLl]Ike$f%PPϚ𶕮 Ek=&Jyi$Ӡ]Ö7Zk]RKK%1aqcvUTc> 𷍭,YêXE{ Sѭ91^M6gss|{6[rk!մ-xo}~!RHOؚXYdrpk@>Xg{SOet]QE# ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( |)/PQ@((((((((((((((?7֭eIEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPObE2բ(horizon-13.0.3/doc/source/user/figures/dashboard_admin_tab.png0000664000175000017500000033560613553660754024465 0ustar zuulzuul00000000000000JFIFHH@ExifMM*i8Photoshop 3.08BIM8BIM%ُ B~DICC_PROFILE4applmntrRGB XYZ  acspAPPLAPPL-appldescPbdscm6cprt#wtptrXYZ$gXYZ8bXYZLrTRC` aargl vcgtndin>chad,mmod (bTRC` gTRC` aabgl aaggl descDisplaymluc# hrHRkoKR nbNOidhuHUcsCZ daDK"ukUA>arZzhTW nroROznlNLheILesESzfiFIitITviVNskSKzhCN nruRU$ms$frFR6hiINLthTH ^caESjesXLzdeDEenUSptBRplPLelGR"svSEtrTRjaJP ptPT LCD u boji LCDFarge-LCDLCD WarnaSznes LCDBarevn LCDLCD-farveskrm>;L>@>289 LCD LCD EDHF)_ir LCDLCD colorKleuren-LCD LCD Vri-LCDLCD coloriLCD MuFarebn LCD&25B=>9 -48A?;59Warna LCDLCD couleur 0   @ ( LCDLCD *5LCD en colorFarb-LCDColor LCDLCD ColoridoKolor LCD  LCDFrg-LCDRenkli LCD000LCDLCD a CorestextCopyright Apple Inc., 2018XYZ RXYZ c7 XYZ m MXYZ %^curv #(-26;@EJOTY^chmrw| %+28>ELRY`gnu| &/8AKT]gqz !-8COZfr~ -;HUcq~ +:IXgw'7HYj{+=Oat 2FZn  % : O d y  ' = T j " 9 Q i  * C \ u & @ Z t .Id %A^z &Ca~1Om&Ed#Cc'Ij4Vx&IlAe@e Ek*Qw;c*R{Gp@j>i  A l !!H!u!!!"'"U"""# #8#f###$$M$|$$% %8%h%%%&'&W&&&''I'z''( (?(q(())8)k))**5*h**++6+i++,,9,n,,- -A-v--..L.../$/Z///050l0011J1112*2c223 3F3334+4e4455M555676r667$7`7788P8899B999:6:t::;-;k;;<' >`>>?!?a??@#@d@@A)AjAAB0BrBBC:C}CDDGDDEEUEEF"FgFFG5G{GHHKHHIIcIIJ7J}JK KSKKL*LrLMMJMMN%NnNOOIOOP'PqPQQPQQR1R|RSS_SSTBTTU(UuUVV\VVWDWWX/X}XYYiYZZVZZ[E[[\5\\]']x]^^l^__a_``W``aOaabIbbcCccd@dde=eef=ffg=ggh?hhiCiijHjjkOkklWlmm`mnnknooxop+ppq:qqrKrss]sttptu(uuv>vvwVwxxnxy*yyzFz{{c{|!||}A}~~b~#G k͂0WGrׇ;iΉ3dʋ0cʍ1fΏ6n֑?zM _ɖ4 uL$h՛BdҞ@iءG&vVǥ8nRĩ7u\ЭD-u`ֲK³8%yhYѹJº;.! zpg_XQKFAǿ=ȼ:ɹ8ʷ6˶5̵5͵6ζ7ϸ9к<Ѿ?DINU\dlvۀ܊ݖޢ)߯6DScs 2F[p(@Xr4Pm8Ww)Kmparaff Y [vcgtV.-Fn L )   !"#$%&'() * +",$-&.'/,0013263:4?5E6I7I8F9F:F;C:?4@.A&BCD DEFGHIJKLMjNXOEP5Q'RSTUVWXY Z[\$]-^)_$`abcdefgh"i,j7kFlVmin|opqrstuvwxyz{}~,_ۃ"o#2BSfzڒ-JfҜ*?w@`AJB2CDDEFGHsITJ6KKLMNOP{QfRSSAT1U!VWWXYZ[\]^_`|arbicadZeTfQgNhKiKjHk;l.m!no ppqrsuvw#x:yTzs{|} -=Nbwڊ2Qnȓ5PkО)=Pcu̫3SqĴܵ*:JXft…ÆćňƊnjȍɍʍˍ̌͋ΉχЄс}vqopt{مړۢܳ"1>Ib5{;vKRVEA/S( R  m9JOFn#>H H !":"#}$ $%^%&'4'(e()*+*+S+,-%-./B/01[2 23p4!4562678=89:I:;Y??@cAABpC CDE4EFGOHHIqJ+JKLZMMNORPPQR^S$STUzVEWWXYsZB[ [\]h^2^_`a\b)bcdeaf3gghijTk,llmnowpWq:rsstuvwxyzw{k|d}]~XTRQQRTY]eksÑђޓ)\Ś.bʠ2eϧt??@@ABCDEFGHIJKLMOPQ&R8SLTcU|VWXZ[A\p]^` aAb{cdf8g{hj kUlmoHpqs>?@ABCDEFGHIJKLMNPQR'S9TMUdV}WXYZ\"]J^t_`bc6dnefh&ijjkmGnoq%Uz޲TYKj6g3 (6FXm"Ju DQJ rTNc 9 @ { w*b){[@+  !"#$.%=&R'g()*+,./20W1~2346)7Y89;@=ABD3EFH( =;ڼfvy_= eڙ 9 }_xv%Q!dingPᥐE# q dg");xj \iس5Ϸy3FT4{E{vE ) cCmu{Jn; $@;(nS{"Q4 [< cVRqrȣ!6}E|/ڋw5_x/m4x#\oJ73'c H , | Ьu?Obu &1[_6Y = BlYb6wX^K1MK:rH b+῍&ӭ>),a$kkșOP7\UT ^| Uݑ4\mU,V9g7lJ94'h_})|4Mwc]@%8]K"2򒥆=+xsZ׀>x/-b[k ,A\ *`1>ֵ+ÚM޽\aMxOIn]o=v,Fb%Oӵ ^UҮceh?y"x{J֢M垃iΐ-$[mf)lXfwVfh"-zuT8H d:A:wfϵûvqns) UQH(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((+χ"L^;;mA M`4H<iq qTu=OMt[X O<Q"YUQܓco aN'm-X6`8Qqٝ?ƏwX^|l ,R P$'o[L3Xh>"5mIVv:ǫXi^i9w#*vF5o᧏_u[vͮ~_ [i_KZ[jnw>Z`3^GF," `msZFkXe]ǀI'&yQ^ߺ=pUy')IZ<[[]}nOk^kVڼ7v & `xV ,xbG)a!?= xD[]^$ Qi}s M״]JFeG :*</^*,u+ DRJ7>K$k$֖\۹m6Xt< ?I_e]51( \)1A8z_v}zuv֐Ć$TS |%O K7G32^hg;}gMαr^ɨM:,h]@AU6ʡs r}p|׌t<#cg[YŲh'#jJ3 R+Zû8/mm<-ATF0*`怘|0n/=jG#kwX4)%U ^ȯj>$|vg['-Zjh M*"BVTp;9|_umzb%p^km5ڇ?x.vW4x?GѦXXWlൊ+y *9jܞ(ܺUUE3lX2+08p?,? gi^xN?#X[-ne]D+2ʬ:|U57ե桪ϯ]jvdϫIDn#m,b3:rw!`COuv_zVG:gsVgu Dj]_=}%p7`k[]w_φu[Vhp;&[#perG(k9~Di~ +m_N_(> sxsug<*4<ʂ׆|/#-H{r;I0;0T gۨz[|W)co {^L #Ybhԇr?|e_x?x8&yar'I"FyEp' p}ECC?.>|o ty|=q4V"eѭY{IV}VRp/ lۊ~C񽾤4)֏/N&}?@?-}mA(~ ̷6~O{y5{S :#KhP~#4qڜ/?'N/^)cWZ4>*XH_ f;UQp` u}E5&ܿ6d⿯D$y j=f,b{g-dl2B$ӂ $Myj~A4M_nڼ}~*z%%+oow ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (8_xῊf$m/L:r  y[Amp ~Sv[>$:]#dOpKwx&1,H袊'|AM;:ZjnH <ncY-cq2Co~\E75_vw"חOnu}QT[@$V5J z(vZNZ՟x'<__D2յkI %mNd{ʛHIe-7|9mkuWӈa"mke *6!5G~I%"Qsu?gZooxUk:Ȁm#[\HBBc#+ ^(alcld@`O< A utV9S}]7D_>l -|4fu G-He^hmKe- p7@-P>g4dxxC#?kk8O_3GG獯?? 2񤺾"xG^(+LCgR ?~Ht)]{X!-+-k.nn&YΛ0U mRY7~i_9?ɬִ㸚 qiZX7J ^^9NxMŵOWck{I2 x9q\o/Ah$ڑg?uaT|Q_ xWutD(ُ8Q2By,YEPT~|?1UDV-t~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Et~|?1G_W9Etq}iՅdH\w((((((((((((((5l?cPG Znj>ˋ(giV35xK㞃q<} ݍVj-Rv<3ȹQYYH5wߊmm*-E-g2,V ʩ+Mٻ5ޯROUIek]& 3ެ,҈綑 ˽%A}zVOM'sp|AtZG]}k%h$")M9}VD55ޣ3Z$1vZ,p܀|e:|OԼaftkKu2%&$6V$PH#+i~ xE Yk걙mDqM+ʪlH ;◇j:NgN/П{f217ePK?GxQ6{)ki?%ugfkO{Bf7H K0'>M&7VW_hèu|5&oWfύw`#09|&xcWYWF8nx}0F^:\Ij>hC) uDh8BĊfpI#5 @*JZP) (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((e)*ެ/OVϨ?źvrbyEt-97,F'b N9}5O(5ơy{-L6=wt/Rd7lgLhN迴Oo cφ4MgZ_PKy;o2F&pK(lzZw"ƢMuhNOTͯ\jVVsNVU(nkm^M!'YQ%[RfԼ!ꗺ=M+]-l"R? 9M1#V4)hZJQE((((((((u|::Ε6kVlu]~-8h[U|C~.j_ M-v'_Etհ@elj(kfEu* [s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4Eo}VM`[bh?S@4Vح>m +o~[s4e)*ިc(c\FwZ' 5.#Ib`TNNY'5Q@5[~^Ҵ<7DK60H!`4qI3]5?xs^wwec1/1mtd&[Dd%IGF_?Ӻ%M||Jo͕ʺK|;]sFAI8 <#V eglgY_s9=(wQ@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@t?s4sZN~.&0ܯ.97?ůh@\M|aEnW@4>@\MߋI(M!GߋIt?s5_??t?s4sZN~.&07+xgsZN~.&n_ $Q\sO#n_ $ k:Š?ܯi?} k:?-'C?_QG@\MߋI(M!GߋIt?s5_??t?s4sZN~.&07+xj߈(h t* G>cC:eiReid)AW}5|~W_y|%M_|B6ڦy96+ "R%R+Vz5f\-Sߥ~Q^O+ڟ>xGZz%WFyK$f bbY8{S4TQYQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE袊?Š((((((((5 Z=55ԯ|e{|4,έ0?tjM'B4~oc#x\v{{ h%Sg MɳNy)b=myn}uSβg7մsY߮K\[w.,]CE*@s{$yRHc'PC%Ƙ%v8;e^&2' kdFwmw 0sfu`O-: `[^!?ϧIꪍ]ǨE{h {ݸFĒy$7ϩX$ۿ7k|>I?<i;+yI+Ŵ恲HUrIj:?xNOƨ^'>L!XǟK#@l|\.~9y&xjNvCv%'$'#ĄbTN3r c<HXM] BwunU 31JN˭;sZkGGáX[/ v-ZiZFm Hfktbd<@0XkZ['wNJ4@յ TTGս:q^ZF_s-bc6,x[̺g4[ aXmu\IMo:}ȯ_'otӵ|Ks>.@ p#\tĢA;~gP:fauulMu5Zfai>YXĐA ,qDQTz*Nܤ_m~#{9oi'zO} Q6e@ w9+oK=ߋb+ Ju1KusTvs F<'whմK֍2IZ!B"=nJ0z{3\^Vg?f_/O➱hj^5dEQ򕻪mQjo:ÍqZm19aK|мG7ᾐ>f%rˀ;䐠rⸯ?σ~%o N.2:ȩ<"ȧQ8i0ge5)S[w}ӵ{{QYQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE?T I[mAns2BG+_t??P|%'Cm"|A?3rAnT I[m2(xP|%'Cm_t??,̾ 1?s?(?T I[mAn8/BG+_t??P|%'Cm"|A?3rAnT I[m2(xP|%'Cm_t??,̾ 1?s?(?T I[mAn8/BG+_t??P|%'Cm"|A?3rAnT I[m2(xP|%'Cm_t??,̾ 1?s?(?T I[mAn8/BGo;KH B-[B𶕧_A˞eMU a$A"Jߌ8&5(7p4NJMɽ=(>(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((#^O h^ :nd渝q,xIrkGſnl|%ݢ,ۼRN66"#sÅ*r9 څ~|Cqx3בg[T>Q_ڏdP݇ug[]A&EH;&՜;|15b8]xŷ_ċzd2][2T) mߏ/ͯk՛OoϗNvI$!u<\"nRFu*è`AU%ZW-tG7`&n" 6PͨE&|l YM_Q˛29Im/,6!1|*o<_QJkh)QEQEW-Gv6k};XpJp I=]Mxg > oߗOp|nr0 bZ'/ڳ`oq_dPEP$dXbʢ,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j_zzPƏƬQ@>EW,^h,^j @|Wb?"qSQE(((($cljzu1ih9 xGEƍկ5) sTں\ Sl: 2-R((([ž,mi6ŭwQGpfX:JA"( )h ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( ( (?((((,' ~$xw2`ce.FJ#=E0/mquZPe~v"8t pqЕshjOAL ~ 4R%"%Tl<k?,|9m?P4֑n⹌aPybV I8慪[ {u;+<L|1/MԵ%cIJs4tw5\|t9i+7^2еɚKdb!!IVR<`ﰺ_j+tڧs׵;#KIsbK.륎?./#$9,J3aA=m|<> Wծ4/ֵd2=N)E8gD)u E4WkOᴝZDOmpėYhLQ<5X~o5?zHVͶ1O)CH] B\F/!j#|34i,z-dI~I3RI+/n<9_ڮZ& ,ZH$ҚWv[ޗ=?~/k:LJ)Yڗu {iVI-dɲPu# WmG((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((h}zNx~6Yj[3Gn*#@HWnE}UE'Q[]⿅~Z;[]>3pE&+ȊͣY%2. v:g|!XU/,,uB;%U6 VȠc?~|6/okJ.t\l/U`d&?N袀 (>u$jX ~ a:,n#cKgreY:m%v< Su7hP#)*A5 F~L t;؍r '9SӚ̩$]W["BpcYP.f9 WxĿ iυ}O&^[;ٖv*KyFޫXŀ#|&~ iZ1뺅2*!T!S_<8>E}jBhjH,7,ADHRySQj1袮U9nOO4['M}Zx_OHN84;Yk&ID"\9g;yt񿂿i_a/^#|\Ge?aCi,&dVfM (/Nx#Ym!m-soo%r"0Pa3u|6A{z[sʏzD!drrK]+V>W[Omd_?c=Kľ6t۹UmkRXg흁{ryr(R{v㟉F=m:vWjԖ4I|VP{Ws7~ j x*yX/<_L{n?v@he՗Jo]Oϩ0`bTaU Z>eRQӥԓ/3+_yz?xV@t;w}v id)H+9[ZF3ptK)%$^7dg'ʧܒO|ռ:֯D^j:qq$Cj"*9尹n73-+Vv}NUFwdwmfh?~&iQ4#־E>k~֑| [_x B*~-mC?FWDUl1ns2 ]Yᧂ~'z{Kx3⯏< 7wKY"H%LN.b_pO=0 /?~ח YxgRdC.dQ.c 6ЩY%aTxV::/}\y3Nü-?}p SG~ßQz.fOyn "zq\T1^u.3e-~~u_ '`IpuU7}#ÚxX~|X~9ȼ99Q%Y3ÕT†ʂű) f ~՞>KxC ڞo+B%w.Vd~7~%hƩc;bx#hy!"GPKf_'ۿjcUԵ]ZѬDኴ*(YU-\Thыܯ+/VJSV ZV>x9N@w2б+o 7L?멯7FtHдVN;h"QHP(hj>WQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((|;OS I?M:^"ԫal`w+cuS_M75> i (hΞEOOyću!K_O?Mn6.וrvz#oFIuyAKK['s>MgX[،mTG:B#nToǷ~yeuzw:JX%o&Flau%վ |J^ГHE7l\ thr<)HSK7I;?k_{Kh|Uዘm-{9b{.#e=B ]2pV~cz/4CNUm! v,\83_1o~KXgңM[%5I9O8l% Xm97EO_gIoY\if{73-ȠGP@mSz6'kxn~;^ߍTc@vo̺Ko'?l7]0|AǧQ^_kiaCV;BI?%Z 7p|/0#8VDLo(,((+~3X3񦣣k)u cU&)i7GՊ#9^)ÿ7zṼW})O PtOGcv@x[:t /x^=GKcA k2Hk994"#l#u'*8*$3McjS*[{.V kx,{AUHAe(߭޶]uSgo?M#o_+O] Q#&da DxN<3<+[|ۈz 2}_ m>+CC5j *Ø'26%،ۗ``Eg&ὧJhj}6iGۥ݌Jpnѧwp\Ӷߊ_#{Goi.b&Ǫ5k>U)ҴwhQy}V:.9Pz7:ŧo I4&8am6/"3Efx9< I@=xrSt{),xno,ᑬ(YE&+H'%_OpWR}?Vj >7WQ?}'A:׉{`\"7d&t')r/Kⷯ5$nZ}켿mo~!hV u Ե mZ~scH5-,`1 3 ăm1u8q^Fqo:sk0;_>K!@7gM3HvrXk!{YĿsF>VכU{,r`$x2Ҵm7J:/+~)ulkw}?%#7]ς|/}g~{[Aww y|v$6]#EXyhN~<\_{6kn|=&O;KQs6qV;cuY|ݫ wrs1hab$j$+gˌqE8&߲֩n';%zەݾ~ oFy"yp6G#6O^ |'>&Tj[C+Y(g m@l/u~7ǭКZqIwc%2lM6#YOʬ6ݐ'X)j}zC׎; z܇L>Z"00EV;{hߟ)5lz9KI?G{Nƿ>|q ۤ–#薗Z=RVn 2`H<79*fݫVwbZt_ ϷxLўnۙĎ \GAA]C^| YGv jM qo"6H]ab]M7VWj.<6r\H֨DWh ap0A.VK壷'k}?5/ivqZk#Xw0 qsҶGBT-ȪIh /=k5OG?A/_j5ńR^`!norTzğkǟ.EO`h%THp@ %𪦓J﷦տNn$5|Yeol0B-=ݿZ]5y$3OҼymwV4Oiچf̓[,2Y^4bT0#Zسִ}FO຺RGq*P ~n <)1xs~^ƚ6ʩ%"Nɍ˝ycw?/c=>1x᷀j/~-!n,4\[j0(ueB0effGs]uOMÙ_ߞˮ|F{{+xK`Ԕ=w@ HF 4w4N>< Hʧ#ҿ$Ý'ZFV>["ZvaX(viQ]_s}^k~I?]?o~}56fS~.#6Xv}󊣠*0/G7gy ǙmxMюm5k >M.;(W&HJ2U=cI(%uL_?3CHA]x{Aj9E3\CD]px9u=;SI$nHYRT05]^Z~ լƚzխ ~5$MN~X+;l-} i]^#VK1gՕcC$)p֜}]}5t}=t}!ETQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((įh|5{(hXɦR椷S&$j)#n.V)F>4o \xGk~ׯ Ic"joP 75f}H޻z-nZiᵉbMMF:7uq秮'k{ii5i}NoZVmBY<%؉)IT*+ gj:[捣kZJٸ{+D"eLtxdl6_sN-;[>/x~JcmDv9DdB>9FS=jŭRO`*FQEQEQ^9QMgݽַdkH~Wp\>:+ƾ |m {A&Ԉ >S92c2e(H HTg vgmJ68 #w@9FRz^'Ywš5=B%wY^6eɿDݿ&4fT^Sn![֬m9owa2ħpBӟNխm?ȖZZd_}"PhP]W-*Oa]kt.m_Ǫax"UQB  7b]^Ip!R |sְouj> Rm-ɼH3 dg3hߌn8םxmF-3LR UC!`lA+aZv/4TC FbḧU8ӚmZo":g2#]C9WG [ZkuY"W1(#A<+VZ}G[@C,2FH J nUC3>^Ci+yal$nTdpGk:>.|~'+zG".E:`; Z@W3'hYom20tnj9ѵi2gk}^v[Ą1Șp3p9ޛEy'˖?j֗sɫKx-#{1$9> -.fX?YPzo‡Һ=#H(< sN |[:}˨XWH ’y 1]vr1]+958aq &[no2}jO+t r'???O _oo}??:_ǻm3D75m7L"ԂȒ"eYm 9mmVP*`o3jo' T(K ۹,|3x]&y4iuy0?y c~4AieƊ7]Ќ}:p)Wv 3ޝE1@##84 6FFyQ@U{.1x{Qu-kKookwea )aa3xPXoM[ZXWD 3rF >h8t_bƊ3u r~(EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP((((/MK>iռpjv.Ś>xu RjNwmu kK G,ڶvwvO86n74%]ő{_|־%Z,hHM&(!bu(BxYZj7{6Wb_d|u-= ΙMVK4t8 7ycIx2&~ѵ&ZC᳠;Ga i#w< 0"U 2I5 .^^_Nm˛? 0OxNB&?öbۼ[I o8f?^E9j[3I/ * ( ( ώzwŭch-Bx溸sXHPױ@5K~Ph:7WDKjOݘ;$('Ř-PEP=4hml,:c-{9<1ۢ9~:ث:I"HvVVd=^ѡяQ(b9 99$$99lQEN)>xIwiQ癛j*1tJzxY Hɛ#ݿ!<]RH.cj5(V:]];A<{g +Z} !O;UQ@ѭtt(-NdMK|F|/5ݡ.9ݑ3޷bݬֳY].nuV##{TCO]8iB?Q'^ݸsӌ5v8,--#mQU2N dXZiQii4\d3V:INQr[ğ\Da]>1}Etc[M-my:쑰N9QcXx_Al-D"3 9z~χt:-),c+$,0y+8x˦%lvD $ey#ݫgDito*Ky;c,٦^[AM鼴@ 4#A*;=렢G5gXbiʹfb[x$jֈ1}_V)w Jjd$4X͍am=x0 Qڸ[x?}.DRrsخC^ XlWd6豢*x5b(QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((Z!=g sK$i'bunIRx'҉(VAjkS߸?^u+Z55˅y`HHe$ '3^]g^_yb_m;[!8s"*"?kS߸?ZpttSVԯ_D"W'yW|Ac;s:|'?AMN{ˆe.U!@[U%udz~x? M~Tek_ƨo6#dw.6D'12: 減?^`/t$]>+ǵle%\d]pH qEQ_KVAjkS߸?dgGej8ITda,*n Q"ay _:ud%hm-,vE."B(ǀ~E+r_kS߸?Zp.:xGִ#[kk 9.vͶvxr&_v9f/At>"Z~ O*pq<}ɹO+Z5GVAj:(ek_ƨֿ57WGEsZp? M~U@VAjkS߸?ttP9jo+Z5]R떺V"@|3gY)>ѬV5S1\׵-A@5 |AƨFAY?|}R<5;6VMl.{0l,fBɻk[CZ|ڭPIrC:+ v"*G4_iֻjg81F+f4Ȯn@s">hݐ;W5E_/wPEEy~čwU׼;eo}u9@Hdi Ds+x{xO TphY8'8jՍ-b`^L"W#!FrqHO+Z5V5o<շ@s+Z5GVAji?u]a(羖kl!6 cW=m˹n-7Z%׀{ps;`VAjkS߸?d|I^W}^pq<00@+)!$p8?ߍ^G{^;+-%s筴$1lヂhZ nvZp? M~UOh>XSk[[8yv"8Rx W+ᆙ[X 2)K%O*Xݱ!x/RvBa M~Tek_ƫ{Wh<_/!_j+eu@n.n_ zO$oWK4{K7١+~cXÞ~)V{7/?h? utrcf__2:\[q]m`66*pbSY3NԦ#3ª;C;2y<38jm-~>$c9Suev7/?h? utrcf__?/+Kgk5k+ w';oQֶTb*1Ź>@n.?⺊MCF.-8F^dkrŤwf+ t%2+N6[UΌ'-O ((((((((((((((((((((((((((((((((((((((((5>!x&}q`O YZH+jqA#)wl.pN|':~!i3[̱Ii6ڰLI-d^~vظ5g~z#~/E;߈BGYXiPFkxB̑|6t_ږaxWvψIX[1[KԈqë+.`gĚޫS_SnVasm%oq(PrnG7֕]K\u_J7DY.mMV8#Hȑ* ܜsK^Oux"KУZk6eҜ8O dL|X.<'eU:φ>βyi :NB %ꚕΙꚚRCìHd@0vat|U~Yx-5{z -6kx,Ûuvs΅T*oe/.]n_uc4"v_Ll\tF;ע׌e ՟c5 D*,%w`c 8sI;/aF}Š((((((((+>:xw< *#??XuNo[LK+n $O([%FO |&𕧃|h-] @,`;(+?}DXyO_wP*΀)Hdp#Iv+K'^fTu۾q-.dK,RlbY܅¯㢒V|֟ݧV9;Ǘ#5fЯ;d,fwyJ xm*FƓm#j2 ",nYo7s_rQM$Ks>}/{~(x"OWAHV}Jk9nO!p͸'#q>7uM jW7ZH_ckpDym椂0#~Ԣv}Ywfh^>(_ GbZ܋OStwdd܅88 2 s~#kvi:\%)r\t.xcTr%mZ/?<|"ϊtiFivܥZPEpx0 $I? bL =T`~ͻz%Qv"a-eQE ( ( ( (8-[F+*}[F+(_i3i\=ݨJ ̟9O\bXQﳿL#]8xNuy.14]ffcI)IMjhχZWg4 pm3zݮ¿_uv/ z;xşټe*Q2-w]= .T!ᇅ|sɟVORl}oʲ( ʻWgE}EW3N>N4_j:rh LjZɧHrw p ݝ55B>zuEոxm0If)Ds}8فQhI]/o̗x/S^mf|O9chc@Nqzj( {QEQEQEQEouO@5X&wTi@xWH5]29 izaV o'p=G޽ֱh!852S#UҀ>gFkF*_X4ьi>jsC&^9A]@`ҿk۳INa?Ε]DK_6ݟJt/Awu#L? [_^| ޡ闺tu1q>5 h|Qsm+>o[ !_ҹbi{+TFٕ YUJ5Vxƞ_tjVKmhklw#!-&97T*F)  .è];||{u9{]ku׌ςuV׭o!)%:4gm00kZ(pQT!KnO̱u[ςG<`/IcL3-$s:N̿*gv]O+ ((((((((((((((((((((((((((((((((((((((((((((((((((((((wȚI'{~[)cD|^Eceb4eb5Eceb4eb5Eceb4eb5Eceb4eb5ů|#tصڅXLl)SA{%_ ;?Ds?"_?|DKOugȔ |)֢4_ Sa/?׿"U?gSzH2[r,bGٴR|e5oK;t84 kh(ADAUG`4Q_4g%mQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((((((?8߆ 1𙭌ܵ 0w`>=~ŷ^9|3:㹱7XZj$7V2,*ӡ7.$'u}E~?yir֍~~|qq,}GFwjpxsTsXԚ:ԉST6XX_Eo|%MLjc]ۭXs)\ bV\.F2~Ѣ]2nxNNQqѫ~_z'~$O4{ NUsXdX .4|aK9>/cⷍ|AO?#~ v13jмmtmS@YQɣ(ơZI'ocZFK?xj{-RF{ dvAy LnʹT#Ekܯе9)ɭ1xĞ!%-쎻kڛ;" JɓHi6\C;X~yNC,ux2xzCkjcTyW6p1&flFCj麥Z_-Z2H'K߰$XNfLaB ^߂Vľ&M >jDm$*BT ^-ix^?3_hPjȐ.XOi<0ێi>nEtO}enD/ſ|W2ϥ֒vooC eq _\_ AmuSfkw,{% ʤ>=ޣumq6/ G3(B,6dTh'͵3j+i Iͨ[m /̳7\"Rؤ|ÿivA"&Aɧ\$rG#l8Z0Z84} ̣$Och(((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((6j*ψ5]*H̓_VIqiiĉVW5cQoc+ _mJ-/vQ(tX#qc(:2|Xuu V]2An :ǶV99, SVIɋ]=*|qy>-[D2%K5ko|D|;M^mXŽ#I*q`@)|\`[gԴWegj:wM%WE{m!;BFsXzWk^)<3]Z=.5<`?5e,$o02^Ө6/ ?sĬw5diØԂ@廊{=l/o?_ "-{"JwA!]ݿ~:st$5 hzߑmYl>Wʮ7q N3zVfLLj4^Lmpq-g#f(sـj#xX?`!s,@" ^WhT\/̬罿 >7'+h^:ԼUխQSV]\X %'(ҒS{/Ƌ^|1 ^4kWRmZ9"I|ؗA+H9N'>.in.lWU;Do[3͘`NA?lu LQW5^Yc}rCN΀85szylM&~%6#?e/^$e%hgr ?\Lymt(U7n@@]1a>$3iC5SYFNdB/l;^8)X8e#i9+Fk tbTSfm'm D/ 3\0%ʪNIuFu#UVn\җQ_o} ~zgmugL ͼgOQKV,ܰA=7/>h4rf(olV('"tE߆7>ּKNGkM^?z2`\]?);2E3)^5>|kkizuo}OEoi݇1|ghRN0k­>3mVd2˨$bydH4odupV h&^z8M藦>?\f&&/NIS=kdm.Wmݾg}Qē4Qʭ"/r򞇵iVj9I[-(AEYy~I|׬|յ?R[mk#4c\} xf Z!ႺJ?u_ ÚA+}B[+ #݉']eE;];?oךSW \Y{8tfPo7R}PC潑0S"ۆ(>]w ~3u?|^;NZL7F Uo`HG{p G`cwz/R4;-*Pm/1VimqR@() Mq? h:|Cݑ;xm9iX`.rˆ{[[s|C#[MPm,-u; ϛkO/̰GYC>)īzHMZHM[To*m?xr$NQOeeOo (aEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEP((((((((((((((?kxxO[ԅء^C+2 #XgWtQغsq|Y%ď!Լ5mPlZ*kͺ )ܒ@ #p|~̟ Hu__5mUElyIu' +zGc஧iMnD(mn bA>aD>oorFմ^W_~|Nѿg_x;ź<:.񥍽Fnןa"\6ۍ9?SY|ӥumg%/4-Y4K6ZOo\~` }wߊ:66KR?m:`{m?źM[VS[wTH3EYnHS V ڵ_$w3[Zevzy+~_~*5 Yh]ƥ,W13!%)F..9h+xP|e&Վۏ v6 ņ6PZhf~x^|->&wu1Va!yjjp' uL |u VHMvAqk$KD.crvg$V9MI|K_+߉䴶#so?LghѼҾQ%2@I^Y)=o/+3X+ĸ./ > :]%"Dٗ.EҩӾ4~gNh!j>xM*@ H$r0U~5+^k_u- \K~1[5n1`RhbU)78w6v%ܚ-K>BMwx26?5sKQM2Q ݬ<]僼 rXe@ sⶳZhYIwj)AncKhI+wGU\1 ߡ?i L<A걼,3}e*ܼȐܤYx!$kojs<3]wӿ޿,|93◈񗆼I7H=N )4tR_2A, nH=}COx~5O6}6 {ͦ̌8v1}BR-J1me1eIĶT |KAGԴF!=-9#n:O^;r)'Z+Kg嗁aCB7^rGN%YêrlN=|4}42_ZOx6KVM7dA6½Ogī uf޳{iv [MGă3 BNkjgκ?vSY9_w/t~O|.<mK"8R]JٶK3Bo1UTR]AB짫>XxOh0jRSI8ԯ,cyogM8:6\ Cv׮JM75xڭ~,Ҭ5 BO<Xx1Vaz+IJvsB?-P~37njv(h2b]>82񘌦s/}x:of:ֶzwnZjw37ܱh*T{_l_Oo~( In xm:Wӆp4&6&_)yf (ggKLJu#T4.ZX[CI%))KS,$<ŝ#Z4OZơ0$ ghQgq$(Di@ ]قƎrX9|8ռi\5LbreIf #V& [ UмloYTn{?01֖)QEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE(((((((((((((((mxxWL5)4Kk8^fXw+aPX8z஑{sj0^&fjMN=W[iw xX<5o -E"WL#7$]m/^ݣѵ;[X`"d.Ah%! % J7FN+FkْI$"" 3d(˪viNoF_6.KiOjN>E3-[Pd-`kg45/MYk}j Q6@wxo>񆏮_l2y:̻lٰ;b+xn]𵮫i6aMqbP'y!z+ ڦjfW-nWti[-|HK? 42Oz'X!Uycd;qW_hg7^|1^E6ex]@c, dXJ>!xU5?i%/4KoaѢ] W+E!9+{Hִivރ^ iRh%CI"=i)^->OnfJ$?D~=?~#鿳;|A:ܝ"Rܗ+(TVR0Na߂.tKo[NGm[ZYO%bIClgivKR?#ͣZϛcmOSѾ|?oxԮ%Ӯ56]B$n )%] y_ >=֗=~jiF{mopv#5 !O'FvT*Vqmmŧџ'|Y!LkfJ }9fw/uϙ/19h-m~ /+xmޱO֞7:axuչK+.}$abta"}s:nj1oBֱmxiӭL/o O.¤jK1‚Us[Z5loK4Om2 FdrXy~D.d{(Hxo_|1ρh]@ڬH\a4kWtߵ|T-  G u(붂jY[y' Ry-7=S@Xh] i[݅η QJx卖[cն{ SoO _+Y%pa)"TYAq +HַLӧծͣǛ>.!LH<sI鿯 +Eq熼ymqyn%w6g,7 -Q3;#޻ mXQE!Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@(((((((((((((((O >ٟ/<%OWanup2%ĒelO<]T\8oC__| Y%:M5&C5,֚Zu}sZ߀~[ij8ВXڙpurі8W Ԁqtԝ^ߧ R]տW?##|8;r iMC ]'KXd-EĬuȈNZ_ 1T3Th)[Q 6k!j դs2}a ƿW` 08:K gR7VN.h:_?$1S\=ίmL<䄀H<s>(\%շ]gWmmqE8O'lHRDUazFH\5nWREj~kxBGῈmDž5?i -l`#kH$e{*7{5Լu&xS6 +/RY' -uo +rCh` 081Ok;~=-緥~xkS"~̿ol+)f[/OkM$}:Y!ENd Oÿ|7WW_>$wזE.yP1ds,+l+]@qNTWuQi1p9<{?o'*C_n/^\̅URr6}# ~Z*u V}!'m> -FH0[͸+3 2=4߅|ݭJM{JO?'mY 9/|Y]kw^ntNf]q9h=v1Vŷ-4"?_ x~gz.ُ챻K;NѓLʖ!fY%6v&)-0v\{,xC)?]xGL-y%ƪ[XeĈ$ba3]Vq[Yu}wZou+/ KMƛ\7E5[&WȠ~QD]Y&w|ɮע>gKޏxwixĊ4qa5ZM-wu E,YεO<_.-%tٚDoMv Jg;IܦƢk~_ϙ& ğ;X$xޛwۼo;|<7g_LE\JAET ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((b@Ү5wC0;ԺVjk"0 23FТ#:~Z-rS(BI#!=;]-EPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPEPYS6L,di#8o-[j.s7vޱW_qޱW_qf_nxiz}Ǚ~(;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4P7;7G;7[4Puey7gIqdLTu HHkTHa.3m8(((((((((((((("6\CZooA)跚<]FOCcy.}+:h +osXz^ xuVe2~>zicz `/Nk" 1m$q9\αw7/Iι\toY4W"sr3z5-?H46#kYR ,͌qRI_'ԆaI]298?/񟍵NC͑bLuQy5/ď\>?ďgy'hߟ h78ݻ{3xG⏄uxOͱHw2,[aҾl?d)gPյEmXd]I(Þ<q '^+߀!l<k:ng7r_1 GIp&ܮ"nM}}CiqimtZmrh OV&ՓB)M\p,1pl|HZ\nX-ݥVsAf]kk4p9x &iWkOjOj5IJ̉AV bd YXV--~ѝDV~V}kQqP/kiKk;Z=ѕt2DY5?xWK|&6m_K{=a YxgM}oO./+ּ;5د4{GIhmaB%I<^8ɹюq t)h=ex xff\e)KKhrTۿx'k3x@WYQ/5; ɊK٦h,*C 7? 5;iQPkڣ;yDd;و 0 ⵄ'm6JO}>4|Cλxn6} XHo n[&fo_5ê@»񶵠ǯ+Z^s\Y\v[e@a o_x[nauXR[ FMt1kg<+8U%GzqT]Kw e\}MKש,$N֌>QTʎ?u +ՃZ麭Si u:R[Tʰ5d <vp4Z\e;u?ϘmhlŒ.<G\ ] ;:VԤZ bG+-#@N@eR2W% mk//>J{}+KY5_k:fc%e,|\[h@!p}i6&"m7b0\lh1-~v䓌dגx4U@i]o>J'mz~(CΕi E.nhm[Q mijH$f6f࿁ukXյm.kB,WGl?_;_~{[?@诚hk!Х״]A|FgN up>;B< ;7Ư RhӢB}O,ki|G=XK wkVOAK]xQ-,?}~mi Z@F#pG yRȻԞ> !!y T֟JOP~"P`ڴ/t)|f'd.6Fpo_'5} j׿3J(Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@Q@((((((((((((((Ot~}wCq5شks WD񙣕2$swtQa|߳o5|a=wZh 5m-"mg%hj}Kwu}jP6q[jzѥ[Xl05K5v)2f*(o篮_ֺ~Zzh|}sdφ-C_'ԣ~L,FV; |hG;f' 4t-> Ѭ@Oh}ڙLK4Z#9 [W4RqMZ姦su_S |էV x+@дnVkNDB(o:C^ =i|#`mė;#=6mIZ*^7w [?<&:+Sv^5ȳ-e+820mʻvG׾ [?.55/g PʅѸdHOwM6WzIGZsoz/ڼ!2\nB/ ?]>h e,nnV{nY/.orgW 6x @4TJ j-4}jM4~z<\n/6ݯYX6!"BΒDܕ Wmi4W(n{/5 |tx+{xa I6L' zsn\{{w%EZ

    >2]]oY@4{Ց$1 UI]Q%t@pʻ}ފ$~)>ESQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQE((((((((((((((? ?6k=5#}d[ i64W^]==CΫoj[5H"f89 }QH:=n6mBT3@]/S; VvZEy= pYG5&p=ԂT1kxm|G0iRK+nRd0B ^`okm6x3ᾫJ/YxG$H\iRI;1P˂[apvq^_ >j?a8$Ac4l'[RPJH''ew\{M'j?x=-&@,MKBHYWp?$n}>={ǿW>֬%f_\J[٪ O>( * d/ŸtDox#{"/6%1lRgZ_zymOh<m|k^>M3KK{gFEgVpHGo ~],|9uqk% y/{ [tk_x&z/?Šé~EriUܢ8Y^kŭ*I) (4sx Bo$3S)$c[vGiE|QCNA#a)tMRC-̗ 2FC(*` n"u۝GU)pϷrJ69 Y[Uݣch>۵[h|;$d5N`uK8gѴW7 _04+M0thثu#:~Κ cݛ_ ݭޚd $tYEO0[][_5>բ|iWZg{x4n"xD}GRzSN]COU.bIBHfŧ%}E((s_Ibxb)?x ɡê ^BIghZ/T6vIbQ_x˗F|Ug࿇4o7=RXcD!W\4?0v$ckQ=>V.,n~V ]I8⒒jѻY7}+J}E|o/x+^o?zSy-Hl6 o~%j5,lZ̺4# ̈E!;?B/]~Q_.?!t+<XjG\6s zbz/_YY^C r >Py*y? ]}=[[Y] Q%h,EޭqPeG#7odOM6;>א&!B1 :+|Y ;6.w FhYlv<^.f|69M|/$jSEU-c[͝ |`>Ƣgƽw52Hkt[LHw 8== ( ( (GoχKumZDVGh2qש4{k}?^{Wf>JMV Cu$̡ʯ-=wԴ jTxoSxUd-&98$sW?N}N_Mj|I{ᘖ$7wvl[A~>.<9 ž-|[:EՒc ,H,Q a8qYZ47w!wyeg3~|m "|F%[ d Yu| +2鍫ۭ5#$In=0zp (y_Ԗ=hǦt&:i,Z^_%UFg=8?g?hxCso+Lկ4%.#5,U>QԭU+tV/_j+^m1l9δ-67qơ1UH$@&%8>&͒.!OVmEq?i&.Aj MC{ +Fr>5Ѷ Z)҈ַVݿ6{WQQ\V'֯>kxJͩ$7HJn11Y3|h;m;bM~ϪZWLO37cwlЁEq?xR LHoy{ܖ IAs֍s|1+C_L;-00IؤLD;MGaE` \ͦ[[rͭDآ\Fw(f2 R d_<ik٦KSd}{spA饪W~ˢRQ_^~⁴ݿTT / _-aEہ=Tz/ߴϊ~%|*񗀵oƝ&`ג[AqX݉ wFH.dz?v}KEx|c_ԟ_fLF[[~󞘬~> W|S~x|'xW_..5;aeBG* WW];~z[[_=ފ<]=B=*k j=t3iN/.61Bvg{lך4[tki%5 8-XշvWil|Oaӣcv 9ns漧"Լ;DŞ4l[̖b3$\J Cbs+EJox|,\X.awjv:HimniS#8 (((((((((((((((((((?l2=x辰hm!WdARya`s_QI~?ܨž?{/:ϋ}xzm}sV{ɖ) 4R뜒1j>*}M׆l->7Vgԥ5E+ΠC,JIhJL!+$?^0>&ӿfueW1I%!T F O~ȿCZ^x/T𝦉Դ P4)k ye"h\+.J,02ƢI0lt﫿n~k_?~ h~42.muv2 ~"n&(8C{ǟx쇩x^MڅǠsi٠GgVK|*!`CzxrNN.-tD~^gį~'>Y[xíjybKM/Wt-O$\JD(02k5ϧlF4˨kl+>I.U~cEE_z_/O+?w̏~Ǒ[k~iiZV̨͔|ZOgJ={NԶ_^)wkMkg.wl$ -Khb~~Dr~7&ע]NZ[O"q_ 9y#KuivrJg%clR3'#l]vJh7)9>pIQPPQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEQEW|iyo_G'\YU.6OWQR Qq}KQJKWof/I㛛Yndu4Ag2&e%wzRӴx&Y/IE V0boxbwFJIheh!V#$ȯ/N 4_kiⷊ'-5riڽIڬh%MZo]om.>WŻjYAasu]K>"FmÕWxKEO"kQPPWwηO9׫k:-%?2ydEQr w WQQR Qq}JZg7>~ҿ <;_gu擨]jRjΠ<^\i6>g;1C[Կf?ex*SVD˧jZY(5Y ε[̤od#z*>i9=k__q'dk~_t]^Þ "05i ssj*2 ;V4 0^յ W|?m̑MYv !fO pF:tU.?y(RJJ߂_QEQEWƟ|U_<1֗U?M-Vxl)i%.;s_8Zg?|;X\oo-nF5{%-:K gNj!ʿcC: C;6LvYhé闰Z=SjK p9z7^K$O?~ğufǩVi5$wA|fʔ9~hYhwl[ E(i7_aEPEP_Ï¿at`5-%;ֵ R4HqF0x$OE4_3{*4?%`u_5xFWj΄V(3+ZHwq0Mٷۼ}bll-N;HχKiɔy ;J*cȶ_$oV+Rr{ݿ[EGC xQ|gM Q_6f̋#xxxR,5l Hy 71Uq5ѧ;=o|~4HO ZWXԼ/OéAy>/֋5vouk~ q(؏$:g?_QIh}i/;>xU_=KD>дmOınַ̨Q|pʊWg߁t/zz t;-N`ƨe*vyL+795QEU)r|>U==?;ƟݧڷXįI}ݵzړG8,mg|ra8 KxKWM<њ{=OjX+\H;D~$q ;{z*iuOԩmG姃>{U'?i> XE|Q}qMFM~4X-fHY`(K$ePFXh3k 6x2AZ^Zq,e`Y;H :NҌ4}Dxwݿ?0< ?|Ym'W|4 Ě{=֙ifNfܢjO)䪞E`_evkVF%jJKo~ɿ"wz^xC]Vchi\_[ QCS0}ăRyK-m6elOܠ,ʌwUHtz(??Ǜo/QoW_Q~|/kj_u-eq}bj0gq&v|w,n^}>//C]>)h?mOxv;˭o>2լfYQK@$FѸURHzOt?nwV ڲJɊ ^_!cDG8Fן˙Qy_Wڵ>0w3Ix/ GgWhS![i#v9#>eB4O x~0X,<-%YIBI֊* U{_<.N^/_;5_C| om+gEV;hf݄$"3,01^?/~j?|k&c/kk Ϝ"AuQ~W ˚-_jmy&~\߳şw/t1)u.GI6rsf5ߠ|)?ࠟ>*¿a.thVQ},23(s󺲂#%Q褥(v$@jrkόi-. 𝬶>ӵ x!K(1Ʋ;6z\noeTQEQEQEQE((((((((((((((;SKǷQeqg2`X^FAoZ@>FAoyV3so"ʙ#r*JA/?K7zVgX'h[ %FAoq/dIq\0࿔Nvzf445mMl͑S{]d ,#_@J<)gtKmDh:W \ wc4 ݱIl`w5$z5j^J 3bu%~h/#_@J<*͵ ui*O *#U5 gHdTKD4Va粌-#_@Jke۶<7`cKcinb%w"uRFEU5 ->Go=ܗpRY 6^8R35b REdt!A@FAoyVk G[ %YͥFWyzO{kdKF+:XoeB0^'GP5 -մLw"J"8'E_-Ս7YdxWT+1G||(5 A{ @gYw4iiV 9DX'޼u(5 snQ]Mfgv9G,SDDpYNTG5 - R6ldY#&dã}ST#Ski_j<Ȣ|rrG!5 -..m#Je@F`=وw$P&K}&OyxT%3z@(5 մg2N 5K~Pszu<7q\Ij&XWh%O FAoyS5/{F5K[<;lM A۹s$T^EPXUr3@2?#_@J<*ݭ̐,v UXԞb2|(5 `sȦI$qFff8I$k G[ %Oj^i7dUu*Jyk G[ %kQ@>FAoyVk G[ %kQ@>FAoyVk G[ %kQ@>FAoyVk G[ %kQ@>FAoyV2Yc6gƀ3$z@FAoyU+J/4"pᔐjdQk ZPO[ %FAoEdQk ZPO[ %FAoEdQk M}ONvͶY`@þvF^SDXym靄>#_@J<+[8EdQk ZPO[ %FAoEdQk ZJRӴΧu%5z 'Ҁ+yQk UOcD ֵ{; %]̽7 oq sk" 2:zG5 -ڊ5 - #_@J<+Z5 -zk߾q5j)Tb([ %FAofQmJ+HݶJ,y,GFAoyVk G[ %kQ@>FAoyVk G[ %kQ@>FAoyVk G[ %kQ@>FAoyVk G[ %kQ@l"QyJ^k*Ҡ((((((((((((((7žPu̖wH~xt0py]ZIcNڣþ/G߆|g]o%P% 0BHm Ğ@Eii,HGba?okMVDZZǩ:dy ^Eo5²)mgTL{^x#X\S4Z!BT&B\9c5y~_CGtg 7 +|Py>KYJZ-~!i?E復x@|%Ʋuדmuqqki`"7$f1;rсGS?5 kOE%([aj0jK)o/H$S~~|+D6%na[-ޟo89Xbʸҭk >=6x[J,V j>QDXWA+9ǚmzͯ\~WrƕO/|gǨ7Kwg}q3Myyog WjH^/]^ ? Z7Oo/Z%&&:xyync{|%"GGęA$叅~|1h:tKL%ͅZBXP* wŸj_>&f_?fEޟo kʼnE iZhmYAp-G1*=)jR[|ӷ}X_qW|>/?|u.^6it&Ѵ8x2Kxci+U5xWƁ<c_ˠxC]nkzo$ES |War_л/ xcM}:];H}٬Z+xڶ@UG(Sxc.K6|1]xoKe G읐Ȼrqj%W-/5}k䖝W#idZXzA)팰%qFJ/$|0߀uźG/) gHav_Gć:4{|RB[ w/Sy̅!E*wevtn?ewyvPt<Ð0r܎6QݽOw_:$ڏ iS&|u]k^ n+s#1;-EY kk?f+N<`g \6>YhegMC|ҭS,me~s/~} xgLuMOq 76d-֗?|૫xkMЮ5"K 8mr *bFO^[&t~y|Puڗx K1q݄elGvl 0g|W׼1n~5]+i`mHksKw̻KaA5o^U ^seq% @I9TPWG#|O=hV7$!#DHYE5_]_}. jZ X^4}g㼐Z§zƸO H𦻯ьz)U *?zw]5s˾{[|j"%$(((((((+?څloact }[m ue6_<$H2]'Kt۝cheXaVR8 +jN=Gt{ŭYxr"cvjZhEcۻ>y'NW? [zZ~=9[XmwĪ=2kDQEHŠ((C,g7:[~+-COtk-V YCmrWG8*=#g|eO^um gXD ~u)zsYzlϺӗ徝|G⟎oUro [IƶHڅ#\F~faHlnܧsQ?7:s&Yh jؐV41qswS$?#__xK?7ީx' S Ymv Zk>*o iGZ#HoڭvE.誤 @4ҕ^WvѤ?"n-um^Wٷi>SKk%ĭO׈acpiz%Y?IE&8ս@elqzOzn]x_Kӵḧ dFMv7b_3I[Mu^>ERWt_~&PdžmG{=J\Ftf#*8o^(iް-zJNT-U.m6@&-cBG5ۻ&/kGgtmmEӭLHcm;*{V/~#LJf]¾{+hb{9kH1`o2 ]Yﯓmݯ}ui+vvti[k^"3 |j+yq=vq=l#g!yrd]+'şh<$KEWÖ˭̳[%Ķ ,{Cp+^5xAWzIkp̉de.#|q\ݗ/n}M?z-uclkK@F  RN6Ukq.ԿG6|gN?j7:zeٽkW i$HȤ# {aw>=*E ,c;xc*I$ry5U9_$QE %<[Kka&f:.ὂWOmUT<(BU7<7KV]M,[P5O0@@ANӌY;z;YW'{M~n|k/>iptspGRNY{#?-BD-<ȢX՘aK{_s WRӵ_ Z=bݯBA<Ĕy_qӥ}˩)]h~|it-}I!,L,TI9]]ދ_Z_X[Ae$SA#RCDp'eJRߙER$((((((+柏vZ.×!\uMstqʯ|Da{Շ? xkLtMoLIk{w09CKG e%Hpy?&/,3vI|KdPL>tw$Wf`  ̾3|3 [TOړb cspfLω pNqciG64H,U ڣ0Ldpjn xgL.u|[;8`{OѢ9b~lO薾Qqɯwo5\ڧݽZ<:+BFmڄzr۳uNxS$92(g'/x Ӽ;ۉ&N$p0%P5UWIv/oDcI~6QEAaEPEPEPEPEPEPEPߴޅM[u߆4w~!A.5-: g5&K&xV1zox;_vgºmhʩ ٕq"K60 zbHu9쮵+(.eEhȋQ;.t&xgzis{=XIao;6ȕW'dֱi&7i[~CETQEQEQEQEQEQEQEQEQEMoZUoZT(((((((((((((((C2eFsPMd??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??>KQ_":(l/GcS߉s)ĿEmyO~%+9϶<}??ŴI.r9墑GOVP+j(((((((((((((((S[Ԕ"#>"|Xw>Gޡ}$-ŗI,% jdTKY5kǞ1S6=;K[-o;vM)1@L7Ὲ_mZշ/ӻwQtVO$ɒoO<~$]|q|uYxún5+Rq4cEvݱ5OڏGkVV:j6B;R+(LѴrmZY4}晧_wV6ְ:#eI.^TY rŒc#X|Q[|D:-4.[ZYs$I3U@*y?Foj.> ڭkq.=+溍̼%6o mSNK_m^~ro66GŸHr |Qwm_n3NO8VKW|mPc*Tr'?RjLjZ]a3]ȍ櫀J:OޝcuŹ.xFxKI.)/gyǘ7mW8;u-{Fixߧ~kG-⏎z>2x(xsVMՎk DsuIJB-f[Te"+8 `ew򡉶a#tv-GD>óhph?0iunD3%G;dmc$Q? Yg ]>]Mo܍>EZ{X.!ߌ\a5Q{_//u靷eF?篭_|uό:ׅ[ŶҬwڔ2_"]J)b %'qĿ>7SzU.i)MMmJK2Osò8FWM?~,U<{<;.կb8 MBYDcy)9wW >E+IC4{C( ##14xׯvvW7lOm^:,ꉥ^"I[3_$nt%2 $"So_`þOڥƿs>C"l\&vcvCkGGYoo֧g/KiU^v2|m[EM%MՇHu{X?[̊o5|\cG|?~0x.S6oK͍Ʊ:?ُ\gɷ;! `GCUׂ '­#[eԗO:ǒwwe$M#JT8ޣGGk֒X6+Qa%̈́MknmdhV(gI櫺^f[~ms1voٷPg7Tƿ FvI&QǗ*:ZSx{^Ԯ|9xUt%GlHK$SA j@ ?*Ϻ7xv\:xk{S <䤊Cdl;g/s+igI})u59J/!t+P*FwKnIvǗsn-z|m_v?j˽KW]|C ơ_bV.~V$k>vy;aK VRk:j/t:kTS&qDrmb2߲ύ|}?-oVQ0iCȸHgtb4npX+|Qo_^=~Sd.5O͚A $}˴ɴ `}+[m|W[|スv9S_ |`<'rVvքiG\!_;z:PYC$u?zVyɧAuҟIY[$]3̟hˡª:$UWo^F}+_}5|A|]ѿ~g!.[ x甥я+#<$y礪&:^IY;m7R|3Eooyq8yc;X"K.b̺gIk+<=ṴtmI55$A A.%ȭoEd(Ccb -=WGۻgÖӐ݊6-'!po:3܄[gkHGîz:+g6_hB4ȐMlDz[S t\ M=e[kTiA?i ZHM$ku3yh k_8o7+iowi1-䔂ĺ<Qdu$*v*\w#η~WmtfaYRHט6>9|S?]տ%g|ejh0 gmeBi[&B>c`.r뵗 Ͽ˥7}o~|+Q@Q@Q@Q@Q@Q@Q@yx BsX՞)+Y  kk>*|?o>mRmQ,/%ΗHbbE:n]H3M]uK{ =m?OxƟ>!^Zj-͎ Z!K1&> >#x]&g5E$bU*2܇ @eӷ2Oi(aiWzhVZ֟A4ڟe4cMBA o K֩bƚ;KsΛ4ra$ SOjmV}~nm;;%kQ5nسC]5s()$ s%vs|5^a/T%k_FLԵ!x vwk WNAZrǽ4g (A^#/~#? >v:kw|V;I K;.ŸU`k߫>!4?N?ψK  \ݛ4m^kZ*zhHu gԵ赻-Nzm# IZ& ?< }j mmͦGmOH:^;]Iy-1ZIvaןCu14Ҥ5"յ/Cj՝1Rk9*KeK֩[oY;?$OYN׺l[>k]2z֭jO3a5KdVtp;uTKt YqSx/L:kXAy_]Qm[#G"B$p g;Y4]巇ldd[ Ly~h)fm7N}b]Szv}6N=3 I,d"=Ptt~?mn_fJ=o~Ox0CُJtI#[yK,-nl9V5WL~xZͨ|o C˴66~{+C,D v1푻*n_}B(WG7z{˭1\liihSi_jZ%2FbGTB_ҿgf\ӬnX>ѵO rsI%E%VKxn%ycA;rNom/un޶ u;~6k_5^٦8$(-LW+@9^(ߑ>8Gѵ 75oZ] ٣WD>T@"̐skѼs1<5|Iiy۶u(IddHe# rY p1u/?l JmkM1t4uuFQ`͛;}7T={)kg|-ml#5F~]PҴ=Kla% '~6sRRPJ{ (4 ( ( ( ( ( O-~x3^jrZ۸Mvֶ]xDAX{]yWzúφu [;ho4ki,pƪ(=on[ҵն/ܯ~lx░>?5-?\k=jͬo0tV&RCA#5Ux߅+]q)Zd:Et0Zlb3y6WPpV]c%ցETQEQEQEQEQEQEQExGSUeuzﭮmd8V#5 s4^#TBvV}o>0uwm4˝* [f $ɿ n+@վ)|#rdȒO$QΑ;F&DY"/x6/ ɨw%م-kv!'j$d]{N뮝wOhQ@Q@Q@Q@Q@Q@Q@Q@Q@((((((((((((((_%+39Gd=u OأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/GأsQY]bzϱG=gt}??>(_.::+/Gأ[KdI+Ytf"((((((((((((((~X͟s퇃RyemO%mw0hn9Q]ѫefN~/]J%yį>@ȷT#,<>!7>|9sx#Q$2nա켫 YWñ{5d1D uo7g5? ^˯36mΑ-q=ٚl]#Sn͔ۼdS"HQal~/Rp5'5bF\[olHAiX3&fxǷzUpZCLX$y!b$GvBpvƻPE%ݧ8~F}Pi:&oi]6᭴DFmnbG)͌P3]%O _xuyyitl繃IOuA#+ެFvN:ʟK߁<xsR52K,Kw[KGSKq$) FWO-x-["M+ƚGg/'%̻]9݁c_U*WI)8~I]ߪK]SFU)~vVvAx#Wnn|:Ӽb [֞}*{nNQ vbY_z n–Ub ?W/iwhs^[_cqy6ܥ!#rJF*N |;Yivm7.I>iwy 0Q(gmj<@𖳩޽(JYa^p "w3 ɕߕr ~z]ne/|OB2Xi7B]R}F[ycD4ެNA<CmkjkVz5i\۝4r ȡd_IoFoɭ>dƇE6rœQj|pu*?>v~i:U֣Oc \ح.Ȫ'yr!gܡ>Uw+yK*^kc*|dZ+ˍK{k[s$)$.%NI rF+m'mj蚥Ç4Iu}NŴPܭѶ.._1ZcDIgh-}[ޣoXsM8gA:Єqۄj1Ƭfݿ v~О OiC_kyjaO$"&Qb6)l:#ĚD%Ռٗ~Nqs^.Ź}'NJ4>D7XB7P{4PnRqna^!Ě| t quyc-fNMTu\%ykp)WZY7mֲקOjOl75˫khluȢxP]*%POx⿅#\ZvSO;2-mjvML;HШ,Jx<-0Q2xsL'ѤPJi4O9vyUQwiy+_O[(~-~?;m5=Z l-mla..gr%ɫ Em\<}o&Tq޿9?P뭤=|;o]:[c˭٭_[Y]\Am$"C*U!NAd>2&:ܳ޸5Dݖd!do?.g%Deu"^U >4> 2s-bL6vL7yƣ%lp>Kºm|Qiqݏ 9T}Y/iLMl!0R$W>;iZJQԵ'L;k-V? J YﮍjA)BAUNҗ+VwTg%d?\~'<]ZsuhϱS ~Ice0qH C׏zonؾ;[fvG$\*4/Ӽ%>m7'۲×ڽ---@H卥HfL;>?ު˔,_џ_3h"9bP\EfᕐpT>MƗk$!!]2Y/MĈ-fM*i"p=7P|SHOѾKorw &ai d)=|ݻ?K̚Q}ս5k}ERQEQEQEQEQEQEQEW+_xwY⛆Ӭb<:qgwFTUPI$ ¿hm3X-{\ѵy#gi夂5.,W 7K[]~ɻ7Zu[|3 Iumu[+i,C2auea`x'z|!JwF6gb7'Oķ&uIQYtYBPQE# ( ( u':%땲t%(aR8 կ"kiCOQc l%b>6sغqNI3hjLO*h!I* d0+Ə xhZܖ^oiYDw[i27dLj/A_x[!l<3cJgUt1$g,w*:nr6dӾ_O֋Z/7iZt߰}kfwZY<+ŠzJӚ}okwSoD5}[Z~tpE\9$H߯ WZZω>}T/- +xZZ7VE=Afu8?xޠֺOocha1š-m-$w6DI|iF)˖?'kyݧ{y>Vj]J+5 kfΩ+]>iWnj7N~@.CP+bؾ̹EJ/0)+|_¿.4 f;GVY䶲l+UVQ $asr zU|"|5"FZ+iVj3[]K=EY% *.CBꗯZK4o+.,ooJZm'O}W(Ł ~xZYdI+<2[43E,R?u+H/c2KOù`1# 3_. x V&DמkiZKiEsqn# HЌr FL_ Ml4iiILOs{ެ f#e.csJڥM k˯Ÿ߯\o-um?6im^y1Za$DYRݛj܀+_HUN6WP*?j3}JnNQVyc)P#*AmMKI'Ïæ^g5" U[xcE#VpX  u o\x/<3 kWhx Y8/ЄM~ݡ~_4>}u C[-Fu+ wq΋'(raxT|^D @t-L6a>4WIpbXŲAp nc~RIm/$fQ~)76˸QEQ@Q@Q@Q@Q@Q@Q@q+τz:?5}*9-wU+o .Brp9ůEVogkV֖6[]%d0>6s\O3C⿂+5uY$vh1S1?/<Jw ox{CӴR^dθZI2uD39O]OѴirhxox^ 譮5 n.-4ܤyq4p*H|w?=C/<\wWW+8aVv;Q񀠒@gXX!0_fM\}H.4頖K^k&Y'1p 4Kݿ~W;wg {QP0((((((((k*Ҭ*Ҡ((((((((((((((vn -v*1f:7 h[xAn%uoo7G?տN< Z*/'Vty:7 h[xAn%uoo7G?տN< Z*/'Vty:7 h[xAn%uoo7G?տN< Z*/'Vty:7 h[xAn%IլXQ+{?yw| kBá*տN< Z*/'Vty:7 h[xAn%MG/KH5A/%)X : 9':N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-P4kL8; $RR$pA^N< uoo7I՘ӶO h~݌Znư#5G'lT^N< uoo7Tn씒VDT^N< uoo7HdT^N< uoo7@Qy:7տK\Ƴ xYAijIl%n6JY6c#5y:7տ`KEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@Qy:7տKEEn'Vt-?<[xAT^N< uoo7@/JmVu39+#1P:֠horizon-13.0.3/doc/source/user/configure-access-and-security-for-instances.rst0000664000175000017500000001624313553660754027507 0ustar zuulzuul00000000000000=========================================== Configure access and security for instances =========================================== Before you launch an instance, you should add security group rules to enable users to ping and use SSH to connect to the instance. Security groups are sets of IP filter rules that define networking access and are applied to all instances within a project. To do so, you either add rules to the default security group :ref:`security_groups_add_rule` or add a new security group with rules. Key pairs are SSH credentials that are injected into an instance when it is launched. To use key pair injection, the image that the instance is based on must contain the ``cloud-init`` package. Each project should have at least one key pair. For more information, see the section :ref:`keypair_add`. If you have generated a key pair with an external tool, you can import it into OpenStack. The key pair can be used for multiple instances that belong to a project. For more information, see the section :ref:`dashboard_import_keypair`. .. note:: A key pair belongs to an individual user, not to a project. To share a key pair across multiple users, each user needs to import that key pair. When an instance is created in OpenStack, it is automatically assigned a fixed IP address in the network to which the instance is assigned. This IP address is permanently associated with the instance until the instance is terminated. However, in addition to the fixed IP address, a floating IP address can also be attached to an instance. Unlike fixed IP addresses, floating IP addresses are able to have their associations modified at any time, regardless of the state of the instances involved. .. _security_groups_add_rule: Add a rule to the default security group ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ This procedure enables SSH and ICMP (ping) access to instances. The rules apply to all instances within a given project, and should be set for every project unless there is a reason to prohibit SSH or ICMP access to the instances. This procedure can be adjusted as necessary to add additional security group rules to a project, if your cloud requires them. .. note:: When adding a rule, you must specify the protocol used with the destination port or source port. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Network` tab. The :guilabel:`Security Groups` tab shows the security groups that are available for this project. #. Select the default security group and click :guilabel:`Manage Rules`. #. To allow SSH access, click :guilabel:`Add Rule`. #. In the :guilabel:`Add Rule` dialog box, enter the following values: * **Rule**: ``SSH`` * **Remote**: ``CIDR`` * **CIDR**: ``0.0.0.0/0`` .. note:: To accept requests from a particular range of IP addresses, specify the IP address block in the :guilabel:`CIDR` box. #. Click :guilabel:`Add`. Instances will now have SSH port 22 open for requests from any IP address. #. To add an ICMP rule, click :guilabel:`Add Rule`. #. In the :guilabel:`Add Rule` dialog box, enter the following values: * **Rule**: ``All ICMP`` * **Direction**: ``Ingress`` * **Remote**: ``CIDR`` * **CIDR**: ``0.0.0.0/0`` #. Click :guilabel:`Add`. Instances will now accept all incoming ICMP packets. .. _keypair_add: Add a key pair ~~~~~~~~~~~~~~ Create at least one key pair for each project. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab. #. Click the :guilabel:`Key Pairs` tab, which shows the key pairs that are available for this project. #. Click :guilabel:`Create Key Pair`. #. In the :guilabel:`Create Key Pair` dialog box, enter a name for your key pair, and click :guilabel:`Create Key Pair`. #. Respond to the prompt to download the key pair. .. _dashboard_import_keypair: Import a key pair ~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab. #. Click the :guilabel:`Key Pairs` tab, which shows the key pairs that are available for this project. #. Click :guilabel:`Import Key Pair`. #. In the :guilabel:`Import Key Pair` dialog box, enter the name of your key pair, copy the public key into the :guilabel:`Public Key` box, and then click :guilabel:`Import Key Pair`. #. Save the ``*.pem`` file locally. #. To change its permissions so that only you can read and write to the file, run the following command: .. code-block:: console $ chmod 0600 yourPrivateKey.pem .. note:: If you are using the Dashboard from a Windows computer, use PuTTYgen to load the ``*.pem`` file and convert and save it as ``*.ppk``. For more information see the `WinSCP web page for PuTTYgen `__. #. To make the key pair known to SSH, run the :command:`ssh-add` command. .. code-block:: console $ ssh-add yourPrivateKey.pem The Compute database registers the public key of the key pair. The Dashboard lists the key pair on the :guilabel:`Key Pairs` tab. Allocate a floating IP address to an instance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ When an instance is created in OpenStack, it is automatically assigned a fixed IP address in the network to which the instance is assigned. This IP address is permanently associated with the instance until the instance is terminated. However, in addition to the fixed IP address, a floating IP address can also be attached to an instance. Unlike fixed IP addresses, floating IP addresses can have their associations modified at any time, regardless of the state of the instances involved. This procedure details the reservation of a floating IP address from an existing pool of addresses and the association of that address with a specific instance. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Network` tab. #. Click the :guilabel:`Floating IPs` tab, which shows the floating IP addresses allocated to instances. #. Click :guilabel:`Allocate IP To Project`. #. Choose the pool from which to pick the IP address. #. Click :guilabel:`Allocate IP`. #. In the :guilabel:`Floating IPs` list, click :guilabel:`Associate`. #. In the :guilabel:`Manage Floating IP Associations` dialog box, choose the following options: - The :guilabel:`IP Address` field is filled automatically, but you can add a new IP address by clicking the :guilabel:`+` button. - In the :guilabel:`Port to be associated` field, select a port from the list. The list shows all the instances with their fixed IP addresses. #. Click :guilabel:`Associate`. .. note:: To disassociate an IP address from an instance, click the :guilabel:`Disassociate` button. To release the floating IP address back into the floating IP pool, click the :guilabel:`Release Floating IP` option in the :guilabel:`Actions` column. horizon-13.0.3/doc/source/user/manage-images.rst0000664000175000017500000001475213553660754021607 0ustar zuulzuul00000000000000======================== Upload and manage images ======================== A virtual machine image, referred to in this document simply as an image, is a single file that contains a virtual disk that has a bootable operating system installed on it. Images are used to create virtual machine instances within the cloud. For information about creating image files, see the `OpenStack Virtual Machine Image Guide `_. Depending on your role, you may have permission to upload and manage virtual machine images. Operators might restrict the upload and management of images to cloud administrators or operators only. If you have the appropriate privileges, you can use the dashboard to upload and manage images in the admin project. .. note:: You can also use the :command:`openstack` and :command:`glance` command-line clients or the Image service to manage images. Upload an image ~~~~~~~~~~~~~~~ Follow this procedure to upload an image to a project: #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab and click :guilabel:`Images` category. #. Click :guilabel:`Create Image`. The :guilabel:`Create An Image` dialog box appears. .. figure:: figures/create_image.png **Dashboard — Create Image** #. Enter the following values: +-------------------------------+---------------------------------+ | :guilabel:`Image Name` | Enter a name for the image. | +-------------------------------+---------------------------------+ | :guilabel:`Image Description` | Enter a brief description of | | | the image. | +-------------------------------+---------------------------------+ | :guilabel:`Image Source` | Choose the image source from | | | the dropdown list. Your choices | | | are :guilabel:`Image Location` | | | and :guilabel:`Image File`. | +-------------------------------+---------------------------------+ | :guilabel:`Image File` or | Based on your selection for | | :guilabel:`Image Location` | :guilabel:`Image Source`, you | | | either enter the location URL | | | of the image in the | | | :guilabel:`Image Location` | | | field, or browse for the image | | | file on your file system and | | | add it. | +-------------------------------+---------------------------------+ | :guilabel:`Format` | Select the image format (for | | | example, QCOW2) for the image. | +-------------------------------+---------------------------------+ | :guilabel:`Architecture` | Specify the architecture. For | | | example, ``i386`` for a 32-bit | | | architecture or ``x86_64`` for | | | a 64-bit architecture. | +-------------------------------+---------------------------------+ | :guilabel:`Minimum Disk (GB)` | Leave this field empty. | +-------------------------------+---------------------------------+ | :guilabel:`Minimum RAM (MB)` | Leave this field empty. | +-------------------------------+---------------------------------+ | :guilabel:`Copy Data` | Specify this option to copy | | | image data to the Image service.| +-------------------------------+---------------------------------+ | :guilabel:`Visibility` | The access permission for the | | | image. | | | ``Public`` or ``Private``. | +-------------------------------+---------------------------------+ | :guilabel:`Protected` | Select this check box to ensure | | | that only users with | | | permissions can delete the | | | image. ``Yes`` or ``No``. | +-------------------------------+---------------------------------+ | :guilabel:`Image Metadata` | Specify this option to add | | | resource metadata. The glance | | | Metadata Catalog provides a list| | | of metadata image definitions. | | | (Note: Not all cloud providers | | | enable this feature.) | +-------------------------------+---------------------------------+ #. Click :guilabel:`Create Image`. The image is queued to be uploaded. It might take some time before the status changes from Queued to Active. Update an image ~~~~~~~~~~~~~~~ Follow this procedure to update an existing image. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. Select the image that you want to edit. #. In the :guilabel:`Actions` column, click the menu button and then select :guilabel:`Edit Image` from the list. #. In the :guilabel:`Edit Image` dialog box, you can perform various actions. For example: * Change the name of the image. * Change the description of the image. * Change the format of the image. * Change the minimum disk of the image. * Change the minimum RAM of the image. * Select the :guilabel:`Public` button to make the image public. * Clear the :guilabel:`Private` button to make the image private. * Change the metadata of the image. #. Click :guilabel:`Edit Image`. Delete an image ~~~~~~~~~~~~~~~ Deletion of images is permanent and **cannot** be reversed. Only users with the appropriate permissions can delete images. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab and click :guilabel:`Images` category. #. Select the images that you want to delete. #. Click :guilabel:`Delete Images`. #. In the :guilabel:`Confirm Delete Images` dialog box, click :guilabel:`Delete Images` to confirm the deletion. horizon-13.0.3/doc/source/user/index.rst0000664000175000017500000000111513553660754020210 0ustar zuulzuul00000000000000======================================== OpenStack Dashboard User Documentation ======================================== As a cloud end user, you can use the OpenStack dashboard to provision your own resources within the limits set by administrators. You can modify the examples provided in this section to create other types and sizes of server instances. .. toctree:: :maxdepth: 2 log-in.rst manage-images.rst configure-access-and-security-for-instances.rst launch-instances.rst create-networks.rst manage-containers.rst manage-volumes.rst browser_support horizon-13.0.3/doc/source/user/create-networks.rst0000664000175000017500000001102513553660754022217 0ustar zuulzuul00000000000000========================== Create and manage networks ========================== The OpenStack Networking service provides a scalable system for managing the network connectivity within an OpenStack cloud deployment. It can easily and quickly react to changing network needs (for example, creating and assigning new IP addresses). Networking in OpenStack is complex. This section provides the basic instructions for creating a network and a router. For detailed information about managing networks, refer to the `OpenStack Networking Guide `__. Create a network ~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Network` tab and click :guilabel:`Networks` category. #. Click :guilabel:`Create Network`. #. In the :guilabel:`Create Network` dialog box, specify the following values. :guilabel:`Network` tab :guilabel:`Network Name`: Specify a name to identify the network. :guilabel:`Shared`: Share the network with other projects. Non admin users are not allowed to set shared option. :guilabel:`Admin State`: The state to start the network in. :guilabel:`Create Subnet`: Select this check box to create a subnet You do not have to specify a subnet when you create a network, but if you do not specify a subnet, the network can not be attached to an instance. :guilabel:`Subnet` tab :guilabel:`Subnet Name`: Specify a name for the subnet. :guilabel:`Network Address`: Specify the IP address for the subnet. :guilabel:`IP Version`: Select IPv4 or IPv6. :guilabel:`Gateway IP`: Specify an IP address for a specific gateway. This parameter is optional. :guilabel:`Disable Gateway`: Select this check box to disable a gateway IP address. :guilabel:`Subnet Details` tab :guilabel:`Enable DHCP`: Select this check box to enable DHCP. :guilabel:`Allocation Pools`: Specify IP address pools. :guilabel:`DNS Name Servers`: Specify a name for the DNS server. :guilabel:`Host Routes`: Specify the IP address of host routes. #. Click :guilabel:`Create`. The dashboard shows the network on the :guilabel:`Networks` tab. Create a router ~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Network` tab and click :guilabel:`Routers` category. #. Click :guilabel:`Create Router`. #. In the :guilabel:`Create Router` dialog box, specify a name for the router and :guilabel:`External Network`, and click :guilabel:`Create Router`. The new router is now displayed in the :guilabel:`Routers` tab. #. To connect a private network to the newly created router, perform the following steps: A) On the :guilabel:`Routers` tab, click the name of the router. B) On the :guilabel:`Router Details` page, click the :guilabel:`Interfaces` tab, then click :guilabel:`Add Interface`. C) In the :guilabel:`Add Interface` dialog box, select a :guilabel:`Subnet`. Optionally, in the :guilabel:`Add Interface` dialog box, set an :guilabel:`IP Address` for the router interface for the selected subnet. If you choose not to set the :guilabel:`IP Address` value, then by default OpenStack Networking uses the first host IP address in the subnet. The :guilabel:`Router Name` and :guilabel:`Router ID` fields are automatically updated. #. Click :guilabel:`Add Interface`. You have successfully created the router. You can view the new topology from the :guilabel:`Network Topology` tab. Create a port ~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop-down menu at the top left. #. On the :guilabel:`Project` tab, click :guilabel:`Networks` category. #. Click on the :guilabel:`Network Name` of the network in which the port has to be created. #. Go to the :guilabel:`Ports` tab and click :guilabel:`Create Port`. #. In the :guilabel:`Create Port` dialog box, specify the following values. :guilabel:`Name`: Specify name to identify the port. :guilabel:`Device ID`: Device ID attached to the port. :guilabel:`Device Owner`: Device owner attached to the port. :guilabel:`Binding Host`: The ID of the host where the port is allocated. :guilabel:`Binding VNIC Type`: Select the VNIC type that is bound to the neutron port. #. Click :guilabel:`Create Port`. The new port is now displayed in the :guilabel:`Ports` list. horizon-13.0.3/doc/source/user/manage-volumes.rst0000664000175000017500000001270213553660754022025 0ustar zuulzuul00000000000000========================= Create and manage volumes ========================= Volumes are block storage devices that you attach to instances to enable persistent storage. You can attach a volume to a running instance or detach a volume and attach it to another instance at any time. You can also create a snapshot from or delete a volume. Only administrative users can create volume types. Create a volume ~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Volumes` tab and click :guilabel:`Volumes` category. #. Click :guilabel:`Create Volume`. In the dialog box that opens, enter or select the following values. :guilabel:`Volume Name`: Specify a name for the volume. :guilabel:`Description`: Optionally, provide a brief description for the volume. :guilabel:`Volume Source`: Select one of the following options: * No source, empty volume: Creates an empty volume. An empty volume does not contain a file system or a partition table. * Snapshot: If you choose this option, a new field for :guilabel:`Use snapshot as a source` displays. You can select the snapshot from the list. * Image: If you choose this option, a new field for :guilabel:`Use image as a source` displays. You can select the image from the list. * Volume: If you choose this option, a new field for :guilabel:`Use volume as a source` displays. You can select the volume from the list. Options to use a snapshot or a volume as the source for a volume are displayed only if there are existing snapshots or volumes. :guilabel:`Type`: Leave this field blank. :guilabel:`Size (GB)`: The size of the volume in gibibytes (GiB). :guilabel:`Availability Zone`: Select the Availability Zone from the list. By default, this value is set to the availability zone given by the cloud provider (for example, ``us-west`` or ``apac-south``). For some cases, it could be ``nova``. #. Click :guilabel:`Create Volume`. The dashboard shows the volume on the :guilabel:`Volumes` tab. .. _attach_a_volume_to_an_instance_dash: Attach a volume to an instance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ After you create one or more volumes, you can attach them to instances. You can attach a volume to one instance at a time. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Volumes` tab and click :guilabel:`Volumes` category. #. Select the volume to add to an instance and click :guilabel:`Manage Attachments`. #. In the :guilabel:`Manage Volume Attachments` dialog box, select an instance. #. Enter the name of the device from which the volume is accessible by the instance. .. note:: The actual device name might differ from the volume name because of hypervisor settings. #. Click :guilabel:`Attach Volume`. The dashboard shows the instance to which the volume is now attached and the device name. You can view the status of a volume in the Volumes tab of the dashboard. The volume is either Available or In-Use. Now you can log in to the instance and mount, format, and use the disk. Detach a volume from an instance ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Volumes` tab and click the :guilabel:`Volumes` category. #. Select the volume and click :guilabel:`Manage Attachments`. #. Click :guilabel:`Detach Volume` and confirm your changes. A message indicates whether the action was successful. Create a snapshot from a volume ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Volumes` tab and click :guilabel:`Volumes` category. #. Select a volume from which to create a snapshot. #. In the :guilabel:`Actions` column, click :guilabel:`Create Snapshot`. #. In the dialog box that opens, enter a snapshot name and a brief description. #. Confirm your changes. The dashboard shows the new volume snapshot in Volume Snapshots tab. Edit a volume ~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Volumes` tab and click :guilabel:`Volumes` category. #. Select the volume that you want to edit. #. In the :guilabel:`Actions` column, click :guilabel:`Edit Volume`. #. In the :guilabel:`Edit Volume` dialog box, update the name and description of the volume. #. Click :guilabel:`Edit Volume`. .. note:: You can extend a volume by using the :guilabel:`Extend Volume` option available in the :guilabel:`More` dropdown list and entering the new value for volume size. Delete a volume ~~~~~~~~~~~~~~~ When you delete an instance, the data in its attached volumes is not deleted. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Volumes` tab and click :guilabel:`Volumes` category. #. Select the check boxes for the volumes that you want to delete. #. Click :guilabel:`Delete Volumes` and confirm your choice. A message indicates whether the action was successful. horizon-13.0.3/doc/source/user/launch-instances.rst0000664000175000017500000002377613553660754022361 0ustar zuulzuul00000000000000=========================== Launch and manage instances =========================== Instances are virtual machines that run inside the cloud. You can launch an instance from the following sources: * Images uploaded to the Image service. * Image that you have copied to a persistent volume. The instance launches from the volume, which is provided by the ``cinder-volume`` API through iSCSI. * Instance snapshot that you took. Launch an instance ~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab and click :guilabel:`Instances` category. The dashboard shows the instances with its name, its private and floating IP addresses, size, status, task, power state, and so on. #. Click :guilabel:`Launch Instance`. #. In the :guilabel:`Launch Instance` dialog box, specify the following values: :guilabel:`Details` tab Instance Name Assign a name to the virtual machine. .. note:: The name you assign here becomes the initial host name of the server. If the name is longer than 63 characters, the Compute service truncates it automatically to ensure dnsmasq works correctly. After the server is built, if you change the server name in the API or change the host name directly, the names are not updated in the dashboard. Server names are not guaranteed to be unique when created so you could have two instances with the same host name. Description You can assign a brief description of the virtual machine. Availability Zone By default, this value is set to the availability zone given by the cloud provider (for example, ``us-west`` or ``apac-south``). For some cases, it could be ``nova``. Count To launch multiple instances, enter a value greater than ``1``. The default is ``1``. :guilabel:`Source` tab Instance Boot Source Your options are: Boot from image If you choose this option, a new field for :guilabel:`Image Name` displays. You can select the image from the list. Boot from snapshot If you choose this option, a new field for :guilabel:`Instance Snapshot` displays. You can select the snapshot from the list. Boot from volume If you choose this option, a new field for :guilabel:`Volume` displays. You can select the volume from the list. Boot from image (creates a new volume) With this option, you can boot from an image and create a volume by entering the :guilabel:`Device Size` and :guilabel:`Device Name` for your volume. Click the :guilabel:`Delete Volume on Instance Delete` option to delete the volume on deleting the instance. Boot from volume snapshot (creates a new volume) Using this option, you can boot from a volume snapshot and create a new volume by choosing :guilabel:`Volume Snapshot` from a list and adding a :guilabel:`Device Name` for your volume. Click the :guilabel:`Delete Volume on Instance Delete` option to delete the volume on deleting the instance. Image Name This field changes based on your previous selection. If you have chosen to launch an instance using an image, the :guilabel:`Image Name` field displays. Select the image name from the dropdown list. Instance Snapshot This field changes based on your previous selection. If you have chosen to launch an instance using a snapshot, the :guilabel:`Instance Snapshot` field displays. Select the snapshot name from the dropdown list. Volume This field changes based on your previous selection. If you have chosen to launch an instance using a volume, the :guilabel:`Volume` field displays. Select the volume name from the dropdown list. If you want to delete the volume on instance delete, check the :guilabel:`Delete Volume on Instance Delete` option. :guilabel:`Flavor` tab Flavor Specify the size of the instance to launch. .. note:: The flavor is selected based on the size of the image selected for launching an instance. For example, while creating an image, if you have entered the value in the :guilabel:`Minimum RAM (MB)` field as 2048, then on selecting the image, the default flavor is ``m1.small``. :guilabel:`Networks` tab Selected Networks To add a network to the instance, click the :guilabel:`+` in the :guilabel:`Available` field. :guilabel:`Network Ports` tab Ports Activate the ports that you want to assign to the instance. :guilabel:`Security Groups` tab Security Groups Activate the security groups that you want to assign to the instance. Security groups are a kind of cloud firewall that define which incoming network traffic is forwarded to instances. If you have not created any security groups, you can assign only the default security group to the instance. :guilabel:`Key Pair` tab Key Pair Specify a key pair. If the image uses a static root password or a static key set (neither is recommended), you do not need to provide a key pair to launch the instance. :guilabel:`Configuration` tab Customization Script Source Specify a customization script that runs after your instance launches. :guilabel:`Metadata` tab Available Metadata Add Metadata items to your instance. #. Click :guilabel:`Launch Instance`. The instance starts on a compute node in the cloud. .. note:: If you did not provide a key pair, security groups, or rules, users can access the instance only from inside the cloud through VNC. Even pinging the instance is not possible without an ICMP rule configured. You can also launch an instance from the :guilabel:`Images` or :guilabel:`Volumes` category when you launch an instance from an image or a volume respectively. When you launch an instance from an image, OpenStack creates a local copy of the image on the compute node where the instance starts. For details on creating images, see `Creating images manually `_ in the *OpenStack Virtual Machine Image Guide*. When you launch an instance from a volume, note the following steps: * To select the volume from which to launch, launch an instance from an arbitrary image on the volume. The arbitrary image that you select does not boot. Instead, it is replaced by the image on the volume that you choose in the next steps. To boot a Xen image from a volume, the image you launch in must be the same type, fully virtualized or paravirtualized, as the one on the volume. * Select the volume or volume snapshot from which to boot. Enter a device name. Enter ``vda`` for KVM images or ``xvda`` for Xen images. .. note:: When running QEMU without support for the hardware virtualization, set ``cpu_mode="none"`` alongside ``virt_type=qemu`` in ``/etc/nova/nova-compute.conf`` to solve the following error: .. code-block:: console libvirtError: unsupported configuration: CPU mode 'host-model' for ``x86_64`` qemu domain on ``x86_64`` host is not supported by hypervisor Connect to your instance by using SSH ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ To use SSH to connect to your instance, use the downloaded keypair file. .. note:: The user name is ``ubuntu`` for the Ubuntu cloud images on TryStack. #. Copy the IP address for your instance. #. Use the :command:`ssh` command to make a secure connection to the instance. For example: .. code-block:: console $ ssh -i MyKey.pem ubuntu@10.0.0.2 #. At the prompt, type ``yes``. It is also possible to SSH into an instance without an SSH keypair, if the administrator has enabled root password injection. For more information about root password injection, see `Injecting the administrator password `_ in the *OpenStack Administrator Guide*. Track usage for instances ~~~~~~~~~~~~~~~~~~~~~~~~~ You can track usage for instances for each project. You can track costs per month by showing meters like number of vCPUs, disks, RAM, and uptime for all your instances. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab and click :guilabel:`Overview` category. #. To query the instance usage for a month, select a month and click :guilabel:`Submit`. #. To download a summary, click :guilabel:`Download CSV Summary`. Create an instance snapshot ~~~~~~~~~~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab and click the :guilabel:`Instances` category. #. Select the instance from which to create a snapshot. #. In the actions column, click :guilabel:`Create Snapshot`. #. In the :guilabel:`Create Snapshot` dialog box, enter a name for the snapshot, and click :guilabel:`Create Snapshot`. The :guilabel:`Images` category shows the instance snapshot. To launch an instance from the snapshot, select the snapshot and click :guilabel:`Launch`. Proceed with launching an instance. Manage an instance ~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Compute` tab and click :guilabel:`Instances` category. #. Select an instance. #. In the menu list in the actions column, select the state. You can resize or rebuild an instance. You can also choose to view the instance console log, edit instance or the security groups. Depending on the current state of the instance, you can pause, resume, suspend, soft or hard reboot, or terminate it. horizon-13.0.3/doc/source/user/manage-containers.rst0000664000175000017500000001272413553660754022504 0ustar zuulzuul00000000000000=================================== Create and manage object containers =================================== OpenStack Object Storage (swift) is used for redundant, scalable data storage using clusters of standardized servers to store petabytes of accessible data. It is a long-term storage system for large amounts of static data which can be retrieved and updated. OpenStack Object Storage provides a distributed, API-accessible storage platform that can be integrated directly into an application or used to store any type of file, including VM images, backups, archives, or media files. In the OpenStack dashboard, you can only manage containers and objects. In OpenStack Object Storage, containers provide storage for objects in a manner similar to a Windows folder or Linux file directory, though they cannot be nested. An object in OpenStack consists of the file to be stored in the container and any accompanying metadata. Create a container ~~~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Object Store` tab and click :guilabel:`Containers` category. #. Click :guilabel:`Container`. #. In the :guilabel:`Create Container` dialog box, enter a name for the container, and then click :guilabel:`Create`. You have successfully created a container. .. note:: To delete a container, click the :guilabel:`More` button and select :guilabel:`Delete Container`. Upload an object ~~~~~~~~~~~~~~~~ #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Object Store` tab and click :guilabel:`Containers` category. #. Select the container in which you want to store your object. #. Click the :guilabel:`Upload File` icon. The :guilabel:`Upload File To Container: ` dialog box appears. ```` is the name of the container to which you are uploading the object. #. Enter a name for the object. #. Browse to and select the file that you want to upload. #. Click :guilabel:`Upload File`. You have successfully uploaded an object to the container. .. note:: To delete an object, click the :guilabel:`More button` and select :guilabel:`Delete Object`. Manage an object ~~~~~~~~~~~~~~~~ **To edit an object** #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Object Store` tab and click :guilabel:`Containers` category. #. Select the container in which you want to store your object. #. Click the menu button and choose :guilabel:`Edit` from the dropdown list. The :guilabel:`Edit Object` dialog box is displayed. #. Browse to and select the file that you want to upload. #. Click :guilabel:`Update Object`. .. note:: To delete an object, click the menu button and select :guilabel:`Delete Object`. **To copy an object from one container to another** #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Object Store` tab and click :guilabel:`Containers` category. #. Select the container in which you want to store your object. #. Click the menu button and choose :guilabel:`Copy` from the dropdown list. #. In the :guilabel:`Copy Object` launch dialog box, enter the following values: * :guilabel:`Destination Container`: Choose the destination container from the list. * :guilabel:`Path`: Specify a path in which the new copy should be stored inside of the selected container. * :guilabel:`Destination object name`: Enter a name for the object in the new container. #. Click :guilabel:`Copy Object`. **To create a metadata-only object without a file** You can create a new object in container without a file available and can upload the file later when it is ready. This temporary object acts a place-holder for a new object, and enables the user to share object metadata and URL info in advance. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Object Store` tab and click :guilabel:`Containers` category. #. Select the container in which you want to store your object. #. Click :guilabel:`Upload Object`. The :guilabel:`Upload Object To Container`: ```` dialog box is displayed. ```` is the name of the container to which you are uploading the object. #. Enter a name for the object. #. Click :guilabel:`Update Object`. **To create a pseudo-folder** Pseudo-folders are similar to folders in your desktop operating system. They are virtual collections defined by a common prefix on the object's name. #. Log in to the dashboard. #. Select the appropriate project from the drop down menu at the top left. #. On the :guilabel:`Project` tab, open the :guilabel:`Object Store` tab and click :guilabel:`Containers` category. #. Select the container in which you want to store your object. #. Click :guilabel:`Create Pseudo-folder`. The :guilabel:`Create Pseudo-Folder in Container` ```` dialog box is displayed. ```` is the name of the container to which you are uploading the object. #. Enter a name for the pseudo-folder. A slash (/) character is used as the delimiter for pseudo-folders in Object Storage. #. Click :guilabel:`Create`. horizon-13.0.3/MANIFEST.in0000664000175000017500000000111513553660754015062 0ustar zuulzuul00000000000000recursive-include doc *.py *.rst *.scss *.js *.html *.conf *.jpg *.gif *.png recursive-include horizon *.html *.scss *.js *.csv *.template *.tmpl *.mo *.po recursive-include openstack_dashboard *.html *.js *.scss *.mo *.po *.example *.eot *.svg *.ttf *.woff *.png *.ico *.wsgi *.gif *.csv *.template recursive-include tools *.py *.sh include AUTHORS include ChangeLog include LICENSE include manage.py include README.rst include tox.ini include doc/source/_templates/.placeholder include requirements.txt include test-requirements.txt exclude openstack_dashboard/local/local_settings.py horizon-13.0.3/.eslintignore0000664000175000017500000000003513553660754016027 0ustar zuulzuul00000000000000horizon/static/horizon/lib/**horizon-13.0.3/setup.cfg0000664000175000017500000000310213553661043015133 0ustar zuulzuul00000000000000[metadata] name = horizon summary = OpenStack Dashboard description-file = README.rst author = OpenStack author-email = openstack-dev@lists.openstack.org home-page = https://docs.openstack.org/horizon/latest/ classifier = Development Status :: 5 - Production/Stable Environment :: OpenStack Framework :: Django Intended Audience :: Developers Intended Audience :: Information Technology Intended Audience :: System Administrators License :: OSI Approved :: Apache Software License Operating System :: OS Independent Operating System :: POSIX :: Linux Programming Language :: Python Programming Language :: Python :: 2 Programming Language :: Python :: 2.7 Programming Language :: Python :: 3.5 Topic :: Internet :: WWW/HTTP [extras] test = nosehtmloutput>=0.0.3 # Apache-2.0 [global] setup-hooks = openstack_dashboard.hooks.setup_hook [files] packages = horizon openstack_dashboard openstack_auth [build_sphinx] all-files = 1 warning-is-error = 1 build-dir = doc/build source-dir = doc/source [nosetests] verbosity = 2 detailed-errors = 1 [coverage:report] ignore_errors = True include = horizon/* openstack_dashboard/* openstack_auth/* omit = /usr* setup.py *egg* .venv/* [coverage:html] directory = reports [extract_messages] keywords = gettext_noop gettext_lazy ngettext_lazy:1,2 ugettext_noop ugettext_lazy ungettext_lazy:1,2 npgettext:1c,2,3 pgettext_lazy:1c,2 npgettext_lazy:1c,2,3 add_comments = Translators: [entry_points] oslo.config.opts = openstack_dashboard = openstack_dashboard.utils.config:list_options [egg_info] tag_build = tag_date = 0 horizon-13.0.3/requirements.txt0000664000175000017500000000525013553660755016615 0ustar zuulzuul00000000000000# The order of packages is significant, because pip processes them in the order # of appearance. Changing the order has an impact on the overall integration # process, which may cause wedges in the gate later. # Order matters to the pip dependency resolver, so sorting this file # changes how packages are installed. New dependencies should be # added in alphabetical order, however, some dependencies may need to # be installed in a specific order. # # PBR should always appear first pbr!=2.1.0,>=2.0.0 # Apache-2.0 # Horizon Core Requirements Babel!=2.4.0,>=2.3.4 # BSD Django<2.0,>=1.8 # BSD Pint>=0.5 # BSD django-babel>=0.5.1 # BSD django-compressor>=2.0 # MIT django-pyscss>=2.0.2 # BSD License (2 clause) futurist>=1.2.0 # Apache-2.0 iso8601>=0.1.11 # MIT keystoneauth1>=3.3.0 # Apache-2.0 netaddr>=0.7.18 # BSD oslo.concurrency>=3.25.0 # Apache-2.0 oslo.config>=5.1.0 # Apache-2.0 oslo.i18n>=3.15.3 # Apache-2.0 oslo.policy>=1.30.0 # Apache-2.0 oslo.serialization!=2.19.1,>=2.18.0 # Apache-2.0 oslo.utils>=3.33.0 # Apache-2.0 osprofiler>=1.4.0 # Apache-2.0 pymongo!=3.1,>=3.0.2 # Apache-2.0 pyScss!=1.3.5,>=1.3.4 # MIT License python-cinderclient>=3.3.0 # Apache-2.0 python-glanceclient>=2.8.0 # Apache-2.0 python-keystoneclient>=3.8.0 # Apache-2.0 python-neutronclient>=6.3.0 # Apache-2.0 python-novaclient>=9.1.0 # Apache-2.0 python-swiftclient>=3.2.0 # Apache-2.0 pytz>=2013.6 # MIT PyYAML>=3.10 # MIT semantic-version>=2.3.1 # BSD six>=1.10.0 # MIT XStatic>=1.0.0 # MIT License XStatic-Angular>=1.5.8.0 # MIT License XStatic-Angular-Bootstrap>=2.2.0.0 # MIT License XStatic-Angular-FileUpload>=12.0.4.0 # MIT License XStatic-Angular-Gettext>=2.3.8.0 # MIT License XStatic-Angular-lrdragndrop>=1.0.2.2 # MIT License XStatic-Angular-Schema-Form>=0.8.13.0 # MIT XStatic-Bootstrap-Datepicker>=1.3.1.0 # Apache 2.0 License XStatic-Bootstrap-SCSS>=3.3.7.1 # Apache 2.0 License XStatic-bootswatch>=3.3.7.0 # MIT License XStatic-D3>=3.5.17.0 # BSD License (3 clause) XStatic-Hogan>=2.0.0.2 # Apache 2.0 License XStatic-Font-Awesome>=4.7.0.0 # SIL OFL 1.1 License, MIT License XStatic-Jasmine>=2.4.1.1 # MIT License XStatic-jQuery>=1.8.2.1 # MIT License XStatic-JQuery-Migrate>=1.2.1.1 # MIT License XStatic-JQuery.quicksearch>=2.0.3.1 # MIT License XStatic-JQuery.TableSorter>=2.14.5.1 # MIT License XStatic-jquery-ui>=1.10.4.1 # MIT License XStatic-JSEncrypt>=2.3.1.1 # MIT License XStatic-mdi>=1.4.57.0 # SIL OPEN FONT LICENSE Version 1.1 XStatic-objectpath>=1.2.1.0 # MIT XStatic-Rickshaw>=1.5.0.0 # BSD License (prior) XStatic-roboto-fontface>=0.5.0.0 # Apache 2.0 License XStatic-smart-table>=1.4.13.2 # MIT License XStatic-Spin>=1.2.5.2 # MIT License XStatic-term.js>=0.0.7.0 # MIT License XStatic-tv4>=1.2.7.0 # MIT horizon-13.0.3/.zuul.yaml0000664000175000017500000000452413553660754015274 0ustar zuulzuul00000000000000- job: name: horizon-openstack-tox-base parent: openstack-tox irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^releasenotes/.*$ - ^horizon/locale/.*$ - ^openstack_dashboard/locale/.*$ - ^openstack_auth/locale/.*$ - job: name: horizon-openstack-tox-py27dj18 parent: horizon-openstack-tox-base vars: tox_envlist: py27dj18 - job: name: horizon-openstack-tox-py27dj19 parent: horizon-openstack-tox-base vars: tox_envlist: py27dj19 - job: name: horizon-openstack-tox-py27dj110 parent: horizon-openstack-tox-base vars: tox_envlist: py27dj110 - job: name: horizon-selenium-headless parent: horizon-openstack-tox-base vars: tox_envlist: selenium-headless - job: name: horizon-dsvm-tempest-plugin parent: devstack-tempest required-projects: - name: openstack/horizon - name: openstack/tempest - name: openstack/tempest-horizon irrelevant-files: - ^.*\.rst$ - ^doc/.*$ - ^releasenotes/.*$ - ^horizon/locale/.*$ - ^openstack_dashboard/locale/.*$ - ^openstack_auth/locale/.*$ # Test codes are not related to tempest tests # as tempest is defined in a separete repository - ^horizon/test/.*$ - ^openstack_auth/tests/.*$ - ^openstack_dashboard/test/.*$ - ^openstack_dashboard/dashboards/.*/tests.py$ vars: devstack_localrc: TEMPEST_PLUGINS: "'{{ ansible_user_dir }}/src/opendev.org/openstack/tempest-horizon'" devstack_services: horizon: true tempest: true tempest_concurrency: 2 tempest_test_regex: horizon tox_envlist: all - project: templates: - openstack-python-jobs - openstack-python35-jobs - publish-openstack-sphinx-docs - periodic-stable-jobs - check-requirements - release-notes-jobs - nodejs4-jobs check: jobs: - horizon-openstack-tox-py27dj110 - horizon-openstack-tox-py27dj19 - horizon-openstack-tox-py27dj18 - horizon-selenium-headless - horizon-dsvm-tempest-plugin gate: queue: horizon jobs: - horizon-openstack-tox-py27dj110 - horizon-openstack-tox-py27dj19 - horizon-openstack-tox-py27dj18 - horizon-selenium-headless - horizon-dsvm-tempest-plugin horizon-13.0.3/package.json0000664000175000017500000000177713553660754015630 0ustar zuulzuul00000000000000{ "version": "0.0.0", "private": true, "name": "horizon", "description": "OpenStack Horizon - Angular", "repository": "none", "license": "Apache 2.0", "devDependencies": { "eslint": "1.10.3", "eslint-config-openstack": "1.2.4", "eslint-plugin-angular": "1.0.1", "jasmine-core": "2.4.1", "karma": "1.1.2", "karma-chrome-launcher": "1.0.1", "karma-cli": "1.0.1", "karma-coverage": "1.1.1", "karma-jasmine": "1.0.2", "karma-ng-html2js-preprocessor": "1.0.0", "karma-threshold-reporter": "0.1.15" }, "scripts": { "postinstall": "if [ ! -d .tox/npm ]; then tox -e npm --notest; fi", "test": "karma start horizon/karma.conf.js --single-run && karma start openstack_dashboard/karma.conf.js --single-run", "lint": "eslint --no-color openstack_dashboard/static horizon/static openstack_dashboard/dashboards/*/static", "lintq": "eslint --quiet openstack_dashboard/static horizon/static openstack_dashboard/dashboards/*/static" }, "dependencies": {} } horizon-13.0.3/babel-django.cfg0000664000175000017500000000021013553660754016305 0ustar zuulzuul00000000000000[extractors] django = django_babel.extract:extract_django [python: **.py] [django: **/templates/**.html] [django: **/templates/**.csv] horizon-13.0.3/manage.py0000775000175000017500000000147613553660754015143 0ustar zuulzuul00000000000000#!/usr/bin/env python # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import os import sys from django.core.management import execute_from_command_line if __name__ == "__main__": os.environ.setdefault("DJANGO_SETTINGS_MODULE", "openstack_dashboard.settings") execute_from_command_line(sys.argv) horizon-13.0.3/babel-djangojs.cfg0000664000175000017500000000110113553660754016642 0ustar zuulzuul00000000000000[extractors] # We use a custom extractor to find translatable strings in AngularJS # templates. The extractor is included in horizon.utils for now. # See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for # details on how this works. angular = horizon.utils.babel_extract_angular:extract_angular [javascript: **.js] # We need to look into all static folders for HTML files. # The **/static ensures that we also search within # /openstack_dashboard/dashboards/XYZ/static which will ensure # that plugins are also translated. [angular: **/static/**.html] horizon-13.0.3/.eslintrc0000664000175000017500000000264613553660754015162 0ustar zuulzuul00000000000000# Set up globals globals: angular: false extends: openstack # Most environment options are not explicitly enabled or disabled, only # included here for completeness' sake. They are commented out, because the # global updates.py script would otherwise override them during a global # requirements synchronization. # # Individual projects should choose which platforms they deploy to. env: # browser global variables. browser: true # Adds all of the Jasmine testing global variables for version 1.3 and 2.0. jasmine: true # Below we adjust rules specific to horizon's usage of openstack's linting # rules, and its own plugin inclusions. rules: ############################################################################# # Disabled Rules from eslint-config-openstack ############################################################################# valid-jsdoc: [1, { requireParamDescription: false }] no-undefined: 1 brace-style: 1 no-extra-parens: 1 callback-return: 1 block-scoped-var: 1 quote-props: 0 space-in-parens: 1 no-use-before-define: 1 no-unneeded-ternary: 1 ############################################################################# # Angular Plugin Customization ############################################################################# angular/controller-as-vm: - 1 - "ctrl" # Remove after migrating to angular 1.4 or later. angular/no-cookiestore: - 1 horizon-13.0.3/HACKING.rst0000664000175000017500000000110313553660754015117 0ustar zuulzuul00000000000000Horizon Style Commandments ========================== - Step 1: Read the OpenStack Style Commandments https://docs.openstack.org/hacking/latest/ - Step 2: Read [hacking] section in tox.ini to find the list of names which can be imported directly without triggering the "H302: import only modules" flake8 warning - Step 3: Read on Horizon Specific Commandments ----------------------------- - Read the Horizon contributing documentation at https://docs.openstack.org/horizon/latest/contributor/contributing.html - [M322] Method's default argument shouldn't be mutable. horizon-13.0.3/horizon.egg-info/0000775000175000017500000000000013553661042016477 5ustar zuulzuul00000000000000horizon-13.0.3/horizon.egg-info/PKG-INFO0000664000175000017500000000612413553661041017576 0ustar zuulzuul00000000000000Metadata-Version: 2.1 Name: horizon Version: 13.0.3 Summary: OpenStack Dashboard Home-page: https://docs.openstack.org/horizon/latest/ Author: OpenStack Author-email: openstack-dev@lists.openstack.org License: UNKNOWN Description: ============================= Horizon (OpenStack Dashboard) ============================= Horizon is a Django-based project aimed at providing a complete OpenStack Dashboard along with an extensible framework for building new dashboards from reusable components. The ``openstack_dashboard`` module is a reference implementation of a Django site that uses the ``horizon`` app to provide web-based interactions with the various OpenStack projects. * Release management: https://launchpad.net/horizon * Blueprints and feature specifications: https://blueprints.launchpad.net/horizon * Issue tracking: https://bugs.launchpad.net/horizon .. image:: https://governance.openstack.org/tc/badges/horizon.svg :target: https://governance.openstack.org/tc/reference/tags/index.html Using Horizon ============= See ``doc/source/install/index.rst`` about how to install Horizon in your OpenStack setup. It describes the example steps and has pointers for more detailed settings and configurations. It is also available at `Installation Guide `_. Getting Started for Developers ============================== ``doc/source/quickstart.rst`` or `Quickstart Guide `_ describes how to setup Horizon development environment and start development. Building Contributor Documentation ================================== This documentation is written by contributors, for contributors. The source is maintained in the ``doc/source`` directory using `reStructuredText`_ and built by `Sphinx`_ .. _reStructuredText: http://docutils.sourceforge.net/rst.html .. _Sphinx: http://sphinx-doc.org/ To build the docs, use:: $ tox -e docs Results are in the ``doc/build/html`` directory Platform: UNKNOWN Classifier: Development Status :: 5 - Production/Stable Classifier: Environment :: OpenStack Classifier: Framework :: Django Classifier: Intended Audience :: Developers Classifier: Intended Audience :: Information Technology Classifier: Intended Audience :: System Administrators Classifier: License :: OSI Approved :: Apache Software License Classifier: Operating System :: OS Independent Classifier: Operating System :: POSIX :: Linux Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 2 Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.5 Classifier: Topic :: Internet :: WWW/HTTP Provides-Extra: test horizon-13.0.3/horizon.egg-info/SOURCES.txt0000664000175000017500000052530513553661042020375 0ustar zuulzuul00000000000000.eslintignore .eslintrc .mailmap .pylintrc .zuul.yaml AUTHORS CONTRIBUTING.rst ChangeLog HACKING.rst LICENSE MANIFEST.in README.rst babel-django.cfg babel-djangojs.cfg manage.py package.json requirements.txt setup.cfg setup.py test-requirements.txt test-shim.js tox.ini doc/source/conf.py doc/source/glossary.rst doc/source/index.rst doc/source/admin/admin-manage-roles.rst doc/source/admin/customize-configure.rst doc/source/admin/index.rst doc/source/admin/manage-flavors.rst doc/source/admin/manage-host-aggregates.rst doc/source/admin/manage-images.rst doc/source/admin/manage-instances.rst doc/source/admin/manage-projects-and-users.rst doc/source/admin/manage-services.rst doc/source/admin/manage-volumes.rst doc/source/admin/sessions.rst doc/source/admin/set-quotas.rst doc/source/admin/figures/change_instance_state.png doc/source/admin/figures/create_flavor.png doc/source/admin/figures/create_image.png doc/source/admin/figures/create_volume_type_encryption.png doc/source/configuration/branding.rst doc/source/configuration/customizing.rst doc/source/configuration/index.rst doc/source/configuration/pluggable_panels.rst doc/source/configuration/settings.rst doc/source/configuration/themes.rst doc/source/contributor/contributing.rst doc/source/contributor/faq.rst doc/source/contributor/index.rst doc/source/contributor/intro.rst doc/source/contributor/quickstart.rst doc/source/contributor/testing.rst doc/source/contributor/ref/context_processors.rst doc/source/contributor/ref/decorators.rst doc/source/contributor/ref/exceptions.rst doc/source/contributor/ref/forms.rst doc/source/contributor/ref/horizon.rst doc/source/contributor/ref/index.rst doc/source/contributor/ref/local_conf.rst doc/source/contributor/ref/middleware.rst doc/source/contributor/ref/tables.rst doc/source/contributor/ref/tabs.rst doc/source/contributor/ref/test.rst doc/source/contributor/ref/workflows.rst doc/source/contributor/ref/openstack_auth/backend.rst doc/source/contributor/ref/openstack_auth/forms.rst doc/source/contributor/ref/openstack_auth/user.rst doc/source/contributor/ref/openstack_auth/utils.rst doc/source/contributor/ref/openstack_auth/views.rst doc/source/contributor/topics/angularjs.rst doc/source/contributor/topics/index.rst doc/source/contributor/topics/javascript_testing.rst doc/source/contributor/topics/microversion_support.rst doc/source/contributor/topics/packaging.rst doc/source/contributor/topics/policy.rst doc/source/contributor/topics/styling.rst doc/source/contributor/topics/tables.rst doc/source/contributor/topics/testing.rst doc/source/contributor/topics/translation.rst doc/source/contributor/topics/workflows.rst doc/source/contributor/tutorials/dashboard.rst doc/source/contributor/tutorials/index.rst doc/source/contributor/tutorials/plugin.rst doc/source/contributor/tutorials/table_actions.rst doc/source/contributor/tutorials/workflow_extend.rst doc/source/images/message_extraction.png doc/source/images/message_substitution.png doc/source/install/from-source.rst doc/source/install/index.rst doc/source/install/install-debian.rst doc/source/install/install-obs.rst doc/source/install/install-rdo.rst doc/source/install/install-ubuntu.rst doc/source/install/next-steps.rst doc/source/install/note_configuration_vary_by_distribution.txt doc/source/install/plugin-registry.rst doc/source/install/system-requirements.rst doc/source/install/verify-debian.rst doc/source/install/verify-obs.rst doc/source/install/verify-rdo.rst doc/source/install/verify-ubuntu.rst doc/source/locale/ko_KR/LC_MESSAGES/doc.po doc/source/user/browser_support.rst doc/source/user/configure-access-and-security-for-instances.rst doc/source/user/create-networks.rst doc/source/user/index.rst doc/source/user/launch-instances.rst doc/source/user/log-in.rst doc/source/user/manage-containers.rst doc/source/user/manage-images.rst doc/source/user/manage-volumes.rst doc/source/user/figures/create_image.png doc/source/user/figures/dashboard_admin_tab.png doc/source/user/figures/dashboard_identity_tab.png doc/source/user/figures/dashboard_project_tab.png doc/source/user/figures/dashboard_settings_tab.png horizon/__init__.py horizon/base.py horizon/context_processors.py horizon/decorators.py horizon/exceptions.py horizon/karma.conf.js horizon/loaders.py horizon/messages.py horizon/notifications.py horizon/site_urls.py horizon/themes.py horizon/version.py horizon/views.py horizon.egg-info/PKG-INFO horizon.egg-info/SOURCES.txt horizon.egg-info/dependency_links.txt horizon.egg-info/entry_points.txt horizon.egg-info/not-zip-safe horizon.egg-info/pbr.json horizon.egg-info/requires.txt horizon.egg-info/top_level.txt horizon/browsers/__init__.py horizon/browsers/base.py horizon/browsers/breadcrumb.py horizon/browsers/views.py horizon/conf/__init__.py horizon/conf/default.py horizon/conf/dash_template/__init__.py horizon/conf/dash_template/dashboard.py.tmpl horizon/conf/dash_template/static/dash_name/js/dash_name.js horizon/conf/dash_template/static/dash_name/scss/dash_name.scss horizon/conf/dash_template/templates/dash_name/base.html horizon/conf/panel_template/__init__.py horizon/conf/panel_template/panel.py.tmpl horizon/conf/panel_template/tests.py.tmpl horizon/conf/panel_template/urls.py.tmpl horizon/conf/panel_template/views.py horizon/conf/panel_template/templates/panel_name/index.html horizon/contrib/__init__.py horizon/contrib/bootstrap_datepicker.py horizon/contrib/staticfiles/__init__.py horizon/contrib/staticfiles/finders.py horizon/forms/__init__.py horizon/forms/base.py horizon/forms/fields.py horizon/forms/views.py horizon/hacking/__init__.py horizon/hacking/checks.py horizon/locale/ca/LC_MESSAGES/django.po horizon/locale/cs/LC_MESSAGES/django.po horizon/locale/cs/LC_MESSAGES/djangojs.po horizon/locale/de/LC_MESSAGES/django.po horizon/locale/de/LC_MESSAGES/djangojs.po horizon/locale/en_AU/LC_MESSAGES/django.po horizon/locale/en_AU/LC_MESSAGES/djangojs.po horizon/locale/en_GB/LC_MESSAGES/django.po horizon/locale/en_GB/LC_MESSAGES/djangojs.po horizon/locale/eo/LC_MESSAGES/django.po horizon/locale/eo/LC_MESSAGES/djangojs.po horizon/locale/es/LC_MESSAGES/django.po horizon/locale/es/LC_MESSAGES/djangojs.po horizon/locale/es_MX/LC_MESSAGES/django.po horizon/locale/fi_FI/LC_MESSAGES/django.po horizon/locale/fil/LC_MESSAGES/django.po horizon/locale/fr/LC_MESSAGES/django.po horizon/locale/fr/LC_MESSAGES/djangojs.po horizon/locale/hi/LC_MESSAGES/django.po horizon/locale/hu/LC_MESSAGES/django.po horizon/locale/id/LC_MESSAGES/django.po horizon/locale/id/LC_MESSAGES/djangojs.po horizon/locale/it/LC_MESSAGES/django.po horizon/locale/it/LC_MESSAGES/djangojs.po horizon/locale/ja/LC_MESSAGES/django.po horizon/locale/ja/LC_MESSAGES/djangojs.po horizon/locale/ko_KR/LC_MESSAGES/django.po horizon/locale/ko_KR/LC_MESSAGES/djangojs.po horizon/locale/nl_NL/LC_MESSAGES/django.po horizon/locale/nl_NL/LC_MESSAGES/djangojs.po horizon/locale/pa_IN/LC_MESSAGES/django.po horizon/locale/pa_IN/LC_MESSAGES/djangojs.po horizon/locale/pl_PL/LC_MESSAGES/django.po horizon/locale/pl_PL/LC_MESSAGES/djangojs.po horizon/locale/pt/LC_MESSAGES/django.po horizon/locale/pt_BR/LC_MESSAGES/django.po horizon/locale/pt_BR/LC_MESSAGES/djangojs.po horizon/locale/ru/LC_MESSAGES/django.po horizon/locale/ru/LC_MESSAGES/djangojs.po horizon/locale/sl_SI/LC_MESSAGES/django.po horizon/locale/sr/LC_MESSAGES/django.po horizon/locale/tr_TR/LC_MESSAGES/django.po horizon/locale/tr_TR/LC_MESSAGES/djangojs.po horizon/locale/zh_CN/LC_MESSAGES/django.po horizon/locale/zh_CN/LC_MESSAGES/djangojs.po horizon/locale/zh_TW/LC_MESSAGES/django.po horizon/locale/zh_TW/LC_MESSAGES/djangojs.po horizon/management/__init__.py horizon/management/commands/__init__.py horizon/management/commands/pull_catalog.py horizon/management/commands/startdash.py horizon/management/commands/startpanel.py horizon/middleware/__init__.py horizon/middleware/base.py horizon/middleware/operation_log.py horizon/static/auth/.eslintrc horizon/static/auth/auth.module.js horizon/static/auth/login/login-finder.directive.js horizon/static/auth/login/login.controller.js horizon/static/auth/login/login.controller.spec.js horizon/static/auth/login/login.module.js horizon/static/auth/login/login.module.spec.js horizon/static/auth/login/login.regular.mock.html horizon/static/auth/login/login.spec.js horizon/static/auth/login/login.websso.mock.html horizon/static/framework/.eslintrc horizon/static/framework/framework.module.js horizon/static/framework/framework.module.spec.js horizon/static/framework/framework.scss horizon/static/framework/conf/conf.js horizon/static/framework/conf/permissions.service.js horizon/static/framework/conf/permissions.service.spec.js horizon/static/framework/conf/resource-type-registry.service.js horizon/static/framework/conf/resource-type-registry.service.spec.js horizon/static/framework/util/util.module.js horizon/static/framework/util/actions/action-result.service.js horizon/static/framework/util/actions/action-result.service.spec.js horizon/static/framework/util/actions/actions.module.js horizon/static/framework/util/bind-scope/bind-scope.directive.js horizon/static/framework/util/bind-scope/bind-scope.module.js horizon/static/framework/util/bind-scope/bind-scope.spec.js horizon/static/framework/util/extensible/extensible.module.js horizon/static/framework/util/extensible/extensible.service.js horizon/static/framework/util/extensible/extensible.service.spec.js horizon/static/framework/util/file/file-reader.service.js horizon/static/framework/util/file/file-reader.service.spec.js horizon/static/framework/util/file/file.module.js horizon/static/framework/util/file/text-download.service.js horizon/static/framework/util/file/text-download.service.spec.js horizon/static/framework/util/filters/filters.js horizon/static/framework/util/filters/filters.module.js horizon/static/framework/util/filters/filters.spec.js horizon/static/framework/util/filters/helpers.borrowed-from-underscore.js horizon/static/framework/util/http/http.js horizon/static/framework/util/http/http.spec.js horizon/static/framework/util/i18n/i18n.js horizon/static/framework/util/i18n/i18n.spec.js horizon/static/framework/util/promise-toggle/hz-promise-toggle.directive.js horizon/static/framework/util/promise-toggle/hz-promise-toggle.directive.mock.js horizon/static/framework/util/promise-toggle/hz-promise-toggle.directive.spec.js horizon/static/framework/util/promise-toggle/promise-toggle.module.js horizon/static/framework/util/q/q.extensions.js horizon/static/framework/util/q/q.extensions.spec.js horizon/static/framework/util/q/q.module.js horizon/static/framework/util/q/q.module.spec.js horizon/static/framework/util/tech-debt/dummy.controller.js horizon/static/framework/util/tech-debt/helper-functions.service.js horizon/static/framework/util/tech-debt/helper-functions.service.spec.js horizon/static/framework/util/tech-debt/image-file-on-change.directive.js horizon/static/framework/util/tech-debt/tech-debt.module.js horizon/static/framework/util/uuid/uuid.js horizon/static/framework/util/uuid/uuid.spec.js horizon/static/framework/util/validators/hz-password-match.directive.js horizon/static/framework/util/validators/hz-password-match.directive.spec.js horizon/static/framework/util/validators/validate-number-max.directive.js horizon/static/framework/util/validators/validate-number-min.directive.js horizon/static/framework/util/validators/validate-unique.js horizon/static/framework/util/validators/validators.module.js horizon/static/framework/util/validators/validators.spec.js horizon/static/framework/util/workflow/workflow.module.js horizon/static/framework/util/workflow/workflow.service.js horizon/static/framework/util/workflow/workflow.spec.js horizon/static/framework/widgets/widgets.module.js horizon/static/framework/widgets/widgets.module.spec.js horizon/static/framework/widgets/widgets.scss horizon/static/framework/widgets/action-list/action-list.directive.js horizon/static/framework/widgets/action-list/action-list.module.js horizon/static/framework/widgets/action-list/action-list.scss horizon/static/framework/widgets/action-list/action-list.single-button-dropdown.mock.html horizon/static/framework/widgets/action-list/action-list.spec.js horizon/static/framework/widgets/action-list/action-list.split-botton-dropdown.mock.html horizon/static/framework/widgets/action-list/action.directive.js horizon/static/framework/widgets/action-list/action.html horizon/static/framework/widgets/action-list/actions-batch.template.html horizon/static/framework/widgets/action-list/actions-create.template.html horizon/static/framework/widgets/action-list/actions-danger.template.html horizon/static/framework/widgets/action-list/actions-delete-selected.template.html horizon/static/framework/widgets/action-list/actions-delete.template.html horizon/static/framework/widgets/action-list/actions-detail.template.html horizon/static/framework/widgets/action-list/actions-row.template.html horizon/static/framework/widgets/action-list/actions.batch.mock.html horizon/static/framework/widgets/action-list/actions.controller.js horizon/static/framework/widgets/action-list/actions.custom.mock.html horizon/static/framework/widgets/action-list/actions.custom2.mock.html horizon/static/framework/widgets/action-list/actions.detail.mock.html horizon/static/framework/widgets/action-list/actions.directive.js horizon/static/framework/widgets/action-list/actions.directive.spec.js horizon/static/framework/widgets/action-list/actions.row.mock.html horizon/static/framework/widgets/action-list/actions.service.js horizon/static/framework/widgets/action-list/button-group.mock.html horizon/static/framework/widgets/action-list/button-tooltip.directive.js horizon/static/framework/widgets/action-list/button-tooltip.row-warning.service.js horizon/static/framework/widgets/action-list/menu-item.html horizon/static/framework/widgets/action-list/menu.directive.js horizon/static/framework/widgets/action-list/menu.html horizon/static/framework/widgets/action-list/single-button.html horizon/static/framework/widgets/action-list/split-button.html horizon/static/framework/widgets/action-list/warning-tooltip.html horizon/static/framework/widgets/charts/chart-tooltip.directive.js horizon/static/framework/widgets/charts/chart-tooltip.html horizon/static/framework/widgets/charts/chart-tooltip.scss horizon/static/framework/widgets/charts/chart-tooltip.spec.js horizon/static/framework/widgets/charts/charts.module.js horizon/static/framework/widgets/charts/charts.spec.js horizon/static/framework/widgets/charts/pie-chart.directive.js horizon/static/framework/widgets/charts/pie-chart.html horizon/static/framework/widgets/charts/pie-chart.scss horizon/static/framework/widgets/charts/pie-chart.spec.js horizon/static/framework/widgets/contenteditable/contenteditable.directive.js horizon/static/framework/widgets/contenteditable/contenteditable.directive.spec.js horizon/static/framework/widgets/contenteditable/contenteditable.module.js horizon/static/framework/widgets/details/details.directive.js horizon/static/framework/widgets/details/details.html horizon/static/framework/widgets/details/details.module.js horizon/static/framework/widgets/details/routed-details-view.controller.js horizon/static/framework/widgets/details/routed-details-view.controller.spec.js horizon/static/framework/widgets/details/routed-details-view.html horizon/static/framework/widgets/form/builders.provider.js horizon/static/framework/widgets/form/builders.provider.spec.js horizon/static/framework/widgets/form/decorator.js horizon/static/framework/widgets/form/decorator.spec.js horizon/static/framework/widgets/form/form.module.js horizon/static/framework/widgets/form/modal-form.controller.js horizon/static/framework/widgets/form/modal-form.controller.spec.js horizon/static/framework/widgets/form/modal-form.html horizon/static/framework/widgets/form/modal-form.service.js horizon/static/framework/widgets/form/modal-form.service.spec.js horizon/static/framework/widgets/form/fields/array.html horizon/static/framework/widgets/form/fields/checkbox.html horizon/static/framework/widgets/form/fields/checkboxes.html horizon/static/framework/widgets/form/fields/default.html horizon/static/framework/widgets/form/fields/fieldset.html horizon/static/framework/widgets/form/fields/help.html horizon/static/framework/widgets/form/fields/password-confirm.html horizon/static/framework/widgets/form/fields/radio-buttons.html horizon/static/framework/widgets/form/fields/radios-inline.html horizon/static/framework/widgets/form/fields/radios.html horizon/static/framework/widgets/form/fields/section.html horizon/static/framework/widgets/form/fields/select.html horizon/static/framework/widgets/form/fields/submit.html horizon/static/framework/widgets/form/fields/tabarray.html horizon/static/framework/widgets/form/fields/tabs.html horizon/static/framework/widgets/form/fields/textarea.html horizon/static/framework/widgets/headers/headers.module.js horizon/static/framework/widgets/headers/headers.module.spec.js horizon/static/framework/widgets/headers/headers.scss horizon/static/framework/widgets/headers/hz-page-header.directive.js horizon/static/framework/widgets/headers/hz-page-header.directive.spec.js horizon/static/framework/widgets/headers/hz-page-header.html horizon/static/framework/widgets/help-panel/help-panel.directive.js horizon/static/framework/widgets/help-panel/help-panel.directive.spec.js horizon/static/framework/widgets/help-panel/help-panel.html horizon/static/framework/widgets/help-panel/help-panel.module.js horizon/static/framework/widgets/help-panel/help-panel.module.spec.js horizon/static/framework/widgets/load-edit/load-edit.directive.js horizon/static/framework/widgets/load-edit/load-edit.directive.spec.js horizon/static/framework/widgets/load-edit/load-edit.html horizon/static/framework/widgets/load-edit/load-edit.module.js horizon/static/framework/widgets/magic-search/hz-magic-search-bar.directive.js horizon/static/framework/widgets/magic-search/hz-magic-search-bar.html horizon/static/framework/widgets/magic-search/hz-magic-search-bar.spec.js horizon/static/framework/widgets/magic-search/hz-magic-search-context.directive.js horizon/static/framework/widgets/magic-search/magic-search.controller.js horizon/static/framework/widgets/magic-search/magic-search.controller.spec.js horizon/static/framework/widgets/magic-search/magic-search.directive.js horizon/static/framework/widgets/magic-search/magic-search.html horizon/static/framework/widgets/magic-search/magic-search.module.js horizon/static/framework/widgets/magic-search/magic-search.scss horizon/static/framework/widgets/magic-search/magic-search.service.js horizon/static/framework/widgets/magic-search/magic-search.service.spec.js horizon/static/framework/widgets/magic-search/st-magic-search.directive.js horizon/static/framework/widgets/magic-search/st-magic-search.directive.spec.js horizon/static/framework/widgets/metadata/metadata.module.js horizon/static/framework/widgets/metadata/metadata.scss horizon/static/framework/widgets/metadata/display/display.module.js horizon/static/framework/widgets/metadata/display/display.scss horizon/static/framework/widgets/metadata/display/display.spec.js horizon/static/framework/widgets/metadata/display/metadata-display.controller.js horizon/static/framework/widgets/metadata/display/metadata-display.directive.js horizon/static/framework/widgets/metadata/display/metadata-display.html horizon/static/framework/widgets/metadata/tree/metadata-tree-item.controller.js horizon/static/framework/widgets/metadata/tree/metadata-tree-item.controller.spec.js horizon/static/framework/widgets/metadata/tree/metadata-tree-item.directive.js horizon/static/framework/widgets/metadata/tree/metadata-tree-item.html horizon/static/framework/widgets/metadata/tree/metadata-tree.controller.js horizon/static/framework/widgets/metadata/tree/metadata-tree.directive.js horizon/static/framework/widgets/metadata/tree/metadata-tree.html horizon/static/framework/widgets/metadata/tree/tree.module.js horizon/static/framework/widgets/metadata/tree/tree.scss horizon/static/framework/widgets/metadata/tree/tree.service.js horizon/static/framework/widgets/metadata/tree/tree.spec.js horizon/static/framework/widgets/modal/delete-modal.service.js horizon/static/framework/widgets/modal/delete-modal.service.spec.js horizon/static/framework/widgets/modal/modal.module.js horizon/static/framework/widgets/modal/simple-modal.controller.js horizon/static/framework/widgets/modal/simple-modal.html horizon/static/framework/widgets/modal/simple-modal.service.js horizon/static/framework/widgets/modal/simple-modal.spec.js horizon/static/framework/widgets/modal/wizard-modal.service.js horizon/static/framework/widgets/modal/wizard-modal.service.spec.js horizon/static/framework/widgets/modal/wizard.controller.js horizon/static/framework/widgets/modal/wizard.controller.spec.js horizon/static/framework/widgets/modal-wait-spinner/modal-wait-spinner.directive.js horizon/static/framework/widgets/modal-wait-spinner/modal-wait-spinner.module.js horizon/static/framework/widgets/modal-wait-spinner/modal-wait-spinner.service.js horizon/static/framework/widgets/modal-wait-spinner/modal-wait-spinner.spec.js horizon/static/framework/widgets/modal-wait-spinner/modal-wait-spinner.template.html horizon/static/framework/widgets/panel/hz-resource-panel.controller.js horizon/static/framework/widgets/panel/hz-resource-panel.controller.spec.js horizon/static/framework/widgets/panel/hz-resource-panel.directive.js horizon/static/framework/widgets/panel/hz-resource-panel.html horizon/static/framework/widgets/panel/panel.module.js horizon/static/framework/widgets/property/hz-field.directive.js horizon/static/framework/widgets/property/hz-resource-property-list.directive.js horizon/static/framework/widgets/property/hz-resource-property-list.html horizon/static/framework/widgets/property/hz-resource-property.controller.js horizon/static/framework/widgets/property/hz-resource-property.directive.js horizon/static/framework/widgets/property/hz-resource-property.directive.spec.js horizon/static/framework/widgets/property/hz-resource-property.html horizon/static/framework/widgets/property/property.module.js horizon/static/framework/widgets/table/hz-cell.directive.js horizon/static/framework/widgets/table/hz-detail-row.directive.js horizon/static/framework/widgets/table/hz-detail-row.html horizon/static/framework/widgets/table/hz-dynamic-table.controller.js horizon/static/framework/widgets/table/hz-dynamic-table.directive.js horizon/static/framework/widgets/table/hz-dynamic-table.directive.spec.js horizon/static/framework/widgets/table/hz-dynamic-table.html horizon/static/framework/widgets/table/hz-dynamic-table.scss horizon/static/framework/widgets/table/hz-dynamic-table.spec.js horizon/static/framework/widgets/table/hz-expand-detail.directive.js horizon/static/framework/widgets/table/hz-no-items.directive.js horizon/static/framework/widgets/table/hz-no-items.directive.spec.js horizon/static/framework/widgets/table/hz-no-items.html horizon/static/framework/widgets/table/hz-resource-table.controller.js horizon/static/framework/widgets/table/hz-resource-table.controller.spec.js horizon/static/framework/widgets/table/hz-resource-table.directive.js horizon/static/framework/widgets/table/hz-resource-table.html horizon/static/framework/widgets/table/hz-search-bar.directive.js horizon/static/framework/widgets/table/hz-select-all.directive.js horizon/static/framework/widgets/table/hz-select.directive.js horizon/static/framework/widgets/table/hz-table-footer.controller.js horizon/static/framework/widgets/table/hz-table-footer.directive.js horizon/static/framework/widgets/table/hz-table-footer.html horizon/static/framework/widgets/table/hz-table.directive.js horizon/static/framework/widgets/table/no-items.mock.html horizon/static/framework/widgets/table/pagination.html horizon/static/framework/widgets/table/search-bar.html horizon/static/framework/widgets/table/search-bar.spec.js horizon/static/framework/widgets/table/st-table.mock.html horizon/static/framework/widgets/table/table.controller.js horizon/static/framework/widgets/table/table.mock.html horizon/static/framework/widgets/table/table.module.js horizon/static/framework/widgets/table/table.scss horizon/static/framework/widgets/table/table.spec.js horizon/static/framework/widgets/toast/toast.directive.js horizon/static/framework/widgets/toast/toast.html horizon/static/framework/widgets/toast/toast.module.js horizon/static/framework/widgets/toast/toast.service.js horizon/static/framework/widgets/toast/toast.spec.js horizon/static/framework/widgets/transfer-table/filter-available.js horizon/static/framework/widgets/transfer-table/filter-available.spec.js horizon/static/framework/widgets/transfer-table/transfer-table.basic.mock.html horizon/static/framework/widgets/transfer-table/transfer-table.clone.mock.html horizon/static/framework/widgets/transfer-table/transfer-table.controller.js horizon/static/framework/widgets/transfer-table/transfer-table.controller.spec.js horizon/static/framework/widgets/transfer-table/transfer-table.directive.js horizon/static/framework/widgets/transfer-table/transfer-table.directive.spec.js horizon/static/framework/widgets/transfer-table/transfer-table.example.html horizon/static/framework/widgets/transfer-table/transfer-table.html horizon/static/framework/widgets/transfer-table/transfer-table.max-1.mock.html horizon/static/framework/widgets/transfer-table/transfer-table.max-2.mock.html horizon/static/framework/widgets/transfer-table/transfer-table.module.js horizon/static/framework/widgets/transfer-table/transfer-table.module.spec.js horizon/static/framework/widgets/wizard/modal-container.controller.js horizon/static/framework/widgets/wizard/wizard.controller.js horizon/static/framework/widgets/wizard/wizard.controller.spec.js horizon/static/framework/widgets/wizard/wizard.directive.js horizon/static/framework/widgets/wizard/wizard.html horizon/static/framework/widgets/wizard/wizard.module.js horizon/static/framework/widgets/wizard/wizard.spec.js horizon/static/horizon/.eslintrc horizon/static/horizon/js/horizon.communication.js horizon/static/horizon/js/horizon.d3barchart.js horizon/static/horizon/js/horizon.d3linechart.js horizon/static/horizon/js/horizon.d3piechart.js horizon/static/horizon/js/horizon.datepickers.js horizon/static/horizon/js/horizon.extensible_header.js horizon/static/horizon/js/horizon.forms.js horizon/static/horizon/js/horizon.formset_table.js horizon/static/horizon/js/horizon.hacks.js horizon/static/horizon/js/horizon.heattop.js horizon/static/horizon/js/horizon.js horizon/static/horizon/js/horizon.lists.js horizon/static/horizon/js/horizon.loader.js horizon/static/horizon/js/horizon.membership.js horizon/static/horizon/js/horizon.messages.js horizon/static/horizon/js/horizon.modals.js horizon/static/horizon/js/horizon.selenium.js horizon/static/horizon/js/horizon.sidebar.js horizon/static/horizon/js/horizon.string.js horizon/static/horizon/js/horizon.tables.js horizon/static/horizon/js/horizon.tables_inline_edit.js horizon/static/horizon/js/horizon.tabs.js horizon/static/horizon/js/horizon.templates.js horizon/static/horizon/js/horizon.users.js horizon/static/horizon/lib/jquery/jquery.bootstrap.wizard.js horizon/static/horizon/tests/jasmine/instances.legacy-spec.js horizon/static/horizon/tests/jasmine/messages.legacy-spec.js horizon/static/horizon/tests/jasmine/modals.legacy-spec.js horizon/static/horizon/tests/jasmine/quota.legacy-spec.js horizon/static/horizon/tests/jasmine/string.legacy-spec.js horizon/static/horizon/tests/jasmine/tables.legacy-spec.js horizon/static/horizon/tests/jasmine/templates.legacy-spec.js horizon/tables/__init__.py horizon/tables/actions.py horizon/tables/base.py horizon/tables/formset.py horizon/tables/views.py horizon/tabs/__init__.py horizon/tabs/base.py horizon/tabs/views.py horizon/templates/not_authorized.html horizon/templates/auth/_description.html horizon/templates/auth/_login.html horizon/templates/auth/_login_form.html horizon/templates/auth/_login_modal.html horizon/templates/auth/_login_page.html horizon/templates/auth/_splash.html horizon/templates/auth/login.html horizon/templates/bootstrap/breadcrumb.html horizon/templates/bootstrap/progress_bar.html horizon/templates/horizon/_messages.html horizon/templates/horizon/_nav_list.html horizon/templates/horizon/_script_i18n.html horizon/templates/horizon/_sidebar.html horizon/templates/horizon/_subnav_list.html horizon/templates/horizon/client_side/_alert_message.html horizon/templates/horizon/client_side/_confirm.html horizon/templates/horizon/client_side/_loading_inline.html horizon/templates/horizon/client_side/_loading_modal.html horizon/templates/horizon/client_side/_membership.html horizon/templates/horizon/client_side/_modal.html horizon/templates/horizon/client_side/_progress.html horizon/templates/horizon/client_side/_script_loader.html horizon/templates/horizon/client_side/_table_row.html horizon/templates/horizon/client_side/template.html horizon/templates/horizon/client_side/templates.html horizon/templates/horizon/common/_breadcrumb.html horizon/templates/horizon/common/_data_table.html horizon/templates/horizon/common/_data_table_action.html horizon/templates/horizon/common/_data_table_cell.html horizon/templates/horizon/common/_data_table_pagination.html horizon/templates/horizon/common/_data_table_row.html horizon/templates/horizon/common/_data_table_row_action_row.html horizon/templates/horizon/common/_data_table_row_actions_dropdown.html horizon/templates/horizon/common/_data_table_row_actions_row.html horizon/templates/horizon/common/_data_table_table_actions.html horizon/templates/horizon/common/_data_table_view.html horizon/templates/horizon/common/_datepicker.html horizon/templates/horizon/common/_datepicker_form.html horizon/templates/horizon/common/_detail.html horizon/templates/horizon/common/_detail_header.html horizon/templates/horizon/common/_detail_tab_group.html horizon/templates/horizon/common/_detail_table.html horizon/templates/horizon/common/_domain_page_header.html horizon/templates/horizon/common/_form_errors.html horizon/templates/horizon/common/_form_field.html horizon/templates/horizon/common/_form_field_decorator.html horizon/templates/horizon/common/_form_field_required.html horizon/templates/horizon/common/_form_fields.html horizon/templates/horizon/common/_formset_table.html horizon/templates/horizon/common/_formset_table_row.html horizon/templates/horizon/common/_horizontal_field.html horizon/templates/horizon/common/_horizontal_fields.html horizon/templates/horizon/common/_keystone_provider_selector.html horizon/templates/horizon/common/_limit_summary.html horizon/templates/horizon/common/_modal.html horizon/templates/horizon/common/_modal_form.html horizon/templates/horizon/common/_modal_form_add_members.html horizon/templates/horizon/common/_page_header.html horizon/templates/horizon/common/_region_selector.html horizon/templates/horizon/common/_resource_browser.html horizon/templates/horizon/common/_sidebar.html horizon/templates/horizon/common/_sidebar_module.html horizon/templates/horizon/common/_tab_group.html horizon/templates/horizon/common/_usage_summary.html horizon/templates/horizon/common/_workflow.html horizon/templates/horizon/common/_workflow_base.html horizon/templates/horizon/common/_workflow_step.html horizon/templates/horizon/common/_workflow_step_update_members.html horizon/templates/horizon/common/fields/_themable_checkbox.html horizon/templates/horizon/common/fields/_themable_radiobutton.html horizon/templates/horizon/common/fields/_themable_select.html horizon/templates/horizon/common/fields/_themable_spinner.html horizon/templates/horizon/jasmine/index.html horizon/templates/horizon/jasmine/jasmine.html horizon/templates/horizon/jasmine/jasmine_legacy.html horizon/templatetags/__init__.py horizon/templatetags/angular.py horizon/templatetags/bootstrap.py horizon/templatetags/branding.py horizon/templatetags/breadcrumb_nav.py horizon/templatetags/form_helpers.py horizon/templatetags/horizon.py horizon/templatetags/parse_date.py horizon/templatetags/shellfilter.py horizon/templatetags/sizeformat.py horizon/templatetags/truncate_filter.py horizon/test/__init__.py horizon/test/firefox_binary.py horizon/test/helpers.py horizon/test/settings.py horizon/test/urls.py horizon/test/utils.py horizon/test/webdriver.py horizon/test/customization/__init__.py horizon/test/customization/cust_test1.py horizon/test/customization/cust_test2.py horizon/test/dummy_auth/__init__.py horizon/test/dummy_auth/backend.py horizon/test/jasmine/__init__.py horizon/test/jasmine/jasmine.py horizon/test/messages/test_info.json horizon/test/messages/test_invalid.json horizon/test/messages/test_warning.json horizon/test/selenium/selenium_tests.py horizon/test/templates/404.html horizon/test/templates/_tab.html horizon/test/templates/angular.html horizon/test/templates/base-sidebar.html horizon/test/templates/base.html horizon/test/templates/tab_group.html horizon/test/templates/workflow.html horizon/test/templates/registration/login.html horizon/test/test_dashboards/__init__.py horizon/test/test_dashboards/cats/__init__.py horizon/test/test_dashboards/cats/dashboard.py horizon/test/test_dashboards/cats/kittens/__init__.py horizon/test/test_dashboards/cats/kittens/panel.py horizon/test/test_dashboards/cats/kittens/urls.py horizon/test/test_dashboards/cats/kittens/views.py horizon/test/test_dashboards/cats/kittens/templates/kittens/index.html horizon/test/test_dashboards/cats/static/cats/js/cats.js horizon/test/test_dashboards/cats/static/cats/scss/cats.scss horizon/test/test_dashboards/cats/templates/cats/base.html horizon/test/test_dashboards/cats/tigers/__init__.py horizon/test/test_dashboards/cats/tigers/panel.py horizon/test/test_dashboards/cats/tigers/urls.py horizon/test/test_dashboards/cats/tigers/views.py horizon/test/test_dashboards/cats/tigers/templates/tigers/index.html horizon/test/test_dashboards/dogs/__init__.py horizon/test/test_dashboards/dogs/dashboard.py horizon/test/test_dashboards/dogs/puppies/__init__.py horizon/test/test_dashboards/dogs/puppies/panel.py horizon/test/test_dashboards/dogs/puppies/tables.py horizon/test/test_dashboards/dogs/puppies/tabs.py horizon/test/test_dashboards/dogs/puppies/urls.py horizon/test/test_dashboards/dogs/puppies/views.py horizon/test/test_dashboards/dogs/puppies/templates/puppies/index.html horizon/test/test_dashboards/dogs/puppies/templates/puppies/two_tabs.html horizon/test/test_dashboards/dogs/static/dogs/js/dogs.js horizon/test/test_dashboards/dogs/static/dogs/scss/dogs.scss horizon/test/test_dashboards/dogs/templates/dogs/base.html horizon/test/unit/__init__.py horizon/test/unit/test_base.py horizon/test/unit/test_exceptions.py horizon/test/unit/test_messages.py horizon/test/unit/test_notifications.py horizon/test/unit/test_views.py horizon/test/unit/forms/__init__.py horizon/test/unit/forms/test_fields.py horizon/test/unit/forms/test_forms.py horizon/test/unit/hacking/__init__.py horizon/test/unit/hacking/test_checks.py horizon/test/unit/management/__init__.py horizon/test/unit/management/commands/__init__.py horizon/test/unit/management/commands/test_startdash.py horizon/test/unit/management/commands/test_startpanel.py horizon/test/unit/middleware/__init__.py horizon/test/unit/middleware/test_base.py horizon/test/unit/middleware/test_operation_log.py horizon/test/unit/tables/__init__.py horizon/test/unit/tables/test_tables.py horizon/test/unit/tabs/__init__.py horizon/test/unit/tabs/test_tabs.py horizon/test/unit/templatetags/__init__.py horizon/test/unit/templatetags/test_templatetags.py horizon/test/unit/utils/__init__.py horizon/test/unit/utils/test_babel_extract_angular.py horizon/test/unit/utils/test_file_discovery.py horizon/test/unit/utils/test_filters.py horizon/test/unit/utils/test_functions.py horizon/test/unit/utils/test_memoized.py horizon/test/unit/utils/test_secret_key.py horizon/test/unit/utils/test_units.py horizon/test/unit/utils/test_validators.py horizon/test/unit/workflows/__init__.py horizon/test/unit/workflows/test_workflows.py horizon/utils/__init__.py horizon/utils/babel_extract_angular.py horizon/utils/csvbase.py horizon/utils/escape.py horizon/utils/file_discovery.py horizon/utils/filters.py horizon/utils/functions.py horizon/utils/html.py horizon/utils/lazy_encoder.py horizon/utils/memoized.py horizon/utils/scss_filter.py horizon/utils/secret_key.py horizon/utils/settings.py horizon/utils/units.py horizon/utils/validators.py horizon/workflows/__init__.py horizon/workflows/base.py horizon/workflows/views.py openstack_auth/__init__.py openstack_auth/backend.py openstack_auth/exceptions.py openstack_auth/forms.py openstack_auth/middleware.py openstack_auth/models.py openstack_auth/policy.py openstack_auth/urls.py openstack_auth/user.py openstack_auth/utils.py openstack_auth/views.py openstack_auth/locale/de/LC_MESSAGES/django.po openstack_auth/locale/en_GB/LC_MESSAGES/django.po openstack_auth/locale/eo/LC_MESSAGES/django.po openstack_auth/locale/es/LC_MESSAGES/django.po openstack_auth/locale/es_MX/LC_MESSAGES/django.po openstack_auth/locale/fr/LC_MESSAGES/django.po openstack_auth/locale/id/LC_MESSAGES/django.po openstack_auth/locale/ja/LC_MESSAGES/django.po openstack_auth/locale/ko_KR/LC_MESSAGES/django.po openstack_auth/locale/pt_BR/LC_MESSAGES/django.po openstack_auth/locale/zh_CN/LC_MESSAGES/django.po openstack_auth/plugin/__init__.py openstack_auth/plugin/base.py openstack_auth/plugin/k2k.py openstack_auth/plugin/password.py openstack_auth/plugin/token.py openstack_auth/tests/__init__.py openstack_auth/tests/data_v2.py openstack_auth/tests/data_v3.py openstack_auth/tests/settings.py openstack_auth/tests/urls.py openstack_auth/tests/conf/keystone_policy.json openstack_auth/tests/conf/no_default_policy.json openstack_auth/tests/conf/nova_policy.json openstack_auth/tests/conf/policy.v3cloudsample.json openstack_auth/tests/conf/with_default_policy.json openstack_auth/tests/templates/auth/blank.html openstack_auth/tests/templates/auth/login.html openstack_auth/tests/unit/__init__.py openstack_auth/tests/unit/test_auth.py openstack_auth/tests/unit/test_policy.py openstack_auth/tests/unit/test_user.py openstack_auth/tests/unit/test_utils.py openstack_dashboard/.eslintrc openstack_dashboard/__init__.py openstack_dashboard/context_processors.py openstack_dashboard/exceptions.py openstack_dashboard/hooks.py openstack_dashboard/karma.conf.js openstack_dashboard/policy.py openstack_dashboard/settings.py openstack_dashboard/theme_settings.py openstack_dashboard/urls.py openstack_dashboard/views.py openstack_dashboard/api/__init__.py openstack_dashboard/api/base.py openstack_dashboard/api/cinder.py openstack_dashboard/api/glance.py openstack_dashboard/api/keystone.py openstack_dashboard/api/microversions.py openstack_dashboard/api/network.py openstack_dashboard/api/neutron.py openstack_dashboard/api/nova.py openstack_dashboard/api/swift.py openstack_dashboard/api/rest/__init__.py openstack_dashboard/api/rest/cinder.py openstack_dashboard/api/rest/config.py openstack_dashboard/api/rest/glance.py openstack_dashboard/api/rest/json_encoder.py openstack_dashboard/api/rest/keystone.py openstack_dashboard/api/rest/network.py openstack_dashboard/api/rest/neutron.py openstack_dashboard/api/rest/nova.py openstack_dashboard/api/rest/policy.py openstack_dashboard/api/rest/swift.py openstack_dashboard/api/rest/urls.py openstack_dashboard/api/rest/utils.py openstack_dashboard/conf/cinder_policy.json openstack_dashboard/conf/glance_policy.json openstack_dashboard/conf/keystone_policy.json openstack_dashboard/conf/neutron_policy.json openstack_dashboard/conf/nova_policy.json openstack_dashboard/conf/cinder_policy.d/consistencygroup.yaml openstack_dashboard/conf/nova_policy.d/api-extensions.yaml openstack_dashboard/contrib/__init__.py openstack_dashboard/contrib/developer/__init__.py openstack_dashboard/contrib/developer/dashboard.py openstack_dashboard/contrib/developer/tests.py openstack_dashboard/contrib/developer/enabled/_9001_developer.py openstack_dashboard/contrib/developer/enabled/_9010_preview.py openstack_dashboard/contrib/developer/enabled/_9020_resource_browser.py openstack_dashboard/contrib/developer/enabled/_9030_profiler.py openstack_dashboard/contrib/developer/enabled/_9040_form_builder.py openstack_dashboard/contrib/developer/enabled/__init__.py openstack_dashboard/contrib/developer/form_builder/__init__.py openstack_dashboard/contrib/developer/form_builder/panel.py openstack_dashboard/contrib/developer/form_builder/urls.py openstack_dashboard/contrib/developer/form_builder/views.py openstack_dashboard/contrib/developer/profiler/__init__.py openstack_dashboard/contrib/developer/profiler/api.py openstack_dashboard/contrib/developer/profiler/middleware.py openstack_dashboard/contrib/developer/profiler/panel.py openstack_dashboard/contrib/developer/profiler/urls.py openstack_dashboard/contrib/developer/profiler/views.py openstack_dashboard/contrib/developer/profiler/templates/profiler/_mode_picker.html openstack_dashboard/contrib/developer/profiler/templates/profiler/index.html openstack_dashboard/contrib/developer/resource_browser/__init__.py openstack_dashboard/contrib/developer/resource_browser/panel.py openstack_dashboard/contrib/developer/resource_browser/urls.py openstack_dashboard/contrib/developer/static/dashboard/developer/developer.module.js openstack_dashboard/contrib/developer/static/dashboard/developer/developer.module.spec.js openstack_dashboard/contrib/developer/static/dashboard/developer/developer.scss openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.controller.js openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.directive.js openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.html openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.module.js openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-builder.scss openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/form-config-modal.html openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/index.html openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/addons-required-feedback.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/array.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/buttons.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/confirmation-dialog.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/example-help.html openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/radios-checkboxes-select.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/sections-fieldsets.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabarray.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/tabs.json openstack_dashboard/contrib/developer/static/dashboard/developer/form-builder/example-forms/text-inputs.json openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.controller.js openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.details.html openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.directive.js openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.module.js openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.scss openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.trace-table.html openstack_dashboard/contrib/developer/static/dashboard/developer/profiler/profiler.tree-node.html openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser-item.controller.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser-item.directive.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser-item.html openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser.controller.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser.directive.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser.html openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser.module.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resource-browser.module.spec.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/resources.html openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/rb-resource-panel/rb-resource-panel.controller.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/rb-resource-panel/rb-resource-panel.directive.js openstack_dashboard/contrib/developer/static/dashboard/developer/resource-browser/rb-resource-panel/rb-resource-panel.html openstack_dashboard/contrib/developer/static/dashboard/developer/theme-preview/theme-preview.directive.js openstack_dashboard/contrib/developer/static/dashboard/developer/theme-preview/theme-preview.html openstack_dashboard/contrib/developer/static/dashboard/developer/theme-preview/theme-preview.module.js openstack_dashboard/contrib/developer/static/dashboard/developer/theme-preview/theme-preview.module.spec.js openstack_dashboard/contrib/developer/static/dashboard/developer/theme-preview/theme-preview.scss openstack_dashboard/contrib/developer/theme_preview/__init__.py openstack_dashboard/contrib/developer/theme_preview/panel.py openstack_dashboard/contrib/developer/theme_preview/urls.py openstack_dashboard/contrib/developer/theme_preview/views.py openstack_dashboard/contrib/developer/theme_preview/templates/theme_preview/index.html openstack_dashboard/dashboards/__init__.py openstack_dashboard/dashboards/admin/__init__.py openstack_dashboard/dashboards/admin/dashboard.py openstack_dashboard/dashboards/admin/aggregates/__init__.py openstack_dashboard/dashboards/admin/aggregates/constants.py openstack_dashboard/dashboards/admin/aggregates/forms.py openstack_dashboard/dashboards/admin/aggregates/panel.py openstack_dashboard/dashboards/admin/aggregates/tables.py openstack_dashboard/dashboards/admin/aggregates/tests.py openstack_dashboard/dashboards/admin/aggregates/urls.py openstack_dashboard/dashboards/admin/aggregates/views.py openstack_dashboard/dashboards/admin/aggregates/workflows.py openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/_update.html openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/create.html openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/index.html openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/manage_hosts.html openstack_dashboard/dashboards/admin/aggregates/templates/aggregates/update.html openstack_dashboard/dashboards/admin/defaults/__init__.py openstack_dashboard/dashboards/admin/defaults/panel.py openstack_dashboard/dashboards/admin/defaults/tables.py openstack_dashboard/dashboards/admin/defaults/tabs.py openstack_dashboard/dashboards/admin/defaults/tests.py openstack_dashboard/dashboards/admin/defaults/urls.py openstack_dashboard/dashboards/admin/defaults/views.py openstack_dashboard/dashboards/admin/defaults/workflows.py openstack_dashboard/dashboards/admin/defaults/templates/defaults/index.html openstack_dashboard/dashboards/admin/flavors/__init__.py openstack_dashboard/dashboards/admin/flavors/constants.py openstack_dashboard/dashboards/admin/flavors/panel.py openstack_dashboard/dashboards/admin/flavors/tables.py openstack_dashboard/dashboards/admin/flavors/tests.py openstack_dashboard/dashboards/admin/flavors/urls.py openstack_dashboard/dashboards/admin/flavors/views.py openstack_dashboard/dashboards/admin/flavors/workflows.py openstack_dashboard/dashboards/admin/flavors/templates/flavors/create.html openstack_dashboard/dashboards/admin/flavors/templates/flavors/update.html openstack_dashboard/dashboards/admin/floating_ips/__init__.py openstack_dashboard/dashboards/admin/floating_ips/forms.py openstack_dashboard/dashboards/admin/floating_ips/panel.py openstack_dashboard/dashboards/admin/floating_ips/tables.py openstack_dashboard/dashboards/admin/floating_ips/tests.py openstack_dashboard/dashboards/admin/floating_ips/urls.py openstack_dashboard/dashboards/admin/floating_ips/views.py openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/_allocate.html openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/allocate.html openstack_dashboard/dashboards/admin/floating_ips/templates/floating_ips/detail.html openstack_dashboard/dashboards/admin/hypervisors/__init__.py openstack_dashboard/dashboards/admin/hypervisors/panel.py openstack_dashboard/dashboards/admin/hypervisors/tables.py openstack_dashboard/dashboards/admin/hypervisors/tabs.py openstack_dashboard/dashboards/admin/hypervisors/tests.py openstack_dashboard/dashboards/admin/hypervisors/urls.py openstack_dashboard/dashboards/admin/hypervisors/views.py openstack_dashboard/dashboards/admin/hypervisors/compute/__init__.py openstack_dashboard/dashboards/admin/hypervisors/compute/forms.py openstack_dashboard/dashboards/admin/hypervisors/compute/tables.py openstack_dashboard/dashboards/admin/hypervisors/compute/tabs.py openstack_dashboard/dashboards/admin/hypervisors/compute/tests.py openstack_dashboard/dashboards/admin/hypervisors/compute/urls.py openstack_dashboard/dashboards/admin/hypervisors/compute/views.py openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/detail.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/index.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/compute/_disable_service.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/compute/_evacuate_host.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/compute/_migrate_host.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/compute/disable_service.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/compute/evacuate_host.html openstack_dashboard/dashboards/admin/hypervisors/templates/hypervisors/compute/migrate_host.html openstack_dashboard/dashboards/admin/images/__init__.py openstack_dashboard/dashboards/admin/images/forms.py openstack_dashboard/dashboards/admin/images/panel.py openstack_dashboard/dashboards/admin/images/tables.py openstack_dashboard/dashboards/admin/images/tests.py openstack_dashboard/dashboards/admin/images/urls.py openstack_dashboard/dashboards/admin/images/views.py openstack_dashboard/dashboards/admin/images/templates/images/_create.html openstack_dashboard/dashboards/admin/images/templates/images/_update.html openstack_dashboard/dashboards/admin/images/templates/images/create.html openstack_dashboard/dashboards/admin/images/templates/images/update.html openstack_dashboard/dashboards/admin/info/__init__.py openstack_dashboard/dashboards/admin/info/constants.py openstack_dashboard/dashboards/admin/info/panel.py openstack_dashboard/dashboards/admin/info/tables.py openstack_dashboard/dashboards/admin/info/tabs.py openstack_dashboard/dashboards/admin/info/tests.py openstack_dashboard/dashboards/admin/info/urls.py openstack_dashboard/dashboards/admin/info/views.py openstack_dashboard/dashboards/admin/info/templates/info/_cell_endpoints_v2.html openstack_dashboard/dashboards/admin/info/templates/info/_cell_endpoints_v3.html openstack_dashboard/dashboards/admin/info/templates/info/_cell_status.html openstack_dashboard/dashboards/admin/info/templates/info/index.html openstack_dashboard/dashboards/admin/instances/__init__.py openstack_dashboard/dashboards/admin/instances/forms.py openstack_dashboard/dashboards/admin/instances/panel.py openstack_dashboard/dashboards/admin/instances/tables.py openstack_dashboard/dashboards/admin/instances/tabs.py openstack_dashboard/dashboards/admin/instances/tests.py openstack_dashboard/dashboards/admin/instances/urls.py openstack_dashboard/dashboards/admin/instances/views.py openstack_dashboard/dashboards/admin/instances/templates/instances/_live_migrate.html openstack_dashboard/dashboards/admin/instances/templates/instances/live_migrate.html openstack_dashboard/dashboards/admin/metadata_defs/__init__.py openstack_dashboard/dashboards/admin/metadata_defs/constants.py openstack_dashboard/dashboards/admin/metadata_defs/forms.py openstack_dashboard/dashboards/admin/metadata_defs/panel.py openstack_dashboard/dashboards/admin/metadata_defs/tables.py openstack_dashboard/dashboards/admin/metadata_defs/tabs.py openstack_dashboard/dashboards/admin/metadata_defs/tests.py openstack_dashboard/dashboards/admin/metadata_defs/urls.py openstack_dashboard/dashboards/admin/metadata_defs/views.py openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/_create.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/_detail_contents.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/_detail_overview.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/_resource_types.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/_update.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/create.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/resource_types.html openstack_dashboard/dashboards/admin/metadata_defs/templates/metadata_defs/update.html openstack_dashboard/dashboards/admin/networks/__init__.py openstack_dashboard/dashboards/admin/networks/forms.py openstack_dashboard/dashboards/admin/networks/panel.py openstack_dashboard/dashboards/admin/networks/tables.py openstack_dashboard/dashboards/admin/networks/tests.py openstack_dashboard/dashboards/admin/networks/urls.py openstack_dashboard/dashboards/admin/networks/views.py openstack_dashboard/dashboards/admin/networks/workflows.py openstack_dashboard/dashboards/admin/networks/agents/__init__.py openstack_dashboard/dashboards/admin/networks/agents/forms.py openstack_dashboard/dashboards/admin/networks/agents/tables.py openstack_dashboard/dashboards/admin/networks/agents/tabs.py openstack_dashboard/dashboards/admin/networks/agents/tests.py openstack_dashboard/dashboards/admin/networks/agents/views.py openstack_dashboard/dashboards/admin/networks/ports/__init__.py openstack_dashboard/dashboards/admin/networks/ports/forms.py openstack_dashboard/dashboards/admin/networks/ports/tables.py openstack_dashboard/dashboards/admin/networks/ports/tabs.py openstack_dashboard/dashboards/admin/networks/ports/tests.py openstack_dashboard/dashboards/admin/networks/ports/urls.py openstack_dashboard/dashboards/admin/networks/ports/views.py openstack_dashboard/dashboards/admin/networks/ports/workflows.py openstack_dashboard/dashboards/admin/networks/ports/extensions/__init__.py openstack_dashboard/dashboards/admin/networks/ports/extensions/allowed_address_pairs/__init__.py openstack_dashboard/dashboards/admin/networks/ports/extensions/allowed_address_pairs/forms.py openstack_dashboard/dashboards/admin/networks/ports/extensions/allowed_address_pairs/views.py openstack_dashboard/dashboards/admin/networks/subnets/__init__.py openstack_dashboard/dashboards/admin/networks/subnets/tables.py openstack_dashboard/dashboards/admin/networks/subnets/tests.py openstack_dashboard/dashboards/admin/networks/subnets/urls.py openstack_dashboard/dashboards/admin/networks/subnets/views.py openstack_dashboard/dashboards/admin/networks/subnets/workflows.py openstack_dashboard/dashboards/admin/networks/templates/networks/_update.html openstack_dashboard/dashboards/admin/networks/templates/networks/update.html openstack_dashboard/dashboards/admin/networks/templates/networks/agents/_add.html openstack_dashboard/dashboards/admin/networks/templates/networks/agents/add.html openstack_dashboard/dashboards/admin/networks/templates/networks/ports/_create.html openstack_dashboard/dashboards/admin/networks/templates/networks/ports/_edit_port_help.html openstack_dashboard/dashboards/admin/networks/templates/networks/ports/create.html openstack_dashboard/dashboards/admin/ngflavors/__init__.py openstack_dashboard/dashboards/admin/ngflavors/panel.py openstack_dashboard/dashboards/admin/ngflavors/urls.py openstack_dashboard/dashboards/admin/ngflavors/views.py openstack_dashboard/dashboards/admin/ngflavors/templates/ngflavors/index.html openstack_dashboard/dashboards/admin/overview/__init__.py openstack_dashboard/dashboards/admin/overview/panel.py openstack_dashboard/dashboards/admin/overview/tests.py openstack_dashboard/dashboards/admin/overview/urls.py openstack_dashboard/dashboards/admin/overview/views.py openstack_dashboard/dashboards/admin/overview/templates/overview/usage.csv openstack_dashboard/dashboards/admin/overview/templates/overview/usage.html openstack_dashboard/dashboards/admin/routers/__init__.py openstack_dashboard/dashboards/admin/routers/forms.py openstack_dashboard/dashboards/admin/routers/panel.py openstack_dashboard/dashboards/admin/routers/tables.py openstack_dashboard/dashboards/admin/routers/tabs.py openstack_dashboard/dashboards/admin/routers/tests.py openstack_dashboard/dashboards/admin/routers/urls.py openstack_dashboard/dashboards/admin/routers/views.py openstack_dashboard/dashboards/admin/routers/extensions/__init__.py openstack_dashboard/dashboards/admin/routers/extensions/extraroutes/__init__.py openstack_dashboard/dashboards/admin/routers/extensions/extraroutes/tables.py openstack_dashboard/dashboards/admin/routers/ports/__init__.py openstack_dashboard/dashboards/admin/routers/ports/tables.py openstack_dashboard/dashboards/admin/routers/ports/tabs.py openstack_dashboard/dashboards/admin/routers/ports/urls.py openstack_dashboard/dashboards/admin/routers/ports/views.py openstack_dashboard/dashboards/admin/routers/templates/routers/update.html openstack_dashboard/dashboards/admin/snapshots/__init__.py openstack_dashboard/dashboards/admin/snapshots/forms.py openstack_dashboard/dashboards/admin/snapshots/panel.py openstack_dashboard/dashboards/admin/snapshots/tables.py openstack_dashboard/dashboards/admin/snapshots/tabs.py openstack_dashboard/dashboards/admin/snapshots/tests.py openstack_dashboard/dashboards/admin/snapshots/urls.py openstack_dashboard/dashboards/admin/snapshots/views.py openstack_dashboard/dashboards/admin/snapshots/templates/snapshots/_update_status.html openstack_dashboard/dashboards/admin/snapshots/templates/snapshots/update_status.html openstack_dashboard/dashboards/admin/trunks/__init__.py openstack_dashboard/dashboards/admin/trunks/panel.py openstack_dashboard/dashboards/admin/trunks/urls.py openstack_dashboard/dashboards/admin/volume_types/__init__.py openstack_dashboard/dashboards/admin/volume_types/forms.py openstack_dashboard/dashboards/admin/volume_types/panel.py openstack_dashboard/dashboards/admin/volume_types/tables.py openstack_dashboard/dashboards/admin/volume_types/tests.py openstack_dashboard/dashboards/admin/volume_types/urls.py openstack_dashboard/dashboards/admin/volume_types/views.py openstack_dashboard/dashboards/admin/volume_types/extras/__init__.py openstack_dashboard/dashboards/admin/volume_types/extras/forms.py openstack_dashboard/dashboards/admin/volume_types/extras/tables.py openstack_dashboard/dashboards/admin/volume_types/extras/tests.py openstack_dashboard/dashboards/admin/volume_types/extras/urls.py openstack_dashboard/dashboards/admin/volume_types/extras/views.py openstack_dashboard/dashboards/admin/volume_types/qos_specs/__init__.py openstack_dashboard/dashboards/admin/volume_types/qos_specs/forms.py openstack_dashboard/dashboards/admin/volume_types/qos_specs/tables.py openstack_dashboard/dashboards/admin/volume_types/qos_specs/tests.py openstack_dashboard/dashboards/admin/volume_types/qos_specs/urls.py openstack_dashboard/dashboards/admin/volume_types/qos_specs/views.py openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_associate_qos_spec.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_create_qos_spec.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_create_volume_type.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_create_volume_type_encryption.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_edit_qos_spec_consumer.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_update_access.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_update_volume_type.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_update_volume_type_encryption.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/_volume_encryption_type_detail.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/associate_qos_spec.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/create_qos_spec.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/create_volume_type.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/create_volume_type_encryption.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/edit_qos_spec_consumer.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/update_access.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/update_volume_type.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/update_volume_type_encryption.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/volume_encryption_type_detail.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/volume_types_tables.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/extras/_create.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/extras/_edit.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/extras/_index.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/extras/create.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/extras/edit.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/extras/index.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/qos_specs/_create.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/qos_specs/_edit.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/qos_specs/_index.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/qos_specs/create.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/qos_specs/edit.html openstack_dashboard/dashboards/admin/volume_types/templates/volume_types/qos_specs/index.html openstack_dashboard/dashboards/admin/volumes/__init__.py openstack_dashboard/dashboards/admin/volumes/forms.py openstack_dashboard/dashboards/admin/volumes/panel.py openstack_dashboard/dashboards/admin/volumes/tables.py openstack_dashboard/dashboards/admin/volumes/tabs.py openstack_dashboard/dashboards/admin/volumes/tests.py openstack_dashboard/dashboards/admin/volumes/urls.py openstack_dashboard/dashboards/admin/volumes/views.py openstack_dashboard/dashboards/admin/volumes/templates/volumes/_manage_volume.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/_migrate_volume.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/_unmanage_volume.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/_update_status.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/manage_volume.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/migrate_volume.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/unmanage_volume.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/update_status.html openstack_dashboard/dashboards/admin/volumes/templates/volumes/volumes_tables.html openstack_dashboard/dashboards/identity/__init__.py openstack_dashboard/dashboards/identity/dashboard.py openstack_dashboard/dashboards/identity/domains/__init__.py openstack_dashboard/dashboards/identity/domains/constants.py openstack_dashboard/dashboards/identity/domains/panel.py openstack_dashboard/dashboards/identity/domains/tables.py openstack_dashboard/dashboards/identity/domains/tests.py openstack_dashboard/dashboards/identity/domains/urls.py openstack_dashboard/dashboards/identity/domains/views.py openstack_dashboard/dashboards/identity/domains/workflows.py openstack_dashboard/dashboards/identity/domains/templates/domains/index.html openstack_dashboard/dashboards/identity/groups/__init__.py openstack_dashboard/dashboards/identity/groups/constants.py openstack_dashboard/dashboards/identity/groups/forms.py openstack_dashboard/dashboards/identity/groups/panel.py openstack_dashboard/dashboards/identity/groups/tables.py openstack_dashboard/dashboards/identity/groups/tests.py openstack_dashboard/dashboards/identity/groups/urls.py openstack_dashboard/dashboards/identity/groups/views.py openstack_dashboard/dashboards/identity/groups/templates/groups/_add_non_member.html openstack_dashboard/dashboards/identity/groups/templates/groups/_create.html openstack_dashboard/dashboards/identity/groups/templates/groups/_update.html openstack_dashboard/dashboards/identity/groups/templates/groups/add_non_member.html openstack_dashboard/dashboards/identity/groups/templates/groups/create.html openstack_dashboard/dashboards/identity/groups/templates/groups/index.html openstack_dashboard/dashboards/identity/groups/templates/groups/manage.html openstack_dashboard/dashboards/identity/groups/templates/groups/update.html openstack_dashboard/dashboards/identity/identity_providers/__init__.py openstack_dashboard/dashboards/identity/identity_providers/forms.py openstack_dashboard/dashboards/identity/identity_providers/panel.py openstack_dashboard/dashboards/identity/identity_providers/tables.py openstack_dashboard/dashboards/identity/identity_providers/tabs.py openstack_dashboard/dashboards/identity/identity_providers/tests.py openstack_dashboard/dashboards/identity/identity_providers/urls.py openstack_dashboard/dashboards/identity/identity_providers/views.py openstack_dashboard/dashboards/identity/identity_providers/protocols/__init__.py openstack_dashboard/dashboards/identity/identity_providers/protocols/forms.py openstack_dashboard/dashboards/identity/identity_providers/protocols/tables.py openstack_dashboard/dashboards/identity/identity_providers/protocols/tests.py openstack_dashboard/dashboards/identity/identity_providers/protocols/urls.py openstack_dashboard/dashboards/identity/identity_providers/protocols/views.py openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_detail_overview.html openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_register.html openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/_update.html openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/register.html openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/update.html openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/_create.html openstack_dashboard/dashboards/identity/identity_providers/templates/identity_providers/protocols/create.html openstack_dashboard/dashboards/identity/mappings/__init__.py openstack_dashboard/dashboards/identity/mappings/forms.py openstack_dashboard/dashboards/identity/mappings/panel.py openstack_dashboard/dashboards/identity/mappings/tables.py openstack_dashboard/dashboards/identity/mappings/tests.py openstack_dashboard/dashboards/identity/mappings/urls.py openstack_dashboard/dashboards/identity/mappings/views.py openstack_dashboard/dashboards/identity/mappings/templates/mappings/_create.html openstack_dashboard/dashboards/identity/mappings/templates/mappings/_update.html openstack_dashboard/dashboards/identity/mappings/templates/mappings/create.html openstack_dashboard/dashboards/identity/mappings/templates/mappings/update.html openstack_dashboard/dashboards/identity/projects/__init__.py openstack_dashboard/dashboards/identity/projects/panel.py openstack_dashboard/dashboards/identity/projects/tables.py openstack_dashboard/dashboards/identity/projects/tests.py openstack_dashboard/dashboards/identity/projects/urls.py openstack_dashboard/dashboards/identity/projects/views.py openstack_dashboard/dashboards/identity/projects/workflows.py openstack_dashboard/dashboards/identity/projects/templates/projects/_common_horizontal_form.html openstack_dashboard/dashboards/identity/projects/templates/projects/_detail_overview.html openstack_dashboard/dashboards/identity/projects/templates/projects/detail.html openstack_dashboard/dashboards/identity/projects/templates/projects/index.html openstack_dashboard/dashboards/identity/projects/templates/projects/usage.html openstack_dashboard/dashboards/identity/roles/__init__.py openstack_dashboard/dashboards/identity/roles/forms.py openstack_dashboard/dashboards/identity/roles/panel.py openstack_dashboard/dashboards/identity/roles/tables.py openstack_dashboard/dashboards/identity/roles/tests.py openstack_dashboard/dashboards/identity/roles/urls.py openstack_dashboard/dashboards/identity/roles/views.py openstack_dashboard/dashboards/identity/roles/templates/roles/_create.html openstack_dashboard/dashboards/identity/roles/templates/roles/_update.html openstack_dashboard/dashboards/identity/roles/templates/roles/create.html openstack_dashboard/dashboards/identity/roles/templates/roles/update.html openstack_dashboard/dashboards/identity/static/dashboard/identity/identity.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/identity.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/domains.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/panel.html openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/details/details.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/details/details.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/details/drawer.html openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/details/overview.controller.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/details/overview.controller.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/domains/details/overview.html openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/groups.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/groups.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/groups/panel.html openstack_dashboard/dashboards/identity/static/dashboard/identity/projects/projects.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/projects/projects.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/panel.html openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/role.schema.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/role.schema.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/roles.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/roles.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/actions.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/create.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/create.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/delete.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/delete.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/edit.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/roles/actions/edit.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/panel.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/users.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/users.module.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/users.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/users.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/actions.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/create.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/create.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/delete.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/delete.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/disable.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/disable.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/enable.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/enable.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/password.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/password.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/update.action.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/update.action.service.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/error.admin-password-incorrect.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/error.default.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/info.create.help.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/info.password.help.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/info.update.help.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/workflow.service.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/actions/workflow/workflow.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/details/details.module.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/details/drawer.html openstack_dashboard/dashboards/identity/static/dashboard/identity/users/details/overview.controller.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/details/overview.controller.spec.js openstack_dashboard/dashboards/identity/static/dashboard/identity/users/details/overview.html openstack_dashboard/dashboards/identity/users/__init__.py openstack_dashboard/dashboards/identity/users/forms.py openstack_dashboard/dashboards/identity/users/panel.py openstack_dashboard/dashboards/identity/users/tables.py openstack_dashboard/dashboards/identity/users/tests.py openstack_dashboard/dashboards/identity/users/urls.py openstack_dashboard/dashboards/identity/users/views.py openstack_dashboard/dashboards/identity/users/templates/users/_change_password.html openstack_dashboard/dashboards/identity/users/templates/users/_create.html openstack_dashboard/dashboards/identity/users/templates/users/_detail_overview.html openstack_dashboard/dashboards/identity/users/templates/users/_update.html openstack_dashboard/dashboards/identity/users/templates/users/change_password.html openstack_dashboard/dashboards/identity/users/templates/users/create.html openstack_dashboard/dashboards/identity/users/templates/users/detail.html openstack_dashboard/dashboards/identity/users/templates/users/index.html openstack_dashboard/dashboards/identity/users/templates/users/update.html openstack_dashboard/dashboards/project/__init__.py openstack_dashboard/dashboards/project/dashboard.py openstack_dashboard/dashboards/project/api_access/__init__.py openstack_dashboard/dashboards/project/api_access/forms.py openstack_dashboard/dashboards/project/api_access/panel.py openstack_dashboard/dashboards/project/api_access/tables.py openstack_dashboard/dashboards/project/api_access/tests.py openstack_dashboard/dashboards/project/api_access/urls.py openstack_dashboard/dashboards/project/api_access/views.py openstack_dashboard/dashboards/project/api_access/templates/api_access/_credentials.html openstack_dashboard/dashboards/project/api_access/templates/api_access/_recreate_credentials.html openstack_dashboard/dashboards/project/api_access/templates/api_access/clouds.yaml.template openstack_dashboard/dashboards/project/api_access/templates/api_access/credentials.html openstack_dashboard/dashboards/project/api_access/templates/api_access/ec2rc.sh.template openstack_dashboard/dashboards/project/api_access/templates/api_access/openrc.sh.template openstack_dashboard/dashboards/project/api_access/templates/api_access/openrc_v2.sh.template openstack_dashboard/dashboards/project/api_access/templates/api_access/recreate_credentials.html openstack_dashboard/dashboards/project/backups/__init__.py openstack_dashboard/dashboards/project/backups/forms.py openstack_dashboard/dashboards/project/backups/panel.py openstack_dashboard/dashboards/project/backups/tables.py openstack_dashboard/dashboards/project/backups/tabs.py openstack_dashboard/dashboards/project/backups/tests.py openstack_dashboard/dashboards/project/backups/urls.py openstack_dashboard/dashboards/project/backups/views.py openstack_dashboard/dashboards/project/backups/templates/backups/_create_backup.html openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html openstack_dashboard/dashboards/project/backups/templates/backups/_restore_backup.html openstack_dashboard/dashboards/project/backups/templates/backups/create_backup.html openstack_dashboard/dashboards/project/backups/templates/backups/restore_backup.html openstack_dashboard/dashboards/project/cg_snapshots/__init__.py openstack_dashboard/dashboards/project/cg_snapshots/forms.py openstack_dashboard/dashboards/project/cg_snapshots/panel.py openstack_dashboard/dashboards/project/cg_snapshots/tables.py openstack_dashboard/dashboards/project/cg_snapshots/tabs.py openstack_dashboard/dashboards/project/cg_snapshots/tests.py openstack_dashboard/dashboards/project/cg_snapshots/urls.py openstack_dashboard/dashboards/project/cg_snapshots/views.py openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_create.html openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_detail_overview.html openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/_update.html openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/create.html openstack_dashboard/dashboards/project/cg_snapshots/templates/cg_snapshots/update.html openstack_dashboard/dashboards/project/cgroups/__init__.py openstack_dashboard/dashboards/project/cgroups/forms.py openstack_dashboard/dashboards/project/cgroups/panel.py openstack_dashboard/dashboards/project/cgroups/tables.py openstack_dashboard/dashboards/project/cgroups/tabs.py openstack_dashboard/dashboards/project/cgroups/tests.py openstack_dashboard/dashboards/project/cgroups/urls.py openstack_dashboard/dashboards/project/cgroups/views.py openstack_dashboard/dashboards/project/cgroups/workflows.py openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_clone_cgroup.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_create_snapshot.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_delete.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_detail_overview.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_remove_vols.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_snapshot_limits.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/_update.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/clone_cgroup.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/create_snapshot.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/delete.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/remove_vols.html openstack_dashboard/dashboards/project/cgroups/templates/cgroups/update.html openstack_dashboard/dashboards/project/containers/__init__.py openstack_dashboard/dashboards/project/containers/panel.py openstack_dashboard/dashboards/project/containers/urls.py openstack_dashboard/dashboards/project/containers/utils.py openstack_dashboard/dashboards/project/containers/views.py openstack_dashboard/dashboards/project/containers/templates/containers/ngindex.html openstack_dashboard/dashboards/project/floating_ips/__init__.py openstack_dashboard/dashboards/project/floating_ips/forms.py openstack_dashboard/dashboards/project/floating_ips/panel.py openstack_dashboard/dashboards/project/floating_ips/tables.py openstack_dashboard/dashboards/project/floating_ips/tests.py openstack_dashboard/dashboards/project/floating_ips/urls.py openstack_dashboard/dashboards/project/floating_ips/views.py openstack_dashboard/dashboards/project/floating_ips/workflows.py openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/_allocate.html openstack_dashboard/dashboards/project/floating_ips/templates/floating_ips/allocate.html openstack_dashboard/dashboards/project/images/__init__.py openstack_dashboard/dashboards/project/images/panel.py openstack_dashboard/dashboards/project/images/tests.py openstack_dashboard/dashboards/project/images/urls.py openstack_dashboard/dashboards/project/images/utils.py openstack_dashboard/dashboards/project/images/views.py openstack_dashboard/dashboards/project/images/images/__init__.py openstack_dashboard/dashboards/project/images/images/forms.py openstack_dashboard/dashboards/project/images/images/tables.py openstack_dashboard/dashboards/project/images/images/tabs.py openstack_dashboard/dashboards/project/images/images/tests.py openstack_dashboard/dashboards/project/images/images/urls.py openstack_dashboard/dashboards/project/images/images/views.py openstack_dashboard/dashboards/project/images/snapshots/__init__.py openstack_dashboard/dashboards/project/images/snapshots/forms.py openstack_dashboard/dashboards/project/images/snapshots/tests.py openstack_dashboard/dashboards/project/images/snapshots/urls.py openstack_dashboard/dashboards/project/images/snapshots/views.py openstack_dashboard/dashboards/project/images/templates/images/images/_create.html openstack_dashboard/dashboards/project/images/templates/images/images/_detail_overview.html openstack_dashboard/dashboards/project/images/templates/images/images/_update.html openstack_dashboard/dashboards/project/images/templates/images/images/create.html openstack_dashboard/dashboards/project/images/templates/images/images/update.html openstack_dashboard/dashboards/project/images/templates/images/snapshots/_create.html openstack_dashboard/dashboards/project/images/templates/images/snapshots/create.html openstack_dashboard/dashboards/project/instances/__init__.py openstack_dashboard/dashboards/project/instances/audit_tables.py openstack_dashboard/dashboards/project/instances/console.py openstack_dashboard/dashboards/project/instances/forms.py openstack_dashboard/dashboards/project/instances/panel.py openstack_dashboard/dashboards/project/instances/tables.py openstack_dashboard/dashboards/project/instances/tabs.py openstack_dashboard/dashboards/project/instances/tests.py openstack_dashboard/dashboards/project/instances/urls.py openstack_dashboard/dashboards/project/instances/utils.py openstack_dashboard/dashboards/project/instances/views.py openstack_dashboard/dashboards/project/instances/templates/instances/_attach_interface.html openstack_dashboard/dashboards/project/instances/templates/instances/_attach_volume.html openstack_dashboard/dashboards/project/instances/templates/instances/_decryptpassword.html openstack_dashboard/dashboards/project/instances/templates/instances/_detach_interface.html openstack_dashboard/dashboards/project/instances/templates/instances/_detach_volume.html openstack_dashboard/dashboards/project/instances/templates/instances/_detail_audit.html openstack_dashboard/dashboards/project/instances/templates/instances/_detail_console.html openstack_dashboard/dashboards/project/instances/templates/instances/_detail_log.html openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html openstack_dashboard/dashboards/project/instances/templates/instances/_flavors_and_quotas.html openstack_dashboard/dashboards/project/instances/templates/instances/_instance_flavor.html openstack_dashboard/dashboards/project/instances/templates/instances/_instance_ips.html openstack_dashboard/dashboards/project/instances/templates/instances/_launch_advanced_help.html openstack_dashboard/dashboards/project/instances/templates/instances/_launch_customize_help.html openstack_dashboard/dashboards/project/instances/templates/instances/_launch_details_help.html openstack_dashboard/dashboards/project/instances/templates/instances/_launch_network_help.html openstack_dashboard/dashboards/project/instances/templates/instances/_launch_network_ports_help.html openstack_dashboard/dashboards/project/instances/templates/instances/_launch_volumes_help.html openstack_dashboard/dashboards/project/instances/templates/instances/_rebuild.html openstack_dashboard/dashboards/project/instances/templates/instances/_update_networks.html openstack_dashboard/dashboards/project/instances/templates/instances/attach_interface.html openstack_dashboard/dashboards/project/instances/templates/instances/attach_volume.html openstack_dashboard/dashboards/project/instances/templates/instances/decryptpassword.html openstack_dashboard/dashboards/project/instances/templates/instances/detach_interface.html openstack_dashboard/dashboards/project/instances/templates/instances/detach_volume.html openstack_dashboard/dashboards/project/instances/templates/instances/rebuild.html openstack_dashboard/dashboards/project/instances/workflows/__init__.py openstack_dashboard/dashboards/project/instances/workflows/create_instance.py openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py openstack_dashboard/dashboards/project/instances/workflows/update_instance.py openstack_dashboard/dashboards/project/key_pairs/__init__.py openstack_dashboard/dashboards/project/key_pairs/forms.py openstack_dashboard/dashboards/project/key_pairs/panel.py openstack_dashboard/dashboards/project/key_pairs/tables.py openstack_dashboard/dashboards/project/key_pairs/tests.py openstack_dashboard/dashboards/project/key_pairs/urls.py openstack_dashboard/dashboards/project/key_pairs/views.py openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/_import.html openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/detail.html openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/download.html openstack_dashboard/dashboards/project/key_pairs/templates/key_pairs/import.html openstack_dashboard/dashboards/project/network_qos/__init__.py openstack_dashboard/dashboards/project/network_qos/panel.py openstack_dashboard/dashboards/project/network_qos/urls.py openstack_dashboard/dashboards/project/network_topology/__init__.py openstack_dashboard/dashboards/project/network_topology/forms.py openstack_dashboard/dashboards/project/network_topology/panel.py openstack_dashboard/dashboards/project/network_topology/tabs.py openstack_dashboard/dashboards/project/network_topology/tests.py openstack_dashboard/dashboards/project/network_topology/urls.py openstack_dashboard/dashboards/project/network_topology/utils.py openstack_dashboard/dashboards/project/network_topology/views.py openstack_dashboard/dashboards/project/network_topology/instances/__init__.py openstack_dashboard/dashboards/project/network_topology/instances/tables.py openstack_dashboard/dashboards/project/network_topology/networks/__init__.py openstack_dashboard/dashboards/project/network_topology/networks/tables.py openstack_dashboard/dashboards/project/network_topology/ports/__init__.py openstack_dashboard/dashboards/project/network_topology/ports/tables.py openstack_dashboard/dashboards/project/network_topology/routers/__init__.py openstack_dashboard/dashboards/project/network_topology/routers/tables.py openstack_dashboard/dashboards/project/network_topology/subnets/__init__.py openstack_dashboard/dashboards/project/network_topology/subnets/tables.py openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_actions_list.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_graph_view.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_post_massage.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_svg_element.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/_topology_view.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/iframe.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/index.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_container.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_device.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_instance.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_net.html openstack_dashboard/dashboards/project/network_topology/templates/network_topology/client_side/_balloon_port.html openstack_dashboard/dashboards/project/networks/__init__.py openstack_dashboard/dashboards/project/networks/forms.py openstack_dashboard/dashboards/project/networks/panel.py openstack_dashboard/dashboards/project/networks/tables.py openstack_dashboard/dashboards/project/networks/tabs.py openstack_dashboard/dashboards/project/networks/tests.py openstack_dashboard/dashboards/project/networks/urls.py openstack_dashboard/dashboards/project/networks/views.py openstack_dashboard/dashboards/project/networks/workflows.py openstack_dashboard/dashboards/project/networks/ports/__init__.py openstack_dashboard/dashboards/project/networks/ports/forms.py openstack_dashboard/dashboards/project/networks/ports/tables.py openstack_dashboard/dashboards/project/networks/ports/tabs.py openstack_dashboard/dashboards/project/networks/ports/tests.py openstack_dashboard/dashboards/project/networks/ports/urls.py openstack_dashboard/dashboards/project/networks/ports/views.py openstack_dashboard/dashboards/project/networks/ports/workflows.py openstack_dashboard/dashboards/project/networks/ports/extensions/__init__.py openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/__init__.py openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/forms.py openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/tables.py openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/tabs.py openstack_dashboard/dashboards/project/networks/ports/extensions/allowed_address_pairs/views.py openstack_dashboard/dashboards/project/networks/subnets/__init__.py openstack_dashboard/dashboards/project/networks/subnets/tables.py openstack_dashboard/dashboards/project/networks/subnets/tabs.py openstack_dashboard/dashboards/project/networks/subnets/tests.py openstack_dashboard/dashboards/project/networks/subnets/urls.py openstack_dashboard/dashboards/project/networks/subnets/utils.py openstack_dashboard/dashboards/project/networks/subnets/views.py openstack_dashboard/dashboards/project/networks/subnets/workflows.py openstack_dashboard/dashboards/project/networks/templates/networks/_detail_overview.html openstack_dashboard/dashboards/project/networks/templates/networks/_network_ips.html openstack_dashboard/dashboards/project/networks/templates/networks/_update.html openstack_dashboard/dashboards/project/networks/templates/networks/detail.html openstack_dashboard/dashboards/project/networks/templates/networks/update.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/_add_addresspair.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/_create.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/_detail_overview.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/_edit_port_help.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/_port_ips.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/add_addresspair.html openstack_dashboard/dashboards/project/networks/templates/networks/ports/create.html openstack_dashboard/dashboards/project/networks/templates/networks/subnets/_detail_overview.html openstack_dashboard/dashboards/project/overview/__init__.py openstack_dashboard/dashboards/project/overview/panel.py openstack_dashboard/dashboards/project/overview/tests.py openstack_dashboard/dashboards/project/overview/urls.py openstack_dashboard/dashboards/project/overview/views.py openstack_dashboard/dashboards/project/overview/templates/overview/usage.csv openstack_dashboard/dashboards/project/overview/templates/overview/usage.html openstack_dashboard/dashboards/project/routers/__init__.py openstack_dashboard/dashboards/project/routers/forms.py openstack_dashboard/dashboards/project/routers/panel.py openstack_dashboard/dashboards/project/routers/tables.py openstack_dashboard/dashboards/project/routers/tabs.py openstack_dashboard/dashboards/project/routers/tests.py openstack_dashboard/dashboards/project/routers/urls.py openstack_dashboard/dashboards/project/routers/views.py openstack_dashboard/dashboards/project/routers/extensions/__init__.py openstack_dashboard/dashboards/project/routers/extensions/extraroutes/__init__.py openstack_dashboard/dashboards/project/routers/extensions/extraroutes/forms.py openstack_dashboard/dashboards/project/routers/extensions/extraroutes/tables.py openstack_dashboard/dashboards/project/routers/extensions/extraroutes/tabs.py openstack_dashboard/dashboards/project/routers/extensions/extraroutes/views.py openstack_dashboard/dashboards/project/routers/ports/__init__.py openstack_dashboard/dashboards/project/routers/ports/forms.py openstack_dashboard/dashboards/project/routers/ports/tables.py openstack_dashboard/dashboards/project/routers/ports/tabs.py openstack_dashboard/dashboards/project/routers/ports/urls.py openstack_dashboard/dashboards/project/routers/ports/views.py openstack_dashboard/dashboards/project/routers/templates/routers/_create.html openstack_dashboard/dashboards/project/routers/templates/routers/_detail_overview.html openstack_dashboard/dashboards/project/routers/templates/routers/_update.html openstack_dashboard/dashboards/project/routers/templates/routers/create.html openstack_dashboard/dashboards/project/routers/templates/routers/update.html openstack_dashboard/dashboards/project/routers/templates/routers/extensions/routerroutes/_create.html openstack_dashboard/dashboards/project/routers/templates/routers/extensions/routerroutes/create.html openstack_dashboard/dashboards/project/routers/templates/routers/ports/_create.html openstack_dashboard/dashboards/project/routers/templates/routers/ports/_setgateway.html openstack_dashboard/dashboards/project/routers/templates/routers/ports/create.html openstack_dashboard/dashboards/project/routers/templates/routers/ports/setgateway.html openstack_dashboard/dashboards/project/security_groups/__init__.py openstack_dashboard/dashboards/project/security_groups/forms.py openstack_dashboard/dashboards/project/security_groups/panel.py openstack_dashboard/dashboards/project/security_groups/tables.py openstack_dashboard/dashboards/project/security_groups/tests.py openstack_dashboard/dashboards/project/security_groups/urls.py openstack_dashboard/dashboards/project/security_groups/views.py openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_add_rule.html openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_create.html openstack_dashboard/dashboards/project/security_groups/templates/security_groups/_update.html openstack_dashboard/dashboards/project/security_groups/templates/security_groups/add_rule.html openstack_dashboard/dashboards/project/security_groups/templates/security_groups/create.html openstack_dashboard/dashboards/project/security_groups/templates/security_groups/detail.html openstack_dashboard/dashboards/project/security_groups/templates/security_groups/update.html openstack_dashboard/dashboards/project/snapshots/__init__.py openstack_dashboard/dashboards/project/snapshots/forms.py openstack_dashboard/dashboards/project/snapshots/panel.py openstack_dashboard/dashboards/project/snapshots/tables.py openstack_dashboard/dashboards/project/snapshots/tabs.py openstack_dashboard/dashboards/project/snapshots/tests.py openstack_dashboard/dashboards/project/snapshots/urls.py openstack_dashboard/dashboards/project/snapshots/views.py openstack_dashboard/dashboards/project/snapshots/templates/snapshots/_detail_overview.html openstack_dashboard/dashboards/project/snapshots/templates/snapshots/_update.html openstack_dashboard/dashboards/project/snapshots/templates/snapshots/update.html openstack_dashboard/dashboards/project/static/dashboard/project/project.module.js openstack_dashboard/dashboards/project/static/dashboard/project/project.module.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/_containers.scss openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers-model.service.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers-model.service.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers.module.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/containers.module.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/create-container.help.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/create-folder-modal.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/delete-objects-modal.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/delete-objects.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/delete-objects.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/edit-object-modal.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/file-change-directive.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/file-change-directive.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-details-modal.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/object-name-exists.directive.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-batch-actions.service.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-batch-actions.service.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects-row-actions.service.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/objects.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/select-container.html openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/containers/upload-object-modal.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/workflow.module.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/workflow.module.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-modal.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-modal.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-modal.service.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-modal.service.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-model.service.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-wizard.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-wizard.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance-workflow.service.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance.module.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/launch-instance.module.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/configuration/configuration.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/details/details.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/details/details.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/details/details.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/details/details.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/flavor/flavor.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/flavor/flavor.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/flavor/flavor.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/flavor/flavor.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/create-keypair.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/import-keypair.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/import-keypair.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/import-keypair.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair-details.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/keypair/keypair.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/metadata/metadata.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/network/network.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/networkports/ports.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/scheduler-hints/scheduler-hints.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/scheduler-hints/scheduler-hints.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/scheduler-hints/scheduler-hints.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/scheduler-hints/scheduler-hints.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/security-groups/security-group-details.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/security-groups/security-groups.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/security-groups/security-groups.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/security-groups/security-groups.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/security-groups/security-groups.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/server-groups/server-group-details.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/server-groups/server-groups.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/server-groups/server-groups.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/server-groups/server-groups.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/server-groups/server-groups.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source-details.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.controller.spec.js openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.help.html openstack_dashboard/dashboards/project/static/dashboard/project/workflow/launch-instance/source/source.html openstack_dashboard/dashboards/project/trunks/__init__.py openstack_dashboard/dashboards/project/trunks/panel.py openstack_dashboard/dashboards/project/trunks/urls.py openstack_dashboard/dashboards/project/volumes/__init__.py openstack_dashboard/dashboards/project/volumes/forms.py openstack_dashboard/dashboards/project/volumes/panel.py openstack_dashboard/dashboards/project/volumes/tables.py openstack_dashboard/dashboards/project/volumes/tabs.py openstack_dashboard/dashboards/project/volumes/tests.py openstack_dashboard/dashboards/project/volumes/urls.py openstack_dashboard/dashboards/project/volumes/utils.py openstack_dashboard/dashboards/project/volumes/views.py openstack_dashboard/dashboards/project/volumes/templates/volumes/_accept_transfer.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_attach.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_create.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_snapshot.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_create_transfer.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_detail_overview.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_encryption_detail_overview.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_extend_limits.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_limits.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_retype.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_show_transfer.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_snapshot_limits.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_update.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_upload_to_image.html openstack_dashboard/dashboards/project/volumes/templates/volumes/_volume_limits.html openstack_dashboard/dashboards/project/volumes/templates/volumes/accept_transfer.html openstack_dashboard/dashboards/project/volumes/templates/volumes/attach.html openstack_dashboard/dashboards/project/volumes/templates/volumes/create.html openstack_dashboard/dashboards/project/volumes/templates/volumes/create_snapshot.html openstack_dashboard/dashboards/project/volumes/templates/volumes/create_transfer.html openstack_dashboard/dashboards/project/volumes/templates/volumes/download_transfer_creds.html openstack_dashboard/dashboards/project/volumes/templates/volumes/encryption_detail.html openstack_dashboard/dashboards/project/volumes/templates/volumes/extend.html openstack_dashboard/dashboards/project/volumes/templates/volumes/retype.html openstack_dashboard/dashboards/project/volumes/templates/volumes/show_transfer.html openstack_dashboard/dashboards/project/volumes/templates/volumes/update.html openstack_dashboard/dashboards/project/volumes/templates/volumes/upload_to_image.html openstack_dashboard/dashboards/settings/__init__.py openstack_dashboard/dashboards/settings/dashboard.py openstack_dashboard/dashboards/settings/password/__init__.py openstack_dashboard/dashboards/settings/password/forms.py openstack_dashboard/dashboards/settings/password/panel.py openstack_dashboard/dashboards/settings/password/tests.py openstack_dashboard/dashboards/settings/password/urls.py openstack_dashboard/dashboards/settings/password/views.py openstack_dashboard/dashboards/settings/password/templates/password/_change.html openstack_dashboard/dashboards/settings/password/templates/password/change.html openstack_dashboard/dashboards/settings/user/__init__.py openstack_dashboard/dashboards/settings/user/forms.py openstack_dashboard/dashboards/settings/user/panel.py openstack_dashboard/dashboards/settings/user/tests.py openstack_dashboard/dashboards/settings/user/urls.py openstack_dashboard/dashboards/settings/user/views.py openstack_dashboard/dashboards/settings/user/templates/user/_settings.html openstack_dashboard/dashboards/settings/user/templates/user/settings.html openstack_dashboard/django_pyscss_fix/__init__.py openstack_dashboard/enabled/_1000_project.py openstack_dashboard/enabled/_1010_compute_panel_group.py openstack_dashboard/enabled/_1020_project_overview_panel.py openstack_dashboard/enabled/_1030_project_instances_panel.py openstack_dashboard/enabled/_1050_project_images_panel.py openstack_dashboard/enabled/_1080_project_key_pairs_panel.py openstack_dashboard/enabled/_1090_project_api_access_panel.py openstack_dashboard/enabled/_1310_volumes_panel_group.py openstack_dashboard/enabled/_1320_project_volumes_panel.py openstack_dashboard/enabled/_1330_project_backups_panel.py openstack_dashboard/enabled/_1330_project_snapshots_panel.py openstack_dashboard/enabled/_1340_project_consistency_groups_panel.py openstack_dashboard/enabled/_1350_project_cg_snapshots_panel.py openstack_dashboard/enabled/_1410_network_panel_group.py openstack_dashboard/enabled/_1420_project_network_topology_panel.py openstack_dashboard/enabled/_1430_project_network_panel.py openstack_dashboard/enabled/_1440_project_routers_panel.py openstack_dashboard/enabled/_1480_security_groups_panel.py openstack_dashboard/enabled/_1490_project_floating_ips_panel.py openstack_dashboard/enabled/_1500_project_trunks_panel.py openstack_dashboard/enabled/_1510_project_network_qos_panel.py openstack_dashboard/enabled/_1910_object_store_panel_group.py openstack_dashboard/enabled/_1920_project_containers_panel.py openstack_dashboard/enabled/_2000_admin.py openstack_dashboard/enabled/_2010_admin_overview_panel.py openstack_dashboard/enabled/_2110_admin_compute_panel_group.py openstack_dashboard/enabled/_2120_admin_hypervisors_panel.py openstack_dashboard/enabled/_2130_admin_aggregates_panel.py openstack_dashboard/enabled/_2140_admin_instances_panel.py openstack_dashboard/enabled/_2150_admin_flavors_panel.py openstack_dashboard/enabled/_2160_admin_images_panel.py openstack_dashboard/enabled/_2210_admin_volume_panel_group.py openstack_dashboard/enabled/_2220_admin_volumes_panel.py openstack_dashboard/enabled/_2230_admin_snapshots_panel.py openstack_dashboard/enabled/_2240_admin_volume_types_panel.py openstack_dashboard/enabled/_2300_admin_network_panel_group.py openstack_dashboard/enabled/_2310_admin_networks_panel.py openstack_dashboard/enabled/_2320_admin_routers_panel.py openstack_dashboard/enabled/_2330_admin_floating_ips_panel.py openstack_dashboard/enabled/_2340_admin_trunks_panel.py openstack_dashboard/enabled/_2810_admin_system_panel_group.py openstack_dashboard/enabled/_2820_admin_defaults_panel.py openstack_dashboard/enabled/_2830_admin_metadata_defs_panel.py openstack_dashboard/enabled/_2840_admin_info_panel.py openstack_dashboard/enabled/_3000_identity.py openstack_dashboard/enabled/_3010_identity_domains_panel.py openstack_dashboard/enabled/_3020_identity_projects_panel.py openstack_dashboard/enabled/_3030_identity_users_panel.py openstack_dashboard/enabled/_3040_identity_groups_panel.py openstack_dashboard/enabled/_3050_identity_roles_panel.py openstack_dashboard/enabled/_3060_federation_panel_group.py openstack_dashboard/enabled/_3070_identity_identity_providers_panel.py openstack_dashboard/enabled/_3080_identity_mappings_panel.py openstack_dashboard/enabled/_5000_settings.py openstack_dashboard/enabled/_50_admin_add_panel.py.example openstack_dashboard/enabled/_60_admin_remove_panel.py.example openstack_dashboard/enabled/_70_admin_default_panel.py.example openstack_dashboard/enabled/_80_admin_add_panel_group.py.example openstack_dashboard/enabled/_90_admin_add_panel_to_group.py.example openstack_dashboard/enabled/__init__.py openstack_dashboard/local/__init__.py openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/enabled/_50_settings.py.example openstack_dashboard/local/enabled/__init__.py openstack_dashboard/local/local_settings.d/_10_set_custom_theme.py.example openstack_dashboard/local/local_settings.d/_11_toggle_angular_features.py.example openstack_dashboard/local/local_settings.d/_2010_integration_tests_deprecated.py.example openstack_dashboard/local/local_settings.d/_20_integration_tests_scaffolds.py.example openstack_dashboard/local/local_settings.d/_9030_profiler_settings.py.example openstack_dashboard/locale/as/LC_MESSAGES/django.po openstack_dashboard/locale/bn_IN/LC_MESSAGES/django.po openstack_dashboard/locale/brx/LC_MESSAGES/django.po openstack_dashboard/locale/cs/LC_MESSAGES/django.po openstack_dashboard/locale/cs/LC_MESSAGES/djangojs.po openstack_dashboard/locale/de/LC_MESSAGES/django.po openstack_dashboard/locale/de/LC_MESSAGES/djangojs.po openstack_dashboard/locale/en_AU/LC_MESSAGES/django.po openstack_dashboard/locale/en_AU/LC_MESSAGES/djangojs.po openstack_dashboard/locale/en_GB/LC_MESSAGES/django.po openstack_dashboard/locale/en_GB/LC_MESSAGES/djangojs.po openstack_dashboard/locale/eo/LC_MESSAGES/django.po openstack_dashboard/locale/eo/LC_MESSAGES/djangojs.po openstack_dashboard/locale/es/LC_MESSAGES/django.po openstack_dashboard/locale/es/LC_MESSAGES/djangojs.po openstack_dashboard/locale/fr/LC_MESSAGES/django.po openstack_dashboard/locale/fr/LC_MESSAGES/djangojs.po openstack_dashboard/locale/gu/LC_MESSAGES/django.po openstack_dashboard/locale/hi/LC_MESSAGES/django.po openstack_dashboard/locale/id/LC_MESSAGES/django.po openstack_dashboard/locale/id/LC_MESSAGES/djangojs.po openstack_dashboard/locale/it/LC_MESSAGES/django.po openstack_dashboard/locale/it/LC_MESSAGES/djangojs.po openstack_dashboard/locale/ja/LC_MESSAGES/django.po openstack_dashboard/locale/ja/LC_MESSAGES/djangojs.po openstack_dashboard/locale/kn/LC_MESSAGES/django.po openstack_dashboard/locale/ko_KR/LC_MESSAGES/django.po openstack_dashboard/locale/ko_KR/LC_MESSAGES/djangojs.po openstack_dashboard/locale/kok/LC_MESSAGES/django.po openstack_dashboard/locale/ks/LC_MESSAGES/django.po openstack_dashboard/locale/mai/LC_MESSAGES/django.po openstack_dashboard/locale/mni/LC_MESSAGES/django.po openstack_dashboard/locale/mr/LC_MESSAGES/django.po openstack_dashboard/locale/ne/LC_MESSAGES/django.po openstack_dashboard/locale/nl_NL/LC_MESSAGES/django.po openstack_dashboard/locale/pa_IN/LC_MESSAGES/django.po openstack_dashboard/locale/pl_PL/LC_MESSAGES/django.po openstack_dashboard/locale/pt_BR/LC_MESSAGES/django.po openstack_dashboard/locale/pt_BR/LC_MESSAGES/djangojs.po openstack_dashboard/locale/ru/LC_MESSAGES/django.po openstack_dashboard/locale/ru/LC_MESSAGES/djangojs.po openstack_dashboard/locale/sr/LC_MESSAGES/django.po openstack_dashboard/locale/ta/LC_MESSAGES/django.po openstack_dashboard/locale/tr_TR/LC_MESSAGES/django.po openstack_dashboard/locale/tr_TR/LC_MESSAGES/djangojs.po openstack_dashboard/locale/ur/LC_MESSAGES/django.po openstack_dashboard/locale/zh_CN/LC_MESSAGES/django.po openstack_dashboard/locale/zh_CN/LC_MESSAGES/djangojs.po openstack_dashboard/locale/zh_TW/LC_MESSAGES/django.po openstack_dashboard/locale/zh_TW/LC_MESSAGES/djangojs.po openstack_dashboard/management/__init__.py openstack_dashboard/management/commands/__init__.py openstack_dashboard/management/commands/apache_vhost.conf.template openstack_dashboard/management/commands/extract_messages.py openstack_dashboard/management/commands/horizon.wsgi.template openstack_dashboard/management/commands/make_web_conf.py openstack_dashboard/management/commands/migrate_settings.py openstack_dashboard/management/commands/update_catalog.py openstack_dashboard/static/app/_app.scss openstack_dashboard/static/app/app.module.js openstack_dashboard/static/app/app.module.spec.js openstack_dashboard/static/app/redirect.controller.js openstack_dashboard/static/app/redirect.controller.spec.js openstack_dashboard/static/app/core/_core.scss openstack_dashboard/static/app/core/core-constants.module.js openstack_dashboard/static/app/core/core.module.js openstack_dashboard/static/app/core/core.module.spec.js openstack_dashboard/static/app/core/cloud-services/cloud-services.module.js openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-cinder-extensions.directive.spec.js openstack_dashboard/static/app/core/cloud-services/hz-if-neutron-extensions.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-neutron-extensions.directive.spec.js openstack_dashboard/static/app/core/cloud-services/hz-if-nova-extensions.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-nova-extensions.directive.spec.js openstack_dashboard/static/app/core/cloud-services/hz-if-policies.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-policies.directive.spec.js openstack_dashboard/static/app/core/cloud-services/hz-if-services.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-services.directive.spec.js openstack_dashboard/static/app/core/cloud-services/hz-if-settings.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-settings.directive.spec.js openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.js openstack_dashboard/static/app/core/cloud-services/hz-if-version.directive.spec.js openstack_dashboard/static/app/core/conf/conf.module.js openstack_dashboard/static/app/core/flavors/flavors.module.js openstack_dashboard/static/app/core/flavors/flavors.service.js openstack_dashboard/static/app/core/flavors/flavors.service.spec.js openstack_dashboard/static/app/core/flavors/panel.html openstack_dashboard/static/app/core/flavors/summary.html openstack_dashboard/static/app/core/flavors/actions/actions.module.js openstack_dashboard/static/app/core/flavors/actions/delete-flavor.service.js openstack_dashboard/static/app/core/flavors/actions/delete-flavor.service.spec.js openstack_dashboard/static/app/core/flavors/actions/update-metadata.action.service.js openstack_dashboard/static/app/core/flavors/actions/update-metadata.action.service.spec.js openstack_dashboard/static/app/core/images/_images.scss openstack_dashboard/static/app/core/images/admin-panel.html openstack_dashboard/static/app/core/images/images.module.js openstack_dashboard/static/app/core/images/images.module.spec.js openstack_dashboard/static/app/core/images/images.service.js openstack_dashboard/static/app/core/images/images.service.spec.js openstack_dashboard/static/app/core/images/panel.html openstack_dashboard/static/app/core/images/summary.controller.js openstack_dashboard/static/app/core/images/summary.controller.spec.js openstack_dashboard/static/app/core/images/actions/actions.module.js openstack_dashboard/static/app/core/images/actions/actions.module.spec.js openstack_dashboard/static/app/core/images/actions/create-volume.service.js openstack_dashboard/static/app/core/images/actions/create-volume.service.spec.js openstack_dashboard/static/app/core/images/actions/create.action.service.js openstack_dashboard/static/app/core/images/actions/create.action.service.spec.js openstack_dashboard/static/app/core/images/actions/create.workflow.service.js openstack_dashboard/static/app/core/images/actions/create.workflow.service.spec.js openstack_dashboard/static/app/core/images/actions/delete-image.service.js openstack_dashboard/static/app/core/images/actions/delete-image.service.spec.js openstack_dashboard/static/app/core/images/actions/edit.action.service.js openstack_dashboard/static/app/core/images/actions/edit.action.service.spec.js openstack_dashboard/static/app/core/images/actions/edit.workflow.service.js openstack_dashboard/static/app/core/images/actions/edit.workflow.service.spec.js openstack_dashboard/static/app/core/images/actions/launch-instance.service.js openstack_dashboard/static/app/core/images/actions/launch-instance.service.spec.js openstack_dashboard/static/app/core/images/actions/update-metadata.action.service.js openstack_dashboard/static/app/core/images/actions/update-metadata.action.service.spec.js openstack_dashboard/static/app/core/images/details/details.module.js openstack_dashboard/static/app/core/images/details/drawer.html openstack_dashboard/static/app/core/images/details/overview.controller.js openstack_dashboard/static/app/core/images/details/overview.controller.spec.js openstack_dashboard/static/app/core/images/details/overview.html openstack_dashboard/static/app/core/images/filters/image-visibility.filter.js openstack_dashboard/static/app/core/images/filters/image-visibility.filter.spec.js openstack_dashboard/static/app/core/images/steps/create-image/create-image.controller.js openstack_dashboard/static/app/core/images/steps/create-image/create-image.controller.spec.js openstack_dashboard/static/app/core/images/steps/create-image/create-image.help.html openstack_dashboard/static/app/core/images/steps/create-image/create-image.html openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.js openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.controller.spec.js openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.help.html openstack_dashboard/static/app/core/images/steps/create-volume/create-volume.html openstack_dashboard/static/app/core/images/steps/edit-image/edit-image.controller.js openstack_dashboard/static/app/core/images/steps/edit-image/edit-image.controller.spec.js openstack_dashboard/static/app/core/images/steps/edit-image/edit-image.help.html openstack_dashboard/static/app/core/images/steps/edit-image/edit-image.html openstack_dashboard/static/app/core/images/steps/update-metadata/update-metadata.controller.js openstack_dashboard/static/app/core/images/steps/update-metadata/update-metadata.controller.spec.js openstack_dashboard/static/app/core/images/steps/update-metadata/update-metadata.help.html openstack_dashboard/static/app/core/images/steps/update-metadata/update-metadata.html openstack_dashboard/static/app/core/images/workflows/create-volume.service.js openstack_dashboard/static/app/core/images/workflows/create-volume.service.spec.js openstack_dashboard/static/app/core/keypairs/_keypairs.scss openstack_dashboard/static/app/core/keypairs/keypair.controller.js openstack_dashboard/static/app/core/keypairs/keypair.controller.spec.js openstack_dashboard/static/app/core/keypairs/keypairs.module.js openstack_dashboard/static/app/core/keypairs/keypairs.module.spec.js openstack_dashboard/static/app/core/keypairs/keypairs.service.js openstack_dashboard/static/app/core/keypairs/keypairs.service.spec.js openstack_dashboard/static/app/core/keypairs/panel.html openstack_dashboard/static/app/core/keypairs/actions/actions.module.js openstack_dashboard/static/app/core/keypairs/actions/create.description.html openstack_dashboard/static/app/core/keypairs/actions/create.service.js openstack_dashboard/static/app/core/keypairs/actions/create.service.spec.js openstack_dashboard/static/app/core/keypairs/actions/delete.service.js openstack_dashboard/static/app/core/keypairs/actions/delete.service.spec.js openstack_dashboard/static/app/core/keypairs/actions/import.description.html openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.js openstack_dashboard/static/app/core/keypairs/actions/import.public-key.controller.spec.js openstack_dashboard/static/app/core/keypairs/actions/import.public-key.html openstack_dashboard/static/app/core/keypairs/actions/import.service.js openstack_dashboard/static/app/core/keypairs/actions/import.service.spec.js openstack_dashboard/static/app/core/keypairs/details/details.controller.js openstack_dashboard/static/app/core/keypairs/details/details.controller.spec.js openstack_dashboard/static/app/core/keypairs/details/details.html openstack_dashboard/static/app/core/keypairs/details/details.module.js openstack_dashboard/static/app/core/keypairs/details/drawer.html openstack_dashboard/static/app/core/metadata/metadata.module.js openstack_dashboard/static/app/core/metadata/metadata.module.spec.js openstack_dashboard/static/app/core/metadata/metadata.service.js openstack_dashboard/static/app/core/metadata/metadata.service.spec.js openstack_dashboard/static/app/core/metadata/modal/modal-helper.controller.js openstack_dashboard/static/app/core/metadata/modal/modal-helper.controller.spec.js openstack_dashboard/static/app/core/metadata/modal/modal.controller.js openstack_dashboard/static/app/core/metadata/modal/modal.controller.spec.js openstack_dashboard/static/app/core/metadata/modal/modal.html openstack_dashboard/static/app/core/metadata/modal/modal.module.js openstack_dashboard/static/app/core/metadata/modal/modal.module.spec.js openstack_dashboard/static/app/core/metadata/modal/modal.service.js openstack_dashboard/static/app/core/metadata/modal/modal.service.spec.js openstack_dashboard/static/app/core/network_qos/panel.html openstack_dashboard/static/app/core/network_qos/qos.module.js openstack_dashboard/static/app/core/network_qos/qos.module.spec.js openstack_dashboard/static/app/core/network_qos/qos.service.js openstack_dashboard/static/app/core/network_qos/qos.service.spec.js openstack_dashboard/static/app/core/network_qos/details/details.module.js openstack_dashboard/static/app/core/network_qos/details/drawer.html openstack_dashboard/static/app/core/network_qos/details/overview.controller.js openstack_dashboard/static/app/core/network_qos/details/overview.controller.spec.js openstack_dashboard/static/app/core/network_qos/details/overview.html openstack_dashboard/static/app/core/openstack-service-api/cinder-extensions.service.js openstack_dashboard/static/app/core/openstack-service-api/cinder-extensions.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/cinder.service.js openstack_dashboard/static/app/core/openstack-service-api/cinder.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/common-test.mock.js openstack_dashboard/static/app/core/openstack-service-api/extensions.service.js openstack_dashboard/static/app/core/openstack-service-api/extensions.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/glance.service.js openstack_dashboard/static/app/core/openstack-service-api/glance.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/keystone.service.js openstack_dashboard/static/app/core/openstack-service-api/keystone.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/network.service.js openstack_dashboard/static/app/core/openstack-service-api/network.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/neutron-extensions.service.js openstack_dashboard/static/app/core/openstack-service-api/neutron-extensions.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/neutron.service.js openstack_dashboard/static/app/core/openstack-service-api/neutron.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/nova-extensions.service.js openstack_dashboard/static/app/core/openstack-service-api/nova-extensions.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/nova.service.js openstack_dashboard/static/app/core/openstack-service-api/nova.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/openstack-service-api.module.js openstack_dashboard/static/app/core/openstack-service-api/policy.service.js openstack_dashboard/static/app/core/openstack-service-api/policy.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/security-group.service.js openstack_dashboard/static/app/core/openstack-service-api/security-group.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/service-catalog.service.js openstack_dashboard/static/app/core/openstack-service-api/service-catalog.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/settings.service.js openstack_dashboard/static/app/core/openstack-service-api/settings.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/swift.service.js openstack_dashboard/static/app/core/openstack-service-api/swift.service.spec.js openstack_dashboard/static/app/core/openstack-service-api/user-session.service.js openstack_dashboard/static/app/core/openstack-service-api/user-session.service.spec.js openstack_dashboard/static/app/core/trunks/panel.html openstack_dashboard/static/app/core/trunks/summary.html openstack_dashboard/static/app/core/trunks/trunks.module.js openstack_dashboard/static/app/core/trunks/trunks.module.spec.js openstack_dashboard/static/app/core/trunks/trunks.service.js openstack_dashboard/static/app/core/trunks/trunks.service.spec.js openstack_dashboard/static/app/core/trunks/actions/actions.module.js openstack_dashboard/static/app/core/trunks/actions/actions.module.spec.js openstack_dashboard/static/app/core/trunks/actions/create.action.service.js openstack_dashboard/static/app/core/trunks/actions/create.action.service.spec.js openstack_dashboard/static/app/core/trunks/actions/create.workflow.service.js openstack_dashboard/static/app/core/trunks/actions/delete.action.service.js openstack_dashboard/static/app/core/trunks/actions/delete.action.service.spec.js openstack_dashboard/static/app/core/trunks/actions/edit.action.service.js openstack_dashboard/static/app/core/trunks/actions/edit.action.service.spec.js openstack_dashboard/static/app/core/trunks/actions/edit.workflow.service.js openstack_dashboard/static/app/core/trunks/actions/ports-extra.service.js openstack_dashboard/static/app/core/trunks/actions/ports-extra.service.spec.js openstack_dashboard/static/app/core/trunks/details/details.module.js openstack_dashboard/static/app/core/trunks/details/overview.controller.js openstack_dashboard/static/app/core/trunks/details/overview.controller.spec.js openstack_dashboard/static/app/core/trunks/details/overview.html openstack_dashboard/static/app/core/trunks/steps/trunk-details.controller.js openstack_dashboard/static/app/core/trunks/steps/trunk-details.controller.spec.js openstack_dashboard/static/app/core/trunks/steps/trunk-details.help.html openstack_dashboard/static/app/core/trunks/steps/trunk-details.html openstack_dashboard/static/app/core/trunks/steps/trunk-parent-port.controller.js openstack_dashboard/static/app/core/trunks/steps/trunk-parent-port.controller.spec.js openstack_dashboard/static/app/core/trunks/steps/trunk-parent-port.help.html openstack_dashboard/static/app/core/trunks/steps/trunk-parent-port.html openstack_dashboard/static/app/core/trunks/steps/trunk-subports.controller.js openstack_dashboard/static/app/core/trunks/steps/trunk-subports.controller.spec.js openstack_dashboard/static/app/core/trunks/steps/trunk-subports.help.html openstack_dashboard/static/app/core/trunks/steps/trunk-subports.html openstack_dashboard/static/app/core/workflow/decorator.service.js openstack_dashboard/static/app/core/workflow/decorator.service.spec.js openstack_dashboard/static/app/core/workflow/workflow.module.js openstack_dashboard/static/app/core/workflow/workflow.module.spec.js openstack_dashboard/static/app/core/workflow/workflow.service.js openstack_dashboard/static/app/resources/resources.module.js openstack_dashboard/static/app/tech-debt/hz-namespace-resource-type-form.controller.js openstack_dashboard/static/app/tech-debt/hz-namespace-resource-type-form.controller.spec.js openstack_dashboard/static/app/tech-debt/image-form.controller.js openstack_dashboard/static/app/tech-debt/image-form.controller.spec.js openstack_dashboard/static/app/tech-debt/tech-debt.module.js openstack_dashboard/static/app/tech-debt/tech-debt.module.spec.js openstack_dashboard/static/dashboard/img/alarm-gray.gif openstack_dashboard/static/dashboard/img/alarm-gray.svg openstack_dashboard/static/dashboard/img/alarm-green.svg openstack_dashboard/static/dashboard/img/alarm-red.svg openstack_dashboard/static/dashboard/img/apple-touch-icon.png openstack_dashboard/static/dashboard/img/config-gray.gif openstack_dashboard/static/dashboard/img/config-gray.svg openstack_dashboard/static/dashboard/img/config-green.svg openstack_dashboard/static/dashboard/img/config-red.svg openstack_dashboard/static/dashboard/img/db-gray.gif openstack_dashboard/static/dashboard/img/db-gray.svg openstack_dashboard/static/dashboard/img/db-green.svg openstack_dashboard/static/dashboard/img/db-red.svg openstack_dashboard/static/dashboard/img/drag.png openstack_dashboard/static/dashboard/img/favicon.ico openstack_dashboard/static/dashboard/img/firewall-gray.gif openstack_dashboard/static/dashboard/img/firewall-gray.svg openstack_dashboard/static/dashboard/img/firewall-green.svg openstack_dashboard/static/dashboard/img/firewall-red.svg openstack_dashboard/static/dashboard/img/flavor-gray.gif openstack_dashboard/static/dashboard/img/flavor-gray.svg openstack_dashboard/static/dashboard/img/flavor-green.svg openstack_dashboard/static/dashboard/img/flavor-red.svg openstack_dashboard/static/dashboard/img/floatingip-gray.gif openstack_dashboard/static/dashboard/img/floatingip-gray.svg openstack_dashboard/static/dashboard/img/floatingip-green.svg openstack_dashboard/static/dashboard/img/floatingip-red.svg openstack_dashboard/static/dashboard/img/image-gray.gif openstack_dashboard/static/dashboard/img/image-gray.svg openstack_dashboard/static/dashboard/img/image-green.svg openstack_dashboard/static/dashboard/img/image-red.svg openstack_dashboard/static/dashboard/img/keypair-gray.gif openstack_dashboard/static/dashboard/img/keypair-gray.svg openstack_dashboard/static/dashboard/img/keypair-green.svg openstack_dashboard/static/dashboard/img/keypair-red.svg openstack_dashboard/static/dashboard/img/lb-gray.gif openstack_dashboard/static/dashboard/img/lb-gray.svg openstack_dashboard/static/dashboard/img/lb-green.svg openstack_dashboard/static/dashboard/img/lb-red.svg openstack_dashboard/static/dashboard/img/logo-splash.svg openstack_dashboard/static/dashboard/img/logo.svg openstack_dashboard/static/dashboard/img/network-gray.gif openstack_dashboard/static/dashboard/img/network-gray.svg openstack_dashboard/static/dashboard/img/network-green.svg openstack_dashboard/static/dashboard/img/network-red.svg openstack_dashboard/static/dashboard/img/policy-gray.gif openstack_dashboard/static/dashboard/img/policy-gray.svg openstack_dashboard/static/dashboard/img/policy-green.svg openstack_dashboard/static/dashboard/img/policy-red.svg openstack_dashboard/static/dashboard/img/port-gray.gif openstack_dashboard/static/dashboard/img/port-gray.svg openstack_dashboard/static/dashboard/img/port-green.svg openstack_dashboard/static/dashboard/img/port-red.svg openstack_dashboard/static/dashboard/img/router-gray.gif openstack_dashboard/static/dashboard/img/router-gray.svg openstack_dashboard/static/dashboard/img/router-green.svg openstack_dashboard/static/dashboard/img/router-red.svg openstack_dashboard/static/dashboard/img/safari-pinned-tab.svg openstack_dashboard/static/dashboard/img/securitygroup-gray.gif openstack_dashboard/static/dashboard/img/securitygroup-gray.svg openstack_dashboard/static/dashboard/img/securitygroup-green.svg openstack_dashboard/static/dashboard/img/securitygroup-red.svg openstack_dashboard/static/dashboard/img/server-gray.gif openstack_dashboard/static/dashboard/img/server-gray.svg openstack_dashboard/static/dashboard/img/server-green.svg openstack_dashboard/static/dashboard/img/server-red.svg openstack_dashboard/static/dashboard/img/spinner.gif openstack_dashboard/static/dashboard/img/stack-gray.gif openstack_dashboard/static/dashboard/img/stack-gray.svg openstack_dashboard/static/dashboard/img/stack-green.svg openstack_dashboard/static/dashboard/img/stack-red.svg openstack_dashboard/static/dashboard/img/unknown-gray.gif openstack_dashboard/static/dashboard/img/unknown-gray.svg openstack_dashboard/static/dashboard/img/unknown-green.svg openstack_dashboard/static/dashboard/img/unknown-red.svg openstack_dashboard/static/dashboard/img/volume-gray.gif openstack_dashboard/static/dashboard/img/volume-gray.svg openstack_dashboard/static/dashboard/img/volume-green.svg openstack_dashboard/static/dashboard/img/volume-red.svg openstack_dashboard/static/dashboard/img/vpn-gray.svg openstack_dashboard/static/dashboard/img/vpn-green.svg openstack_dashboard/static/dashboard/img/vpn-red.svg openstack_dashboard/static/dashboard/img/vpn.gif openstack_dashboard/static/dashboard/img/wait-gray.gif openstack_dashboard/static/dashboard/img/wait-gray.svg openstack_dashboard/static/dashboard/img/wait-green.svg openstack_dashboard/static/dashboard/img/wait-red.svg openstack_dashboard/static/dashboard/scss/_bootstrap_helpers.scss openstack_dashboard/static/dashboard/scss/_contrib.scss openstack_dashboard/static/dashboard/scss/_debt.scss openstack_dashboard/static/dashboard/scss/_layout.scss openstack_dashboard/static/dashboard/scss/_legacy.scss openstack_dashboard/static/dashboard/scss/_mixins.scss openstack_dashboard/static/dashboard/scss/_util.scss openstack_dashboard/static/dashboard/scss/_variables.scss openstack_dashboard/static/dashboard/scss/horizon.scss openstack_dashboard/static/dashboard/scss/serial_console.scss openstack_dashboard/static/dashboard/scss/components/_bar_charts.scss openstack_dashboard/static/dashboard/scss/components/_breadcrumbs.scss openstack_dashboard/static/dashboard/scss/components/_charts.scss openstack_dashboard/static/dashboard/scss/components/_checkboxes.scss openstack_dashboard/static/dashboard/scss/components/_code.scss openstack_dashboard/static/dashboard/scss/components/_datepicker.scss openstack_dashboard/static/dashboard/scss/components/_dl_lists.scss openstack_dashboard/static/dashboard/scss/components/_dropdowns.scss openstack_dashboard/static/dashboard/scss/components/_forms.scss openstack_dashboard/static/dashboard/scss/components/_help_panel.scss openstack_dashboard/static/dashboard/scss/components/_icons.scss openstack_dashboard/static/dashboard/scss/components/_inline_edit.scss openstack_dashboard/static/dashboard/scss/components/_loader.scss openstack_dashboard/static/dashboard/scss/components/_login.scss openstack_dashboard/static/dashboard/scss/components/_membership.scss openstack_dashboard/static/dashboard/scss/components/_messages.scss openstack_dashboard/static/dashboard/scss/components/_modals.scss openstack_dashboard/static/dashboard/scss/components/_navbar.scss openstack_dashboard/static/dashboard/scss/components/_network_topology.scss openstack_dashboard/static/dashboard/scss/components/_network_topology_svg.scss openstack_dashboard/static/dashboard/scss/components/_pie_charts.scss openstack_dashboard/static/dashboard/scss/components/_progress_bars.scss openstack_dashboard/static/dashboard/scss/components/_quota.scss openstack_dashboard/static/dashboard/scss/components/_radiobuttons.scss openstack_dashboard/static/dashboard/scss/components/_resource_browser.scss openstack_dashboard/static/dashboard/scss/components/_resource_topology.scss openstack_dashboard/static/dashboard/scss/components/_selection_menu.scss openstack_dashboard/static/dashboard/scss/components/_selects.scss openstack_dashboard/static/dashboard/scss/components/_sidebar.scss openstack_dashboard/static/dashboard/scss/components/_spinners.scss openstack_dashboard/static/dashboard/scss/components/_tab.scss openstack_dashboard/static/dashboard/scss/components/_tables.scss openstack_dashboard/static/dashboard/scss/components/_transfer_tables.scss openstack_dashboard/static/dashboard/scss/components/_wizard.scss openstack_dashboard/static/js/.eslintrc openstack_dashboard/static/js/horizon.flatnetworktopology.js openstack_dashboard/static/js/horizon.instances.js openstack_dashboard/static/js/horizon.metering.js openstack_dashboard/static/js/horizon.networktopology.js openstack_dashboard/static/js/horizon.networktopologycommon.js openstack_dashboard/static/js/horizon.quota.js openstack_dashboard/static/js/horizon.volumes.js openstack_dashboard/static/js/angular/directives/serialConsole.js openstack_dashboard/templates/403.html openstack_dashboard/templates/404.html openstack_dashboard/templates/500.html openstack_dashboard/templates/_footer.html openstack_dashboard/templates/_login_footer.html openstack_dashboard/templates/_login_form_footer.html openstack_dashboard/templates/_stylesheets.html openstack_dashboard/templates/angular.html openstack_dashboard/templates/base.html openstack_dashboard/templates/serial_console.html openstack_dashboard/templates/angular/angular_templates.html openstack_dashboard/templates/angular/angular_templates.js openstack_dashboard/templates/context_selection/_anti_clickjack.html openstack_dashboard/templates/context_selection/_domain_list.html openstack_dashboard/templates/context_selection/_overview.html openstack_dashboard/templates/context_selection/_project_list.html openstack_dashboard/templates/context_selection/_region_list.html openstack_dashboard/templates/header/_brand.html openstack_dashboard/templates/header/_context_selection.html openstack_dashboard/templates/header/_header.html openstack_dashboard/templates/header/_header_sections.html openstack_dashboard/templates/header/_keystone_provider_selection.html openstack_dashboard/templates/header/_region_selection.html openstack_dashboard/templates/header/_theme_list.html openstack_dashboard/templates/header/_user_menu.html openstack_dashboard/templates/horizon/_conf.html openstack_dashboard/templates/horizon/_custom_head_js.html openstack_dashboard/templates/horizon/_custom_meta.html openstack_dashboard/templates/horizon/_script_i18n.html openstack_dashboard/templates/horizon/_scripts.html openstack_dashboard/templates/themes/themes.scss openstack_dashboard/templatetags/__init__.py openstack_dashboard/templatetags/context_selection.py openstack_dashboard/templatetags/themes.py openstack_dashboard/test/__init__.py openstack_dashboard/test/error_pages_urls.py openstack_dashboard/test/extensible_header_urls.py openstack_dashboard/test/helpers.py openstack_dashboard/test/settings.py openstack_dashboard/test/urls.py openstack_dashboard/test/api_tests/microversions_tests.py openstack_dashboard/test/integration_tests/README.rst openstack_dashboard/test/integration_tests/__init__.py openstack_dashboard/test/integration_tests/basewebobject.py openstack_dashboard/test/integration_tests/config.py openstack_dashboard/test/integration_tests/decorators.py openstack_dashboard/test/integration_tests/helpers.py openstack_dashboard/test/integration_tests/horizon.conf openstack_dashboard/test/integration_tests/video_recorder.py openstack_dashboard/test/integration_tests/pages/__init__.py openstack_dashboard/test/integration_tests/pages/basepage.py openstack_dashboard/test/integration_tests/pages/loginpage.py openstack_dashboard/test/integration_tests/pages/navigation.py openstack_dashboard/test/integration_tests/pages/pageobject.py openstack_dashboard/test/integration_tests/pages/admin/__init__.py openstack_dashboard/test/integration_tests/pages/admin/system/__init__.py openstack_dashboard/test/integration_tests/pages/admin/system/defaultspage.py openstack_dashboard/test/integration_tests/pages/admin/system/flavorspage.py openstack_dashboard/test/integration_tests/pages/admin/system/hostaggregatespage.py openstack_dashboard/test/integration_tests/pages/admin/system/imagespage.py openstack_dashboard/test/integration_tests/pages/admin/system/instancespage.py openstack_dashboard/test/integration_tests/pages/admin/system/metadatadefinitionspage.py openstack_dashboard/test/integration_tests/pages/admin/system/overviewpage.py openstack_dashboard/test/integration_tests/pages/admin/system/routerspage.py openstack_dashboard/test/integration_tests/pages/admin/system/resource_usage/__init__.py openstack_dashboard/test/integration_tests/pages/admin/system/system_info/__init__.py openstack_dashboard/test/integration_tests/pages/admin/system/volumes/__init__.py openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumesnapshotspage.py openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumespage.py openstack_dashboard/test/integration_tests/pages/admin/system/volumes/volumetypespage.py openstack_dashboard/test/integration_tests/pages/identity/__init__.py openstack_dashboard/test/integration_tests/pages/identity/groupspage.py openstack_dashboard/test/integration_tests/pages/identity/projectspage.py openstack_dashboard/test/integration_tests/pages/identity/userspage.py openstack_dashboard/test/integration_tests/pages/project/__init__.py openstack_dashboard/test/integration_tests/pages/project/compute/__init__.py openstack_dashboard/test/integration_tests/pages/project/compute/imagespage.py openstack_dashboard/test/integration_tests/pages/project/compute/instancespage.py openstack_dashboard/test/integration_tests/pages/project/compute/overviewpage.py openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/__init__.py openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/apiaccesspage.py openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/floatingipspage.py openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/keypairspage.py openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/managerulespage.py openstack_dashboard/test/integration_tests/pages/project/compute/access_and_security/securitygroupspage.py openstack_dashboard/test/integration_tests/pages/project/compute/volumes/__init__.py openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumesnapshotspage.py openstack_dashboard/test/integration_tests/pages/project/compute/volumes/volumespage.py openstack_dashboard/test/integration_tests/pages/project/network/__init__.py openstack_dashboard/test/integration_tests/pages/project/network/networkoverviewpage.py openstack_dashboard/test/integration_tests/pages/project/network/networkspage.py openstack_dashboard/test/integration_tests/pages/project/network/routerinterfacespage.py openstack_dashboard/test/integration_tests/pages/project/network/routeroverviewpage.py openstack_dashboard/test/integration_tests/pages/project/network/routerspage.py openstack_dashboard/test/integration_tests/pages/project/object_store/__init__.py openstack_dashboard/test/integration_tests/pages/settings/__init__.py openstack_dashboard/test/integration_tests/pages/settings/changepasswordpage.py openstack_dashboard/test/integration_tests/pages/settings/usersettingspage.py openstack_dashboard/test/integration_tests/regions/__init__.py openstack_dashboard/test/integration_tests/regions/bars.py openstack_dashboard/test/integration_tests/regions/baseregion.py openstack_dashboard/test/integration_tests/regions/exceptions.py openstack_dashboard/test/integration_tests/regions/forms.py openstack_dashboard/test/integration_tests/regions/menus.py openstack_dashboard/test/integration_tests/regions/messages.py openstack_dashboard/test/integration_tests/regions/tables.py openstack_dashboard/test/integration_tests/tests/__init__.py openstack_dashboard/test/integration_tests/tests/test_credentials.py openstack_dashboard/test/integration_tests/tests/test_defaults.py openstack_dashboard/test/integration_tests/tests/test_flavors.py openstack_dashboard/test/integration_tests/tests/test_floatingips.py openstack_dashboard/test/integration_tests/tests/test_groups.py openstack_dashboard/test/integration_tests/tests/test_host_aggregates.py openstack_dashboard/test/integration_tests/tests/test_images.py openstack_dashboard/test/integration_tests/tests/test_instances.py openstack_dashboard/test/integration_tests/tests/test_keypairs.py openstack_dashboard/test/integration_tests/tests/test_login.py openstack_dashboard/test/integration_tests/tests/test_metadata_definitions.py openstack_dashboard/test/integration_tests/tests/test_networks.py openstack_dashboard/test/integration_tests/tests/test_projects.py openstack_dashboard/test/integration_tests/tests/test_router.py openstack_dashboard/test/integration_tests/tests/test_router_gateway.py openstack_dashboard/test/integration_tests/tests/test_security_groups.py openstack_dashboard/test/integration_tests/tests/test_user_settings.py openstack_dashboard/test/integration_tests/tests/test_users.py openstack_dashboard/test/integration_tests/tests/test_volume_snapshots.py openstack_dashboard/test/integration_tests/tests/test_volumes.py openstack_dashboard/test/integration_tests/tests/test_volumetypes.py openstack_dashboard/test/integration_tests/tests/test-data/empty_namespace.json openstack_dashboard/test/integration_tests/tests/test-data/stack_template openstack_dashboard/test/jasmine/__init__.py openstack_dashboard/test/jasmine/jasmine.py openstack_dashboard/test/selenium/__init__.py openstack_dashboard/test/selenium/selenium_tests.py openstack_dashboard/test/templates/404.html openstack_dashboard/test/templates/500.html openstack_dashboard/test/templates/_tab.html openstack_dashboard/test/templates/base-sidebar.html openstack_dashboard/test/templates/tab_group.html openstack_dashboard/test/templates/workflow.html openstack_dashboard/test/templates/registration/login.html openstack_dashboard/test/test_data/__init__.py openstack_dashboard/test/test_data/cinder_data.py openstack_dashboard/test/test_data/exceptions.py openstack_dashboard/test/test_data/glance_data.py openstack_dashboard/test/test_data/keystone_data.py openstack_dashboard/test/test_data/neutron_data.py openstack_dashboard/test/test_data/nova_data.py openstack_dashboard/test/test_data/swift_data.py openstack_dashboard/test/test_data/utils.py openstack_dashboard/test/test_panels/__init__.py openstack_dashboard/test/test_panels/another_panel/__init__.py openstack_dashboard/test/test_panels/another_panel/panel.py openstack_dashboard/test/test_panels/another_panel/urls.py openstack_dashboard/test/test_panels/another_panel/views.py openstack_dashboard/test/test_panels/another_panel/templates/another_panel/index.html openstack_dashboard/test/test_panels/nonloading_panel/__init__.py openstack_dashboard/test/test_panels/nonloading_panel/panel.py openstack_dashboard/test/test_panels/nonloading_panel/urls.py openstack_dashboard/test/test_panels/nonloading_panel/views.py openstack_dashboard/test/test_panels/nonloading_panel/templates/nonloading_panel/index.html openstack_dashboard/test/test_panels/plugin_panel/__init__.py openstack_dashboard/test/test_panels/plugin_panel/panel.py openstack_dashboard/test/test_panels/plugin_panel/urls.py openstack_dashboard/test/test_panels/plugin_panel/views.py openstack_dashboard/test/test_panels/plugin_panel/static/plugin_panel/plugin.scss openstack_dashboard/test/test_panels/plugin_panel/static/plugin_panel/plugin.spec.js openstack_dashboard/test/test_panels/plugin_panel/static/plugin_panel/plugin_module.js openstack_dashboard/test/test_panels/plugin_panel/templates/plugin_panel/header.html openstack_dashboard/test/test_panels/plugin_panel/templates/plugin_panel/index.html openstack_dashboard/test/test_panels/second_panel/__init__.py openstack_dashboard/test/test_panels/second_panel/panel.py openstack_dashboard/test/test_panels/second_panel/urls.py openstack_dashboard/test/test_panels/second_panel/views.py openstack_dashboard/test/test_panels/second_panel/templates/second_panel/index.html openstack_dashboard/test/test_plugins/__init__.py openstack_dashboard/test/test_plugins/panel_group_tests.py openstack_dashboard/test/test_plugins/panel_tests.py openstack_dashboard/test/test_plugins/panel_config/_10_admin_add_panel.py openstack_dashboard/test/test_plugins/panel_config/_20_admin_remove_panel.py openstack_dashboard/test/test_plugins/panel_config/_30_admin_default_panel.py openstack_dashboard/test/test_plugins/panel_config/_40_admin_nonloading_panel.py openstack_dashboard/test/test_plugins/panel_config/__init__.py openstack_dashboard/test/test_plugins/panel_group_config/_10_admin_add_panel_group.py openstack_dashboard/test/test_plugins/panel_group_config/_20_admin_add_panel_to_group.py openstack_dashboard/test/test_plugins/panel_group_config/_30_admin_add_second_panel_group.py openstack_dashboard/test/test_plugins/panel_group_config/_40_admin_add_panel_to_second_group.py openstack_dashboard/test/test_plugins/panel_group_config/_50_admin_add_panel_to_default_group.py openstack_dashboard/test/test_plugins/panel_group_config/__init__.py openstack_dashboard/test/unit/__init__.py openstack_dashboard/test/unit/test_error_pages.py openstack_dashboard/test/unit/test_policy.py openstack_dashboard/test/unit/test_themes.py openstack_dashboard/test/unit/test_views.py openstack_dashboard/test/unit/api/__init__.py openstack_dashboard/test/unit/api/test_base.py openstack_dashboard/test/unit/api/test_cinder.py openstack_dashboard/test/unit/api/test_glance.py openstack_dashboard/test/unit/api/test_keystone.py openstack_dashboard/test/unit/api/test_network.py openstack_dashboard/test/unit/api/test_neutron.py openstack_dashboard/test/unit/api/test_nova.py openstack_dashboard/test/unit/api/test_swift.py openstack_dashboard/test/unit/api/rest/__init__.py openstack_dashboard/test/unit/api/rest/test_cinder.py openstack_dashboard/test/unit/api/rest/test_config.py openstack_dashboard/test/unit/api/rest/test_glance.py openstack_dashboard/test/unit/api/rest/test_keystone.py openstack_dashboard/test/unit/api/rest/test_network.py openstack_dashboard/test/unit/api/rest/test_neutron.py openstack_dashboard/test/unit/api/rest/test_nova.py openstack_dashboard/test/unit/api/rest/test_policy.py openstack_dashboard/test/unit/api/rest/test_swift.py openstack_dashboard/test/unit/api/rest/test_utils.py openstack_dashboard/test/unit/usage/__init__.py openstack_dashboard/test/unit/usage/test_quotas.py openstack_dashboard/test/unit/utils/__init__.py openstack_dashboard/test/unit/utils/test_config_types.py openstack_dashboard/test/unit/utils/test_filters.py openstack_dashboard/themes/default/_styles.scss openstack_dashboard/themes/default/_variables.scss openstack_dashboard/themes/default/horizon/components/_selects.scss openstack_dashboard/themes/material/static/_styles.scss openstack_dashboard/themes/material/static/_variables.scss openstack_dashboard/themes/material/static/bootstrap/_styles.scss openstack_dashboard/themes/material/static/bootstrap/_variable_customizations.scss openstack_dashboard/themes/material/static/bootstrap/_variables.scss openstack_dashboard/themes/material/static/horizon/_animations.scss openstack_dashboard/themes/material/static/horizon/_icons.scss openstack_dashboard/themes/material/static/horizon/_styles.scss openstack_dashboard/themes/material/static/horizon/_variables.scss openstack_dashboard/themes/material/static/horizon/components/_checkboxes.scss openstack_dashboard/themes/material/static/horizon/components/_context_selection.scss openstack_dashboard/themes/material/static/horizon/components/_datepicker.scss openstack_dashboard/themes/material/static/horizon/components/_dropdowns.scss openstack_dashboard/themes/material/static/horizon/components/_hamburger.scss openstack_dashboard/themes/material/static/horizon/components/_help_panel.scss openstack_dashboard/themes/material/static/horizon/components/_loader_circular_example.scss openstack_dashboard/themes/material/static/horizon/components/_loader_line_example.scss openstack_dashboard/themes/material/static/horizon/components/_loader_spinner.scss openstack_dashboard/themes/material/static/horizon/components/_magic_search.scss openstack_dashboard/themes/material/static/horizon/components/_messages.scss openstack_dashboard/themes/material/static/horizon/components/_navbar.scss openstack_dashboard/themes/material/static/horizon/components/_progress_bars.scss openstack_dashboard/themes/material/static/horizon/components/_radiobuttons.scss openstack_dashboard/themes/material/static/horizon/components/_selects.scss openstack_dashboard/themes/material/static/horizon/components/_sidebar.scss openstack_dashboard/themes/material/static/horizon/components/_spinners.scss openstack_dashboard/themes/material/static/horizon/components/_trees.scss openstack_dashboard/themes/material/static/js/material.hamburger.js openstack_dashboard/themes/material/templates/auth/_splash.html openstack_dashboard/themes/material/templates/header/_brand.html openstack_dashboard/themes/material/templates/header/_header.html openstack_dashboard/themes/material/templates/horizon/_sidebar.html openstack_dashboard/themes/material/templates/horizon/client_side/_confirm.html openstack_dashboard/themes/material/templates/horizon/client_side/_loading_inline_example.html openstack_dashboard/themes/material/templates/horizon/client_side/_loading_modal_example.html openstack_dashboard/themes/material/templates/material/openstack-one-color-alt.svg openstack_dashboard/themes/material/templates/material/openstack-one-color-alt.svg.old openstack_dashboard/themes/material/templates/material/openstack-one-color.svg openstack_dashboard/usage/__init__.py openstack_dashboard/usage/base.py openstack_dashboard/usage/quotas.py openstack_dashboard/usage/tables.py openstack_dashboard/usage/views.py openstack_dashboard/utils/__init__.py openstack_dashboard/utils/config.py openstack_dashboard/utils/config_types.py openstack_dashboard/utils/filters.py openstack_dashboard/utils/identity.py openstack_dashboard/utils/settings.py openstack_dashboard/wsgi/django.wsgi releasenotes/notes/.placeholder releasenotes/notes/Selectable-Themes-Setting-863c4686f71c70d8.yaml releasenotes/notes/action-results-303c282165b60f47.yaml releasenotes/notes/add-clouds-yaml-f72b9a0b7df2aa79.yaml releasenotes/notes/add-domain-dropdown-65006187e5605735.yaml releasenotes/notes/add-virtio-forwarder-vnic-type-03eba218d36e2aae.yaml releasenotes/notes/admin-neutron-l3-agents-dd6274467572906b.yaml releasenotes/notes/angular-direct-1b156f152590ab93.yaml releasenotes/notes/angular-features-d677356f161322d6.yaml releasenotes/notes/bp-add-scheduler-hints-77600faec041e134.yaml releasenotes/notes/bp-add-server-metadata-a5d5582966ef25c5.yaml releasenotes/notes/bp-admin-manage-fips-5aa409d3502b031a.yaml releasenotes/notes/bp-admin-views-filter-first-5b0d8a02b1271135.yaml releasenotes/notes/bp-allow-launching-ports-b1fcc495777b7f4c.yaml releasenotes/notes/bp-angular-performance-strict-di-3cf325d8bfca8487.yaml releasenotes/notes/bp-angular-schema-form-bbe1aedf644b53db.yaml releasenotes/notes/bp-angular-table-directive-1b593f2ad28c2845.yaml releasenotes/notes/bp-angular-template-overrides-9f05ffd61367245a.yaml releasenotes/notes/bp-angularize-swift-9a1b44aa3646bc8c.yaml releasenotes/notes/bp-bootstrap-theme-preview-65da171c9b943646.yaml releasenotes/notes/bp-cache-templates-4ab00dcda195a03a.yaml releasenotes/notes/bp-cinder-consistency-groups-7cc98fda0ff3bb7a.yaml releasenotes/notes/bp-cinder-consistency-groups-b0aba555b1ed4a6c.yaml releasenotes/notes/bp-configurable-boot-sources-4ba89f3b2a927801.yaml releasenotes/notes/bp-dj110-438f26c21f283c46.yaml releasenotes/notes/bp-edit-server-metadata-7e6b00946a2e793a.yaml releasenotes/notes/bp-enable-angular-launch-instance-897f7bb227711c86.yaml releasenotes/notes/bp-horizon-glance-large-image-upload-c987dc86bab38761.yaml releasenotes/notes/bp-horizon-vendor-split-4451bc1988485957.yaml releasenotes/notes/bp-integrate-magic-search-03a97d4431d7c3d1.yaml releasenotes/notes/bp-integration-tests-hardening-8e94e87bc548c1fe.yaml releasenotes/notes/bp-integration-with-cinder-volume-encryption-80a3fe4ff66314b2.yaml releasenotes/notes/bp-k2k-horizon-9577253d626337c1.yaml releasenotes/notes/bp-keystone-federation-protocol-mapping-a6ea9f7c35d2d6f0.yaml releasenotes/notes/bp-launch-instance-volume-quotas-490070a36cadfe8d.yaml releasenotes/notes/bp-local-settings-override-mechanism-6c8632432cf1f44c.yaml releasenotes/notes/bp-mks-console-support-a943797a8bad14ca.yaml releasenotes/notes/bp-network-bandwidth-limiting-qos-df3d667b1a644e30.yaml releasenotes/notes/bp-network-ports-tenant-58a9d5ba925f1d3d.yaml releasenotes/notes/bp-neutron-trunk-ui-72e05888e68502c4.yaml releasenotes/notes/bp-neutron-trunk-ui-queens-1d59df887b9a079a.yaml releasenotes/notes/bp-next-steps-4c7064e52d5abcf5.yaml releasenotes/notes/bp-ng-keypairs-876c38a1a8aed60f.yaml releasenotes/notes/bp-pagination-for-flavor-f603fd7630e13756.yaml releasenotes/notes/bp-password-expires-validation-28b7ea155404b778.yaml releasenotes/notes/bp-pike-docs-overhaul-b4f01f45ced58e07.yaml releasenotes/notes/bp-port-allowed-address-pairs-extension-a05c3a864f494b0c.yaml releasenotes/notes/bp-restrict-private-network-input-5e5bd5978b273c62.yaml releasenotes/notes/bp-support-extra-prop-for-project-and-user-e8a4578c395a8ade.yaml releasenotes/notes/bp-update-dependencies-newton-3bd257faa37d3dda.yaml releasenotes/notes/bp-update-nova-enforce-policies-c207166bc3a714d5.yaml releasenotes/notes/bug-123741-4be1aa90b9d44e40.yaml releasenotes/notes/bug-1568764-cached-template-loaders-3536f35e11099eba.yaml releasenotes/notes/bug-1585682-abd815f290e494d3.yaml releasenotes/notes/bug-1588663-6fab83e9d89b20d2.yaml releasenotes/notes/bug-1593903-8fb8721dc2449f71.yaml releasenotes/notes/bug-1595913-5f0cd019b7c2173a.yaml releasenotes/notes/bug-1618235-59865fa0e5991e63.yaml releasenotes/notes/bug-1635505-3807fd0151702a5f.yaml releasenotes/notes/bug-1640049-1195315b5f591ab0.yaml releasenotes/notes/bug-1678109-4440ebe90908647d.yaml releasenotes/notes/bug-1699144-configurable-image-visibility-fda69cb25d960efb.yaml releasenotes/notes/bug-1709056-33d18a562840e334.yaml releasenotes/notes/cinder-api-v3-by-default-d6e3c12760fdf655.yaml releasenotes/notes/default-keystone-api-v3-dc201adba4255752.yaml releasenotes/notes/deprecate-lbaas-v1-dashboard-1f9c48d7c6997b1f.yaml releasenotes/notes/deprecation-of-default-subnet-pool-label-options-b05ebccbf6f68ecf.yaml releasenotes/notes/django-version-queens-b7785b96ecbceaf0.yaml releasenotes/notes/domains-0581aa42773d5f41.yaml releasenotes/notes/drop-LBaaS-v1-dashboard-d767b0bde5274af5.yaml releasenotes/notes/drop-action-strings-attributes-64f0cb0323f629ee.yaml releasenotes/notes/drop-nova-network-2186d008f696cfa7.yaml releasenotes/notes/drop-settings-enable-fireall-vpn-fad7c1a4cd96df2b.yaml releasenotes/notes/dynamic-themes-b6b02238e47b99f8.yaml releasenotes/notes/enable-js-catalog-plugin-1885df911148247a.yaml releasenotes/notes/enable-phantomjs-selenium-cce59f25cb327ca2.yaml releasenotes/notes/excise-sahara-7eff95feb416ce4b.yaml releasenotes/notes/excise-trove-ce576b50fbcd15ad.yaml releasenotes/notes/extensible-header-ac3c94f3057c1b2a.yaml releasenotes/notes/extensible-service-a8689c89a71f8961.yaml releasenotes/notes/filter-first-identity-panels-139c4a5b7a696707.yaml releasenotes/notes/fix-precise-keystone-endpoint-message-03129e37a6377715.yaml releasenotes/notes/flavor-panel-switch-6b5cd5f0964f4ba3.yaml releasenotes/notes/floating_ip_description-f4d2df7949b9fde9.yaml releasenotes/notes/fwaas-panel-splitout-635c63d4da0e6d97.yaml releasenotes/notes/gb-to-gib-conversion-8a91839030a2f570.yaml releasenotes/notes/generic-details-4f78452b14005e5b.yaml releasenotes/notes/glance-v2-ba86ba34611f95ce.yaml releasenotes/notes/global-class-name-convention-71ff68913c39b800.yaml releasenotes/notes/heat-panel-splitout-b609b157aa4bf29b.yaml releasenotes/notes/horizon-without-nova-3cd0a84109ed2187.yaml releasenotes/notes/hz-select-fixes-c9bfe6a53e0daa20.yaml releasenotes/notes/image-description-3fc00c02f46a80c7.yaml releasenotes/notes/image-panel-switch-38e9d3716451f9e3.yaml releasenotes/notes/introduce_default_service_regions_config-26a41e0d06582d7a.yaml releasenotes/notes/ip-availability-be217ba59cc02b40.yaml releasenotes/notes/keystone-federation-idp-d4456dd3b3081a53.yaml releasenotes/notes/launch-instance-defaults-c6ab65b7ab822162.yaml releasenotes/notes/merge-openstack-auth-aa101f9432ba799a.yaml releasenotes/notes/message-of-the-day-19eb745a147ca56d.yaml releasenotes/notes/move-developer-enabled-files-b1ad2265cd79b11e.yaml releasenotes/notes/move-policy-engine-b19e434a62912e5f.yaml releasenotes/notes/network-type-geneve-71eed4104699754e.yaml releasenotes/notes/network-type-midonet-6c78bdfe1e3186a0.yaml releasenotes/notes/neutron-default-quotas-ddd237af2935fde3.yaml releasenotes/notes/ngdetail-reload-e711a77b2d07191a.yaml releasenotes/notes/openstack-auth-policy-dirs-c5d77665eac415ea.yaml releasenotes/notes/openstack-profiler-at-developer-dashboard-da1b1556e30aa858.yaml releasenotes/notes/operation-history-log-64354f66614cb1dd.yaml releasenotes/notes/plugin-enabled-override-f317e7c9c352f58f.yaml releasenotes/notes/port-security-81278703eae4f927.yaml releasenotes/notes/provider-net-config-713f0672c8e49888.yaml releasenotes/notes/quota-usage-reduce-unnecessary-API-calls-1ca424e093bec135.yaml releasenotes/notes/removal-of-default-subnet-pool-label-options-9aeaa816ad6cc2f8.yaml releasenotes/notes/removal-of-webroot-theme-108db1d2f11da449.yaml releasenotes/notes/remove-ceilometer-support-376d38802a3ef833.yaml releasenotes/notes/remove-default-styles-1d8ba7ad46a51381.yaml releasenotes/notes/remove-deprecated-init-scope-342153755181f0a4.yaml releasenotes/notes/remove-deprecated-swift-ui-1165b60bab5771d6.yaml releasenotes/notes/remove-i9n-scaffolds-from-production-6e52eb1da2a4bc9e.yaml releasenotes/notes/remove-inline-edit-63f92054238378d3.yaml releasenotes/notes/remove-transfer-table-avail-changed-cfae61341b5fea71.yaml releasenotes/notes/reorganize-admin-dashboard-e180216d200357ec.yaml releasenotes/notes/resource-directives-44629f1116545141.yaml releasenotes/notes/security-group-associate-per-port-c81ca7beb7dca409.yaml releasenotes/notes/security-group-in-port-detail-10a7f5d6d50d1571.yaml releasenotes/notes/security-group-no-rules-list-bugfix-b77ab5aff1d3e45e.yaml releasenotes/notes/security-group-rule-wildcard-protocol-and-port-support-7dd6f5acfaba55ba.yaml releasenotes/notes/setting-OVERVIEW_DAYS_RANGE-9b87e8b077952a32.yaml releasenotes/notes/setting-openstack-endpoint-type-ebdeda92ba0d1587.yaml releasenotes/notes/setting-retrieve-instance-ip-addresses-b9db6703d8b010c8.yaml releasenotes/notes/table-row-warning-class-2fdb3434440d0c22.yaml releasenotes/notes/theme-modal-sizes-924e5835efe9bb79.yaml releasenotes/notes/token-delete-disabled-bb50c3cddd315ee6.yaml releasenotes/notes/use-toast-instead-of-alert-ef2f7dec2bd1121a.yaml releasenotes/notes/vpnaas-panel-splitout-5783df1675bc984f.yaml releasenotes/notes/workflow-step-allowed-support-d0a770da1d30efb7.yaml releasenotes/notes/workflow-step-policy-1ca99b0249294337.yaml releasenotes/notes/xstatic-settings-cleanup-8de0e2ba00fd43ec.yaml releasenotes/notes/xstatic-updates-c3ff5e2b4750ae96.yaml releasenotes/notes/bp/enhance-tox-26f73a048b88df2f.yaml releasenotes/notes/bp/horizon-vendor-split-e16aa3d81dea9708.yaml releasenotes/notes/bp/navigation-improvements-ab101299eb1a8d54.yaml releasenotes/notes/bp/reorganise-access-and-security-ea7780aa9e7b83e7.yaml releasenotes/notes/bug/1637490-c29444e4eb458087.yaml releasenotes/source/conf.py releasenotes/source/essex.rst releasenotes/source/folsom.rst releasenotes/source/grizzly.rst releasenotes/source/havana.rst releasenotes/source/icehouse.rst releasenotes/source/index.rst releasenotes/source/juno.rst releasenotes/source/kilo.rst releasenotes/source/liberty.rst releasenotes/source/mitaka.rst releasenotes/source/newton.rst releasenotes/source/ocata.rst releasenotes/source/pike.rst releasenotes/source/unreleased.rst releasenotes/source/_static/.placeholder releasenotes/source/_templates/.placeholder tools/abandon_old_reviews.sh tools/executable_files.txt tools/find_executables.sh tools/policy-diff.py tools/unit_tests.sh tools/gate/integration/devstack_gate_rc tools/gate/integration/post_test_hook.sh tools/gate/integration/pre_test_hook.shhorizon-13.0.3/horizon.egg-info/top_level.txt0000664000175000017500000000005313553661041021226 0ustar zuulzuul00000000000000horizon openstack_auth openstack_dashboard horizon-13.0.3/horizon.egg-info/pbr.json0000664000175000017500000000006013553661041020150 0ustar zuulzuul00000000000000{"git_version": "919870a13", "is_release": true}horizon-13.0.3/horizon.egg-info/not-zip-safe0000664000175000017500000000000113553661041020724 0ustar zuulzuul00000000000000 horizon-13.0.3/horizon.egg-info/entry_points.txt0000664000175000017500000000013013553661041021766 0ustar zuulzuul00000000000000[oslo.config.opts] openstack_dashboard = openstack_dashboard.utils.config:list_options horizon-13.0.3/horizon.egg-info/requires.txt0000664000175000017500000000266413553661041021106 0ustar zuulzuul00000000000000pbr!=2.1.0,>=2.0.0 Babel!=2.4.0,>=2.3.4 Django<2.0,>=1.8 Pint>=0.5 django-babel>=0.5.1 django-compressor>=2.0 django-pyscss>=2.0.2 futurist>=1.2.0 iso8601>=0.1.11 keystoneauth1>=3.3.0 netaddr>=0.7.18 oslo.concurrency>=3.25.0 oslo.config>=5.1.0 oslo.i18n>=3.15.3 oslo.policy>=1.30.0 oslo.serialization!=2.19.1,>=2.18.0 oslo.utils>=3.33.0 osprofiler>=1.4.0 pymongo!=3.1,>=3.0.2 pyScss!=1.3.5,>=1.3.4 python-cinderclient>=3.3.0 python-glanceclient>=2.8.0 python-keystoneclient>=3.8.0 python-neutronclient>=6.3.0 python-novaclient>=9.1.0 python-swiftclient>=3.2.0 pytz>=2013.6 PyYAML>=3.10 semantic-version>=2.3.1 six>=1.10.0 XStatic>=1.0.0 XStatic-Angular>=1.5.8.0 XStatic-Angular-Bootstrap>=2.2.0.0 XStatic-Angular-FileUpload>=12.0.4.0 XStatic-Angular-Gettext>=2.3.8.0 XStatic-Angular-lrdragndrop>=1.0.2.2 XStatic-Angular-Schema-Form>=0.8.13.0 XStatic-Bootstrap-Datepicker>=1.3.1.0 XStatic-Bootstrap-SCSS>=3.3.7.1 XStatic-bootswatch>=3.3.7.0 XStatic-D3>=3.5.17.0 XStatic-Hogan>=2.0.0.2 XStatic-Font-Awesome>=4.7.0.0 XStatic-Jasmine>=2.4.1.1 XStatic-jQuery>=1.8.2.1 XStatic-JQuery-Migrate>=1.2.1.1 XStatic-JQuery.quicksearch>=2.0.3.1 XStatic-JQuery.TableSorter>=2.14.5.1 XStatic-jquery-ui>=1.10.4.1 XStatic-JSEncrypt>=2.3.1.1 XStatic-mdi>=1.4.57.0 XStatic-objectpath>=1.2.1.0 XStatic-Rickshaw>=1.5.0.0 XStatic-roboto-fontface>=0.5.0.0 XStatic-smart-table>=1.4.13.2 XStatic-Spin>=1.2.5.2 XStatic-term.js>=0.0.7.0 XStatic-tv4>=1.2.7.0 [test] nosehtmloutput>=0.0.3 horizon-13.0.3/horizon.egg-info/dependency_links.txt0000664000175000017500000000000113553661041022544 0ustar zuulzuul00000000000000 horizon-13.0.3/LICENSE0000664000175000017500000002363713553660754014346 0ustar zuulzuul00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. horizon-13.0.3/tox.ini0000664000175000017500000001230213553660755014640 0ustar zuulzuul00000000000000[tox] envlist = pep8,py27dj{18,19,110},py35,releasenotes,npm minversion = 2.3.2 skipsdist = True [testenv] install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=stable/queens} {opts} {packages} usedevelop = True setenv = VIRTUAL_ENV={envdir} INTEGRATION_TESTS=0 NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_SHOW_ELAPSED=1 whitelist_externals = bash find deps = .[test] -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt commands = horizon: {envpython} {toxinidir}/manage.py test --settings=horizon.test.settings {posargs} py27: {[unit_tests]commands} py35: {[unit_tests]commands} openstack_dashboard: {envpython} {toxinidir}/manage.py test --settings=openstack_dashboard.test.settings {posargs} runserver: {envpython} {toxinidir}/manage.py runserver {posargs} venv: {posargs} [testenv:py35] setenv = PYTHONUNBUFFERED = 1 {[testenv]setenv} commands = {[unit_tests]commands} [testenv:py27dj18] commands = pip install -U django>=1.8,<1.9 {[unit_tests]commands} [testenv:py27dj19] commands = pip install -U django>=1.9,<1.10 {[unit_tests]commands} [testenv:py27dj110] commands = pip install -U django>=1.10,<1.11 {[unit_tests]commands} [testenv:py27dj111] commands = pip install -U django>=1.11,<2.0 {[unit_tests]commands} [testenv:py35dj20] basepython = python3.5 deps = -r{toxinidir}/test-requirements.txt commands = pip install -U --pre django {[unit_tests]commands} [unit_tests] commands = find . -type f -name "*.pyc" -delete bash {toxinidir}/tools/unit_tests.sh {envpython} {toxinidir} {posargs} [testenv:pep8] commands = flake8 {posargs} {envpython} {toxinidir}/manage.py extract_messages --verbosity 0 --check-only bash {toxinidir}/tools/find_executables.sh doc8 doc/source releasenotes/source releasenotes/notes [testenv:cover] commands = coverage erase coverage run {toxinidir}/manage.py test horizon --settings=horizon.test.settings {posargs} coverage run -a {toxinidir}/manage.py test openstack_dashboard --settings=openstack_dashboard.test.settings --exclude-dir=openstack_dashboard/test/integration_tests {posargs} coverage run -a {toxinidir}/manage.py test openstack_auth --settings=openstack_auth.test.settings {posargs} coverage xml coverage html [testenv:selenium] setenv = {[testenv]setenv} WITH_SELENIUM=1 SKIP_UNITTESTS=1 commands = {[unit_tests]commands} [testenv:selenium-headless] setenv = {[testenv]setenv} SELENIUM_HEADLESS=1 WITH_SELENIUM=1 SKIP_UNITTESTS=1 commands = {[unit_tests]commands} [testenv:selenium-phantomjs] setenv = {[testenv]setenv} SELENIUM_PHANTOMJS=1 WITH_SELENIUM=1 SKIP_UNITTESTS=1 commands = {[unit_tests]commands} [testenv:py27integration] # Run integration tests only passenv = AVCONV_INSTALLED setenv = PYTHONHASHSEED=0 INTEGRATION_TESTS=1 SELENIUM_HEADLESS=1 NOSE_WITH_OPENSTACK=1 NOSE_OPENSTACK_SHOW_ELAPSED=1 basepython = python2.7 commands = nosetests openstack_dashboard.test.integration_tests {posargs} [testenv:npm] passenv = HOME DISPLAY commands = nodeenv -p npm install npm run {posargs:test} [testenv:tests_system_packages] # Provide an environment for system packagers that dont want anything from pip # Any extra deps needed for this env can be passed by setting TOX_EXTRA_DEPS sitepackages = True passenv = TOX_EXTRA_DEPS http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY # Sets deps to an empty list so nothing is installed from pip deps = commands = pip install -U {env:TOX_EXTRA_DEPS:} {[unit_tests]commands} [testenv:docs] commands = doc8 doc/source sphinx-build -W -b html doc/source doc/build/html [testenv:releasenotes] commands = doc8 releasenotes/source releasenotes/notes sphinx-build -a -E -W -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html [testenv:manage] # we don't need to install test-requirements.txt # modules in requirements.txt are installed due to usedevelop=True # so 'deps' can be empty. deps = commands = {envpython} {toxinidir}/manage.py {posargs} [testenv:manage-py35dj20] basepython = python3.5 deps = {[testenv:manage]deps} commands = pip install -U --pre django {[testenv:manage]commands} [flake8] filename = *.py,django.wsgi exclude = .venv,.git,.tox,dist,*lib/python*,*egg,build,panel_template,dash_template,local_settings.py,*/local/*,*/test/test_plugins/*,.ropeproject,node_modules,openstack_dashboard/enabled/* # E402 module level import not at top of file # W503 line break before binary operator ignore = E402,W503 # Enable the following hacking rules which are disabled by default # H203 Use assertIs(Not)None to check for None # H904 Delay string interpolations at logging calls enable-extensions=H203,H904 max-complexity = 20 # flake8-import-order configurations import-order-style = pep8 application-import-names = horizon,openstack_dashboard [hacking] local-check-factory = horizon.hacking.checks.factory [doc8] # File extensions to check extensions = .rst, .yaml # Maximal line length should be 80 but we have some overlong lines. # Let's not get far more in. max-line-length = 80 # Disable some doc8 checks: # D000: Check RST validity # - cannot handle "none" for code-block directive ignore = D000 horizon-13.0.3/openstack_dashboard/0000775000175000017500000000000013553661042017313 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/theme_settings.py0000664000175000017500000001004713553660755022723 0ustar zuulzuul00000000000000# Copyright 2016 Hewlett Packard Enterprise Software, LLC # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. import logging import os from django.utils.translation import pgettext_lazy def get_theme_static_dirs(available_themes, collection_dir, root): static_dirs = [] # Collect and expose the themes that have been configured for theme in available_themes: theme_name, theme_label, theme_path = theme theme_url = os.path.join(collection_dir, theme_name) theme_path = os.path.join(root, theme_path) if os.path.exists(os.path.join(theme_path, 'static')): # Only expose the subdirectory 'static' if it exists from a custom # theme, allowing other logic to live with a theme that we might # not want to expose statically theme_path = os.path.join(theme_path, 'static') static_dirs.append( (theme_url, theme_path), ) return static_dirs def get_available_themes(available_themes, custom_path, default_path, default_theme, selectable_themes): new_theme_list = [] # We can only support one path at a time, because of static file # collection. custom_ndx = -1 default_ndx = -1 default_theme_ndx = -1 for ndx, each_theme in enumerate(available_themes): # Maintain Backward Compatibility for CUSTOM_THEME_PATH if custom_path: if each_theme[2] == custom_path: custom_ndx = ndx # Maintain Backward Compatibility for DEFAULT_THEME_PATH if default_path: if each_theme[0] == 'default': default_ndx = ndx each_theme = ( 'default', pgettext_lazy('Default style theme', 'Default'), default_path ) # Make sure that DEFAULT_THEME is configured for use if each_theme[0] == default_theme: default_theme_ndx = ndx new_theme_list.append(each_theme) if custom_ndx != -1: # If CUSTOM_THEME_PATH is set, then we should set that as the default # theme to make sure that upgrading Horizon doesn't jostle anyone default_theme = available_themes[custom_ndx][0] logging.warning("Your AVAILABLE_THEMES already contains your " "CUSTOM_THEME_PATH, therefore using configuration in " "AVAILABLE_THEMES for %s.", custom_path) elif custom_path is not None: new_theme_list.append( ('custom', pgettext_lazy('Custom style theme', 'Custom'), custom_path) ) default_theme = 'custom' # If 'default' isn't present at all, add it with the default_path if default_ndx == -1 and default_path is not None: new_theme_list.append( ('default', pgettext_lazy('Default style theme', 'Default'), default_path) ) # If default is not configured, we have to set one, # just grab the first theme if default_theme_ndx == -1 and custom_ndx == -1: default_theme = available_themes[0][0] if selectable_themes is None: selectable_themes = new_theme_list if default_theme not in [x[0] for x in selectable_themes]: default_theme = selectable_themes[0][0] logging.warning("Your DEFAULT_THEME is not configured in your " "selectable themes, therefore using %s as your " "default theme." % default_theme) return new_theme_list, selectable_themes, default_theme horizon-13.0.3/openstack_dashboard/urls.py0000664000175000017500000000443713553660755020674 0ustar zuulzuul00000000000000# Copyright 2012 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2012 Nebula, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. """ URL patterns for the OpenStack Dashboard. """ from django.conf import settings from django.conf.urls import include from django.conf.urls.static import static from django.conf.urls import url from django.contrib.staticfiles.urls import staticfiles_urlpatterns from django.views import defaults import horizon import horizon.base from horizon.browsers import views as browsers_views from horizon.decorators import require_auth from openstack_dashboard.api import rest from openstack_dashboard import views urlpatterns = [ url(r'^$', views.splash, name='splash'), url(r'^api/', include(rest.urls)), url(r'^header/', views.ExtensibleHeaderView.as_view()), url(r'', include(horizon.urls)), ] # add URL for ngdetails ngdetails_url = url(r'^ngdetails/', browsers_views.AngularDetailsView.as_view(), name='ngdetails') urlpatterns.append(ngdetails_url) horizon.base._decorate_urlconf([ngdetails_url], require_auth) for u in getattr(settings, 'AUTHENTICATION_URLS', ['openstack_auth.urls']): urlpatterns.append(url(r'^auth/', include(u))) # Development static app and project media serving using the staticfiles app. urlpatterns += staticfiles_urlpatterns() # Convenience function for serving user-uploaded media during # development. Only active if DEBUG==True and the URL prefix is a local # path. Production media should NOT be served by Django. urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) if settings.DEBUG: urlpatterns.append(url(r'^500/$', defaults.server_error)) horizon-13.0.3/openstack_dashboard/exceptions.py0000664000175000017500000000404413553660754022061 0ustar zuulzuul00000000000000# Copyright 2012 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. # All Rights Reserved. # # Copyright 2012 Nebula, Inc. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain # a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. from cinderclient import exceptions as cinderclient from glanceclient.common import exceptions as glanceclient from keystoneclient import exceptions as keystoneclient from neutronclient.common import exceptions as neutronclient from novaclient import exceptions as novaclient from requests import exceptions as requests from swiftclient import client as swiftclient UNAUTHORIZED = ( keystoneclient.Unauthorized, cinderclient.Unauthorized, novaclient.Unauthorized, glanceclient.Unauthorized, neutronclient.Unauthorized, ) NOT_FOUND = ( keystoneclient.NotFound, cinderclient.NotFound, novaclient.NotFound, glanceclient.NotFound, neutronclient.NotFound, ) # NOTE(gabriel): This is very broad, and may need to be dialed in. RECOVERABLE = ( keystoneclient.ClientException, # AuthorizationFailure is raised when Keystone is "unavailable". keystoneclient.AuthorizationFailure, keystoneclient.Forbidden, cinderclient.ClientException, cinderclient.ConnectionError, cinderclient.Forbidden, novaclient.ClientException, novaclient.Forbidden, glanceclient.ClientException, glanceclient.CommunicationError, neutronclient.Forbidden, neutronclient.NeutronClientException, swiftclient.ClientException, requests.RequestException, ) horizon-13.0.3/openstack_dashboard/local/0000775000175000017500000000000013553661043020406 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/local/local_settings.d/0000775000175000017500000000000013553661043023642 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/local/local_settings.d/_9030_profiler_settings.py.example0000664000175000017500000000032513553660754032232 0ustar zuulzuul00000000000000OPENSTACK_PROFILER.update({ 'enabled': True, 'keys': ['SECRET_KEY'], 'notifier_connection_string': 'mongodb://%s' % OPENSTACK_HOST, 'receiver_connection_string': 'mongodb://%s' % OPENSTACK_HOST }) horizon-13.0.3/openstack_dashboard/local/local_settings.d/_11_toggle_angular_features.py.example0000664000175000017500000000006113553660754033203 0ustar zuulzuul00000000000000ANGULAR_FEATURES.update({'images_panel': False}) ././@LongLink0000000000000000000000000000015000000000000011211 Lustar 00000000000000horizon-13.0.3/openstack_dashboard/local/local_settings.d/_2010_integration_tests_deprecated.py.examplehorizon-13.0.3/openstack_dashboard/local/local_settings.d/_2010_integration_tests_deprecated.py.exam0000664000175000017500000000045013553660754033702 0ustar zuulzuul00000000000000# This file is to be included for configuring integration tests when # wanting to only test legacy panels. Since 'local' modules are evaluated # after settings.py, these configurations will override the default settings. ANGULAR_FEATURES.update({"images_panel": False, "flavors_panel": False}) horizon-13.0.3/openstack_dashboard/local/local_settings.d/_20_integration_tests_scaffolds.py.example0000664000175000017500000000050613553660754034110 0ustar zuulzuul00000000000000# Enable both Launch Instance wizards for the sake of testing LAUNCH_INSTANCE_LEGACY_ENABLED = True LAUNCH_INSTANCE_NG_ENABLED = True # Provide a global setting for switching on/off various integration tests # scaffolds INTEGRATION_TESTS_SUPPORT = True HORIZON_CONFIG['integration_tests_support'] = INTEGRATION_TESTS_SUPPORT horizon-13.0.3/openstack_dashboard/local/local_settings.d/_10_set_custom_theme.py.example0000664000175000017500000000020513553660754031661 0ustar zuulzuul00000000000000# override the AVAILABLE_THEMES variable with this settings snippet # AVAILABLE_THEMES=[('material', 'Material', 'themes/material')] horizon-13.0.3/openstack_dashboard/local/__init__.py0000664000175000017500000000000013553660754022515 0ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/local/local_settings.py.example0000664000175000017500000010351613553660755025443 0ustar zuulzuul00000000000000# -*- coding: utf-8 -*- import os from django.utils.translation import ugettext_lazy as _ from horizon.utils import secret_key from openstack_dashboard.settings import HORIZON_CONFIG DEBUG = True # This setting controls whether or not compression is enabled. Disabling # compression makes Horizon considerably slower, but makes it much easier # to debug JS and CSS changes #COMPRESS_ENABLED = not DEBUG # This setting controls whether compression happens on the fly, or offline # with `python manage.py compress` # See https://django-compressor.readthedocs.io/en/latest/usage/#offline-compression # for more information #COMPRESS_OFFLINE = not DEBUG # WEBROOT is the location relative to Webserver root # should end with a slash. WEBROOT = '/' #LOGIN_URL = WEBROOT + 'auth/login/' #LOGOUT_URL = WEBROOT + 'auth/logout/' # # LOGIN_REDIRECT_URL can be used as an alternative for # HORIZON_CONFIG.user_home, if user_home is not set. # Do not set it to '/home/', as this will cause circular redirect loop #LOGIN_REDIRECT_URL = WEBROOT # If horizon is running in production (DEBUG is False), set this # with the list of host/domain names that the application can serve. # For more information see: # https://docs.djangoproject.com/en/dev/ref/settings/#allowed-hosts #ALLOWED_HOSTS = ['horizon.example.com', ] # Set SSL proxy settings: # Pass this header from the proxy after terminating the SSL, # and don't forget to strip it from the client's request. # For more information see: # https://docs.djangoproject.com/en/dev/ref/settings/#secure-proxy-ssl-header #SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https') # If Horizon is being served through SSL, then uncomment the following two # settings to better secure the cookies from security exploits #CSRF_COOKIE_SECURE = True #SESSION_COOKIE_SECURE = True # The absolute path to the directory where message files are collected. # The message file must have a .json file extension. When the user logins to # horizon, the message files collected are processed and displayed to the user. #MESSAGES_PATH=None # Overrides for OpenStack API versions. Use this setting to force the # OpenStack dashboard to use a specific API version for a given service API. # Versions specified here should be integers or floats, not strings. # NOTE: The version should be formatted as it appears in the URL for the # service API. For example, The identity service APIs have inconsistent # use of the decimal point, so valid options would be 2.0 or 3. # Minimum compute version to get the instance locked status is 2.9. #OPENSTACK_API_VERSIONS = { # "data-processing": 1.1, # "identity": 3, # "image": 2, # "volume": 2, # "compute": 2, #} # Set this to True if running on a multi-domain model. When this is enabled, it # will require the user to enter the Domain name in addition to the username # for login. #OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = False # Set this to True if you want available domains displayed as a dropdown menu # on the login screen. It is strongly advised NOT to enable this for public # clouds, as advertising enabled domains to unauthenticated customers # irresponsibly exposes private information. This should only be used for # private clouds where the dashboard sits behind a corporate firewall. #OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN = False # If OPENSTACK_KEYSTONE_DOMAIN_DROPDOWN is enabled, this option can be used to # set the available domains to choose from. This is a list of pairs whose first # value is the domain name and the second is the display name. #OPENSTACK_KEYSTONE_DOMAIN_CHOICES = ( # ('Default', 'Default'), #) # Overrides the default domain used when running on single-domain model # with Keystone V3. All entities will be created in the default domain. # NOTE: This value must be the name of the default domain, NOT the ID. # Also, you will most likely have a value in the keystone policy file like this # "cloud_admin": "rule:admin_required and domain_id:" # This value must be the name of the domain whose ID is specified there. #OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'Default' # Set this to True to enable panels that provide the ability for users to # manage Identity Providers (IdPs) and establish a set of rules to map # federation protocol attributes to Identity API attributes. # This extension requires v3.0+ of the Identity API. #OPENSTACK_KEYSTONE_FEDERATION_MANAGEMENT = False # Set Console type: # valid options are "AUTO"(default), "VNC", "SPICE", "RDP", "SERIAL", "MKS" # or None. Set to None explicitly if you want to deactivate the console. #CONSOLE_TYPE = "AUTO" # Toggle showing the openrc file for Keystone V2. # If set to false the link will be removed from the user dropdown menu # and the API Access page #SHOW_KEYSTONE_V2_RC = True # If provided, a "Report Bug" link will be displayed in the site header # which links to the value of this setting (ideally a URL containing # information on how to report issues). #HORIZON_CONFIG["bug_url"] = "http://bug-report.example.com" # Show backdrop element outside the modal, do not close the modal # after clicking on backdrop. #HORIZON_CONFIG["modal_backdrop"] = "static" # Specify a regular expression to validate user passwords. #HORIZON_CONFIG["password_validator"] = { # "regex": '.*', # "help_text": _("Your password does not meet the requirements."), #} # Disable simplified floating IP address management for deployments with # multiple floating IP pools or complex network requirements. #HORIZON_CONFIG["simple_ip_management"] = False # Turn off browser autocompletion for forms including the login form and # the database creation workflow if so desired. #HORIZON_CONFIG["password_autocomplete"] = "off" # Setting this to True will disable the reveal button for password fields, # including on the login form. #HORIZON_CONFIG["disable_password_reveal"] = False LOCAL_PATH = os.path.dirname(os.path.abspath(__file__)) # Set custom secret key: # You can either set it to a specific value or you can let horizon generate a # default secret key that is unique on this machine, e.i. regardless of the # amount of Python WSGI workers (if used behind Apache+mod_wsgi): However, # there may be situations where you would want to set this explicitly, e.g. # when multiple dashboard instances are distributed on different machines # (usually behind a load-balancer). Either you have to make sure that a session # gets all requests routed to the same dashboard instance or you set the same # SECRET_KEY for all of them. SECRET_KEY = secret_key.generate_or_read_from_file( os.path.join(LOCAL_PATH, '.secret_key_store')) # We recommend you use memcached for development; otherwise after every reload # of the django development server, you will have to login again. To use # memcached set CACHES to something like #CACHES = { # 'default': { # 'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache', # 'LOCATION': '127.0.0.1:11211', # }, #} CACHES = { 'default': { 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache', }, } # Send email to the console by default EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' # Or send them to /dev/null #EMAIL_BACKEND = 'django.core.mail.backends.dummy.EmailBackend' # Configure these for your outgoing email host #EMAIL_HOST = 'smtp.my-company.com' #EMAIL_PORT = 25 #EMAIL_HOST_USER = 'djangomail' #EMAIL_HOST_PASSWORD = 'top-secret!' # For multiple regions uncomment this configuration, and add (endpoint, title). #AVAILABLE_REGIONS = [ # ('http://cluster1.example.com:5000/v3', 'cluster1'), # ('http://cluster2.example.com:5000/v3', 'cluster2'), #] OPENSTACK_HOST = "127.0.0.1" OPENSTACK_KEYSTONE_URL = "http://%s:5000/v3" % OPENSTACK_HOST OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_" # For setting the default service region on a per-endpoint basis. Note that the # default value for this setting is {}, and below is just an example of how it # should be specified. #DEFAULT_SERVICE_REGIONS = { # OPENSTACK_KEYSTONE_URL: 'RegionOne' #} # Enables keystone web single-sign-on if set to True. #WEBSSO_ENABLED = False # Authentication mechanism to be selected as default. # The value must be a key from WEBSSO_CHOICES. #WEBSSO_INITIAL_CHOICE = "credentials" # The list of authentication mechanisms which include keystone # federation protocols and identity provider/federation protocol # mapping keys (WEBSSO_IDP_MAPPING). Current supported protocol # IDs are 'saml2' and 'oidc' which represent SAML 2.0, OpenID # Connect respectively. # Do not remove the mandatory credentials mechanism. # Note: The last two tuples are sample mapping keys to a identity provider # and federation protocol combination (WEBSSO_IDP_MAPPING). #WEBSSO_CHOICES = ( # ("credentials", _("Keystone Credentials")), # ("oidc", _("OpenID Connect")), # ("saml2", _("Security Assertion Markup Language")), # ("acme_oidc", "ACME - OpenID Connect"), # ("acme_saml2", "ACME - SAML2"), #) # A dictionary of specific identity provider and federation protocol # combinations. From the selected authentication mechanism, the value # will be looked up as keys in the dictionary. If a match is found, # it will redirect the user to a identity provider and federation protocol # specific WebSSO endpoint in keystone, otherwise it will use the value # as the protocol_id when redirecting to the WebSSO by protocol endpoint. # NOTE: The value is expected to be a tuple formatted as: (, ). #WEBSSO_IDP_MAPPING = { # "acme_oidc": ("acme", "oidc"), # "acme_saml2": ("acme", "saml2"), #} # The Keystone Provider drop down uses Keystone to Keystone federation # to switch between Keystone service providers. # Set display name for Identity Provider (dropdown display name) #KEYSTONE_PROVIDER_IDP_NAME = "Local Keystone" # This id is used for only for comparison with the service provider IDs. This ID # should not match any service provider IDs. #KEYSTONE_PROVIDER_IDP_ID = "localkeystone" # Disable SSL certificate checks (useful for self-signed certificates): #OPENSTACK_SSL_NO_VERIFY = True # The CA certificate to use to verify SSL connections #OPENSTACK_SSL_CACERT = '/path/to/cacert.pem' # The OPENSTACK_KEYSTONE_BACKEND settings can be used to identify the # capabilities of the auth backend for Keystone. # If Keystone has been configured to use LDAP as the auth backend then set # can_edit_user to False and name to 'ldap'. # # TODO(tres): Remove these once Keystone has an API to identify auth backend. OPENSTACK_KEYSTONE_BACKEND = { 'name': 'native', 'can_edit_user': True, 'can_edit_group': True, 'can_edit_project': True, 'can_edit_domain': True, 'can_edit_role': True, } # Setting this to True, will add a new "Retrieve Password" action on instance, # allowing Admin session password retrieval/decryption. #OPENSTACK_ENABLE_PASSWORD_RETRIEVE = False # This setting allows deployers to control whether a token is deleted on log # out. This can be helpful when there are often long running processes being # run in the Horizon environment. #TOKEN_DELETION_DISABLED = False # The Launch Instance user experience has been significantly enhanced. # You can choose whether to enable the new launch instance experience, # the legacy experience, or both. The legacy experience will be removed # in a future release, but is available as a temporary backup setting to ensure # compatibility with existing deployments. Further development will not be # done on the legacy experience. Please report any problems with the new # experience via the Launchpad tracking system. # # Toggle LAUNCH_INSTANCE_LEGACY_ENABLED and LAUNCH_INSTANCE_NG_ENABLED to # determine the experience to enable. Set them both to true to enable # both. #LAUNCH_INSTANCE_LEGACY_ENABLED = True #LAUNCH_INSTANCE_NG_ENABLED = False # A dictionary of settings which can be used to provide the default values for # properties found in the Launch Instance modal. #LAUNCH_INSTANCE_DEFAULTS = { # 'config_drive': False, # 'enable_scheduler_hints': True, # 'disable_image': False, # 'disable_instance_snapshot': False, # 'disable_volume': False, # 'disable_volume_snapshot': False, # 'create_volume': True, #} # The Xen Hypervisor has the ability to set the mount point for volumes # attached to instances (other Hypervisors currently do not). Setting # can_set_mount_point to True will add the option to set the mount point # from the UI. OPENSTACK_HYPERVISOR_FEATURES = { 'can_set_mount_point': False, 'can_set_password': False, 'requires_keypair': False, 'enable_quotas': True } # This settings controls whether IP addresses of servers are retrieved from # neutron in the project instance table. Setting this to ``False`` may mitigate # a performance issue in the project instance table in large deployments. #OPENSTACK_INSTANCE_RETRIEVE_IP_ADDRESSES = True # The OPENSTACK_CINDER_FEATURES settings can be used to enable optional # services provided by cinder that is not exposed by its extension API. OPENSTACK_CINDER_FEATURES = { 'enable_backup': False, } # The OPENSTACK_NEUTRON_NETWORK settings can be used to enable optional # services provided by neutron. Options currently available are load # balancer service, security groups, quotas, VPN service. OPENSTACK_NEUTRON_NETWORK = { 'enable_router': True, 'enable_quotas': True, 'enable_ipv6': True, 'enable_distributed_router': False, 'enable_ha_router': False, 'enable_fip_topology_check': True, # Default dns servers you would like to use when a subnet is # created. This is only a default, users can still choose a different # list of dns servers when creating a new subnet. # The entries below are examples only, and are not appropriate for # real deployments # 'default_dns_nameservers': ["8.8.8.8", "8.8.4.4", "208.67.222.222"], # Set which provider network types are supported. Only the network types # in this list will be available to choose from when creating a network. # Network types include local, flat, vlan, gre, vxlan and geneve. # 'supported_provider_types': ['*'], # You can configure available segmentation ID range per network type # in your deployment. # 'segmentation_id_range': { # 'vlan': [1024, 2048], # 'vxlan': [4094, 65536], # }, # You can define additional provider network types here. # 'extra_provider_types': { # 'awesome_type': { # 'display_name': 'Awesome New Type', # 'require_physical_network': False, # 'require_segmentation_id': True, # } # }, # Set which VNIC types are supported for port binding. Only the VNIC # types in this list will be available to choose from when creating a # port. # VNIC types include 'normal', 'direct', 'direct-physical', 'macvtap', # 'baremetal' and 'virtio-forwarder' # Set to empty list or None to disable VNIC type selection. 'supported_vnic_types': ['*'], # Set list of available physical networks to be selected in the physical # network field on the admin create network modal. If it's set to an empty # list, the field will be a regular input field. # e.g. ['default', 'test'] 'physical_networks': [], } # The OPENSTACK_HEAT_STACK settings can be used to disable password # field required while launching the stack. OPENSTACK_HEAT_STACK = { 'enable_user_pass': True, } # The OPENSTACK_IMAGE_BACKEND settings can be used to customize features # in the OpenStack Dashboard related to the Image service, such as the list # of supported image formats. #OPENSTACK_IMAGE_BACKEND = { # 'image_formats': [ # ('', _('Select format')), # ('aki', _('AKI - Amazon Kernel Image')), # ('ami', _('AMI - Amazon Machine Image')), # ('ari', _('ARI - Amazon Ramdisk Image')), # ('docker', _('Docker')), # ('iso', _('ISO - Optical Disk Image')), # ('ova', _('OVA - Open Virtual Appliance')), # ('qcow2', _('QCOW2 - QEMU Emulator')), # ('raw', _('Raw')), # ('vdi', _('VDI - Virtual Disk Image')), # ('vhd', _('VHD - Virtual Hard Disk')), # ('vhdx', _('VHDX - Large Virtual Hard Disk')), # ('vmdk', _('VMDK - Virtual Machine Disk')), # ], #} # The IMAGE_CUSTOM_PROPERTY_TITLES settings is used to customize the titles for # image custom property attributes that appear on image detail pages. IMAGE_CUSTOM_PROPERTY_TITLES = { "architecture": _("Architecture"), "kernel_id": _("Kernel ID"), "ramdisk_id": _("Ramdisk ID"), "image_state": _("Euca2ools state"), "project_id": _("Project ID"), "image_type": _("Image Type"), } # The IMAGE_RESERVED_CUSTOM_PROPERTIES setting is used to specify which image # custom properties should not be displayed in the Image Custom Properties # table. IMAGE_RESERVED_CUSTOM_PROPERTIES = [] # Set to 'legacy' or 'direct' to allow users to upload images to glance via # Horizon server. When enabled, a file form field will appear on the create # image form. If set to 'off', there will be no file form field on the create # image form. See documentation for deployment considerations. #HORIZON_IMAGES_UPLOAD_MODE = 'legacy' # Allow a location to be set when creating or updating Glance images. # If using Glance V2, this value should be False unless the Glance # configuration and policies allow setting locations. #IMAGES_ALLOW_LOCATION = False # A dictionary of default settings for create image modal. #CREATE_IMAGE_DEFAULTS = { # 'image_visibility': "public", #} # OPENSTACK_ENDPOINT_TYPE specifies the endpoint type to use for the endpoints # in the Keystone service catalog. Use this setting when Horizon is running # external to the OpenStack environment. The default is 'publicURL'. #OPENSTACK_ENDPOINT_TYPE = "publicURL" # SECONDARY_ENDPOINT_TYPE specifies the fallback endpoint type to use in the # case that OPENSTACK_ENDPOINT_TYPE is not present in the endpoints # in the Keystone service catalog. Use this setting when Horizon is running # external to the OpenStack environment. The default is None. This # value should differ from OPENSTACK_ENDPOINT_TYPE if used. #SECONDARY_ENDPOINT_TYPE = None # The number of objects (Swift containers/objects or images) to display # on a single page before providing a paging element (a "more" link) # to paginate results. API_RESULT_LIMIT = 1000 API_RESULT_PAGE_SIZE = 20 # The size of chunk in bytes for downloading objects from Swift SWIFT_FILE_TRANSFER_CHUNK_SIZE = 512 * 1024 # The default number of lines displayed for instance console log. INSTANCE_LOG_LENGTH = 35 # Specify a maximum number of items to display in a dropdown. DROPDOWN_MAX_ITEMS = 30 # The timezone of the server. This should correspond with the timezone # of your entire OpenStack installation, and hopefully be in UTC. TIME_ZONE = "UTC" # When launching an instance, the menu of available flavors is # sorted by RAM usage, ascending. If you would like a different sort order, # you can provide another flavor attribute as sorting key. Alternatively, you # can provide a custom callback method to use for sorting. You can also provide # a flag for reverse sort. For more info, see # http://docs.python.org/2/library/functions.html#sorted #CREATE_INSTANCE_FLAVOR_SORT = { # 'key': 'name', # # or # 'key': my_awesome_callback_method, # 'reverse': False, #} # Set this to True to display an 'Admin Password' field on the Change Password # form to verify that it is indeed the admin logged-in who wants to change # the password. #ENFORCE_PASSWORD_CHECK = False # Modules that provide /auth routes that can be used to handle different types # of user authentication. Add auth plugins that require extra route handling to # this list. #AUTHENTICATION_URLS = [ # 'openstack_auth.urls', #] # The Horizon Policy Enforcement engine uses these values to load per service # policy rule files. The content of these files should match the files the # OpenStack services are using to determine role based access control in the # target installation. # Path to directory containing policy.json files #POLICY_FILES_PATH = os.path.join(ROOT_PATH, "conf") # Map of local copy of service policy files. # Please insure that your identity policy file matches the one being used on # your keystone servers. There is an alternate policy file that may be used # in the Keystone v3 multi-domain case, policy.v3cloudsample.json. # This file is not included in the Horizon repository by default but can be # found at # http://git.openstack.org/cgit/openstack/keystone/tree/etc/ \ # policy.v3cloudsample.json # Having matching policy files on the Horizon and Keystone servers is essential # for normal operation. This holds true for all services and their policy files. #POLICY_FILES = { # 'identity': 'keystone_policy.json', # 'compute': 'nova_policy.json', # 'volume': 'cinder_policy.json', # 'image': 'glance_policy.json', # 'network': 'neutron_policy.json', #} # TODO: (david-lyle) remove when plugins support adding settings. # Note: Only used when trove-dashboard plugin is configured to be used by # Horizon. # Trove user and database extension support. By default support for # creating users and databases on database instances is turned on. # To disable these extensions set the permission here to something # unusable such as ["!"]. #TROVE_ADD_USER_PERMS = [] #TROVE_ADD_DATABASE_PERMS = [] # Change this patch to the appropriate list of tuples containing # a key, label and static directory containing two files: # _variables.scss and _styles.scss #AVAILABLE_THEMES = [ # ('default', 'Default', 'themes/default'), # ('material', 'Material', 'themes/material'), #] LOGGING = { 'version': 1, # When set to True this will disable all logging except # for loggers specified in this configuration dictionary. Note that # if nothing is specified here and disable_existing_loggers is True, # django.db.backends will still log unless it is disabled explicitly. 'disable_existing_loggers': False, # If apache2 mod_wsgi is used to deploy OpenStack dashboard # timestamp is output by mod_wsgi. If WSGI framework you use does not # output timestamp for logging, add %(asctime)s in the following # format definitions. 'formatters': { 'console': { 'format': '%(levelname)s %(name)s %(message)s' }, 'operation': { # The format of "%(message)s" is defined by # OPERATION_LOG_OPTIONS['format'] 'format': '%(message)s' }, }, 'handlers': { 'null': { 'level': 'DEBUG', 'class': 'logging.NullHandler', }, 'console': { # Set the level to "DEBUG" for verbose output logging. 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'console', }, 'operation': { 'level': 'INFO', 'class': 'logging.StreamHandler', 'formatter': 'operation', }, }, 'loggers': { 'horizon': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'horizon.operation_log': { 'handlers': ['operation'], 'level': 'INFO', 'propagate': False, }, 'openstack_dashboard': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'novaclient': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'cinderclient': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'keystoneauth': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'keystoneclient': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'glanceclient': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'neutronclient': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'swiftclient': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'oslo_policy': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'openstack_auth': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'nose.plugins.manager': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, 'django': { 'handlers': ['console'], 'level': 'DEBUG', 'propagate': False, }, # Logging from django.db.backends is VERY verbose, send to null # by default. 'django.db.backends': { 'handlers': ['null'], 'propagate': False, }, 'requests': { 'handlers': ['null'], 'propagate': False, }, 'urllib3': { 'handlers': ['null'], 'propagate': False, }, 'chardet.charsetprober': { 'handlers': ['null'], 'propagate': False, }, 'iso8601': { 'handlers': ['null'], 'propagate': False, }, 'scss': { 'handlers': ['null'], 'propagate': False, }, }, } # 'direction' should not be specified for all_tcp/udp/icmp. # It is specified in the form. SECURITY_GROUP_RULES = { 'all_tcp': { 'name': _('All TCP'), 'ip_protocol': 'tcp', 'from_port': '1', 'to_port': '65535', }, 'all_udp': { 'name': _('All UDP'), 'ip_protocol': 'udp', 'from_port': '1', 'to_port': '65535', }, 'all_icmp': { 'name': _('All ICMP'), 'ip_protocol': 'icmp', 'from_port': '-1', 'to_port': '-1', }, 'ssh': { 'name': 'SSH', 'ip_protocol': 'tcp', 'from_port': '22', 'to_port': '22', }, 'smtp': { 'name': 'SMTP', 'ip_protocol': 'tcp', 'from_port': '25', 'to_port': '25', }, 'dns': { 'name': 'DNS', 'ip_protocol': 'tcp', 'from_port': '53', 'to_port': '53', }, 'http': { 'name': 'HTTP', 'ip_protocol': 'tcp', 'from_port': '80', 'to_port': '80', }, 'pop3': { 'name': 'POP3', 'ip_protocol': 'tcp', 'from_port': '110', 'to_port': '110', }, 'imap': { 'name': 'IMAP', 'ip_protocol': 'tcp', 'from_port': '143', 'to_port': '143', }, 'ldap': { 'name': 'LDAP', 'ip_protocol': 'tcp', 'from_port': '389', 'to_port': '389', }, 'https': { 'name': 'HTTPS', 'ip_protocol': 'tcp', 'from_port': '443', 'to_port': '443', }, 'smtps': { 'name': 'SMTPS', 'ip_protocol': 'tcp', 'from_port': '465', 'to_port': '465', }, 'imaps': { 'name': 'IMAPS', 'ip_protocol': 'tcp', 'from_port': '993', 'to_port': '993', }, 'pop3s': { 'name': 'POP3S', 'ip_protocol': 'tcp', 'from_port': '995', 'to_port': '995', }, 'ms_sql': { 'name': 'MS SQL', 'ip_protocol': 'tcp', 'from_port': '1433', 'to_port': '1433', }, 'mysql': { 'name': 'MYSQL', 'ip_protocol': 'tcp', 'from_port': '3306', 'to_port': '3306', }, 'rdp': { 'name': 'RDP', 'ip_protocol': 'tcp', 'from_port': '3389', 'to_port': '3389', }, } # Deprecation Notice: # # The setting FLAVOR_EXTRA_KEYS has been deprecated. # Please load extra spec metadata into the Glance Metadata Definition Catalog. # # The sample quota definitions can be found in: # /etc/metadefs/compute-quota.json # # The metadata definition catalog supports CLI and API: # $glance --os-image-api-version 2 help md-namespace-import # $glance-manage db_load_metadefs # # See Metadata Definitions on: http://docs.openstack.org/developer/glance/ # TODO: (david-lyle) remove when plugins support settings natively # Note: This is only used when the Sahara plugin is configured and enabled # for use in Horizon. # Indicate to the Sahara data processing service whether or not # automatic floating IP allocation is in effect. If it is not # in effect, the user will be prompted to choose a floating IP # pool for use in their cluster. False by default. You would want # to set this to True if you were running Nova Networking with # auto_assign_floating_ip = True. #SAHARA_AUTO_IP_ALLOCATION_ENABLED = False # The hash algorithm to use for authentication tokens. This must # match the hash algorithm that the identity server and the # auth_token middleware are using. Allowed values are the # algorithms supported by Python's hashlib library. #OPENSTACK_TOKEN_HASH_ALGORITHM = 'md5' # AngularJS requires some settings to be made available to # the client side. Some settings are required by in-tree / built-in horizon # features. These settings must be added to REST_API_REQUIRED_SETTINGS in the # form of ['SETTING_1','SETTING_2'], etc. # # You may remove settings from this list for security purposes, but do so at # the risk of breaking a built-in horizon feature. These settings are required # for horizon to function properly. Only remove them if you know what you # are doing. These settings may in the future be moved to be defined within # the enabled panel configuration. # You should not add settings to this list for out of tree extensions. # See: https://wiki.openstack.org/wiki/Horizon/RESTAPI REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES', 'LAUNCH_INSTANCE_DEFAULTS', 'OPENSTACK_IMAGE_FORMATS', 'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN', 'CREATE_IMAGE_DEFAULTS', 'ENFORCE_PASSWORD_CHECK'] # Additional settings can be made available to the client side for # extensibility by specifying them in REST_API_ADDITIONAL_SETTINGS # !! Please use extreme caution as the settings are transferred via HTTP/S # and are not encrypted on the browser. This is an experimental API and # may be deprecated in the future without notice. #REST_API_ADDITIONAL_SETTINGS = [] # DISALLOW_IFRAME_EMBED can be used to prevent Horizon from being embedded # within an iframe. Legacy browsers are still vulnerable to a Cross-Frame # Scripting (XFS) vulnerability, so this option allows extra security hardening # where iframes are not used in deployment. Default setting is True. # For more information see: # http://tinyurl.com/anticlickjack #DISALLOW_IFRAME_EMBED = True # Help URL can be made available for the client. To provide a help URL, edit the # following attribute to the URL of your choice. #HORIZON_CONFIG["help_url"] = "http://openstack.mycompany.org" # Settings for OperationLogMiddleware # OPERATION_LOG_ENABLED is flag to use the function to log an operation on # Horizon. # mask_targets is arrangement for appointing a target to mask. # method_targets is arrangement of HTTP method to output log. # format is the log contents. #OPERATION_LOG_ENABLED = False #OPERATION_LOG_OPTIONS = { # 'mask_fields': ['password'], # 'target_methods': ['POST'], # 'ignored_urls': ['/js/', '/static/', '^/api/'], # 'format': ("[%(client_ip)s] [%(domain_name)s]" # " [%(domain_id)s] [%(project_name)s]" # " [%(project_id)s] [%(user_name)s] [%(user_id)s] [%(request_scheme)s]" # " [%(referer_url)s] [%(request_url)s] [%(message)s] [%(method)s]" # " [%(http_status)s] [%(param)s]"), #} # The default date range in the Overview panel meters - either minus N # days (if the value is integer N), or from the beginning of the current month # until today (if set to None). This setting should be used to limit the amount # of data fetched by default when rendering the Overview panel. #OVERVIEW_DAYS_RANGE = 1 # To allow operators to require users provide a search criteria first # before loading any data into the views, set the following dict # attributes to True in each one of the panels you want to enable this feature. # Follow the convention . #FILTER_DATA_FIRST = { # 'admin.instances': False, # 'admin.images': False, # 'admin.networks': False, # 'admin.routers': False, # 'admin.volumes': False, # 'identity.users': False, # 'identity.projects': False, # 'identity.groups': False, # 'identity.roles': False #} # Dict used to restrict user private subnet cidr range. # An empty list means that user input will not be restricted # for a corresponding IP version. By default, there is # no restriction for IPv4 or IPv6. To restrict # user private subnet cidr range set ALLOWED_PRIVATE_SUBNET_CIDR # to something like #ALLOWED_PRIVATE_SUBNET_CIDR = { # 'ipv4': ['10.0.0.0/8', '192.168.0.0/16'], # 'ipv6': ['fc00::/7'] #} ALLOWED_PRIVATE_SUBNET_CIDR = {'ipv4': [], 'ipv6': []} # Projects and users can have extra attributes as defined by keystone v3. # Horizon has the ability to display these extra attributes via this setting. # If you'd like to display extra data in the project or user tables, set the # corresponding dict key to the attribute name, followed by the display name. # For more information, see horizon's customization (http://docs.openstack.org/developer/horizon/topics/customizing.html#horizon-customization-module-overrides) #PROJECT_TABLE_EXTRA_INFO = { # 'phone_num': _('Phone Number'), #} #USER_TABLE_EXTRA_INFO = { # 'phone_num': _('Phone Number'), #} # Password will have an expiration date when using keystone v3 and enabling the # feature. # This setting allows you to set the number of days that the user will be alerted # prior to the password expiration. # Once the password expires keystone will deny the access and users must # contact an admin to change their password. #PASSWORD_EXPIRES_WARNING_THRESHOLD_DAYS = 0 horizon-13.0.3/openstack_dashboard/local/enabled/0000775000175000017500000000000013553661043022000 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/local/enabled/__init__.py0000664000175000017500000000000013553660754024107 0ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/local/enabled/_50_settings.py.example0000664000175000017500000000027113553660754026317 0ustar zuulzuul00000000000000# The name of the dashboard to be added to HORIZON['dashboards']. Required. DASHBOARD = 'settings' # If set to True, this dashboard will not be added to the settings. DISABLED = False horizon-13.0.3/openstack_dashboard/static/0000775000175000017500000000000013553661042020602 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/static/dashboard/0000775000175000017500000000000013553661042022531 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/static/dashboard/scss/0000775000175000017500000000000013553661043023505 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/static/dashboard/scss/_variables.scss0000664000175000017500000000514713553660755026531 0ustar zuulzuul00000000000000/* Import the Bootstrap Helper Variables */ @import 'bootstrap_helpers'; /* When used with Horizon via Django, this value is set automatically from settings.py and is added dynamically to the namespace through horizon/utils/scss_filter.py */ $static_url: "/static/" !default; /* Horizon Custom Variables */ $main-content-min-width: 900px !default; $sidebar-width: 220px !default; $border-color: #dddddd !default; $table-bg-odd: $table-bg-accent !default; /* Resource Browser */ $rbrowser-data-table-border-width: 1px; $rbrowser-data-table-border-color: $border-color; $rbrowser-actions-column-padding: 10px; $rbrowser-small-button-height: 28px; $rbrowser-td-height: $rbrowser-small-button-height; $rbrowser-table-cell-padding: 8px; $rbrowser-selected-color: #e9f5fa; $rbrowser-wrapper-width: 100%; $rbrowser-navigation-table-width: 40%; $rbrowser-content-table-width: $rbrowser-wrapper-width - $rbrowser-navigation-table-width; $rbrowser-body-background-color: $body-bg; $rbrowser-header-background-color: $gray-lighter; $rbrowser-footer-background-color: #f1f1f1; // Note: the content-wrapper background colors are determined by // .table-striped-datatable styles. // Font-awesome path to the icon fonts $fa-font-path: $static_url + "horizon/lib/font_awesome/fonts"; /* Charts */ $overview_chart_height: 81px; /* Responsive Table */ $detail-row-padding: 1em !default; $expander-width: 1.5em !default; $reorder-border: 2px solid #1f83c6 !default; $table-col-avg-width: 150px !default; $table-border: 1px solid $table-border-color !default; $table-gap-height: 0.5em !default; $table-padding: 0.5em !default; /* Tooltip */ $tooltip-padding: 0.3em 0.8em !default; /* Magic Search */ $magic-search-min-width: 40em !default; $magic-search-border-color: #cccccc !default; $magic-search-margin-bottom: 0 !default; $magic-search-border-radius: 3px !default; $magic-search-margin-padding: 2px !default; /* Member lists */ $members-list-padding: 3px !default; $members-list-border: 1px solid $gray-light !default; // TODO: These values are hardcoded lengths but they are actually // very dependant on the modal size set in the theme. We need // to eventually readdress these and calculate them dynamically $members-list-item-width: 130px !default; $members-list-item-max-width: 327px !default; $members-list-roles-width: 125px !default; // This defines the max-width for a breadcrumb item before it will be truncated $breadcrumb-item-width: 15em !default; // This is the max height for the domain/project selector in the top nav. After // this point, it will scroll. $context-selector-max-height: calc(99vh - #{$navbar-height}) !default; horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/0000775000175000017500000000000013553661043025672 5ustar zuulzuul00000000000000horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_checkboxes.scss0000664000175000017500000000151113553660754031052 0ustar zuulzuul00000000000000@import '/horizon/lib/font_awesome/scss/variables'; @import '/horizon/lib/font_awesome/scss/mixins'; // // Checkboxes // This will ONLY work when the label's 'for' attribute // shares the input[type=checkbox]'s 'id' value // -------------------------------------------------- .themable-checkbox { // Hide the real checkbox input[type=checkbox] { display:none; // The checkbox - Unchecked & + label { margin-bottom: 0; // remove the Bootstrap margin &:before { @include fa-icon(); content: $fa-var-square-o; width: 1em; vertical-align: middle; } & > span { padding-left: $padding-small-vertical; vertical-align: middle; } } // The checkbox - Checked &:checked + label:before { content: $fa-var-check-square-o; } } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_sidebar.scss0000664000175000017500000000451713553660754030356 0ustar zuulzuul00000000000000/* * This file defines the styling for side navigation in Horizon, which uses * nested panels to utilise Bootstraps native JS handling for accordion menus * in panels. However, Bootstrap does *not* natively support nested panels * in its markup; to work around this, we remove the panel styling and inherit * list group styling. */ #sidebar-accordion { width: $sidebar-width; } #sidebar-mask { background-color: rgba(0, 0, 0, 0.5); height: 100%; left: 0; position: fixed; top: 0; visibility: hidden; opacity: 0; width: 100%; z-index: 2; transition: all 0.3s ease 0s; @media (max-width: $screen-xs-max) { &.on-screen { visibility: visible; opacity: 1; } } } #sidebar { width: $sidebar-width; // Make sure the side nav is always shown at larger screen sizes, // regardless of previous state @media (min-width: $screen-sm-min) { display: block; } @media (max-width: $screen-xs-max) { transition: all 0.3s ease 0s; position: fixed; top: $navbar-height; bottom: 0; z-index: 3; left: -$sidebar-width; overflow-y: auto; overflow-x: hidden; &.on-screen { left: 0; } } // Sets the arrow toggles for each dashboard list .openstack-toggle.fa { line-height: $line-height-computed; text-align: center; @include transition(transform 0.3s ease 0s); @extend .fa-chevron-down; } // Rotate the arrow toggle for closed panels .collapsed > .openstack-toggle.fa { @include rotate(-90deg); } // Remove panel default styling for the side nav only .panel { margin: 0; border-radius: 0; border: 0; box-shadow: none; cursor: pointer; .list-group-item { border-radius: 0; border: 0; } // Use the list group styling for consistency. We use panels in the markup // for accordion, but style should be list-group. > a { color: $list-group-link-color; background: $list-group-bg; &:hover { background: $list-group-hover-bg; } } } // Remove Chromes glowing blue border for focus. This should not affect // accessibility, as the tabs already have a focus effect. a:focus { outline: 0; } // Center align panel groups .openstack-panel-group { text-align: center; } // Right align panels .openstack-panel { text-align: right; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_loader.scss0000664000175000017500000000042513553660754030205 0ustar zuulzuul00000000000000.loader { width: 100%; &-caption { margin-bottom: 0; padding-top: ($line-height-computed / 2); // Matches h4 margin in _type.scss } &-inline { overflow: hidden; } } // Special Angular Override .modal-wait-spinner .modal-dialog { @extend .modal-xs; }horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_resource_topology.scss0000664000175000017500000000676313553660755032536 0ustar zuulzuul00000000000000/**** Resource Topology SCSS ****/ .link { stroke: #999; stroke-width: 1.5px; } .node { cursor:pointer; text { font: 12px sans-serif; } } #resource_container { position:relative; } #stack_box { position: absolute; width: 300px; top: 10px; left: 10px; h3 { font-size: 11pt; line-height: 20px; } p { margin: 0; font-size: 9pt; line-height: 14px; } a { margin: 0; font-size: 9pt; line-height: 14px; } img { float:left; } // Note (hurgleburgler) Double IDs?! #stack_info { float:left; white-space:normal; width:200px; } } #info_box { position: absolute; width: 300px; top: 100px; left: 10px; h3 { font-size: 9pt; line-height: 20px; } p { margin: 0; font-size: 9pt; line-height: 14px; } a { margin: 0; font-size: 9pt; line-height: 14px; } .error { color: darken($brand-danger, 20%); } } /* Shared sort list UI in use by Instances and Firewall policies */ @mixin common_box_list_selected($text) { margin-bottom: 1.5em; counter-reset:v1 0; background: #edf9ff; border:1px solid #c0d9e4; li { position: relative; a.btn:before { content: "-"; } &:before { content:$text":"counter(v1); counter-increment:v1; display: inline-block; margin-right: 5px; background: $gray; color: $body-bg; font-size: 90%; padding: 0 4px; vertical-align: middle; border-radius: 2px; position: absolute; left: -2em; } } &.dragging { li { &:before { content:$text":"; background-color:rgba(102,102,102,0.5); padding-right: 10px; } &.ui-state-highlight:before { content:""; background:transparent; } } } } .sort-container { display: none; .box-list, .box-list-selected { padding: 6px; background: #eee; border: 1px solid $border-color; min-height: 2em; width: auto !important; @include box-sizing(border-box); li { width: 226px; list-style-type: none; margin: 6px auto; padding: 3px; background: $body-bg; border: 1px solid $border-color; line-height: 18px; border-radius: 3px; cursor: move; padding-left: 23px; background: $body-bg url("../img/drag.png") no-repeat 11px 50%; em { font-size: 0.5em; line-height: 1em; color:#999; font-style: normal; margin-left: 0.8em; } i { margin-right: 5px; vertical-align: middle; } a.btn { @include box-sizing(border-box); font-size: 11px; line-height: 12px; padding: 2px 5px 3px; margin-right: 1px; width: 18px; text-align: center; right:5px; vertical-align: middle; float: right; &:before { content: "+"; } } &.ui-sortable-helper { background-color: #def; } &.ui-state-highlight { border: 1px dotted $border-color; background: #efefef; height: 0.5em; } &:after { visibility: hidden; display: block; font-size: 0; content: " "; clear: both; height: 0; } } } #selected_network { @include common_box_list_selected("NIC"); } #selected_rule { @include common_box_list_selected("rule"); } #selected_router { @include common_box_list_selected("router"); } }horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_resource_browser.scss0000664000175000017500000000631213553660754032332 0ustar zuulzuul00000000000000/* ResourceBrowser style */ #browser_wrapper { width: $rbrowser-wrapper-width; min-width: 1000px; background-color: $rbrowser-header-background-color; border: $rbrowser-data-table-border-width solid $rbrowser-data-table-border-color; border-radius: 4px; .tfoot { clear: both; padding: 8px; border-top: 1px solid $rbrowser-data-table-border-color; background-color: $rbrowser-footer-background-color; font-size: 11px; line-height: 14px; span { display: inline-block; &.navigation_table_count { width: $rbrowser-navigation-table-width; } } } form, table { margin-bottom: 0; } .navigation_wrapper, .content_wrapper { position: relative; float: left; } div.navigation_wrapper { z-index: 2; width: $rbrowser-navigation-table-width; div.table_wrapper, thead th.table_header { border-right: 0 none; border-top-right-radius: 0; } td { &:first-child { border-left: 0 none; } &.breadcrumb_td { padding-right: 0; max-width: 200px; } } tr.current_selected td { background-color: $rbrowser-selected-color; } tfoot td { border-right: 0 none; border-bottom-right-radius: 0; } ul.breadcrumb { padding-right: 0; border-top-right-radius: 0; border-bottom-right-radius: 0; border-right: 0; white-space: nowrap; } tbody td { border-right: $rbrowser-data-table-border-width solid $rbrowser-data-table-border-color; background-color: $rbrowser-body-background-color; &.anchor { word-wrap: break-word; white-space: normal; max-width: 145px; } } } div.content_wrapper { width: $rbrowser-content-table-width; div.table_wrapper, thead th.table_header { border-left: 0 none; border-top-left-radius: 0; } td { border-bottom: $rbrowser-data-table-border-width solid $rbrowser-data-table-border-color; &:last-child { border-right: 0 none; } &.breadcrumb_td { padding-left: 0; } } tfoot td { border-left: 0 none; border-bottom-left-radius: 0; } /* FIXME(Ke Wu): for now there are two breadcrumb tr in both table * and this one in the content table is hidden. This hack is made to * fix the alignment of two table, needs a better solution in the * future. */ ul.breadcrumb { padding-left: 0; border-top-left-radius: 0; border-bottom-left-radius: 0; border-left: 0; li { visibility: hidden; } } } table { border-collapse: collapse; thead { tr th { border-bottom: 0; background-color: $rbrowser-header-background-color; } } tbody { tr { &:last-child td { border-bottom: 1px solid $rbrowser-data-table-border-color; border-radius: 0; } &.empty td { height: $rbrowser-td-height; padding: $rbrowser-actions-column-padding; } } td.actions_column { position: static; } } } .breadcrumb { padding: 6px; margin: 0 0 1px; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_tables.scss0000664000175000017500000000522313553660755030213 0ustar zuulzuul00000000000000.table { & > thead, & > tbody, & > tfoot { > tr { & > th, & > td { vertical-align: middle; } } } & > caption { text-align: left; & > .table-title { font-size: $font-size-h3 } } .multi_select_column { text-align: center; } .empty { text-align: center; } // Specificity Required! & > tbody > tr > td { &.loading { background-color: $gray-lighter; } &.success { background-color: lighten($brand-success, 35%); } } tr { &.deleted, &.terminated { color: $gray-light; } } th.multi_select_column, td.multi_select_column { width: $font-size-base * 3; text-align: center; } .normal_column ul { padding: 0; } .dl-horizontal { margin-bottom: 0; @media (min-width: $grid-float-breakpoint) { dt { width: ($dl-horizontal-offset/2) - $padding-large-vertical; } dd { margin-left: $dl-horizontal-offset/2; } } } } // Sometimes the header is empty, lets keep the same look either way .table_header { min-height: $input-height-base; // We put headings in the table for the title, so these headings shouldn't // function like normal Type H3's ... remove the margin on top h3 { display: inline-block; margin-top: 0; } } // Sort Indicator .tablesorter-header.sortable { cursor: pointer; } .tablesorter-header-inner { display: inline; } .tablesorter-headerAsc, .tablesorter-headerDesc { .table-sort-indicator { @extend .fa; } } .tablesorter-headerAsc .table-sort-indicator { @extend .fa-caret-up; } .tablesorter-headerDesc .table-sort-indicator { @extend .fa-caret-down; } ///* // * Bootstrap styles table backgrounds using nth-child(2n+1), which is // * oblivious to hidden elements. The styles below allow us to override // * the bootstrap style when necessary by setting the odd/even classes. // */ .table-striped.datatable tbody { td { background-clip: padding-box; } tr.odd { td { background-color: $table-bg-odd; } } tr.even { td { background-color: inherit; } } // Hover stuffs! tr.odd:hover td, tr.even:hover td, tr:hover th { background-color: $table-bg-hover; } } // Force Table Fixed .table-fixed { table-layout: fixed; } // Don't wrap a column .nowrap-col { white-space: nowrap; } // Make sure long UUIDs, names etc. don't force the table outside a // modal, by forcing a word break. The 'td' specificity is required // because the class is applied in a column, but we don't want the // to wrap. // See launchpad bugs 1565724 && 1584785 td.word-break { word-break: break-all; } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_dropdowns.scss0000664000175000017500000000406113553660754030756 0ustar zuulzuul00000000000000/* Dropdown Actions */ /* Unfortunately, we want to style a button in a dropdown the same way that we style an anchor. This isn't possible in the current Bootstrap: https://github.com/twbs/bootstrap/issues/10248 */ /* Specificity required */ .dropdown-menu > li > .btn { border: none; box-shadow: none; border-radius: 0; margin: 0; // prevent the form-inline styles from messing with margin padding: $bs-dropdown-item-padding-vertical $bs-dropdown-item-padding-horizontal; white-space: nowrap; // prevent links from breaking onto new lines min-width: 100%; text-align: left; background: transparent; display: block; clear: both; font-weight: normal; line-height: $line-height-base; &:hover, &:focus { text-decoration: none; } &.disabled, &[disabled] { cursor: not-allowed; pointer-events: none; // Future-proof disabling of clicks @include opacity(.65); @include box-shadow(none); } &.btn-primary { color: $brand-primary; } &.btn-danger { color: $brand-danger; } &.btn-warning { color: $brand-warning; } &.btn-info { color: $brand-info; } @include dropdown-button('default', $dropdown-link-color, $dropdown-link-hover-bg); @include dropdown-button('primary', $btn-primary-bg, $btn-primary-bg, $btn-primary-color); @include dropdown-button('info', $btn-info-bg, $btn-info-bg, $btn-info-color); @include dropdown-button('warning', $btn-warning-bg, $btn-warning-bg, $btn-warning-color); @include dropdown-button('danger', $btn-danger-bg, $btn-danger-bg, $btn-danger-color); } .table_search .themable-select, .table_actions_menu { display: inline-block; position: relative; } .table_actions { float: right; @extend .form-inline; } .table_search, .table_filter { display: inline-block; } // Push the table filter padding over to compensate for that little magnifying glass icon .table_search { input[type="text"] { padding-right: $input-height-base; } } td.actions_column { width: 1px; // Slight hack to make sure the column shrinks to the button width } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_transfer_tables.scss0000664000175000017500000000064413553660754032120 0ustar zuulzuul00000000000000.transfer-table { .fa[title] { cursor: pointer; } .transfer-heading{ @extend h4; font-size: $font-size-h4; .help-text{ @extend h5; font-size: $font-size-h5; } } .transfer-section { margin-top: $padding-large-vertical; .row .pie-chart { margin-top: 10px; } } .magic-search-bar, .basic-search-bar { margin: $padding-small-vertical 0; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_inline_edit.scss0000664000175000017500000000632413553660754031226 0ustar zuulzuul00000000000000@mixin btn-icon-inline_edit($x, $y, $top: 1px, $left: 5px, $icons: None) { padding: 9px 12px 9px 12px; position: relative; border-radius: 0px; &:before { display: inline-block; content: ""; width: 18px; height: 20px; margin-top: 0px; *margin-right: .3em; line-height: 14px; @if $icons != None { background-image: url($icons); } background-position: $x $y; background-repeat: no-repeat; position: absolute; top: $top; left: $left; } } td.inline_edit_available div.table_cell_wrapper .table_cell_action button.ajax-inline-edit { @include btn-icon-inline_edit(0, -72px, 2px, 4px); position: relative; display: block; background: none; border: 0 none; } @mixin status-icon($x, $y, $top: 1px, $left: 5px, $icons: None) { padding: 9px 12px 9px 12px; position: relative; border-radius: 0px; &:before { display: inline-block; content: ""; width: 20px; height: 20px; margin-top: 0px; *margin-right: .3em; line-height: 14px; @if $icons != None { background-image: url($icons); } background-position: $x $y; background-repeat: no-repeat; position: absolute; top: $top; left: $left; } } td.has-form { padding: 0 !important; } div.table_cell_wrapper { position: relative; .table_cell_data_wrapper { padding-right: 35px; min-height: 15px; } .inline-edit-label { display: inline; } .inline-edit-form { float: left; padding: 1px; padding-right: 30px; width: 100%; input, textarea { width: 100%; height: 45px; border: 2px inset; border-color: lightgray; background: oldlace; } input[type='checkbox'] { width: auto; height: auto; margin-left: 5%; margin-top: 10%; } label { vertical-align: text-bottom; } textarea { resize: vertical; padding: 6px; } } .inline-edit-actions { float: right; width: 0; button.btn-xs { margin: 1px; &:not(:first-child) { margin-top: 0; } } } .table_cell_action { float: right; width: 28px; margin: 0; button.ajax-inline-edit { padding: 10px; position: relative; display: none; background: none; border: 0 none; } } .table_cell_action { width: auto; margin: auto 0px 0px 0px; display: none; position: absolute; top: -10px; right: 0px; z-index: 99; } .inline-edit-error { .error { @include status-icon(-144px, -120px, 0px, 0px); position: absolute; width: 18px; height: 20px; top: 20px; left: 2px; padding: 0; } } .inline-edit-status { .success { @include status-icon(-288px, 0px, 0px, 0px); padding: 0; position:absolute; top: 2px; right: 18px; width: 18px; height: 20px; z-index: 100; } .loading { @include status-icon(0px, 0px, 0px, 0px, '../img/spinner.gif'); padding: 0; position:absolute; top: 0px; right: 24px; width: 18px; height: 20px; z-index: 100; } } .inline-edit-status.inline-edit-mod { .loading { top: 15px; right: 34px; } } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_navbar.scss0000664000175000017500000000237613553660754030217 0ustar zuulzuul00000000000000.navbar-brand { padding: 0 $padding-small-horizontal; margin: 0; display: block; img { display: inline-block; height: $navbar-height - $padding-small-vertical*2; vertical-align: middle; } } //Specifity Required .navbar-header .navbar-brand { // These allow the vertical centering to work properly line-height: $navbar-height; font-size: 0; } .topbar { .navbar { margin-bottom: 1px; } .navbar-nav .header-overflow ul li{ white-space: nowrap; padding: $bs-dropdown-item-padding-vertical $bs-dropdown-item-padding-horizontal; } .dropdown-toggle > .fa { padding-left: $padding-small-vertical; padding-right: $padding-small-vertical; &:first-child { padding-left: 0; } &:last-child { padding-right: 0; } } } .context-delimiter { font-size: $padding-small-vertical; vertical-align: middle; padding-right: $padding-small-vertical; padding-left: $padding-small-vertical; } // Scrollbar for the project menu on larger screens // Not needed for smaller screens, as it is already scrollable // and can be collapsed to access the settings menu etc. @media(min-width: $screen-sm-min) { .context-selection { max-height: $context-selector-max-height; overflow-y: scroll; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_bar_charts.scss0000664000175000017500000000264213553660754031052 0ustar zuulzuul00000000000000// The idea behind this mixin is to allow a variety of // colors to be configured, from 1 - $num, that will // toggle between an incrementing percentage ($increment) // from the theme's primary brand color. This should // adapt nicely to most themes. @mixin make_bar_chart_distribution($num, $increment) { @for $ii from 1 through $num { $color_increment: $increment * ($ii/2); // Set the arc color .legacy-bar-chart-section:nth-child(#{$ii}n) { @if $ii % 2 == 0 { fill: lighten($progress-bar-bg, $color_increment * 1%); } @else { fill: darken($progress-bar-bg, $color_increment * 1%); } } } } .legacy-bar-chart { @extend .progress; fill: $progress-bg; // Set the colors! @include make_bar_chart_distribution(8, 8); .legacy-bar-chart-section { @extend .progress-bar; } .unused_component.legacy-bar-chart-section { fill: transparent; } .average_component { stroke: $gray-dark; stroke-dasharray: 6, 2; stroke-width: 3px; } .average_component_hover { stroke-width: 5px; } .used_component_label { font-size: $font-size-base; fill: $text-color; } .used_component_label_arrow { stroke: $text-color; } } // (hurgleburgler) Remove when we've upgraded to Bootstrap 3.3.5, because we need // https://github.com/twbs/bootstrap/commit/2c2564faefd99b044273f132275bb620b5eccb93 .progress-bar { min-width: 0 !important; } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_help_panel.scss0000664000175000017500000000122213553660755031043 0ustar zuulzuul00000000000000$help-panel-width: 400px; .help-toggle, .wizard-help, .help-panel { position: absolute; top: $padding-xs-horizontal; right: 0; z-index: 2; // TODO(robcresswell) untangle the need for this sorcery } .help-panel > div { width: $help-panel-width; } // Controls the size of the "?" icon on the right of the wizards .help-toggle { @extend .btn-xs; font-size: $font-size-h3; .fa { @extend .fa-question-circle; } &:not(.collapsed) { z-index: 3; &, &:hover, &:active, &:focus { @extend .close; box-shadow: none; margin: $padding-xs-horizontal; } .fa { @extend .fa-times; } } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_membership.scss0000664000175000017500000000566513553660755031106 0ustar zuulzuul00000000000000/* Membership widget UI */ .membership { min-height: 200px; /* Header */ .help_text { margin-bottom: 15px; } .fake_table_header { padding: 8px; border: 1px solid $table-border-color; border-bottom: none; overflow: hidden; } .members_title { color: $gray; font-weight: bold; float: left; padding: 6px 0; } input.filter { width: 120px; float: right; &[type="text"]:disabled { background-color: $gray-lighter; & + span.search-icon { color: $gray-light; } } } .form-control-feedback { top: 0; } .no_results { border: 1px solid $table-border-color; padding: 9px 10px 8px 10px; opacity: 0.5; } li.scope input{ background: none; margin-top: 10px; margin-bottom: 10px; width: 120px; margin-left: 10px; } li.select_resource { margin-left: 5px; margin-top: 15px; } li.display_name { width: 130px; margin: 15px 7px 15px 10px; } /* Member lists */ .update_members_filterable { overflow-y: auto; height: 500px; } .members, .available_members { padding: 0; .btn-primary { @include button-variant($btn-primary-color, $btn-primary-bg, $btn-primary-border); } // reset nav-pills display to block ul.nav-pills { display: block; & > li > a { border-radius: $border-radius-base; } } ul.btn-group { margin-left:0; padding: $members-list-padding; margin-bottom: 0; border: $members-list-border; border-bottom: none; &.last_stripe { border-bottom: 1px solid $table-border-color; } &.light_stripe { background-color: $table-bg; } &.dark_stripe { background-color: $table-bg-accent; } &:hover { background-color: $border-color; } li.active { float: right; a:hover { background-color: $link-hover-color; } } .member, .role_options .roles_display { overflow: hidden; text-overflow: ellipsis; } .member { padding: $nav-link-padding; padding-right: 0; padding-left: $padding-base-vertical; max-width: $members-list-item-width; } .role_options { margin-left: 0; & > a { padding: nth($nav-link-padding, 1) $padding-small-horizontal; .roles_display { display: inline-block; max-width: $members-list-roles-width; line-height: 1; padding-right: $padding-base-vertical; } } .role_dropdown > li { word-break: break-all; } } } } /* Role dropdown menus */ .role_dropdown { right: 0; left: auto; & > li { .fa-check { visibility: hidden; } &.selected .fa-check { visibility: visible; } } } .nav .role_options { float: right; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_network_topology_svg.scss0000664000175000017500000000414513553660754033246 0ustar zuulzuul00000000000000#topology_canvas { font-family: sans-serif; .network-rect { cursor: pointer; &.nourl { cursor: auto; } } .network-name { font-weight: $badge-font-weight; font-size: $font-size-base; fill: $component-active-color; text-anchor: middle; } .network-cidr { font-size: $font-size-small; text-anchor: end; } text.network-type { font-family: FontAwesome; text-anchor: end; } .port_text { font-size: $font-size-small; fill: $gray; &.left { text-anchor: end; } } .base_bg_normal { fill: $gray-dark; } .loading_bg_normal { fill: $gray; } .base_bg_small, .loading_bg_small { fill: $body-bg; } .active { fill: $brand-success; } .icon polygon { fill: $gray-dark; } .instance_small, .router_small { .frame { fill: url(#device_small_bg); stroke: $gray-dark; stroke-width: 3; } .port_text { display: none; } } .router_normal, .instance_normal { .frame { fill: #fff; stroke: $gray-dark; stroke-width: 4; } .icon_bg { fill: #fff; stroke: $gray-dark; stroke-width: 4; } .texts_bg { fill: url('#device_normal_bg'); } .texts { .name { text-anchor: middle; fill: $gray-dark; font-size: $font-size-base; } .type { text-anchor: middle; fill: #fff; font-size: $font-size-base; } } .instance_bg { fill: $gray-dark; } } g.loading { .active { fill: $gray; } .icon polygon { fill: $gray; } .instance_bg { fill: $gray; } .instance_small, .router_small { .frame { stroke: $gray; fill: url(#device_small_bg_loading); } } .instance_normal { .frame { stroke: $gray; } .name { fill: $gray-light; } .texts_bg { fill: url(#device_normal_bg_loading); } .icon_bg { stroke: $gray; } } } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_login.scss0000664000175000017500000000065113553660754030050 0ustar zuulzuul00000000000000/** * Styling for the splash/login page. */ .login { margin-top: $navbar-height*2; .splash-logo { margin: $padding-large-horizontal $padding-large-vertical; max-width: 65%; } .help_text { display: none; } .login-title { display: inline-block; } ul.errorlist { padding-left: 0; } .modal-content .panel { margin-bottom: 0; } .hz-icon-required { display: none; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_progress_bars.scss0000664000175000017500000000155013553660754031612 0ustar zuulzuul00000000000000.progress-text { position: relative; color: $text-color; .progress { margin-bottom: $padding-small-vertical; height: 1.5em; } .progress-bar-text { top: 0; line-height: 1.5em; position: absolute; z-index: 1; &, & span { text-align: center; display: inline-block; width: 100%; @include text-overflow(); } } &.container-pending-bar .progress { margin-bottom: 0; } } .file-upload .progress-bar { transition: width 0.1s ease 0s; } .horizon-loading-bar .progress { height: 1.5em; .progress-bar { width: 100%; } } .modal-progress-loader { display: flex; flex-direction: column; height: 100%; .progress-text { flex: 1 0 auto; position: relative; .progress, .progress-bar-text { top: 50%; } .progress { position: relative; } } }horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_modals.scss0000664000175000017500000000112313553660754030212 0ustar zuulzuul00000000000000.modal-body { // Subtract the help-button width and add the modal inner padding // because the element is in the place which is applied the modal inner padding. .step-description { width: calc(100% - (#{$padding-xs-horizontal} * 2 + #{$font-size-h3}) + #{$modal-inner-padding}); } textarea { resize: vertical; } & > .nav-pills { padding-bottom: $padding-base-horizontal; } } // Custom Horizon Modal Sizes @media (min-width: $screen-sm-min) { .modal-xs { width: $modal-xs; } } @media (min-width: $screen-md-min) { .modal-xl { width: $modal-xl; } } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_network_topology.scss0000664000175000017500000001007613553660755032370 0ustar zuulzuul00000000000000#topologyCanvasContainer, #flatTopologyCanvasContainer { @include box-sizing(border-box); width: 100%; height: auto; padding: 25px; padding-left: 50px; background: #ffffff; min-height: 400px; cursor: grab; cursor: -webkit-grab; div.nodata { font-size: 150%; text-align: center; padding-top: 150px; display: none; } &.noinfo { div.nodata { display: block; } #topology_canvas { display: none; } } } .topology-navi { overflow: hidden; margin: 10px 0; .toggle-view { float: left; span.glyphicon { margin-right: 4px; } } .launchButtons { float: right; text-align: right; a.btn { margin-left: 5px; } } } .topologyBalloon { display: none; background: $body-bg; @include box-shadow(0px 1px 6px #777); position: absolute; left:100px; top:20px; z-index: 600; border-radius: 5px; color:#333; min-width: 200px; &.on { display: block; } line-height: 1.2; .vnc_window { margin-left: 10px; } .closeTopologyBalloon { font-size: 16px; line-height: 1; display: block; position: absolute; font-weight: bold; right: 6px; top: 0px; cursor: pointer; padding: 3px; color:#aaa; &:hover { color:#777; text-decoration: none; } } .contentBody { padding: 8px 8px 0; } span.active, span.down { &:before { content: ""; width: 9px; height: 9px; display: inline-block; background: $brand-success; margin-right: 3px; border-radius: 10px; vertical-align: middle; } } span.down { &:before { background: $brand-danger; } } .footer { background: #efefef; border-top: 1px solid $border-color; padding: 8px; border-radius: 0px 0px 7px 7px; .footerInner { display: table; width: 100%; } .cell { display: table-cell; padding-right: 10px; } .link { font-size: 12px; } .delete { padding-right: 0; text-align: right; } } .portTableHeader { border-top: 1px solid $gray-lighter; padding-top: 5px; margin: 5px 0; display: table; width: 100%; .title { display: table-cell; font-size: 13px; font-weight: bold; } .action { display: table-cell; text-align: right; } } table.detailInfoTable { margin-bottom: 5px; caption { text-align: left; font-size: 13px; font-weight: bold; margin-bottom: 0px; } th,td { text-align: left; vertical-align: middle; padding-bottom: 3px; background: transparent; } th { color: $gray-light; padding-right: 8px; width: 80px; span { vertical-align: middle; width:80px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: inline-block; } &.device { text-align: right; } } td { padding-right: 5px; white-space: nowrap; } td.delete { padding-right: 0; text-align: right; } .btn { line-height: 1.4; } } font-size: 11px; &:before { border-top: 7px solid transparent; border-bottom: 7px solid transparent; border-right: 9px solid $border-color; display: block; position: absolute; top: 30px; left: -9px; width: 0; height: 0; content: ""; } &:after { border-top: 6px solid transparent; border-bottom: 6px solid transparent; border-right: 8px solid $body-bg; display: block; position: absolute; top: 31px; left: -8px; width: 0; height: 0; content: ""; } &.leftPosition { &:before { border-right: none; border-left: 9px solid $border-color; right: -9px; top: 30px; left:auto; } &:after { border-right: none; border-left: 8px solid $body-bg; right: -8px; top: 31px; left:auto; } } } #topologyMessages { width:1px; height:1px; visibility: hidden; position: absolute; top: -100px; } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_dl_lists.scss0000664000175000017500000000016413553660754030554 0ustar zuulzuul00000000000000.dl-readable { dd { padding-left: $padding-base-vertical; padding-bottom: $padding-base-horizontal; } }horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_quota.scss0000664000175000017500000000070313553660754030067 0ustar zuulzuul00000000000000.d3_quota_bar { text-align: center; .pie-chart-usage { width: $font-size-h2 * 3; display: inline-block; } } .quota-dynamic { overflow: hidden; padding-bottom: $padding-large-horizontal; } .quota_title { font-size: $font-size-h6; margin-bottom: 0; @include clearfix(); & > span { color: $gray-light; } } // This extend is important as it inherits the padding as well as font size .quota_subtitle { @extend .h6; } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_wizard.scss0000664000175000017500000000035613553660754030242 0ustar zuulzuul00000000000000// Reapply border colour and prevent float right .wizard-nav-toggle { border-color: $btn-default-border; float: none; margin-bottom: $padding-small-vertical; } // Prevent extra padding on the side nav .wizard-nav { padding: 0; } horizon-13.0.3/openstack_dashboard/static/dashboard/scss/components/_selection_menu.scss0000664000175000017500000000246613553660754031757 0ustar zuulzuul00000000000000// FYI Note (hurgleburgler) // The Selection Menu is not a normal drop down element // This menu can contain other dropdown menus, and will // treat them as a selection group element. // The context menu shows this functionality. // This drop down is composed of other