diff --git a/designatedashboard/api/rest/__init__.py b/designatedashboard/api/rest/__init__.py new file mode 100644 index 0000000..6e741ff --- /dev/null +++ b/designatedashboard/api/rest/__init__.py @@ -0,0 +1,16 @@ +# (c) Copyright Hewlett Packard Enterprise Development LP +# +# 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. +"""REST API for Horizon dashboard Javascript code. +""" +from . import passthrough # noqa diff --git a/designatedashboard/api/rest/passthrough.py b/designatedashboard/api/rest/passthrough.py new file mode 100644 index 0000000..8a50569 --- /dev/null +++ b/designatedashboard/api/rest/passthrough.py @@ -0,0 +1,114 @@ +# Copyright 2016, Hewlett Packard Enterprise Development, LP +# +# 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. +"""API for the passthrough service. +""" +from django.conf import settings +from django.views import generic +import functools +import logging +import requests +from requests.exceptions import HTTPError + +from horizon import exceptions +from openstack_dashboard.api import base +from openstack_dashboard.api.rest import urls +from openstack_dashboard.api.rest import utils as rest_utils + +LOG = logging.getLogger(__name__) + + +def _passthrough_request(request_method, url, + request, data=None, params=None): + """Makes a request to the appropriate service API with an optional payload. + + Should set any necessary auth headers and SSL parameters. + """ + service = 'dns' \ + '' + # Set verify if a CACERT is set and SSL_NO_VERIFY isn't True + verify = getattr(settings, 'OPENSTACK_SSL_CACERT', None) + if getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False): + verify = False + + response = request_method( + _get_service_url(request, service) + url, + headers={'X-Auth-Token': request.user.token.id}, + json=data, + verify=verify, + params=params + ) + + try: + response.raise_for_status() + except HTTPError as e: + LOG.debug(e.response.content) + for error in rest_utils.http_errors: + if (e.response.status_code == getattr(error, 'status_code', 0) and + exceptions.HorizonException in error.__bases__): + raise error + raise + + return response + + +# Create some convenience partial functions +passthrough_get = functools.partial(_passthrough_request, requests.get) +passthrough_post = functools.partial(_passthrough_request, requests.post) +passthrough_put = functools.partial(_passthrough_request, requests.put) +passthrough_patch = functools.partial(_passthrough_request, requests.patch) +passthrough_delete = functools.partial(_passthrough_request, requests.delete) + + +def _get_service_url(request, service): + """Get service's URL from keystone; allow an override in settings""" + service_url = getattr(settings, service.upper() + '_URL', None) + try: + service_url = base.url_for(request, service) + except exceptions.ServiceCatalogException: + pass + # Currently the keystone endpoint is http://host:port/ + # without the version. + return service_url + + +@urls.register +class Passthrough(generic.View): + """Pass-through API for executing service requests. + + Horizon only adds auth and CORS proxying. + """ + url_regex = r'dns/(?P.+)$' + + @rest_utils.ajax() + def get(self, request, path): + return passthrough_get(path, request).json() + + @rest_utils.ajax() + def post(self, request, path): + data = dict(request.DATA) if request.DATA else {} + return passthrough_post(path, request, data).json() + + @rest_utils.ajax() + def put(self, request, path): + data = dict(request.DATA) if request.DATA else {} + return passthrough_put(path, request, data).json() + + @rest_utils.ajax() + def patch(self, request, path): + data = dict(request.DATA) if request.DATA else {} + return passthrough_patch(path, request, data).json() + + @rest_utils.ajax() + def delete(self, request, path): + return passthrough_delete(path, request).json() diff --git a/designatedashboard/dashboards/project/dns_domains/views.py b/designatedashboard/dashboards/project/dns_domains/views.py index 9e072f7..425a8f8 100644 --- a/designatedashboard/dashboards/project/dns_domains/views.py +++ b/designatedashboard/dashboards/project/dns_domains/views.py @@ -23,6 +23,7 @@ from openstack_dashboard.api.network import tenant_floating_ip_list from openstack_dashboard.api.nova import server_list from designatedashboard import api +from designatedashboard.api import rest # noqa from .forms import DomainCreate # noqa from .forms import DomainUpdate # noqa diff --git a/designatedashboard/dashboards/project/ngdns/__init__.py b/designatedashboard/dashboards/project/ngdns/__init__.py new file mode 100644 index 0000000..2c57778 --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/__init__.py @@ -0,0 +1 @@ +from designatedashboard.api import rest # noqa diff --git a/designatedashboard/dashboards/project/ngdns/reverse_dns/__init__.py b/designatedashboard/dashboards/project/ngdns/reverse_dns/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/designatedashboard/dashboards/project/ngdns/reverse_dns/panel.py b/designatedashboard/dashboards/project/ngdns/reverse_dns/panel.py new file mode 100644 index 0000000..41f0c7c --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/reverse_dns/panel.py @@ -0,0 +1,25 @@ +# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.dashboards.project import dashboard + + +class ReverseDns(horizon.Panel): + name = _("Reverse DNS") + slug = 'reverse_dns' + permissions = ('openstack.services.dns',) + +dashboard.Project.register(ReverseDns) diff --git a/designatedashboard/dashboards/project/ngdns/reverse_dns/urls.py b/designatedashboard/dashboards/project/ngdns/reverse_dns/urls.py new file mode 100644 index 0000000..7977633 --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/reverse_dns/urls.py @@ -0,0 +1,22 @@ +# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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 designatedashboard.dashboards.project.ngdns.reverse_dns import views + + +urlpatterns = [ + url('', views.IndexView.as_view(), name='index'), +] diff --git a/designatedashboard/dashboards/project/ngdns/reverse_dns/views.py b/designatedashboard/dashboards/project/ngdns/reverse_dns/views.py new file mode 100644 index 0000000..e072aea --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/reverse_dns/views.py @@ -0,0 +1,19 @@ +# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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.views import generic + + +class IndexView(generic.TemplateView): + template_name = 'angular.html' diff --git a/designatedashboard/dashboards/project/ngdns/zones/__init__.py b/designatedashboard/dashboards/project/ngdns/zones/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/designatedashboard/dashboards/project/ngdns/zones/panel.py b/designatedashboard/dashboards/project/ngdns/zones/panel.py new file mode 100644 index 0000000..e3fdaba --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/zones/panel.py @@ -0,0 +1,25 @@ +# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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.utils.translation import ugettext_lazy as _ + +import horizon +from openstack_dashboard.dashboards.project import dashboard + + +class Zones(horizon.Panel): + name = _("Zones") + slug = 'dnszones' + permissions = ('openstack.services.dns',) + +dashboard.Project.register(Zones) diff --git a/designatedashboard/dashboards/project/ngdns/zones/urls.py b/designatedashboard/dashboards/project/ngdns/zones/urls.py new file mode 100644 index 0000000..0eacf22 --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/zones/urls.py @@ -0,0 +1,22 @@ +# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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 designatedashboard.dashboards.project.ngdns.zones import views + + +urlpatterns = [ + url('', views.IndexView.as_view(), name='index'), +] diff --git a/designatedashboard/dashboards/project/ngdns/zones/views.py b/designatedashboard/dashboards/project/ngdns/zones/views.py new file mode 100644 index 0000000..e072aea --- /dev/null +++ b/designatedashboard/dashboards/project/ngdns/zones/views.py @@ -0,0 +1,19 @@ +# (c) Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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.views import generic + + +class IndexView(generic.TemplateView): + template_name = 'angular.html' diff --git a/designatedashboard/enabled/_1720_project_dns_panel.py b/designatedashboard/enabled/_1720_project_dns_panel.py index 7ea410d..c1494d8 100644 --- a/designatedashboard/enabled/_1720_project_dns_panel.py +++ b/designatedashboard/enabled/_1720_project_dns_panel.py @@ -32,3 +32,5 @@ ADD_EXCEPTIONS = { # Python panel class of the PANEL to be added. ADD_PANEL = ( 'designatedashboard.dashboards.project.dns_domains.panel.DNSDomains') + +DISABLED = True diff --git a/designatedashboard/enabled/_1721_dns_zones_panel.py b/designatedashboard/enabled/_1721_dns_zones_panel.py new file mode 100644 index 0000000..da72368 --- /dev/null +++ b/designatedashboard/enabled/_1721_dns_zones_panel.py @@ -0,0 +1,40 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# +# 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 designatedashboard import exceptions + +# The name of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'dnszones' +# The name of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The name of the panel group the PANEL is associated with. +PANEL_GROUP = 'dns' + +ADD_EXCEPTIONS = { + 'recoverable': exceptions.RECOVERABLE, + 'not_found': exceptions.NOT_FOUND, + 'unauthorized': exceptions.UNAUTHORIZED, +} + +ADD_INSTALLED_APPS = ['designatedashboard'] + +# Python panel class of the PANEL to be added. +ADD_PANEL = ( + 'designatedashboard.dashboards.project.ngdns.zones.panel.Zones') + +ADD_ANGULAR_MODULES = ['designatedashboard'] + +ADD_SCSS_FILES = ['designatedashboard/designatedashboard.scss'] + +AUTO_DISCOVER_STATIC_FILES = True diff --git a/designatedashboard/enabled/_1722_dns_reversedns_panel.py b/designatedashboard/enabled/_1722_dns_reversedns_panel.py new file mode 100644 index 0000000..1728715 --- /dev/null +++ b/designatedashboard/enabled/_1722_dns_reversedns_panel.py @@ -0,0 +1,38 @@ +# Copyright 2013 Hewlett-Packard Development Company, L.P. +# +# 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 designatedashboard import exceptions + +# The name of the panel to be added to HORIZON_CONFIG. Required. +PANEL = 'reverse_dns' +# The name of the dashboard the PANEL associated with. Required. +PANEL_DASHBOARD = 'project' +# The name of the panel group the PANEL is associated with. +PANEL_GROUP = 'dns' + +ADD_EXCEPTIONS = { + 'recoverable': exceptions.RECOVERABLE, + 'not_found': exceptions.NOT_FOUND, + 'unauthorized': exceptions.UNAUTHORIZED, +} + +# Python panel class of the PANEL to be added. +ADD_PANEL = ( + 'designatedashboard.dashboards.project.ngdns.reverse_dns.panel.ReverseDns') + +ADD_ANGULAR_MODULES = ['designatedashboard'] + +ADD_SCSS_FILES = ['designatedashboard/designatedashboard.scss'] + +AUTO_DISCOVER_STATIC_FILES = True diff --git a/designatedashboard/static/designatedashboard/designatedashboard.module.js b/designatedashboard/static/designatedashboard/designatedashboard.module.js new file mode 100644 index 0000000..e51e60f --- /dev/null +++ b/designatedashboard/static/designatedashboard/designatedashboard.module.js @@ -0,0 +1,78 @@ +/** + * (c) Copyright 2015 Hewlett-Packard Development Company, L.P. + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard + * + * @description + * Provides the services and widgets required + * to support and display the project search panel. + */ + angular + .module('designatedashboard', [ + 'ngRoute', + 'designatedashboard.resources' + ]) + .constant( + 'designatedashboard.apiPassthroughUrl', '/api/dns/') + .config(config) + .run(run); + + config.$inject = [ + '$provide', + '$routeProvider', + '$windowProvider' + ]; + + /** + * @name designatedashboard.basePath + * @description Base path for the project dashboard + * + * @param {function} $provide ng provide service + * + * @param {function} $routeProvider ng route service + * + * @param {function} $windowProvider NG window provider + * + * @returns {undefined} + */ + function config($provide, $routeProvider, $windowProvider) { + var path = $windowProvider.$get().STATIC_URL + 'designatedashboard/'; + $provide.constant('designatedashboard.basePath', path); + + $routeProvider + .when('/project/dnszones/', { + templateUrl: path + 'zones.html' + }) + .when('/project/reverse_dns/', { + templateUrl: path + 'reverse_dns.html' + }); + } + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.basePath' + ]; + + function run(registry, basePath) { + //registry.setDefaultSummaryTemplateUrl(basePath + 'table/default-drawer.html'); + } + +})(); diff --git a/designatedashboard/static/designatedashboard/designatedashboard.scss b/designatedashboard/static/designatedashboard/designatedashboard.scss new file mode 100644 index 0000000..e69de29 diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/actions.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/actions.module.js new file mode 100644 index 0000000..1efe950 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/actions.module.js @@ -0,0 +1,66 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-floatingip.actions + * + * @description + * Provides all of the actions for DNS Floating IPs. + */ + angular.module('designatedashboard.resources.os-designate-floatingip.actions', [ + 'horizon.framework.conf', + 'horizon.app.core' + ]) + .run(run); + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'designatedashboard.resources.os-designate-floatingip.actions.set', + 'designatedashboard.resources.os-designate-floatingip.actions.unset' + ]; + + function run( + registry, + resourceTypeString, + setAction, + unsetAction) + { + var resourceType = registry.getResourceType(resourceTypeString); + + resourceType + .itemActions + .append({ + id: 'setFloatingIp', + service: setAction, + template: { + text: gettext('Set') + } + }) + .append({ + id: 'unsetFloatingIp', + service: unsetAction, + template: { + text: gettext('Unset') + } + }); + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/set.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/set.service.js new file mode 100644 index 0000000..89e63f0 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/set.service.js @@ -0,0 +1,170 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-floatingip.actions') + .factory('designatedashboard.resources.os-designate-floatingip.actions.set', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-floatingip.api', + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'designatedashboard.resources.util', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name designatedashboard.resources.os-designate-floatingip.actions.set + * + * @Description + * Brings up the Set Floating IP modal. + */ + function action($q, + api, + resourceTypeName, + util, + serviceCatalog, + schemaFormModalService, + toast, + waitSpinner) { + var dnsServiceEnabled; + var title = null; // Set once perform is called + var formConfig = { + "schema": { + "type": "object", + "properties": { + "ptrdname": { + "type": "string", + "pattern": /^.+\.$/ + }, + "description": { + "type": "string" + }, + "ttl": { + "type": "integer", + "minimum": 0, + "maximum": 2147483647 + }, + } + }, + "form": [ + { + "key": "ptrdname", + "title": gettext("Domain Name"), + "description": gettext("Domain name ending in '.'"), + "validationMessage": gettext("Domain must end with '.'"), + "placeholder": "smtp.example.com.", + "type": "text", + "required": true + }, + { + "key": "description", + "type": "textarea", + "title": gettext("Description"), + "description": gettext("Details about the PTR record.") + }, + { + "key": "ttl", + "title": gettext("TTL"), + "description": gettext("Time To Live in seconds."), + "type": "number" + } + ] + }; + + var message = { + success: gettext('Domain name PTR %s was successfully set.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + dnsServiceEnabled = serviceCatalog.ifTypeEnabled('dns'); + } + + function allowed(item) { + return $q.all([ + // TODO (tyr) designate currently has no floating ips policy rules + dnsServiceEnabled, + util.notPending(item) + ]); + } + + function perform(item) { + // Initialize the per-item title for use now and during submit + title = gettext("Set Domain Name PTR for ") + item.address; + formConfig.title = title; + + // Get a form model based on the current item + formConfig.model = util.getModel(formConfig.form, item); + + // Initialize default data + formConfig.model.ttl = formConfig.model.ttl || 3600; + + // Remember the ID for use during submit + formConfig.model.floatingIpId = item.id; + + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + var model = angular.copy(context.model); + var floatingIpId = formConfig.model.floatingIpId; + + waitSpinner.showModalSpinner(title); + return api.set(floatingIpId, model).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + var floatingIp = response.data; + toast.add('success', interpolate(message.success, [floatingIp.ptrdname])); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [], + updated: [{type: resourceTypeName, id: floatingIp.id}], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/unset.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/unset.service.js new file mode 100644 index 0000000..11be68a --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/actions/unset.service.js @@ -0,0 +1,139 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-floatingip.actions') + .factory('designatedashboard.resources.os-designate-floatingip.actions.unset', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-floatingip.api', + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'designatedashboard.resources.util', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name designatedashboard.resources.os-designate-floatingip.actions.unset + * + * @Description + * Brings up the Unset Floating IP modal. + */ + function action($q, + api, + resourceTypeName, + util, + serviceCatalog, + $qExtensions, + schemaFormModalService, + toast, + waitSpinner) { + var dnsServiceEnabled; + var title = null; // Set on perform + var currentFloatingIpId; // Used to remember the ID we are modifying since it isn't returned by the unset API call + + // Unset it just a simple case of "set", but with ptrdname of 'null' + var formConfig = { + "schema": { + "type": "object", + "properties": { + } + }, + "form": [ + ], + "model": { + } + }; + + var message = { + success: gettext('Domain name PTR successfully unset.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + dnsServiceEnabled = serviceCatalog.ifTypeEnabled('dns'); + } + + function allowed(item) { + return $q.all([ + // TODO (tyr) designate currently has no floating ip policy rules + dnsServiceEnabled, + domainNameSet(item), + util.notPending(item) + ]); + } + + function domainNameSet(item) { + return $qExtensions.booleanAsPromise( + angular.isString(item.ptrdname) + ); + } + + function perform(item) { + title = gettext("Unset Domain Name PTR for ") + item.address; + // Store the zone ID so it can be used on submit + formConfig.model.floatingIpId = item.id; + formConfig.title = title; + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + waitSpinner.showModalSpinner(title); + currentFloatingIpId = context.model.floatingIpId; + return api.unset(currentFloatingIpId).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + toast.add('success', message.success); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [], + updated: [{type: resourceTypeName, id: currentFloatingIpId}], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/api.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/api.service.js new file mode 100644 index 0000000..5881dca --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/api.service.js @@ -0,0 +1,121 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-floatingip') + .factory('designatedashboard.resources.os-designate-floatingip.api', apiService); + + apiService.$inject = [ + 'designatedashboard.apiPassthroughUrl', + 'horizon.framework.util.http.service', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc service + * @param {Object} httpService + * @param {Object} toastService + * @name apiService + * @description Provides direct access to Designate Floating IP APIs. + * @returns {Object} The service + */ + function apiService(apiPassthroughUrl, httpService, toastService) { + var service = { + list: list, + get: get, + set: set, + unset: unset + }; + + return service; + + /////////////// + + /** + * @name list + * @description + * Get a list of DNS floating ips. + * + * The listing result is an object with property "items." Each item is + * a floating IP PTR record. + * + * @param {Object} params + * Query parameters. Optional. + * + * @returns {Object} The result of the API call + */ + function list(params) { + var config = params ? {'params': params} : {}; + return httpService.get(apiPassthroughUrl + 'v2/reverse/floatingips', config) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the floating ip PTRs.')); + }); + } + + function get(id, params) { + var config = params ? {'params': params} : {}; + return httpService.get(apiPassthroughUrl + 'v2/reverse/floatingips/' + id, config) + .error(function () { + toastService.add('error', gettext('Unable to get the floating ip PTR ' + id)); + }); + } + + /** + * @name set + * @description + * Set a floating ip PTR record + * + * @param {string} floatingIpID - ID of PTR record to unset + * @param {Object} data + * Specifies the PTR information to set + * + * @returns {Object} The updated DNS floating IP object + */ + function set(floatingIpID, data) { + // The update API will not accept extra data. Restrict the input to only the allowed + // fields + var apiData = { + ptrdname: data.ptrdname, + description: data.description, + ttl: data.ttl + }; + return httpService.patch(apiPassthroughUrl + 'v2/reverse/floatingips/' + floatingIpID, apiData) + .error(function () { + toastService.add('error', gettext('Unable to set the floating IP PTR record.')); + }) + } + + /** + * @name unset + * @description + * Unset a floating ip PTR record + * + * @param {string} floatingIpID - ID of PTR record to unset + * + * @returns {Object} The updated DNS floating IP object + */ + function unset(floatingIpID) { + // Unset is just a special case of 'set' + return set(floatingIpID, { + ptrdname: null, + description: null, + ttl: null + }) + } + } +}()); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/details.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/details.module.js new file mode 100644 index 0000000..99130cd --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/details.module.js @@ -0,0 +1,66 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-floatingip.details + * + * @description + * Provides details features for floating IPs. + */ + angular.module('designatedashboard.resources.os-designate-floatingip.details', + ['horizon.framework.conf', 'horizon.app.core']) + .run(run); + + run.$inject = [ + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'designatedashboard.resources.os-designate-floatingip.api', + 'designatedashboard.resources.os-designate-floatingip.basePath', + 'horizon.framework.conf.resource-type-registry.service' + ]; + + function run( + resourceTypeName, + api, + basePath, + registry + ) { + var resourceType = registry.getResourceType(resourceTypeName); + resourceType + .setLoadFunction(loadFunction) + .setSummaryTemplateUrl(basePath + 'details/drawer.html') + .setItemNameFunction(itemNameFunction); + + resourceType.detailsViews + .prepend({ + id: 'floatingIpDetailsOverview', + name: gettext('Overview'), + template: basePath + 'details/overview.html' + }, 0); + + function loadFunction(identifier) { + return api.get(identifier); + } + + function itemNameFunction(floatingIp) { + return floatingIp.address; + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/drawer.html b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/drawer.html new file mode 100644 index 0000000..d9bc4e9 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/drawer.html @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/overview.controller.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/overview.controller.js new file mode 100644 index 0000000..13446bd --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/overview.controller.js @@ -0,0 +1,46 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function() { + "use strict"; + + angular + .module('designatedashboard.resources.os-designate-floatingip.details') + .controller('designatedashboard.resources.os-designate-floatingip.details.overviewController', controller); + + controller.$inject = [ + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'horizon.framework.conf.resource-type-registry.service', + '$scope' + ]; + + function controller( + resourceTypeCode, + registry, + $scope + ) { + var ctrl = this; + + ctrl.item; + ctrl.resourceType = registry.getResourceType(resourceTypeCode); + + $scope.context.loadPromise.then(onGetResponse); + + function onGetResponse(response) { + ctrl.item = response.data; + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/overview.html b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/overview.html new file mode 100644 index 0000000..6cef428 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/details/overview.html @@ -0,0 +1,11 @@ +
+ + +
\ No newline at end of file diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/os-designate-floatingip.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/os-designate-floatingip.module.js new file mode 100644 index 0000000..2e7981f --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/os-designate-floatingip.module.js @@ -0,0 +1,154 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-floatingip + * + * @description + * Provides all of the services and widgets required + * to support and display DNS (designate) floating ip related content. + */ + angular + .module('designatedashboard.resources.os-designate-floatingip', [ + 'ngRoute', + 'designatedashboard.resources.os-designate-floatingip.actions', + 'designatedashboard.resources.os-designate-floatingip.details' + ]) + .constant( + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'OS::Designate::FloatingIp') + .config(config) + .run(run); + + config.$inject = [ '$provide', '$windowProvider' ]; + + function config($provide, $windowProvider) { + var path = $windowProvider.$get().STATIC_URL + 'designatedashboard/resources/os-designate-floatingip/'; + $provide.constant('designatedashboard.resources.os-designate-floatingip.basePath', path); + } + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.resources.os-designate-floatingip.api', + 'designatedashboard.resources.os-designate-floatingip.resourceType', + 'designatedashboard.resources.util' + ]; + + function run( + registry, + api, + resourceTypeString, + util) + { + var resourceType = registry.getResourceType(resourceTypeString); + resourceType + .setNames(gettext('Floating IP'), gettext('Floating IPs')) + .setListFunction(listFloatingIps) + .setProperty('id', { + label: gettext('ID') + }) + .setProperty('ptrdname', { + label: gettext('PTR Domain Name'), + filters: ['noName'] + }) + .setProperty('description', { + label: gettext('Description'), + filters: ['noName'] + }) + .setProperty('ttl', { + label: gettext('Time To Live'), + filters: ['noValue'] + }) + .setProperty('address', { + label: gettext('Address'), + filters: ['noName'] + }) + .setProperty('status', { + label: gettext('Status'), + filters: ['lowercase', 'noName'], + values: util.statusMap() + }) + .setProperty('action', { + label: gettext('Action'), + filters: ['lowercase', 'noName'], + values: util.actionMap() + }); + + resourceType + .tableColumns + .append({ + id: 'address', + priority: 1, + sortDefault: true, + template: '{$ item.address $}' + }) + .append({ + id: 'ptrdname', + filters: ['noValue'], + priority: 1, + }) + .append({ + id: 'status', + filters: ['lowercase'], + values: util.statusMap(), + priority: 2 + }); + + resourceType + .filterFacets + .append({ + label: gettext('Address'), + name: 'address', + isServer: false, + singleton: true, + persistent: false + }) + .append({ + label: gettext('PTR Domain Name'), + name: 'ptrdname', + isServer: false, + singleton: true, + persistent: false + }) + .append({ + label: gettext('Status'), + name: 'status', + isServer: false, + singleton: true, + persistent: false, + options: [ + {label: gettext('Active'), key: 'active'}, + {label: gettext('Pending'), key: 'pending'} + ] + }); + + function listFloatingIps() { + return api.list().then(function onList(response) { + // listFunctions are expected to return data in "items" + response.data.items = response.data.floatingips; + + util.addTimestampIds(response.data.items); + + return response; + }); + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/actions.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/actions.module.js new file mode 100644 index 0000000..542c689 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/actions.module.js @@ -0,0 +1,79 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-recordset.actions + * + * @description + * Provides all of the actions for DNS Recordsets. + */ + angular.module('designatedashboard.resources.os-designate-recordset.actions', [ + 'horizon.framework.conf', + 'horizon.app.core' + ]) + .run(run); + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'designatedashboard.resources.os-designate-recordset.actions.create', + 'designatedashboard.resources.os-designate-recordset.actions.delete', + 'designatedashboard.resources.os-designate-recordset.actions.update' + ]; + + function run(registry, + resourceTypeString, + createAction, + deleteAction, + updateAction) { + var resourceType = registry.getResourceType(resourceTypeString); + + resourceType + .itemActions + .append({ + id: 'updateRecordset', + service: updateAction, + template: { + text: gettext('Update') + } + }) + .append({ + id: 'deleteRecordset', + service: deleteAction, + template: { + text: gettext('Delete'), + type: 'delete' + } + }); + + // Append a record set view to the zones actions + var zoneResourceType = registry.getResourceType("OS::Designate::Zone"); + zoneResourceType + .itemActions + .append({ + id: 'createRecordset', + service: createAction, + template: { + text: gettext('Create Record Set') + } + }); + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/common-forms.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/common-forms.service.js new file mode 100644 index 0000000..3f18bcc --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/common-forms.service.js @@ -0,0 +1,165 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-recordset.actions') + .factory('designatedashboard.resources.os-designate-recordset.actions.common-forms', service); + + service.$inject = [ + 'designatedashboard.resources.os-designate-recordset.editableTypes', + 'designatedashboard.resources.os-designate-recordset.typeMap' + ]; + + /** + * Service to return a schema form config for action forms. Especially useful for forms + * like create and update that differ only in the readonly state of certain form fields. + * + * @returns {object} A schema form config + */ + function service(editableTypes, typeMap) { + var service = { + getCreateFormConfig: getCreateFormConfig, + getUpdateFormConfig: getUpdateFormConfig + }; + + return service; + + ///////////////// + + /** + * Returns the create form config + * @returns {{schema, form, model}|*} + */ + function getCreateFormConfig() { + return getCreateUpdateFormConfig(false); + } + + /** + * Return the update form config + * @returns {{schema, form, model}|*} + */ + function getUpdateFormConfig() { + return getCreateUpdateFormConfig(true); + } + + /** + * Return the create/update form. The two forms are identical except for + * during update, some fields are read-only. + * + * @param readonly - sets readonly value on form fields that change between update and create + * @returns {object} a schema form config, including default model + */ + function getCreateUpdateFormConfig(readonly) { + return { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": /^.+\.$/ + }, + "description": { + "type": "string" + }, + "type": { + "type": "string", + "enum": editableTypes + }, + "ttl": { + "type": "integer", + "minimum": 1, + "maximum": 2147483647 + }, + "records": { + "type": "array", + "items": { + "type": "object", + "properties": { + "record": { + "type": "string" + } + } + }, + "minItems": 1, + "uniqueItems": true + } + } + }, + "form": [ + { + "key": "type", + "readonly": readonly, + "title": gettext("Type"), + "description": gettext("Select the type of record set"), + "type": "select", + "titleMap": editableTypes.map(function toTitleMap(type) { + return { + "value": type, + "name": typeMap[type] + } + }), + "required": true + }, + { + "key": "name", + "readonly": readonly, + "type": "text", + "title": gettext("Name"), + "description": gettext("DNS name for the record set, ending in '.'"), + "validationMessage": gettext("DNS name must end with '.'"), + "placeholder": "www.example.com.", + "required": true + }, + { + "key": "description", + "type": "textarea", + "title": gettext("Description"), + "description": gettext("Details about the zone.") + }, + { + "key": "ttl", + "title": gettext("TTL"), + "description": gettext("Time To Live in seconds."), + "type": "number", + "required": true + }, + { + "key": "records", + "title": gettext("Records"), + "type": "array", + "description": gettext("Records for the record set."), + "add": gettext("Add Record"), + "items": [ + { + "key": "records[].record", + "title": gettext("Record") + } + ], + "required": true + } + ], + "model": { + "type": "A", + "ttl": 3600 + } + }; + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/create.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/create.service.js new file mode 100644 index 0000000..84c65e2 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/create.service.js @@ -0,0 +1,132 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-recordset.actions') + .factory('designatedashboard.resources.os-designate-recordset.actions.create', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-recordset.actions.common-forms', + 'designatedashboard.resources.os-designate-recordset.api', + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.conf.resource-type-registry.service', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name designatedashboard.resources.os-designate-recordset.actions.create + * + * @Description + * Brings up the Create Record Set modal. + */ + function action($q, + forms, + api, + resourceTypeName, + policy, + serviceCatalog, + registry, + schemaFormModalService, + toast, + waitSpinner) { + var createRecordSetPolicy, dnsServiceEnabled; + var title = gettext("Create Record Set"); + var message = { + success: gettext('Record Set %s was successfully created.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + createRecordSetPolicy = policy.ifAllowed({rules: [['dns', 'create_recordset']]}); + dnsServiceEnabled = serviceCatalog.ifTypeEnabled('dns'); + } + + function allowed(item) { + return $q.all([ + createRecordSetPolicy, + dnsServiceEnabled + ]); + } + + function perform(item) { + var formConfig = forms.getCreateFormConfig(); + + // Store the zone ID so it can be used on submit + formConfig.model.zoneId = item.id; + + formConfig.title = title; + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + var model = angular.copy(context.model); + var zoneId = model.zoneId; + delete model.zoneId; + + // schema form doesn't appear to support populating arrays directly + // Map the records objects to simple array of records + var records = context.model.records.map(function (item) { + return item.record; + }); + model.records = records; + + waitSpinner.showModalSpinner(gettext('Creating Record Set')); + return api.create(zoneId, model).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + var zone = response.data; + toast.add('success', interpolate(message.success, [zone.name])); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [{type: resourceTypeName, id: zone.id}], + updated: [], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/delete.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/delete.service.js new file mode 100644 index 0000000..1d36e9a --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/delete.service.js @@ -0,0 +1,182 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function() { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-recordset.actions') + .factory('designatedashboard.resources.os-designate-recordset.actions.delete', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-recordset.api', + 'designatedashboard.resources.os-designate-recordset.editableTypes', + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.modal.deleteModalService', + 'horizon.framework.widgets.toast.service' + ]; + + /* + * @ngdoc factory + * @name designatedashboard.resources.os-designate-recordset.actions.delete + * + * @Description + * Brings up the delete recordset confirmation modal dialog. + + * On submit, delete given recordset. + * On cancel, do nothing. + */ + function action( + $q, + recordsetApi, + editableTypes, + resourceType, + policy, + actionResultService, + gettext, + $qExtensions, + deleteModal, + toast + ) { + var scope, context, deletePromise; + var notAllowedMessage = gettext("You are not allowed to delete record sets: %s"); + var allowedRecordsets = []; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ////////////// + + function initScope(newScope) { + scope = newScope; + context = { }; + deletePromise = policy.ifAllowed({rules: [['dns', 'delete_recordset']]}); + } + + function perform(items) { + var recordsets = angular.isArray(items) ? items : [items]; + context.labels = labelize(recordsets.length); + context.deleteEntity = deleteRecordSet; + return $qExtensions.allSettled(recordsets.map(checkPermission)).then(afterCheck); + } + + function allowed(recordset) { + // only row actions pass in recordset + // otherwise, assume it is a batch action + if (recordset) { + return $q.all([ + deletePromise, + editableRecordType(recordset) + ]); + } else { + return policy.ifAllowed({ rules: [['dns', 'delete_recordset']] }); + } + } + + function checkPermission(recordset) { + return {promise: allowed(recordset), context: recordset}; + } + + function afterCheck(result) { + var outcome = $q.reject(); // Reject the promise by default + if (result.fail.length > 0) { + toast.add('error', getMessage(notAllowedMessage, result.fail)); + outcome = $q.reject(result.fail); + } + if (result.pass.length > 0) { + // Remember the record sets we are allowed to delete so that on delete modal submit + // we can map the recordset ID back to the full recordset. Then we can fetch the + // corresponding zone ID + allowedRecordsets = result.pass.map(getEntity) + outcome = deleteModal.open(scope, allowedRecordsets, context).then(createResult); + } + return outcome; + } + + function createResult(deleteModalResult) { + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + var actionResult = actionResultService.getActionResult(); + deleteModalResult.pass.forEach(function markDeleted(item) { + actionResult.deleted(resourceType, getEntity(item).id); + }); + deleteModalResult.fail.forEach(function markFailed(item) { + actionResult.failed(resourceType, getEntity(item).id); + }); + return actionResult.result; + } + + function labelize(count) { + return { + + title: ngettext( + 'Confirm Delete Record Set', + 'Confirm Delete Record Sets', count), + + message: ngettext( + 'You have selected "%s". Deleted record set is not recoverable.', + 'You have selected "%s". Deleted record sets are not recoverable.', count), + + submit: ngettext( + 'Delete Record Set', + 'Delete Record Sets', count), + + success: ngettext( + 'Deleted Record Set: %s.', + 'Deleted Record Sets: %s.', count), + + error: ngettext( + 'Unable to delete Record Set: %s.', + 'Unable to delete Record Sets: %s.', count) + }; + } + + function editableRecordType(recordset) { + return $qExtensions.booleanAsPromise( + editableTypes.indexOf(recordset.type) > -1 + ); + } + + function deleteRecordSet(recordSetId) { + var recordSet = allowedRecordsets.find(function(element) { + return element.id === recordSetId; + }) + return recordsetApi.deleteRecordSet(recordSet.zone_id, recordSet.id); + } + + function getMessage(message, entities) { + return interpolate(message, [entities.map(getName).join(", ")]); + } + + function getName(result) { + return getEntity(result).name; + } + + function getEntity(result) { + return result.context; + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/update.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/update.service.js new file mode 100644 index 0000000..caca961 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/actions/update.service.js @@ -0,0 +1,159 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-recordset.actions') + .factory('designatedashboard.resources.os-designate-recordset.actions.update', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.util', + 'designatedashboard.resources.os-designate-recordset.actions.common-forms', + 'designatedashboard.resources.os-designate-recordset.api', + 'designatedashboard.resources.os-designate-recordset.editableTypes', + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name designatedashboard.resources.os-designate-recordset.actions.update + * + * @Description + * Brings up the Update modal. + */ + function action($q, + util, + forms, + api, + editableTypes, + resourceTypeName, + policy, + serviceCatalog, + $qExtensions, + schemaFormModalService, + toast, + waitSpinner) { + var updateRecordSetPolicy, dnsServiceEnabled; + var title = gettext("Update Record Set"); + var message = { + success: gettext('Record Set %s was successfully updated.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + updateRecordSetPolicy = policy.ifAllowed({rules: [['dns', 'update_recordset']]}); + dnsServiceEnabled = serviceCatalog.ifTypeEnabled('dns'); + } + + function allowed(recordset) { + // only supports row action (exactly 1 recordset) + if (recordset) { + return $q.all([ + updateRecordSetPolicy, + util.notDeleted(recordset), + util.notPending(recordset), + editableRecordType(recordset) + ]); + } else { + return false; + } + } + + function editableRecordType(recordset) { + return $qExtensions.booleanAsPromise( + editableTypes.indexOf(recordset.type) > -1 + ); + } + + function perform(item) { + var formConfig = forms.getUpdateFormConfig(); + formConfig.title = title; + formConfig.model = util.getModel(formConfig.form, item); + + // Append the id and zoneId so it can be used on submit + formConfig.model.id = item.id; + formConfig.model.zoneId = item.zone_id; + + // schema form doesn't appear to support populating the records array directly + // Map the records objects to record objects + if (item.hasOwnProperty("records")) { + var records = item.records.map(function (item) { + return {"record": item} + }); + formConfig.model.records = records; + } + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + var model = angular.copy(context.model); + // schema form doesn't appear to support populating the records array directly + // Map the records objects to simple array of records + if (context.model.hasOwnProperty("records")) { + var records = context.model.records.map(function (item) { + return item.record; + }); + model.records = records; + } + + waitSpinner.showModalSpinner(gettext('Updating Record Set')); + + return api.update(model.zoneId, model.id, model).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + var recordset = response.data; + toast.add('success', interpolate(message.success, [recordset.name])); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [], + updated: [{type: resourceTypeName, id: recordset.id}], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/api.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/api.service.js new file mode 100644 index 0000000..25cb51f --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/api.service.js @@ -0,0 +1,136 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-recordset') + .factory('designatedashboard.resources.os-designate-recordset.api', apiService); + + apiService.$inject = [ + '$q', + 'designatedashboard.apiPassthroughUrl', + 'horizon.framework.util.http.service', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc service + * @param {Object} httpService + * @param {Object} toastService + * @name apiService + * @description Provides direct access to Designate Record Set APIs. + * @returns {Object} The service + */ + function apiService($q, apiPassthroughUrl, httpService, toastService) { + var service = { + get: get, + list: list, + deleteRecordSet: deleteRecordSet, + create: create, + update: update + }; + + return service; + + /////////////// + + /** + * @name list + * @description + * Get a list of record sets. + * + * The listing result is an object with property "items." Each item is + * a record set. + * + * @param {Object} params + * Query parameters. Optional. + * + * @returns {Object} The result of the API call + */ + function list(zoneId, params) { + return httpService.get(apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/', params) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the record sets.')); + }); + } + + /** + * @name get + * @description + * Get a single record set by ID. + * + * @param {string} zoneId + * Specifies the id of the zone containing the record set to request. + * + * @param {string} recordSetId + * Specifies the id of the record set to request. + * + * @returns {Object} The result of the API call + */ + function get(zoneId, recordSetId) { + // Unfortunately routed-details-view is not happy when load fails, which is + // common when then delete action removes a record set. Mask this failure by + // always returning a successful promise instead of terminating the $http promise + // in the .error handler. + return httpService.get(apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/' + recordSetId + '/') + .then(undefined, function onError() { + toastService.add('error', gettext('Unable to retrieve the record set.')); + return $q.when({}); + }); + } + + /** + * @name delete + * @description + * Delete a single record set by ID + * @param {string} zoneId + * The id of the zone containing the recordset + * + * @param {string} recordSetId + * The id of the recordset within the zone + * + * @returns {*} + */ + function deleteRecordSet(zoneId, recordSetId) { + return httpService.delete(apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/' + recordSetId + '/') + .error(function () { + toastService.add('error', gettext('Unable to delete the record set.')); + }); + } + + function create(zoneId, data) { + return httpService.post(apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/', data) + .error(function () { + toastService.add('error', gettext('Unable to create the record set.')); + }); + } + + function update(zoneId, recordSetId, data) { + // The update API will not accept extra data. Restrict the input to only the allowed + // fields + var apiData = { + ttl: data.ttl, + description: data.description, + records: data.records + }; + return httpService.put(apiPassthroughUrl + 'v2/zones/' + zoneId + '/recordsets/' + recordSetId, apiData) + .error(function () { + toastService.add('error', gettext('Unable to update the record set.')); + }); + } + } +}()); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/details.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/details.module.js new file mode 100644 index 0000000..8c407b2 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/details.module.js @@ -0,0 +1,109 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-recordset.details + * + * @description + * Provides details features for record sets. + */ + angular.module('designatedashboard.resources.os-designate-recordset.details', + ['horizon.framework.conf', 'horizon.app.core']) + .run(run); + + run.$inject = [ + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'designatedashboard.resources.os-designate-recordset.api', + 'designatedashboard.resources.os-designate-recordset.basePath', + 'horizon.framework.conf.resource-type-registry.service', + ]; + + function run( + recordSetResourceType, + recordSetApi, + basePath, + registry + ) { + var resourceType = registry.getResourceType(recordSetResourceType); + resourceType + .setLoadFunction(loadFunction) + .setPathGenerator(pathGenerator) + .setPathParser(pathParser) + .setSummaryTemplateUrl(basePath + 'details/drawer.html'); + + /** + * + * @param identifier + * The object returned by the pathParser containing the zone ID and record set ID to load + */ + function loadFunction(identifier) { + return recordSetApi.get(identifier.zoneId, identifier.recordSetId); + } + + /** + * Because a record set is contained by a zone, we implement a custom + * pathGenerator to encode the zone ID and record set ID for the generic + * details panel. + * + * @param item + * A record set + * + * @returns {string} In format "/" + */ + function pathGenerator(item) { + return item.zone_id + '/' + item.id; + } + + /** + * Given a path, extract the zone and record set ids + * + * @param path + * created by pathGenerator + * + * @returns {{zoneId: *, recordSetId: *}} + * The identifier to pass to the load function needed to uniquely identify + * a record set. + */ + function pathParser(path) { + var split = path.split('/'); + return { + zoneId: split[0], + recordSetId: split[1] + } + } + + resourceType.detailsViews + .prepend({ + id: 'recordsetDetailsOverview', + name: gettext('Overview'), + template: basePath + 'details/overview.html', + }, 0); + + // Append a record set view to the zones resource view + var zoneResourceType = registry.getResourceType("OS::Designate::Zone"); + zoneResourceType.detailsViews + .append({ + id: 'zoneRecordSets', + name: gettext('Record Sets'), + template: basePath + 'details/zone-recordsets.html', + }); + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/drawer.html b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/drawer.html new file mode 100644 index 0000000..28762b4 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/drawer.html @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/overview.controller.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/overview.controller.js new file mode 100644 index 0000000..0340d2e --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/overview.controller.js @@ -0,0 +1,46 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function() { + "use strict"; + + angular + .module('designatedashboard.resources.os-designate-recordset') + .controller('designatedashboard.resources.os-designate-recordset.detailController', controller); + + controller.$inject = [ + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'horizon.framework.conf.resource-type-registry.service', + '$scope' + ]; + + function controller( + resourceTypeCode, + registry, + $scope + ) { + var ctrl = this; + + ctrl.item = {}; + ctrl.resourceType = registry.getResourceType(resourceTypeCode); + + $scope.context.loadPromise.then(onGetResponse); + + function onGetResponse(response) { + ctrl.item = response.data; + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/overview.html b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/overview.html new file mode 100644 index 0000000..5d48893 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/overview.html @@ -0,0 +1,45 @@ +
+
+
+

Details

+
+ + +
+
+

Associations

+
+ + +
+
+
+
+

Modification Times

+
+ + +
+
+
diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/zone-recordsets.controller.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/zone-recordsets.controller.js new file mode 100644 index 0000000..44184d5 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/zone-recordsets.controller.js @@ -0,0 +1,39 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function() { + "use strict"; + + angular + .module('designatedashboard.resources.os-designate-recordset') + .controller('designatedashboard.resources.os-designate-recordset.zoneRecordSetsController', controller); + + controller.$inject = [ + '$scope', + 'designatedashboard.resources.os-designate-recordset.resourceType' + ]; + + function controller( + $scope, + resourceTypeName + ) { + var ctrl = this; + + ctrl.item = {}; + ctrl.resourceTypeName = resourceTypeName; + ctrl.extraListParams = { zoneId: $scope.context.identifier }; + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/zone-recordsets.html b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/zone-recordsets.html new file mode 100644 index 0000000..d499fe4 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/details/zone-recordsets.html @@ -0,0 +1,8 @@ +
+ + +
\ No newline at end of file diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-recordset/os-designate-recordset.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/os-designate-recordset.module.js new file mode 100644 index 0000000..bda6212 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/os-designate-recordset.module.js @@ -0,0 +1,238 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-recordset + * + * @description + * Provides all of the services and widgets required + * to support and display DNS (designate) record set related content. + */ + angular + .module('designatedashboard.resources.os-designate-recordset', [ + 'ngRoute', + 'designatedashboard.resources.os-designate-recordset.actions', + 'designatedashboard.resources.os-designate-recordset.details' + ]) + .constant( + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'OS::Designate::RecordSet') + .constant( + 'designatedashboard.resources.os-designate-recordset.typeMap', + { + 'A': gettext('A - Address record'), + 'AAAA': gettext('AAAA - IPv6 address record'), + 'CNAME': gettext('CNAME - Canonical name record'), + 'MX': gettext('MX - Mail exchange record'), + 'PTR': gettext('PTR - Pointer record'), + 'SPF': gettext('SPR - Sender Policy Framework'), + 'SRV': gettext('SRV - Service locator'), + 'SSHFP': gettext('SSHFP - SSH Public Key Fingerprint'), + 'TXT': gettext('TXT - Text record'), + 'SOA': gettext('SOA - Start of authority record'), + 'NS': gettext('NS - Name server') + }) + .constant( + 'designatedashboard.resources.os-designate-recordset.editableTypes', + [ + "A", + "AAAA", + "CNAME", + "MX", + "PTR", + "SPF", + "SRV", + "SSHFP", + "TXT" + ]) + .config(config) + .run(run); + + config.$inject = ['$provide', '$windowProvider']; + + function config($provide, $windowProvider) { + var path = $windowProvider.$get().STATIC_URL + 'designatedashboard/resources/os-designate-recordset/'; + $provide.constant('designatedashboard.resources.os-designate-recordset.basePath', path); + } + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.resources.os-designate-recordset.api', + 'designatedashboard.resources.os-designate-recordset.resourceType', + 'designatedashboard.resources.os-designate-recordset.typeMap', + 'designatedashboard.resources.util' + ]; + + function run(registry, + recordSetApi, + resourceTypeString, + typeMap, + util) { + var resourceType = registry.getResourceType(resourceTypeString); + resourceType + .setNames(gettext('DNS Record Set'), gettext('DNS Record Sets')) + .setListFunction(list) + .setProperty('id', { + label: gettext('ID') + }) + .setProperty('zone_id', { + label: gettext('Zone ID'), + filters: ['noValue'] + }) + .setProperty('zone_name', { + label: gettext('Zone Name'), + filters: ['noName'] + }) + .setProperty('project_id', { + label: gettext('Project ID'), + filters: ['noValue'] + }) + .setProperty('name', { + label: gettext('Name'), + filters: ['noName'] + }) + .setProperty('description', { + label: gettext('Description'), + filters: ['noName'] + }) + .setProperty('type', { + label: gettext('Type'), + filters: ['uppercase'], + values: typeMap + }) + .setProperty('ttl', { + label: gettext('Time To Live'), + filters: ['noValue'] + }) + .setProperty('records', { + label: gettext('Records'), + filters: ['noValue'] + }) + .setProperty('notes', { + label: gettext('Notes'), + filters: ['noName'] + }) + .setProperty('status', { + label: gettext('Status'), + filters: ['lowercase', 'noName'], + values: util.statusMap() + }) + .setProperty('action', { + label: gettext('Action'), + filters: ['lowercase', 'noName'], + values: util.actionMap() + }) + .setProperty('version', { + label: gettext('Version'), + filters: ['noValue'] + }) + .setProperty('created_at', { + label: gettext('Created At'), + filters: ['noValue'] + }) + .setProperty('updated_at', { + label: gettext('Updated At'), + filters: ['noValue'] + }); + + resourceType + .tableColumns + .append({ + id: 'name', + priority: 1, + sortDefault: true, + filters: ['noName'], + // For link format, see pathGenerator in details.module.js + template: '{$ item.name $}' + }) + .append({ + id: 'type', + priority: 2, + filters: ['uppercase'], + values: typeMap + }) + .append({ + id: 'records', + priority: 2, + filters: ['noValue'] + }) + .append({ + id: 'status', + filters: ['lowercase'], + values: util.statusMap(), + priority: 2 + }); + + resourceType + .filterFacets + .append({ + label: gettext('Type'), + name: 'type', + isServer: false, + singleton: true, + persistent: false, + options: Object.keys(typeMap).map(function toOptionLabel(key) { + return { + label: typeMap[key], + key: key + } + }) + }) + .append({ + label: gettext('Name'), + name: 'name', + isServer: false, + singleton: true, + persistent: false + }) + .append({ + label: gettext('Status'), + name: 'status', + isServer: false, + singleton: true, + persistent: false, + options: [ + {label: gettext('Active'), key: 'active'}, + {label: gettext('Pending'), key: 'pending'} + ] + }); + + /** + * list all recordsets within a zone. Requires "zoneId" in the params. All other + * params will be passed unmodified as URL params to the API. + * + * @param params + * zoneId (required) list recordsets within the zone + * + * @returns {*|Object} + */ + function list(params) { + return recordSetApi.list(params.zoneId, params).then(function onList(response) { + // listFunctions are expected to return data in "items" + response.data.items = response.data.recordsets; + + util.addTimestampIds(response.data.items); + + return response; + }); + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/actions.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/actions.module.js new file mode 100644 index 0000000..9ccdd04 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/actions.module.js @@ -0,0 +1,76 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-zone.actions + * + * @description + * Provides all of the actions for DNS Zones. + */ + angular.module('designatedashboard.resources.os-designate-zone.actions', [ + 'horizon.framework.conf', + 'horizon.app.core' + ]) + .run(run); + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.resources.os-designate-zone.resourceType', + 'designatedashboard.resources.os-designate-zone.actions.create', + 'designatedashboard.resources.os-designate-zone.actions.delete', + 'designatedashboard.resources.os-designate-zone.actions.update' + ]; + + function run(registry, + resourceTypeString, + createAction, + deleteAction, + updateAction) { + var resourceType = registry.getResourceType(resourceTypeString); + resourceType + .globalActions + .append({ + id: 'create', + service: createAction, + template: { + text: gettext('Create Zone') + } + }); + + resourceType + .itemActions + .append({ + id: 'update', + service: updateAction, + template: { + text: gettext('Update') + } + }) + .append({ + id: 'delete', + service: deleteAction, + template: { + text: gettext('Delete'), + type: 'delete' + } + }); + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/common-forms.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/common-forms.service.js new file mode 100644 index 0000000..7587524 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/common-forms.service.js @@ -0,0 +1,184 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-zone.actions') + .factory('designatedashboard.resources.os-designate-zone.actions.common-forms', service); + + service.$inject = [ + ]; + + /** + * Service to return a schema form config for action forms. Especially useful for forms + * like create and update that differ only in the readonly state of certain form fields. + * + * @returns {object} A schema form config + */ + function service() { + var service = { + getCreateFormConfig: getCreateFormConfig, + getUpdateFormConfig: getUpdateFormConfig + }; + + return service; + + ///////////////// + + /** + * Returns the create zone form config + * @returns {{schema, form, model}|*} + */ + function getCreateFormConfig() { + return getCreateUpdateFormConfig(false); + } + + /** + * Return the update zone form config + * @returns {{schema, form, model}|*} + */ + function getUpdateFormConfig() { + return getCreateUpdateFormConfig(true); + } + + /** + * Return the create/update zone form. The two forms are identical except for + * during update, some fields are read-only. + * + * @param readonly - sets readonly value on form fields that change between update and create + * @returns {object} a schema form config, including default model + */ + function getCreateUpdateFormConfig(readonly) { + return { + "schema": { + "type": "object", + "properties": { + "name": { + "type": "string", + "pattern": /^.+\.$/ + }, + "description": { + "type": "string" + }, + "email": { + "type": "string", + "format": "email", + "pattern": /^[^@]+@[^@]+$/ + }, + "type": { + "type": "string", + "enum": [ + "PRIMARY", + "SECONDARY" + ] + }, + "ttl": { + "type": "integer", + "minimum": 1, + "maximum": 2147483647 + }, + "masters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "address": { + "type": "string" + } + } + }, + "minItems": 1, + "uniqueItems": true + } + } + }, + "form": [ + { + "key": "name", + "readonly": readonly, + "title": gettext("Name"), + "description": gettext("Zone name ending in '.'"), + "validationMessage": gettext("Zone must end with '.'"), + "placeholder": "example.com.", + "type": "text", + "required": true + }, + { + "key": "description", + "type": "textarea", + "title": gettext("Description"), + "description": gettext("Details about the zone.") + }, + { + "key": "email", + "title": gettext("Email Address"), + "placeholder": "owner@example.com", + "description": gettext("Email address to contact the zone owner."), + "validationMessage": gettext("Email address must contain a single '@' character"), + "type": "text", + "required": true + }, + { + "key": "ttl", + "title": gettext("TTL"), + "description": gettext("Time To Live in seconds."), + "type": "number", + "required": true + }, + { + "key": "type", + "readonly": readonly, + "title": gettext("Type"), + "description": gettext("Select the type of zone"), + "type": "select", + "titleMap": [ + { + "value": "PRIMARY", + "name": gettext("Primary") + }, + { + "value": "SECONDARY", + "name": gettext("Secondary") + } + ] + }, + { + "key": "masters", + "readonly": readonly, + "title": gettext("Masters"), + "type": "array", + "description": gettext("DNS master(s) for the Secondary zone."), + "condition": "model.type == 'SECONDARY'", + "add": gettext("Add Master"), + "items": [ + { + "key": "masters[].address", + "title": gettext("IP Address") + } + ] + } + ], + "model": { + "type": "PRIMARY", + "ttl": 3600 + } + }; + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/create.html b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/create.html new file mode 100644 index 0000000..663a695 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/create.html @@ -0,0 +1 @@ +

CREATE TEMPLATE

\ No newline at end of file diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/create.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/create.service.js new file mode 100644 index 0000000..ed53aa3 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/create.service.js @@ -0,0 +1,126 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-zone.actions') + .factory('designatedashboard.resources.os-designate-zone.actions.create', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-zone.actions.common-forms', + 'designatedashboard.resources.os-designate-zone.api', + 'designatedashboard.resources.os-designate-zone.resourceType', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name designatedashboard.resources.os-designate-zone.actions.create + * + * @Description + * Brings up the Create Zone modal. + */ + function action($q, + forms, + api, + resourceTypeName, + policy, + serviceCatalog, + schemaFormModalService, + toast, + waitSpinner) { + var createZonePolicy, dnsServiceEnabled; + var title = gettext("Create Zone"); + var message = { + success: gettext('Zone %s was successfully created.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + createZonePolicy = policy.ifAllowed({rules: [['dns', 'create_zone']]}); + dnsServiceEnabled = serviceCatalog.ifTypeEnabled('dns'); + } + + function allowed() { + return $q.all([ + createZonePolicy, + dnsServiceEnabled + ]); + } + + function perform() { + var formConfig = forms.getCreateFormConfig(); + formConfig.title = title; + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + var zoneModel = angular.copy(context.model); + // schema form doesn't appear to support populating the masters array directly + // Map the masters objects to simple array of addresses + if (context.model.hasOwnProperty("masters")) { + var masters = context.model.masters.map(function (item) { + return item.address; + }); + zoneModel.masters = masters; + } + + waitSpinner.showModalSpinner(gettext('Creating Zone')); + + return api.create(zoneModel).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + var zone = response.data; + toast.add('success', interpolate(message.success, [zone.name])); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [{type: resourceTypeName, id: zone.id}], + updated: [], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/delete.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/delete.service.js new file mode 100644 index 0000000..5882dd6 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/delete.service.js @@ -0,0 +1,169 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function() { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-zone.actions') + .factory('designatedashboard.resources.os-designate-zone.actions.delete', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-zone.api', + 'designatedashboard.resources.util', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.framework.util.actions.action-result.service', + 'horizon.framework.util.i18n.gettext', + 'horizon.framework.util.q.extensions', + 'horizon.framework.widgets.modal.deleteModalService', + 'horizon.framework.widgets.toast.service', + 'designatedashboard.resources.os-designate-zone.resourceType' + ]; + + /* + * @ngdoc factory + * @name designatedashboard.resources.os-designate-zone.actions.delete + * + * @Description + * Brings up the delete zone confirmation modal dialog. + + * On submit, delete given zone. + * On cancel, do nothing. + */ + function action( + $q, + zoneApi, + util, + policy, + actionResultService, + gettext, + $qExtensions, + deleteModal, + toast, + resourceType + ) { + var scope, context, deleteZonePromise; + var notAllowedMessage = gettext("You are not allowed to delete zones: %s"); + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ////////////// + + function initScope(newScope) { + scope = newScope; + context = { }; + deleteZonePromise = policy.ifAllowed({rules: [['dns', 'delete_zone']]}); + } + + function perform(items) { + var zones = angular.isArray(items) ? items : [items]; + context.labels = labelize(zones.length); + context.deleteEntity = deleteZone; + return $qExtensions.allSettled(zones.map(checkPermission)).then(afterCheck); + } + + function allowed(zone) { + // only row actions pass in zone + // otherwise, assume it is a batch action + if (zone) { + return $q.all([ + deleteZonePromise, + util.notDeleted(zone), + util.notPending(zone) + ]); + } else { + return policy.ifAllowed({ rules: [['dns', 'delete_zone']] }); + } + } + + function checkPermission(zone) { + return {promise: allowed(zone), context: zone}; + } + + function afterCheck(result) { + var outcome = $q.reject(); // Reject the promise by default + if (result.fail.length > 0) { + toast.add('error', getMessage(notAllowedMessage, result.fail)); + outcome = $q.reject(result.fail); + } + if (result.pass.length > 0) { + outcome = deleteModal.open(scope, result.pass.map(getEntity), context).then(createResult); + } + return outcome; + } + + function createResult(deleteModalResult) { + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + var actionResult = actionResultService.getActionResult(); + deleteModalResult.pass.forEach(function markDeleted(item) { + actionResult.deleted(resourceType, getEntity(item).id); + }); + deleteModalResult.fail.forEach(function markFailed(item) { + actionResult.failed(resourceType, getEntity(item).id); + }); + return actionResult.result; + } + + function labelize(count) { + return { + + title: ngettext( + 'Confirm Delete Zone', + 'Confirm Delete Zones', count), + + message: ngettext( + 'You have selected "%s". Deleted zone is not recoverable.', + 'You have selected "%s". Deleted zones are not recoverable.', count), + + submit: ngettext( + 'Delete Zone', + 'Delete Zones', count), + + success: ngettext( + 'Deleted Zone: %s.', + 'Deleted Zones: %s.', count), + + error: ngettext( + 'Unable to delete Zone: %s.', + 'Unable to delete Zones: %s.', count) + }; + } + + function deleteZone(zone) { + return zoneApi.deleteZone(zone, true); + } + + function getMessage(message, entities) { + return interpolate(message, [entities.map(getName).join(", ")]); + } + + function getName(result) { + return getEntity(result).name; + } + + function getEntity(result) { + return result.context; + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/update.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/update.service.js new file mode 100644 index 0000000..2610570 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/update.service.js @@ -0,0 +1,147 @@ +/** + * + * (c) Copyright 2016 Hewlett Packard Enterprise Development Company LP + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may + * not use self 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. + */ + +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-zone.actions') + .factory('designatedashboard.resources.os-designate-zone.actions.update', action); + + action.$inject = [ + '$q', + 'designatedashboard.resources.os-designate-zone.actions.common-forms', + 'designatedashboard.resources.os-designate-zone.api', + 'designatedashboard.resources.os-designate-zone.resourceType', + 'designatedashboard.resources.util', + 'horizon.app.core.openstack-service-api.policy', + 'horizon.app.core.openstack-service-api.serviceCatalog', + 'horizon.framework.widgets.form.ModalFormService', + 'horizon.framework.widgets.toast.service', + 'horizon.framework.widgets.modal-wait-spinner.service' + ]; + + /** + * @ngDoc factory + * @name designatedashboard.resources.os-designate-zone.actions.update + * + * @Description + * Brings up the Update Zone modal. + */ + function action($q, + forms, + api, + resourceTypeName, + util, + policy, + serviceCatalog, + schemaFormModalService, + toast, + waitSpinner) { + var updateZonePolicy, dnsServiceEnabled; + var title = gettext("Update Zone"); + var message = { + success: gettext('Zone %s was successfully updated.') + }; + + var service = { + initScope: initScope, + allowed: allowed, + perform: perform + }; + + return service; + + ///////////////// + + function initScope() { + updateZonePolicy = policy.ifAllowed({rules: [['dns', 'update_zone']]}); + dnsServiceEnabled = serviceCatalog.ifTypeEnabled('dns'); + } + + function allowed(zone) { + // only supports row action (exactly 1 zone) + if (zone) { + return $q.all([ + updateZonePolicy, + util.notDeleted(zone), + util.notPending(zone) + ]); + } else { + return false; + } + } + + function perform(item) { + var formConfig = forms.getUpdateFormConfig(); + formConfig.title = title; + formConfig.model = util.getModel(formConfig.form, item); + + // Append the id so it can be used on submit + formConfig.model.id = item.id; + + // schema form doesn't appear to support populating the masters array directly + // Map the masters objects to address objects + if (item.hasOwnProperty("masters")) { + var masters = item.masters.map(function (item) { + return { "address": item } + }) + formConfig.masters = masters; + } + return schemaFormModalService.open(formConfig).then(onSubmit, onCancel); + } + + function onSubmit(context) { + var zoneModel = angular.copy(context.model); + // schema form doesn't appear to support populating the masters array directly + // Map the masters objects to simple array of addresses + if (context.model.hasOwnProperty("masters")) { + var masters = context.model.masters.map(function (item) { + return item.address; + }) + zoneModel.masters = masters; + } + + waitSpinner.showModalSpinner(gettext('Updating Zone')); + + return api.update(zoneModel.id, zoneModel).then(onSuccess, onFailure); + } + + function onCancel() { + waitSpinner.hideModalSpinner(); + } + + function onSuccess(response) { + waitSpinner.hideModalSpinner(); + var zone = response.data; + toast.add('success', interpolate(message.success, [zone.name])); + + // To make the result of this action generically useful, reformat the return + // from the deleteModal into a standard form + return { + created: [], + updated: [{type: resourceTypeName, id: zone.id}], + deleted: [], + failed: [] + }; + } + + function onFailure() { + waitSpinner.hideModalSpinner(); + } + } +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/api.service.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/api.service.js new file mode 100644 index 0000000..44638cb --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/api.service.js @@ -0,0 +1,152 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources.os-designate-zone') + .factory('designatedashboard.resources.os-designate-zone.api', apiService); + + apiService.$inject = [ + 'designatedashboard.apiPassthroughUrl', + 'horizon.framework.util.http.service', + 'horizon.framework.widgets.toast.service' + ]; + + /** + * @ngdoc service + * @param {Object} httpService + * @param {Object} toastService + * @name apiService + * @description Provides direct access to Designate Zone APIs. + * @returns {Object} The service + */ + function apiService(apiPassthroughUrl, httpService, toastService) { + var service = { + get: get, + list: list, + deleteZone: deleteZone, + create: create, + update: update + }; + + return service; + + /////////////// + + /** + * @name list + * @description + * Get a list of zones. + * + * The listing result is an object with property "items." Each item is + * a zone. + * + * @param {Object} params + * Query parameters. Optional. + * + * @returns {Object} The result of the API call + */ + /* + function list(params) { + var config = params ? {'params': params} : {}; + return httpService.get('/api/designate/zones/', config) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the zone.')); + }); + }*/ + function list(params) { + var config = params ? {'params': params} : {}; + return httpService.get(apiPassthroughUrl + 'v2/zones/', config) + .error(function () { + toastService.add('error', gettext('Unable to retrieve the zone.')); + }); + } + + /** + * @name get + * @description + * Get a single zone by ID. + * + * @param {string} id + * Specifies the id of the zone to request. + * + * @returns {Object} The result of the API call + */ + function get(id) { + return httpService.get(apiPassthroughUrl + 'v2/zones/' + id + '/') + .error(function () { + toastService.add('error', gettext('Unable to retrieve the zone.')); + }); + } + + /** + * @name deleteZone + * @description + * Delete a single zone by ID + * @param id + * @returns {*} + */ + function deleteZone(id) { + return httpService.delete(apiPassthroughUrl + 'v2/zones/' + id + '/') + .error(function () { + toastService.add('error', gettext('Unable to delete the zone.')); + }); + } + + /** + * @name create + * @description + * Create a zone + * + * @param {Object} data + * Specifies the zone information to create + * + * @returns {Object} The created zone object + */ + function create(data) { + return httpService.post(apiPassthroughUrl + 'v2/zones/', data) + .error(function() { + toastService.add('error', gettext('Unable to create the zone.')); + }) + } + + /** + * @name create + * @description + * Update a zone + * + * @param {Object} id - zone id + * @param {Object} data to pass directly to zone update API + * Specifies the zone information to update + * + * @returns {Object} The updated zone object + */ + function update(id, data) { + // The update API will not accept extra data. Restrict the input to only the allowed + // fields + var apiData = { + email: data.email, + ttl: data.ttl, + description: data.description + }; + return httpService.patch(apiPassthroughUrl + 'v2/zones/' + id + '/', apiData ) + .error(function() { + toastService.add('error', gettext('Unable to update the zone.')); + }) + } + } +}()); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/details.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/details.module.js new file mode 100644 index 0000000..96a12b6 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/details.module.js @@ -0,0 +1,61 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function() { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-zone.details + * + * @description + * Provides details features for zones. + */ + angular.module('designatedashboard.resources.os-designate-zone.details', + ['horizon.framework.conf', 'horizon.app.core']) + .run(run); + + run.$inject = [ + 'designatedashboard.resources.os-designate-zone.resourceType', + 'designatedashboard.resources.os-designate-zone.api', + 'designatedashboard.resources.os-designate-zone.basePath', + 'horizon.framework.conf.resource-type-registry.service' + ]; + + function run( + zoneResourceType, + zoneApi, + basePath, + registry + ) { + var resourceType = registry.getResourceType(zoneResourceType); + resourceType + .setLoadFunction(loadFunction) + .setSummaryTemplateUrl(basePath + 'details/drawer.html'); + + resourceType.detailsViews + .prepend({ + id: 'zoneDetailsOverview', + name: gettext('Overview'), + template: basePath + 'details/overview.html', + }, 0); + + function loadFunction(identifier) { + return zoneApi.get(identifier); + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/drawer.html b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/drawer.html new file mode 100644 index 0000000..0f425b2 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/drawer.html @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.controller.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.controller.js new file mode 100644 index 0000000..f9396b1 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.controller.js @@ -0,0 +1,46 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function() { + "use strict"; + + angular + .module('designatedashboard.resources.os-designate-zone') + .controller('designatedashboard.resources.os-designate-zone.detailController', controller); + + controller.$inject = [ + 'designatedashboard.resources.os-designate-zone.resourceType', + 'horizon.framework.conf.resource-type-registry.service', + '$scope' + ]; + + function controller( + resourceTypeCode, + registry, + $scope + ) { + var ctrl = this; + + ctrl.item; + ctrl.resourceType = registry.getResourceType(resourceTypeCode); + + $scope.context.loadPromise.then(onGetResponse); + + function onGetResponse(response) { + ctrl.item = response.data; + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.html b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.html new file mode 100644 index 0000000..6a0c4f9 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.html @@ -0,0 +1,58 @@ +
+
+
+

Details

+
+ + +
+
+

Attributes

+
+ + +
+
+
+
+

Modification Times

+
+ + +
+
+

Associations

+
+ + +
+
+
diff --git a/designatedashboard/static/designatedashboard/resources/os-designate-zone/os-designate-zone.module.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/os-designate-zone.module.js new file mode 100644 index 0000000..827e7c3 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/os-designate-zone.module.js @@ -0,0 +1,202 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ + +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @ngname designatedashboard.resources.os-designate-zone + * + * @description + * Provides all of the services and widgets required + * to support and display DNS (designate) zone related content. + */ + angular + .module('designatedashboard.resources.os-designate-zone', [ + 'ngRoute', + 'designatedashboard.resources.os-designate-zone.actions', + 'designatedashboard.resources.os-designate-zone.details' + ]) + .constant( + 'designatedashboard.resources.os-designate-zone.resourceType', + 'OS::Designate::Zone') + .config(config) + .run(run); + + config.$inject = ['$provide', '$windowProvider']; + + function config($provide, $windowProvider) { + var path = $windowProvider.$get().STATIC_URL + 'designatedashboard/resources/os-designate-zone/'; + $provide.constant('designatedashboard.resources.os-designate-zone.basePath', path); + } + + run.$inject = [ + 'horizon.framework.conf.resource-type-registry.service', + 'designatedashboard.resources.os-designate-zone.api', + 'designatedashboard.resources.os-designate-zone.resourceType', + 'designatedashboard.resources.util' + ]; + + function run(registry, + zoneApi, + resourceTypeString, + util) { + var resourceType = registry.getResourceType(resourceTypeString); + resourceType + .setNames(gettext('DNS Zone'), gettext('DNS Zones')) + .setListFunction(listZones) + .setProperty('action', { + label: gettext('Action'), + filters: ['lowercase', 'noName'], + values: util.actionMap() + }) + .setProperty('attributes', { + label: gettext('Attributes') + }) + .setProperty('created_at', { + label: gettext('Created At'), + filters: ['noValue'] + }) + .setProperty('description', { + label: gettext('Description'), + filters: ['noName'] + }) + .setProperty('email', { + label: gettext('Email'), + filters: ['noName'] + }) + .setProperty('id', { + label: gettext('ID') + }) + .setProperty('masters', { + label: gettext('Masters'), + filters: ['noValue'] + }) + .setProperty('name', { + label: gettext('Name'), + filters: ['noName'] + }) + .setProperty('pool_id', { + label: gettext('Pool ID') + }) + .setProperty('project_id', { + label: gettext('Project ID') + }) + .setProperty('serial', { + label: gettext('Serial'), + filters: ['noValue'] + }) + .setProperty('status', { + label: gettext('Status'), + filters: ['lowercase', 'noName'], + values: util.statusMap() + }) + .setProperty('transferred_at', { + label: gettext('Transferred At'), + filters: ['noValue'] + }) + .setProperty('ttl', { + label: gettext('Time To Live'), + filters: ['noValue'] + }) + .setProperty('type', { + label: gettext('Type'), + filters: ['lowercase', 'noName'], + values: typeMap() + }) + .setProperty('updated_at', { + label: gettext('Updated At'), + filters: ['noValue'] + }) + .setProperty('version', { + label: gettext('Version'), + filters: ['noValue'] + }); + + resourceType + .tableColumns + .append({ + id: 'name', + priority: 1, + sortDefault: true, + template: '{$ item.name $}' + }) + .append({ + id: 'type', + filters: ['lowercase'], + values: typeMap(), + priority: 2 + }) + .append({ + id: 'status', + filters: ['lowercase'], + values: util.statusMap(), + priority: 2 + }); + + resourceType + .filterFacets + .append({ + label: gettext('Name'), + name: 'name', + isServer: false, + singleton: true, + persistent: false + }) + .append({ + label: gettext('Type'), + name: 'type', + isServer: false, + singleton: true, + persistent: false, + options: [ + {label: gettext('Primary'), key: 'primary'}, + {label: gettext('Secondary'), key: 'secondary'} + ] + }) + .append({ + label: gettext('Status'), + name: 'status', + isServer: false, + singleton: true, + persistent: false, + options: [ + {label: gettext('Active'), key: 'active'}, + {label: gettext('Pending'), key: 'pending'} + ] + }); + + function typeMap() { + return { + 'primary': gettext('Primary'), + 'secondary': gettext('Secondary') + } + } + + function listZones() { + return zoneApi.list().then(function onList(response) { + // listFunctions are expected to return data in "items" + response.data.items = response.data.zones; + + util.addTimestampIds(response.data.items, 'updated_at'); + + return response; + }); + } + } + +})(); diff --git a/designatedashboard/static/designatedashboard/resources/resources.module.js b/designatedashboard/static/designatedashboard/resources/resources.module.js new file mode 100644 index 0000000..57febbe --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/resources.module.js @@ -0,0 +1,36 @@ +/* + * (c) Copyright 2016 Hewlett-Packard Development Company, L.P. + * + * 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. + */ +(function () { + 'use strict'; + + /** + * @ngdoc overview + * @name designatedashboard.resources + * @description + * + * # designatedashboard.resources + * + * This module hosts registered resource types. This module file may + * contain individual registrations, or may have sub-modules that + * more fully contain registrations. + */ + angular + .module('designatedashboard.resources', [ + 'designatedashboard.resources.os-designate-recordset', + 'designatedashboard.resources.os-designate-zone', + 'designatedashboard.resources.os-designate-floatingip' + ]); +})(); diff --git a/designatedashboard/static/designatedashboard/resources/util.service.js b/designatedashboard/static/designatedashboard/resources/util.service.js new file mode 100644 index 0000000..cfaa787 --- /dev/null +++ b/designatedashboard/static/designatedashboard/resources/util.service.js @@ -0,0 +1,107 @@ +/** + * (c) Copyright 2016 Hewlett Packard Enterprise Development LP + * + * 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. + */ +(function () { + 'use strict'; + + angular + .module('designatedashboard.resources') + .factory('designatedashboard.resources.util', utilService); + + utilService.$inject = [ + 'horizon.framework.util.q.extensions', + ]; + + + function utilService($qExtensions) { + var service = { + notDeleted: notDeleted, + notPending: notPending, + getModel: getModel, + actionMap: actionMap, + statusMap: statusMap, + addTimestampIds: addTimestampIds + }; + + return service; + + /////////////// + + function notDeleted(resource) { + return $qExtensions.booleanAsPromise(resource.status !== 'DELETED'); + } + + function notPending(resource) { + return $qExtensions.booleanAsPromise(resource.status !== 'PENDING'); + } + + /** + * Build a model object based on the given item, using only the fields present in the form config 'key's. + * Only 'truthy' values are copied. + * + * @param form - an array of objects describing the form. Must have a 'key' attribute. + * @param item - the data to copy into the model + */ + function getModel(form, item) { + var result = {}; + var value; + form.forEach(function iterateForm(formItem) { + value = item[formItem.key]; + if (value) { + result[formItem.key] = item[formItem.key]; + } + }); + return result; + } + + function actionMap() { + return { + 'none': gettext('None'), + 'create': gettext('Create') + } + } + + function statusMap() { + return { + 'active': gettext('Active'), + 'pending': gettext('Pending') + } + } + + /** + * hz-resource-table tracks by 'id' which doesn't change when an individual item is updated. + * Create a synthetic '_timestampId' using the item id plus the specified timestamp field. + * When this field is used as a track-by in hz-resource-table, items in the table to update + * after row actions. + * + * If a timestamp field is not specified, the current time is used. + * + * @param items {object} - The items to add a _timestampId. + * @param idField {string} - (Optional) A field on the item to use as the id. Defaults to 'id' + * @param timestampField {string} - (Optional) A field on item to use as a timestamp. Defaults + * to current time. + */ + function addTimestampIds(items, idField, timestampField) { + var _idField = idField || 'id'; + var timestamp = Date.now(); + items.map(function annotateFloatingIp(item) { + if ( angular.isDefined(timestampField) ) { + timestamp = item[timestampField]; + } + item._timestampId = item[_idField] + timestamp; + }); + } + } +}()); diff --git a/designatedashboard/static/designatedashboard/reverse_dns.html b/designatedashboard/static/designatedashboard/reverse_dns.html new file mode 100644 index 0000000..c739336 --- /dev/null +++ b/designatedashboard/static/designatedashboard/reverse_dns.html @@ -0,0 +1,4 @@ + + + diff --git a/designatedashboard/static/designatedashboard/zones.html b/designatedashboard/static/designatedashboard/zones.html new file mode 100644 index 0000000..cb977d0 --- /dev/null +++ b/designatedashboard/static/designatedashboard/zones.html @@ -0,0 +1,4 @@ + + +