commit cdbe29c70bcd16f41beeccaf259d3bb92999acea
Author: Pino de Candia <giuseppe.decandia@gmail.com>
Date:   Mon Jan 8 16:34:38 2018 +0000

    Copy Designate-Dashboard to get started on Tatu dashboard.

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..aea1652
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,33 @@
+*.pyc
+*.dat
+TAGS
+*.egg-info
+*.egg
+.eggs
+build
+.coverage
+.coverage.*
+.tox
+cover
+venv
+.venv
+*.sublime-workspace
+*.sqlite
+*.sqlite3
+var/*
+AUTHORS
+ChangeLog
+doc/source/api/*
+doc/build/*
+dist
+*.orig
+*.DS_Store
+*.idea
+.testrepository/*
+functionaltests/tempest.log
+functionaltests/.testrepository/
+*.ipynb
+/.ipynb_checkpoints/*
+releasenotes/build
+node_modules
+npm-debug.log
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..0607a92
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,4 @@
+[gerrit]
+host=review.openstack.org
+port=29418
+project=openstack/designate-dashboard.git
diff --git a/.zuul.yaml b/.zuul.yaml
new file mode 100644
index 0000000..d76eafb
--- /dev/null
+++ b/.zuul.yaml
@@ -0,0 +1,4 @@
+- project:
+    name: openstack/designate-dashboard
+    templates:
+      - designate-devstack-jobs
diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst
new file mode 100644
index 0000000..51e5d63
--- /dev/null
+++ b/CONTRIBUTING.rst
@@ -0,0 +1,17 @@
+If you would like to contribute to the development of OpenStack,
+you must follow the steps in the "If you're a developer, start here"
+section of this page:
+
+   http://wiki.openstack.org/HowToContribute
+
+Once those steps have been completed, changes to OpenStack
+should be submitted for review via the Gerrit tool, following
+the workflow documented at:
+
+   http://wiki.openstack.org/GerritWorkflow
+
+Pull requests submitted through GitHub will be ignored.
+
+Bugs should be filed on Launchpad, not GitHub:
+
+   https://bugs.launchpad.net/designatedashboard
\ No newline at end of file
diff --git a/HACKING.rst b/HACKING.rst
new file mode 100644
index 0000000..e0ddef5
--- /dev/null
+++ b/HACKING.rst
@@ -0,0 +1,4 @@
+designatedashboard Style Commandments
+===============================================
+
+Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..67db858
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,175 @@
+
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..4c030ee
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,79 @@
+========================
+Team and repository tags
+========================
+
+.. image:: http://governance.openstack.org/badges/designate-dashboard.svg
+    :target: http://governance.openstack.org/reference/tags/index.html
+
+.. Change things from this point on
+
+===============================
+designatedashboard
+===============================
+
+Designate Horizon UI bits
+
+* Free software: Apache license
+
+Features
+--------
+
+* TODO
+
+
+Howto
+-----
+
+1. Package the designatedashboard by running::
+
+    python setup.py sdist
+
+   This will create a python egg in the dist folder, which can be used to install
+   on the horizon machine or within horizon's  python virtual environment.
+
+   -- or --
+
+   Install directly from source by running "python setup.py --install"
+
+   Note:  On some systems python may throw an error like
+
+      'Exception: Versioning for this project requires either an sdist tarball, or access 
+       to an upstream git repository'
+
+   this seems to be a result of mismatched pbr versioning.  A hacking workaround for development
+   purposes is replacing the pbr call with a hard-coded version (e.g. '1.0.1') in
+   designatedashboard/__init__.py.
+
+2. Copy panel plugin files into your Horizon config.  These files can be found in designatedashboard/enabled
+   and should be copied to /usr/share/openstack-dashboard/openstack_dashboard/local/enabled or the
+   equivalent directory for your openstack-dashboard install.
+
+3. Make sure your keystone catalog contains endpoints for service type 'dns'.  If no such endpoints are
+   found, the designatedashboard panels will not render.
+
+4. (Optional) Copy the designate policy file into horizon's policy files folder, and add this config::
+
+    'dns': 'designate_policy.json',
+
+5. (Optional) Within your horizon settings file(s) (either the local settings or the other settings.py), add
+   the line below.  This will make it so the record create/update screen uses a drop down of your floating ip
+   addresses instead of a free form text field::
+
+    DESIGNATE = { 'records_use_fips': True }
+
+
+Test
+----
+
+* How to run JS tests:
+
+    * Install npm and nodejs=4.8.4
+
+    $ ``sudo apt-get install npm``
+    $ ``curl -sL https://deb.nodesource.com/setup_4.x | sudo -E bash -``
+    $ ``sudo apt-get install -y nodejs``
+
+  1. ``npm install`` (to create virtual environment and install all dependencies in package.json)
+  2. ``npm run lint`` for eslint
+  3. ``npm run test`` for JS unit tests
+
diff --git a/babel-django.cfg b/babel-django.cfg
new file mode 100644
index 0000000..e78d6c0
--- /dev/null
+++ b/babel-django.cfg
@@ -0,0 +1,5 @@
+[extractors]
+django = django_babel.extract:extract_django
+
+[python: **.py]
+[django: **/templates/**.html]
diff --git a/designatedashboard/__init__.py b/designatedashboard/__init__.py
new file mode 100644
index 0000000..a415499
--- /dev/null
+++ b/designatedashboard/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import pbr.version
+
+
+__version__ = pbr.version.VersionInfo(
+    'designate-dashboard').version_string()
diff --git a/designatedashboard/api/__init__.py b/designatedashboard/api/__init__.py
new file mode 100644
index 0000000..97ba638
--- /dev/null
+++ b/designatedashboard/api/__init__.py
@@ -0,0 +1 @@
+from designatedashboard.api import designate  # noqa
diff --git a/designatedashboard/api/designate.py b/designatedashboard/api/designate.py
new file mode 100644
index 0000000..6095410
--- /dev/null
+++ b/designatedashboard/api/designate.py
@@ -0,0 +1,171 @@
+# 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 __future__ import absolute_import
+
+from designateclient.v1 import Client  # noqa
+from designateclient.v1.domains import Domain  # noqa
+from designateclient.v1.records import Record  # noqa
+from django.conf import settings  # noqa
+
+from horizon import exceptions
+
+from openstack_dashboard.api.base import url_for  # noqa
+from oslo_log import log as logging
+
+LOG = logging.getLogger(__name__)
+
+
+def designateclient(request):
+    designate_url = ""
+    try:
+        designate_url = url_for(request, 'dns')
+    except exceptions.ServiceCatalogException:
+        LOG.debug('no dns service configured.')
+        return None
+
+    insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
+    cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
+
+    return Client(endpoint=designate_url,
+                  token=request.user.token.id,
+                  username=request.user.username,
+                  tenant_id=request.user.project_id,
+                  insecure=insecure,
+                  cacert=cacert)
+
+
+def domain_get(request, domain_id):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.domains.get(domain_id)
+
+
+def domain_list(request):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.domains.list()
+
+
+def domain_create(request, name, email, ttl=None, description=None):
+    d_client = designateclient(request)
+    if d_client is None:
+        return None
+
+    options = {
+        'description': description,
+    }
+
+    # TTL needs to be optionally added as argument because the client
+    # won't accept a None value
+    if ttl is not None:
+        options['ttl'] = ttl
+
+    domain = Domain(name=name, email=email, **options)
+
+    return d_client.domains.create(domain)
+
+
+def domain_update(request, domain_id, email, ttl, description=None):
+    d_client = designateclient(request)
+    if d_client is None:
+        return None
+
+    # A quirk of the designate client is that you need to start with a
+    # base record and then update individual fields in order to persist
+    # the data. The designate client will only send the 'changed' fields.
+    domain = Domain(id=domain_id, name='', email='')
+
+    domain.email = email
+    domain.ttl = ttl
+    domain.description = description
+
+    return d_client.domains.update(domain)
+
+
+def domain_delete(request, domain_id):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.domains.delete(domain_id)
+
+
+def server_list(request, domain_id):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.domains.list_domain_servers(domain_id)
+
+
+def record_list(request, domain_id):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.records.list(domain_id)
+
+
+def record_get(request, domain_id, record_id):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.records.get(domain_id, record_id)
+
+
+def record_delete(request, domain_id, record_id):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+    return d_client.records.delete(domain_id, record_id)
+
+
+def record_create(request, domain_id, **kwargs):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+
+    record = Record(**kwargs)
+    return d_client.records.create(domain_id, record)
+
+
+def record_update(request, domain_id, record_id, **kwargs):
+    d_client = designateclient(request)
+    if d_client is None:
+        return []
+
+    # A quirk of the designate client is that you need to start with a
+    # base record and then update individual fields in order to persist
+    # the data. The designate client will only send the 'changed' fields.
+    record = Record(
+        id=record_id,
+        type='A',
+        name='',
+        data='')
+
+    record.type = kwargs.get('type', None)
+    record.name = kwargs.get('name', None)
+    record.data = kwargs.get('data', None)
+    record.priority = kwargs.get('priority', None)
+    record.ttl = kwargs.get('ttl', None)
+    record.description = kwargs.get('description', None)
+
+    return d_client.records.update(domain_id, record)
+
+
+def quota_get(request, project_id=None):
+    if not project_id:
+        project_id = request.user.project_id
+    d_client = designateclient(request)
+    return d_client.quotas.get(project_id)
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 <year(s)> 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..00e1228
--- /dev/null
+++ b/designatedashboard/api/rest/passthrough.py
@@ -0,0 +1,119 @@
+# 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 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
+from oslo_log import log as logging
+
+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.
+    """
+
+    # 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
+
+    service_url = _get_service_url(request, 'dns')
+    request_url = '{}{}'.format(
+        service_url,
+        url if service_url.endswith('/') else ('/' + url)
+    )
+
+    response = request_method(
+        request_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<path>.+)$'
+
+    @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/__init__.py b/designatedashboard/dashboards/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/designatedashboard/dashboards/project/__init__.py b/designatedashboard/dashboards/project/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/designatedashboard/dashboards/project/dns_domains/__init__.py b/designatedashboard/dashboards/project/dns_domains/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/designatedashboard/dashboards/project/dns_domains/forms.py b/designatedashboard/dashboards/project/dns_domains/forms.py
new file mode 100644
index 0000000..852c2d9
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/forms.py
@@ -0,0 +1,549 @@
+# 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.
+import functools
+import re
+import six
+
+from designateclient import exceptions as designate_exceptions
+from django.core.exceptions import ValidationError  # noqa
+from django.core import validators
+from django.template.loader import render_to_string
+from django.utils.translation import ugettext_lazy as _  # noqa
+
+from horizon import forms
+from horizon import messages
+
+from designatedashboard import api
+from designatedashboard.dashboards.project.dns_domains.utils\
+    import limit_records_to_fips
+from oslo_log import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+MAX_TTL = 2147483647
+# These regexes were given to me by Kiall Mac Innes here:
+# https://gerrit.hpcloud.net/#/c/25300/2/
+DOMAIN_NAME_REGEX = r'^(?!.{255,})(?:(?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)\.)+$'
+WILDCARD_DOMAIN_NAME_REGEX = r'^(?!.{255,})(?:(^\*|(?!\-)[A-Za-z0-9_\-]{1,63})(?<!\-)\.)+$'  # noqa
+SRV_NAME_REGEX = r'^(?:_[A-Za-z0-9_\-]{1,62}\.){2}'
+SRV_DATA_REGEX = r'^(?:(?:6553[0-5]|655[0-2][0-9]|65[0-4][0-9]{2}|6[0-4][0-9]{3}|[1-5][0-9]{4}|[1-9][0-9]{1,3}|[0-9])\s){2}(?!.{255,})((?!\-)[A-Za-z0-9_\-]{1,63}(?<!\-)\.)+$'  # noqa
+SSHFP_DATA_REGEX = r'^[1-4]\s[1-2]\s\b([0-9a-fA-F]{5,40}|[0-9a-fA-F]{64})\b$'
+# The max length for a dns label
+NAME_MAX_LENGTH = 63
+
+
+def handle_exc(func):
+    @functools.wraps(func)
+    def wrapped(form, request, *args, **kwargs):
+        try:
+            return func(form, request, *args, **kwargs)
+        except designate_exceptions.RemoteError as ex:
+            msg = ""
+            data = {}
+
+            if six.text_type(ex) is not None:
+                data['message'] = six.text_type(ex)
+                msg += "Error: %(message)s"
+            else:
+                data["type"] = ex.type
+                msg += "Error Type: %(type)s"
+
+            if ex.code >= 500:
+                msg += " (Request ID: %(request_id)s"
+                data["request_id"] = ex.request_id
+
+            form.api_error(_(msg) % data)  # noqa
+
+            return False
+        except Exception:
+            messages.error(request, form.exc_message)
+            return True
+
+    return wrapped
+
+
+class DomainForm(forms.SelfHandlingForm):
+
+    '''Base class for DomainCreate and DomainUpdate forms.
+
+    Sets-up all of the common form fields.
+    '''
+
+    name = forms.RegexField(
+        label=_("Domain Name"),
+        regex=DOMAIN_NAME_REGEX,
+        error_messages={'invalid': _('Enter a valid domain name.')},
+    )
+
+    email = forms.EmailField(
+        label=_("Email"),
+        max_length=255,
+    )
+
+    ttl = forms.IntegerField(
+        label=_("TTL (seconds)"),
+        min_value=1,
+        max_value=MAX_TTL,
+        required=False,
+    )
+
+    description = forms.CharField(
+        label=_("Description"),
+        required=False,
+        max_length=160,
+        widget=forms.Textarea(),
+    )
+
+
+class DomainCreate(DomainForm):
+
+    '''Form for creating new domain records.
+
+    Name and email address are
+    required.
+    '''
+    exc_message = _("Unable to create domain.")
+
+    @handle_exc
+    def handle(self, request, data):
+        domain = api.designate.domain_create(
+            request,
+            name=data['name'],
+            email=data['email'],
+            ttl=data['ttl'],
+            description=data['description'])
+        messages.success(request,
+                         _('Domain %(name)s created.') %
+                         {"name": domain.name})
+        return domain
+
+
+class DomainUpdate(DomainForm):
+
+    '''Form for displaying domain record details and updating them.'''
+    exc_message = _('Unable to update domain.')
+
+    id = forms.CharField(
+        required=False,
+        widget=forms.HiddenInput()
+    )
+
+    serial = forms.CharField(
+        label=_("Serial"),
+        required=False,
+        widget=forms.TextInput(attrs={'readonly': 'readonly'}),
+    )
+
+    created_at = forms.CharField(
+        label=_("Created At"),
+        required=False,
+        widget=forms.TextInput(attrs={'readonly': 'readonly'}),
+    )
+
+    updated_at = forms.CharField(
+        label=_("Updated At"),
+        required=False,
+        widget=forms.TextInput(attrs={'readonly': 'readonly'}),
+    )
+
+    def __init__(self, request, *args, **kwargs):
+        super(DomainUpdate, self).__init__(request, *args, **kwargs)
+
+        # Mark name as read-only
+        self.fields['name'].required = False
+        self.fields['name'].widget.attrs['readonly'] = 'readonly'
+
+        self.fields['ttl'].required = True
+
+        # Customize display order for fields
+        self.fields.keyOrder = [
+            'id',
+            'name',
+            'serial',
+            'email',
+            'ttl',
+            'description',
+            'created_at',
+            'updated_at',
+        ]
+
+    @handle_exc
+    def handle(self, request, data):
+        domain = api.designate.domain_update(
+            request,
+            domain_id=data['id'],
+            email=data['email'],
+            ttl=data['ttl'],
+            description=data['description'])
+        messages.success(request,
+                         _('Domain %(name)s updated.') %
+                         {"name": domain.name})
+        return domain
+
+
+class PrefixWidget(forms.TextInput):
+
+    def render(self, name, value, attrs=None):
+        template_name = 'project/dns_domains/prefix_html_widget.html'
+        result = super(PrefixWidget, self).render(name, value, attrs)
+        view_data = {'input': result,
+                     'suffix': getattr(self, "suffix", '')}
+        return render_to_string(template_name, view_data)
+
+
+class RecordForm(forms.SelfHandlingForm):
+
+    '''Base class for RecordCreate and RecordUpdate forms.
+
+    Sets-up all of
+    the form fields and implements the complex validation logic.
+    '''
+
+    domain_id = forms.CharField(
+        widget=forms.HiddenInput())
+
+    domain_name = forms.CharField(
+        widget=forms.HiddenInput())
+
+    type = forms.ChoiceField(
+        label=_("Record Type"),
+        required=False,
+        choices=[
+            ('a', _('A - Address record')),
+            ('aaaa', _('AAAA - IPv6 address record')),
+            ('cname', _('CNAME - Canonical name record')),
+            ('mx', _('MX - Mail exchange record')),
+            ('ptr', _('PTR - Pointer record')),
+            ('spf', _('SPF - Sender Policy Framework')),
+            ('srv', _('SRV - Service locator')),
+            ('sshfp', _('SSHFP - SSH Public Key Fingerprint')),
+            ('txt', _('TXT - Text record')),
+        ],
+        widget=forms.Select(attrs={
+            'class': 'switchable',
+            'data-slug': 'record_type',
+        }),
+    )
+
+    name = forms.CharField(
+        required=False,
+        widget=PrefixWidget(attrs={
+            'class': 'switched',
+            'data-switch-on': 'record_type',
+            'data-record_type-a': _('Name'),
+            'data-record_type-aaaa': _('Name'),
+            'data-record_type-cname': _('Name'),
+            'data-record_type-mx': _('Name'),
+            'data-record_type-ns': _('Name'),
+            'data-record_type-ptr': _('Name'),
+            'data-record_type-soa': _('Name'),
+            'data-record_type-spf': _('Name'),
+            'data-record_type-srv': _('Name'),
+            'data-record_type-sshfp': _('Name'),
+            'data-record_type-txt': _('Name'),
+        }),
+    )
+
+    data = forms.CharField(
+        required=False,
+        widget=forms.TextInput(attrs={
+            'class': 'switched',
+            'data-switch-on': 'record_type',
+            'data-record_type-a': _('IP Address'),
+            'data-record_type-aaaa': _('IP Address'),
+            'data-record_type-cname': _('Canonical Name'),
+            'data-record_type-ns': _('Name Server'),
+            'data-record_type-mx': _('Mail Server'),
+            'data-record_type-ptr': _('PTR Domain Name'),
+            'data-record_type-soa': _('Value'),
+            'data-record_type-srv': _('Value'),
+        }),
+    )
+
+    ip_addr = forms.ChoiceField(
+        required=False,
+        widget=forms.Select(attrs={
+            'class': 'switched',
+            'data-switch-on': 'record_type',
+            'data-record_type-a': _('IP Address'),
+            'data-record_type-aaaa': _('IP Address'),
+        }),
+    )
+
+    txt = forms.CharField(
+        label=_('TXT'),
+        required=False,
+        widget=forms.Textarea(attrs={
+            'class': 'switched',
+            'data-switch-on': 'record_type',
+            'data-record_type-txt': _('Text'),
+            'data-record_type-spf': _('Text'),
+            'data-record_type-sshfp': _('Text'),
+        }),
+    )
+
+    priority = forms.IntegerField(
+        min_value=0,
+        max_value=65535,
+        required=False,
+        widget=forms.TextInput(attrs={
+            'class': 'switched',
+            'data-switch-on': 'record_type',
+            'data-record_type-mx': _('Priority'),
+            'data-record_type-srv': _('Priority'),
+        }),
+    )
+
+    ttl = forms.IntegerField(
+        label=_('TTL'),
+        min_value=1,
+        max_value=MAX_TTL,
+        required=False,
+        widget=forms.TextInput(attrs={
+            'class': 'switched',
+            'data-switch-on': 'record_type',
+            'data-record_type-a': _('TTL'),
+            'data-record_type-aaaa': _('TTL'),
+            'data-record_type-cname': _('TTL'),
+            'data-record_type-mx': _('TTL'),
+            'data-record_type-ptr': _('TTL'),
+            'data-record_type-soa': _('TTL'),
+            'data-record_type-spf': _('TTL'),
+            'data-record_type-srv': _('TTL'),
+            'data-record_type-sshfp': _('TTL'),
+            'data-record_type-txt': _('TTL'),
+        }),
+    )
+
+    description = forms.CharField(
+        label=_("Description"),
+        required=False,
+        max_length=160,
+        widget=forms.Textarea(),
+    )
+
+    def __init__(self, request, *args, **kwargs):
+        super(RecordForm, self).__init__(request, *args, **kwargs)
+        initial = kwargs.get('initial', {})
+        domain_suffix = "." + initial['domain_name']
+        self.fields['name'].widget.suffix = domain_suffix
+        self.fields['name'].max_length = min(NAME_MAX_LENGTH,
+                                             255 - len(domain_suffix))
+        if limit_records_to_fips():
+            del self.fields['data'].widget.attrs['data-record_type-a']
+            del self.fields['data'].widget.attrs['data-record_type-aaaa']
+            self.fields['ip_addr'].choices = \
+                self.populate_ip_addr_choices(request,
+                                              initial)
+        else:
+            del self.fields['ip_addr']
+
+    def _generate_fip_list(self, fips, instances):
+        instance_dict = {instance.id: instance for instance in instances}
+        for fip in fips:
+            instance_name = _("Unknown instance name")
+            if getattr(fip, "instance_id", "None") in instance_dict:
+                instance_name = instance_dict[getattr(fip, "instance_id")].name
+            yield (fip.ip, "%s (%s)" % (fip.ip, instance_name))
+
+    def populate_ip_addr_choices(self, request, initial):
+        results = [(None, _('Select an IP')), ]
+        if (initial.get('ip_addr') and
+                initial['ip_addr'] not in [fip.ip for fip in initial['fips']]):
+            """The record is currently using an ip not in the list
+            of fips - this can happen when instance goes away or in
+            multi region setups
+            """
+            results.append((initial['ip_addr'], initial['ip_addr']))
+        results.extend(self._generate_fip_list(initial['fips'],
+                                               initial['instances']))
+        if len(results) == 1:
+            messages.warning(request, _("There are no floating IP addresses "
+                                        "currently in use to select from."))
+        return results
+
+    def clean_type(self):
+        '''Type value needs to be uppercased before it is sent to the API.'''
+        return self.cleaned_data['type'].upper()
+
+    def clean(self):
+        '''Handles the validation logic for the domain record form.
+
+        Validation gets pretty complicated due to the fact that the different
+        record types (A, AAAA, MX, etc) have different requirements for
+        each of the fields.
+        '''
+
+        cleaned_data = super(RecordForm, self).clean()
+        record_type = cleaned_data['type']
+        domain_name = cleaned_data['domain_name']
+        if limit_records_to_fips():
+            ip_addr = cleaned_data.pop('ip_addr')
+            if (record_type in ['AAAA', 'A'] and limit_records_to_fips()):
+                cleaned_data['data'] = ip_addr
+
+        #  Name field
+        if self._is_field_blank(cleaned_data, 'name'):
+            if record_type in ['CNAME', 'SRV']:
+                self._add_required_field_error('name')
+            elif record_type in ['MX', 'A', 'AAAA', 'TXT', 'PTR']:
+                cleaned_data['name'] = domain_name
+        else:
+            if record_type == 'SRV':
+                if not re.match(SRV_NAME_REGEX, cleaned_data['name']):
+                    self._add_field_error('name', _('Enter a valid SRV name'))
+                else:
+                    cleaned_data['name'] += domain_name
+            else:
+                cleaned_data['name'] += "." + domain_name
+                if not re.match(WILDCARD_DOMAIN_NAME_REGEX,
+                                cleaned_data['name']):
+                    self._add_field_error('name',
+                                          _('Enter a valid hostname. The '
+                                            'hostname should contain letters '
+                                            'and numbers, and be no more than '
+                                            '63 characters.'))
+        # Data field
+        if self._is_field_blank(cleaned_data, 'data'):
+            if record_type in ['A', 'AAAA', 'CNAME', 'MX', 'SRV']:
+                self._add_required_field_error('data')
+        else:
+            if record_type == 'A':
+                try:
+                    validators.validate_ipv4_address(cleaned_data['data'])
+                except ValidationError:
+                    self._add_field_error('data',
+                                          _('Enter a valid IPv4 address'))
+
+            elif record_type == 'AAAA':
+                try:
+                    validators.validate_ipv6_address(cleaned_data['data'])
+                except ValidationError:
+                    self._add_field_error('data',
+                                          _('Enter a valid IPv6 address'))
+
+            elif record_type in ['CNAME', 'MX', 'PTR']:
+                if not re.match(DOMAIN_NAME_REGEX, cleaned_data['data']):
+                    self._add_field_error('data', _('Enter a valid hostname'))
+
+            elif record_type == 'SRV':
+                if not re.match(SRV_DATA_REGEX, cleaned_data['data']):
+                    self._add_field_error('data',
+                                          _('Enter a valid SRV record'))
+
+        # Txt field
+        if self._is_field_blank(cleaned_data, 'txt'):
+            if record_type == 'TXT':
+                self._add_required_field_error('txt')
+        else:
+            if record_type == 'TXT':
+                cleaned_data['data'] = cleaned_data['txt']
+
+        if record_type == 'SSHFP':
+            if not re.match(SSHFP_DATA_REGEX, cleaned_data['txt']):
+                self._add_field_error('txt',
+                                      _('Enter a valid SSHFP record'))
+            cleaned_data['data'] = cleaned_data['txt']
+
+        cleaned_data.pop('txt')
+
+        # Priority field
+        # Check against '' instead of using _is_field_blank because we need to
+        # allow a valud of 0.
+        if ('priority' not in cleaned_data or
+                cleaned_data['priority'] == '' or
+                cleaned_data['priority'] is None):
+            if record_type in ['MX', 'SRV']:
+                self._add_required_field_error('priority')
+
+        # Rename 'id' to 'record_id'
+        if 'id' in cleaned_data:
+            cleaned_data['record_id'] = cleaned_data.pop('id')
+
+        # Remove domain_name
+        cleaned_data.pop('domain_name')
+
+        return cleaned_data
+
+    def _add_required_field_error(self, field):
+        '''Set a required field error on the specified field.'''
+        self._add_field_error(field, _('This field is required'))
+
+    def _add_field_error(self, field, msg):
+        '''Set the specified msg as an error on the field.'''
+        self._errors[field] = self.error_class([msg])
+
+    def _is_field_blank(self, cleaned_data, field):
+        '''Returns a flag indicating whether the specified field is blank.'''
+        return field in cleaned_data and not cleaned_data[field]
+
+
+class RecordCreate(RecordForm):
+
+    '''Form for creating a new domain record.'''
+    exc_message = _('Unable to create record.')
+
+    @handle_exc
+    def handle(self, request, data):
+        record = api.designate.record_create(request, **data)
+        messages.success(request,
+                         _('Domain record %(name)s created.') %
+                         {"name": record.name})
+        return record
+
+
+class RecordUpdate(RecordForm):
+
+    '''Form for editing a domain record.'''
+    exc_message = _('Unable to create record.')
+
+    id = forms.CharField(widget=forms.HiddenInput())
+
+    def __init__(self, request, *args, **kwargs):
+        super(RecordUpdate, self).__init__(request, *args, **kwargs)
+
+        # Force the type field to be read-only
+        self.fields['type'].widget.attrs['readonly'] = 'readonly'
+
+        if self['type'].value() in ('soa', 'ns'):
+            self.fields['type'].choices.append(('ns', _('NS')))
+            self.fields['type'].choices.append(('soa', _('SOA')))
+
+            self.fields['name'].widget.attrs['readonly'] = 'readonly'
+            self.fields['data'].widget.attrs['readonly'] = 'readonly'
+            self.fields['description'].widget.attrs['readonly'] = 'readonly'
+            self.fields['ttl'].widget.attrs['readonly'] = 'readonly'
+
+        # Filter the choice list so that it only contains the type for
+        # the current record. Ideally, we would just disable the select
+        # field, but that has the unfortunate side-effect of breaking
+        # the 'selectable' javascript code.
+        self.fields['type'].choices = (
+            [choice for choice in self.fields['type'].choices
+             if choice[0] == self.initial['type']])
+
+    @handle_exc
+    def handle(self, request, data):
+
+        if data['type'] in ('SOA', 'NS'):
+            return True
+
+        record = api.designate.record_update(request, **data)
+
+        messages.success(request,
+                         _('Domain record %(name)s updated.') %
+                         {"name": record.name})
+
+        return record
diff --git a/designatedashboard/dashboards/project/dns_domains/panel.py b/designatedashboard/dashboards/project/dns_domains/panel.py
new file mode 100644
index 0000000..a7df87f
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/panel.py
@@ -0,0 +1,26 @@
+# 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 django.utils.translation import ugettext_lazy as _  # noqa
+
+import horizon
+from openstack_dashboard.dashboards.project import dashboard
+
+
+class DNSDomains(horizon.Panel):
+    name = _("Domains")
+    slug = 'dns_domains'
+    permissions = ('openstack.services.dns',)
+
+
+dashboard.Project.register(DNSDomains)
diff --git a/designatedashboard/dashboards/project/dns_domains/tables.py b/designatedashboard/dashboards/project/dns_domains/tables.py
new file mode 100644
index 0000000..07b4f8e
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/tables.py
@@ -0,0 +1,224 @@
+# 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 django.core import urlresolvers
+from django.utils.translation import ugettext_lazy as _  # noqa
+
+from horizon import messages
+from horizon import tables
+from horizon.utils import memoized
+
+from designatedashboard import api
+
+from openstack_dashboard import policy
+from oslo_log import log as logging
+
+LOG = logging.getLogger(__name__)
+
+EDITABLE_RECORD_TYPES = (
+    "A",
+    "AAAA",
+    "CNAME",
+    "MX",
+    "PTR",
+    "SPF",
+    "SRV",
+    "SSHFP",
+    "TXT",
+)
+
+
+class CreateDomain(tables.LinkAction):
+
+    '''Link action for navigating to the CreateDomain view.'''
+    name = "create_domain"
+    verbose_name = _("Create Domain")
+    url = "horizon:project:dns_domains:create_domain"
+    classes = ("ajax-modal", "btn-create")
+    policy_rules = (("dns", "create_domain"),)
+
+    @memoized.memoized_method
+    def allowed(self, request, datum):
+        if policy.check((("dns", "get_quota"),), request):
+            try:
+                if self.table:
+                    quota = api.designate.quota_get(request)
+                    return quota['domains'] > len(self.table.data)
+            except Exception:
+                msg = _("The quotas could not be retrieved.")
+                messages.warning(request, msg)
+        return True
+
+
+class EditDomain(tables.LinkAction):
+
+    '''Link action for navigating to the UpdateDomain view.'''
+    name = "edit_domain"
+    verbose_name = _("Edit Domain")
+    url = "horizon:project:dns_domains:update_domain"
+    classes = ("ajax-modal", "btn-edit")
+    policy_rules = (("dns", "update_domain"),)
+
+
+class ManageRecords(tables.LinkAction):
+
+    '''Link action for navigating to the ManageRecords view.'''
+    name = "manage_records"
+    verbose_name = _("Manage Records")
+    url = "horizon:project:dns_domains:records"
+    classes = ("btn-edit")
+    policy_rules = (("dns", "get_records"),)
+
+
+class DeleteDomain(tables.BatchAction):
+
+    '''Batch action for deleting domains.'''
+    name = "delete"
+    action_present = _("Delete")
+    action_past = _("Deleted")
+    data_type_singular = _("Domain")
+    data_type_plural = _("Domains")
+    classes = ('btn-danger', 'btn-delete')
+    policy_rules = (("dns", "delete_domain"),)
+
+    def action(self, request, domain_id):
+        api.designate.domain_delete(request, domain_id)
+
+
+class CreateRecord(tables.LinkAction):
+
+    '''Link action for navigating to the CreateRecord view.'''
+    name = "create_record"
+    verbose_name = _("Create Record")
+    classes = ("ajax-modal", "btn-create")
+    policy_rules = (("dns", "create_record"),)
+
+    def get_link_url(self, datum=None):
+        url = "horizon:project:dns_domains:create_record"
+        return urlresolvers.reverse(url, kwargs=self.table.kwargs)
+
+
+class EditRecord(tables.LinkAction):
+
+    '''Link action for navigating to the UpdateRecord view.'''
+    name = "edit_record"
+    verbose_name = _("Edit Record")
+    classes = ("ajax-modal", "btn-edit")
+    policy_rules = (("dns", "update_record"),)
+
+    def get_link_url(self, datum=None):
+        url = "horizon:project:dns_domains:update_record"
+        kwargs = {
+            'domain_id': datum.domain_id,
+            'record_id': datum.id,
+        }
+
+        return urlresolvers.reverse(url, kwargs=kwargs)
+
+    def allowed(self, request, record=None):
+        return record.type in EDITABLE_RECORD_TYPES
+
+
+class DeleteRecord(tables.DeleteAction):
+
+    '''Link action for navigating to the UpdateRecord view.'''
+    data_type_singular = _("Record")
+    policy_rules = (("dns", "delete_record"),)
+
+    def delete(self, request, record_id):
+        domain_id = self.table.kwargs['domain_id']
+        return api.designate.record_delete(request, domain_id, record_id)
+
+    def allowed(self, request, record=None):
+        return record.type in EDITABLE_RECORD_TYPES
+
+
+class BatchDeleteRecord(tables.BatchAction):
+
+    '''Batch action for deleting domain records.'''
+
+    name = "delete"
+    action_present = _("Delete")
+    action_past = _("Deleted")
+    data_type_singular = _("Record")
+    classes = ('btn-danger', 'btn-delete')
+    policy_rules = (("dns", "delete_record"),)
+
+    def action(self, request, record_id):
+        domain_id = self.table.kwargs['domain_id']
+        api.designate.record_delete(request, domain_id, record_id)
+
+
+class DomainsTable(tables.DataTable):
+
+    '''Data table for displaying domain summary information.'''
+
+    name = tables.Column("name",
+                         verbose_name=_("Name"),
+                         link=("horizon:project:dns_domains:domain_detail"))
+
+    email = tables.Column("email",
+                          verbose_name=_("Email"))
+
+    ttl = tables.Column("ttl",
+                        verbose_name=_("TTL"))
+
+    serial = tables.Column("serial",
+                           verbose_name=_("Serial"))
+
+    class Meta(object):
+        name = "domains"
+        verbose_name = _("Domains")
+        table_actions = (CreateDomain, DeleteDomain,)
+        row_actions = (ManageRecords, EditDomain, DeleteDomain,)
+
+
+def record__details_link(record):
+    '''Returns a link to the view for updating DNS records.'''
+
+    return urlresolvers.reverse(
+        "horizon:project:dns_domains:view_record",
+        args=(record.domain_id, record.id))
+
+
+class RecordsTable(tables.DataTable):
+
+    '''Data table for displaying summary information for a domains records.'''
+
+    name = tables.Column("name",
+                         verbose_name=_("Name"),
+                         link=record__details_link,
+                         )
+
+    type = tables.Column("type",
+                         verbose_name=_("Type")
+                         )
+
+    data = tables.Column("data",
+                         verbose_name=_("Data")
+                         )
+
+    priority = tables.Column("priority",
+                             verbose_name=_("Priority"),
+                             )
+
+    ttl = tables.Column("ttl",
+                        verbose_name=_("TTL")
+                        )
+
+    class Meta(object):
+        name = "records"
+        verbose_name = _("Records")
+        table_actions = (CreateRecord,)
+        row_actions = (EditRecord, DeleteRecord,)
+        multi_select = False
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_create_domain.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_create_domain.html
new file mode 100644
index 0000000..3aa22ca
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_create_domain.html
@@ -0,0 +1,38 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n horizon humanize %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url 'horizon:project:dns_domains:create_domain' %}{% endblock %}
+
+{% block modal_id %}create_domain_modal{% endblock %}
+{% block modal-header %}{% trans "Create Domain" %}{% endblock %}
+
+{% block modal-body %}
+  <div class="left">
+    <fieldset>
+      {% include "horizon/common/_form_fields.html" %}
+    </fieldset>
+  </div>
+
+  <div class="right quota-dynamic">
+    <h3>{% trans "Description" %}:</h3>
+    <p>{% blocktrans %}
+      The Name field should contain a full-qualified domain name (with
+      trailing period).
+    {% endblocktrans %}</p>
+    <p>{% blocktrans %}
+      The Email field should contain a valid email address to be associated
+      with the domain.
+    {% endblocktrans %}</p>
+    <p>{% blocktrans %}
+      The optional TTL field can be any value between 1 and 2147483647
+      seconds.
+    {% endblocktrans %}</p>
+  </div>
+
+{% endblock %}
+
+{% block modal-footer %}
+  <input class="btn btn-primary pull-right" type="submit" value="{% trans "Create Domain" %}" />
+  <a href="{% url 'horizon:project:dns_domains:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_create_record.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_create_record.html
new file mode 100644
index 0000000..197443b
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_create_record.html
@@ -0,0 +1,37 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n horizon humanize %}
+
+{% block form_id %}{% endblock %}
+{% block form_action %}{% url 'horizon:project:dns_domains:create_record' domain.id %}{% endblock %}
+
+{% block modal_id %}create_record_modal{% endblock %}
+{% block modal-header %}{% trans "Create Record for" %} {{ domain.name }}{% endblock %}
+
+{% block modal-body %}
+
+   <div id="scoped-content">
+   {% include 'project/dns_domains/prefix_field_style.html' %}
+     <fieldset>
+     {% include "horizon/common/_form_fields.html" %}
+     </fieldset>
+   </div>
+
+  {% blocktrans %}
+  <p>
+    <strong>TTL</strong>
+    The TTL is the time-to-live for the record, in seconds.
+  </p>
+  <p>
+    See <a href="http://en.wikipedia.org/wiki/List_of_DNS_record_types" target="_designate_record_defs">more info</a> on record types.
+  </p>
+  {% endblocktrans %}
+  <script type="text/javascript">
+    // Empty hidden form fields when the record type is switched
+    // https://bugs.launchpad.net/designate/+bug/1525199
+    $("select#id_type.form-control.switchable").eq(0).change(function() {
+      $(this).closest('fieldset')
+        .find("input[type=text], textarea").filter(":hidden")
+        .val("");
+    })
+  </script>
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_domain_detail.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_domain_detail.html
new file mode 100644
index 0000000..2643938
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_domain_detail.html
@@ -0,0 +1,32 @@
+{% load i18n sizeformat %}
+
+<h3>{% trans "Domain Overview" %}</h3>
+
+<div class="info detail">
+  <dl class="dl-horizontal">
+    <dt>{% trans "ID" %}</dt>
+    <dd>{{ domain.id|default:_("None") }}</dd>
+    <dt>{% trans "Name" %}</dt>
+    <dd>{{ domain.name|default:_("None") }}</dd>
+    <dt>{% trans "Description" %}</dt>
+    <dd>{{ domain.description|default:_("None") }}</dd>
+    <dt>{% trans "Serial" %}</dt>
+    <dd>{{ domain.serial|yesno|capfirst }}</dd>
+    <dt>{% trans "Email" %}</dt>
+    <dd>{{ domain.email|default:_("Unknown") }}</dd>
+    <dt>{% trans "TTL" %}</dt>
+    <dd>{{ domain.ttl|default:_("Unknown") }}</dd>
+    <dt>{% trans "Created" %}</dt>
+    {% if domain.created_at %}
+      <dd>{{ domain.created_at|parse_isotime }}</dd>
+    {% else %}
+      <dd>{% trans "Unknown" %}</dd>
+    {% endif %}
+    <dt>{% trans "Updated" %}</dt>
+    {% if domain.updated_at %}
+      <dd>{{ domain.updated_at|parse_isotime }}</dd>
+    {% else %}
+      <dd>{% trans "Unknown" %}</dd>
+    {% endif %}
+  </dl>
+</div>
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_record_detail.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_record_detail.html
new file mode 100644
index 0000000..eb86adc
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_record_detail.html
@@ -0,0 +1,36 @@
+{% load i18n sizeformat %}
+
+<h3><a href="{% url 'horizon:project:dns_domains:records' domain_id %}">{% trans "All Records" %}</a></h3>
+
+<h4>{{ record.name|default:_("None") }}</h4>
+
+<div class="info detail">
+  <dl class="dl-horizontal">
+    <dt>{% trans "Name" %}</dt>
+    <dd>{{ record.name|default:_("None") }}</dd>
+    <dt>{% trans "ID" %}</dt>
+    <dd>{{ record.id|default:_("None") }}</dd>
+    <dt>{% trans "Type" %}</dt>
+    <dd>{{ record.type|default:_("Unknown") }}</dd>
+    <dt>{% trans "Description" %}</dt>
+    <dd>{{ record.description|default:_("None") }}</dd>
+    <dt>{% trans "Record Data" %}</dt>
+    <dd>{{ record.data|default:_("None") }}</dd>
+    <dt>{% trans "Priority" %}</dt>
+    <dd>{{ record.priority|yesno|capfirst }}</dd>
+    <dt>{% trans "TTL" %}</dt>
+    <dd>{{ record.ttl|default:_("None") }}</dd>
+    <dt>{% trans "Created" %}</dt>
+    {% if record.created_at %}
+      <dd>{{ record.created_at|parse_isotime }}</dd>
+    {% else %}
+      <dd>{% trans "Unknown" %}</dd>
+    {% endif %}
+    <dt>{% trans "Updated" %}</dt>
+    {% if record.updated_at %}
+      <dd>{{ record.updated_at|parse_isotime }}</dd>
+    {% else %}
+      <dd>{% trans "Unknown" %}</dd>
+    {% endif %}
+  </dl>
+</div>
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_update_domain.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_update_domain.html
new file mode 100644
index 0000000..e8f6ba9
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_update_domain.html
@@ -0,0 +1,36 @@
+{% extends "horizon/common/_modal_form.html" %}
+{% load i18n %}
+{% load url from future %}
+
+{% block form_id %}update_domain_form{% endblock %}
+{% block form_action %}{% url 'horizon:project:dns_domains:update_domain' domain.id %}{% endblock %}
+
+{% block modal-header %}{% trans "Update Domain" %}{% endblock %}
+
+{% block modal-body %}
+  <div class="left">
+    <fieldset>
+    {% include "horizon/common/_form_fields.html" %}
+    </fieldset>
+  </div>
+
+  <div class="right">
+    <h3>{% trans "Description" %}:</h3>
+    <p>{% blocktrans %}
+      From here you can edit the email address and TTL associated with a domain.
+    {% endblocktrans %}</p>
+    <p>{% blocktrans %}
+      The Email field should contain a valid email address to be associated
+      with the domain.
+    {% endblocktrans %}</p>
+    <p>{% blocktrans %}
+      The optional TTL field can be any value between 1 and 2147483647
+      seconds.
+    {% endblocktrans %}</p>
+  </div>
+{% endblock %}
+
+{% block modal-footer %}
+  <input class="btn btn-primary pull-right" type="submit" value="{% trans "Update Domain" %}" />
+  <a href="{% url 'horizon:project:dns_domains:index' %}" class="btn secondary cancel close">{% trans "Cancel" %}</a>
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_update_record.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_update_record.html
new file mode 100644
index 0000000..4344f36
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/_update_record.html
@@ -0,0 +1,9 @@
+{% extends "project/dns_domains/_create_record.html" %}
+{% load i18n %}
+{% load url from future %}
+
+{% block form_id %}update_record_form{% endblock %}
+{% block form_action %}{% url 'horizon:project:dns_domains:update_record' record.domain_id record.id %}{% endblock %}
+
+{% block modal-header %}{% trans "Update Domain Record" %}{% endblock %}
+
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/create_domain.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/create_domain.html
new file mode 100644
index 0000000..fe6380c
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/create_domain.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Domain" %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Create Domain") %}
+{% endblock page_header %}
+
+{% block main %}
+    {% include 'project/dns_domains/_create_domain.html' %}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/create_record.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/create_record.html
new file mode 100644
index 0000000..4414f32
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/create_record.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Create Domain Record" %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Create Domain Record") %}
+{% endblock page_header %}
+
+{% block main %}
+    {% include 'project/dns_domains/_create_record.html' %}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/domain_detail.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/domain_detail.html
new file mode 100644
index 0000000..1137660
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/domain_detail.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans 'Domain Detail' %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Domain") %}
+{% endblock page_header %}
+
+{% block main %}
+    {% include 'project/dns_domains/_domain_detail.html' %}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/index.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/index.html
new file mode 100644
index 0000000..a7d629b
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/index.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans "Domains" %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Domains") %}
+{% endblock page_header %}
+
+{% block main %}
+  {{ table.render }}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/prefix_field_style.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/prefix_field_style.html
new file mode 100644
index 0000000..90078a1
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/prefix_field_style.html
@@ -0,0 +1,4 @@
+<style type = "text/css" scoped>
+ .form_field_suffix { float: right; padding-top:10px; }
+ .form_field_prefix { display: block; overflow: hidden; }
+</style>
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/prefix_html_widget.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/prefix_html_widget.html
new file mode 100644
index 0000000..78f1154
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/prefix_html_widget.html
@@ -0,0 +1,2 @@
+<label class="form_field_suffix">{{ suffix }}</label>
+<span class="form_field_prefix">{{ input }}</span>
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/record_detail.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/record_detail.html
new file mode 100644
index 0000000..bb94f9f
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/record_detail.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans 'Record Detail' %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title="Record Detail" %}
+{% endblock page_header %}
+
+{% block main %}
+    {% include 'project/dns_domains/_record_detail.html' %}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/records.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/records.html
new file mode 100644
index 0000000..b8de38c
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/records.html
@@ -0,0 +1,32 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans 'Domain Records' %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title=_("Domain Records") %}
+{% endblock page_header %}
+
+{% block main %}
+    <div class="sub-content grid-content">
+        <div class="page_title table_header">
+            <div>
+                <h3>
+                    <a href="{% url 'horizon:project:dns_domains:index' %}">{% trans "Domains" %}</a> : {{ domain.name }} &rarr;
+                    {% trans "Records" %}
+                </h3>
+            </div>
+            <div class="table_actions">
+                <a href="{% url 'horizon:project:dns_domains:index' %}" class="close">&times;</a>
+            </div>
+        </div>
+    <div class="nameservers_wrapper">
+      <h3>{% trans "Nameservers" %}</h3>
+      <ul>
+      {% for server in servers %}
+        <li>{{ server.name }}</li>
+      {% endfor %}
+      </ul>
+    </div>
+
+    {{ table.render }}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/update_domain.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/update_domain.html
new file mode 100644
index 0000000..e1a2752
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/update_domain.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans 'Update Domain' %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title="Domain" %}
+{% endblock page_header %}
+
+{% block main %}
+    {% include 'project/dns_domains/_update_domain.html' %}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/update_record.html b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/update_record.html
new file mode 100644
index 0000000..ca2b7a7
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/templates/dns_domains/update_record.html
@@ -0,0 +1,11 @@
+{% extends 'base.html' %}
+{% load i18n %}
+{% block title %}{% trans 'Update Domain Record' %}{% endblock %}
+
+{% block page_header %}
+  {% include "horizon/common/_page_header.html" with title="Domain Record" %}
+{% endblock page_header %}
+
+{% block main %}
+    {% include 'project/dns_domains/_update_record.html' %}
+{% endblock %}
diff --git a/designatedashboard/dashboards/project/dns_domains/urls.py b/designatedashboard/dashboards/project/dns_domains/urls.py
new file mode 100644
index 0000000..1a2231b
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/urls.py
@@ -0,0 +1,52 @@
+# 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 django.conf.urls import url, patterns  # noqa
+
+from .views import CreateDomainView  # noqa
+from .views import CreateRecordView  # noqa
+from .views import DomainDetailView  # noqa
+from .views import IndexView  # noqa
+from .views import RecordsView  # noqa
+from .views import UpdateDomainView  # noqa
+from .views import UpdateRecordView  # noqa
+from .views import ViewRecordDetailsView  # noqa
+
+
+urlpatterns = patterns(
+    '',
+    url(r'^$',
+        IndexView.as_view(),
+        name='index'),
+    url(r'^create/$',
+        CreateDomainView.as_view(),
+        name='create_domain'),
+    url(r'^(?P<domain_id>[^/]+)/update$',
+        UpdateDomainView.as_view(),
+        name='update_domain'),
+    url(r'^(?P<domain_id>[^/]+)$',
+        DomainDetailView.as_view(),
+        name='domain_detail'),
+    url(r'^(?P<domain_id>[^/]+)/records$',
+        RecordsView.as_view(),
+        name='records'),
+    url(r'^(?P<domain_id>[^/]+)/records/create$',
+        CreateRecordView.as_view(),
+        name='create_record'),
+    url(r'^(?P<domain_id>[^/]+)/records/(?P<record_id>[^/]+)/update$',
+        UpdateRecordView.as_view(),
+        name='update_record'),
+    url(r'^(?P<domain_id>[^/]+)/records/(?P<record_id>[^/]+)/$',
+        ViewRecordDetailsView.as_view(),
+        name='view_record'),
+)
diff --git a/designatedashboard/dashboards/project/dns_domains/utils.py b/designatedashboard/dashboards/project/dns_domains/utils.py
new file mode 100644
index 0000000..eaa74c7
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/utils.py
@@ -0,0 +1,20 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+from django.conf import settings
+
+
+def limit_records_to_fips():
+    # This method checks the settings to determine if the
+    # record creation / update screen should limit the ip input
+    # to be a dropdown of floating ips
+    return getattr(settings, "DESIGNATE",
+                   {}).get("records_use_fips", False)
diff --git a/designatedashboard/dashboards/project/dns_domains/views.py b/designatedashboard/dashboards/project/dns_domains/views.py
new file mode 100644
index 0000000..287bba2
--- /dev/null
+++ b/designatedashboard/dashboards/project/dns_domains/views.py
@@ -0,0 +1,243 @@
+# 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 django.core.urlresolvers import reverse, reverse_lazy  # noqa
+from django.utils.translation import ugettext_lazy as _  # noqa
+
+from horizon import exceptions
+from horizon import forms
+from horizon import tables
+from horizon.views import HorizonTemplateView   # noqa
+
+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
+from .forms import RecordCreate  # noqa
+from .forms import RecordUpdate  # noqa
+from .tables import DomainsTable  # noqa
+from .tables import RecordsTable  # noqa
+from .utils import limit_records_to_fips  # noqa
+
+
+class IndexView(tables.DataTableView):
+    table_class = DomainsTable
+    template_name = 'project/dns_domains/index.html'
+
+    def get_data(self):
+        try:
+            return api.designate.domain_list(self.request)
+        except Exception:
+            exceptions.handle(self.request,
+                              _('Unable to retrieve domain list.'))
+            return []
+
+
+class CreateDomainView(forms.ModalFormView):
+    form_class = DomainCreate
+    template_name = 'project/dns_domains/create_domain.html'
+    success_url = reverse_lazy('horizon:project:dns_domains:index')
+
+    def get_object_display(self, obj):
+        return obj.ip
+
+
+class DomainDetailView(HorizonTemplateView):
+    template_name = 'project/dns_domains/domain_detail.html'
+
+    def get_context_data(self, **kwargs):
+        context = super(DomainDetailView, self).get_context_data(**kwargs)
+        domain_id = self.kwargs['domain_id']
+        try:
+            context["domain"] = api.designate.domain_get(self.request,
+                                                         domain_id)
+            table = DomainsTable(self.request)
+            context["actions"] = table.render_row_actions(context["domain"])
+        except Exception:
+            redirect = reverse('horizon:project:dns_domains:index')
+            exceptions.handle(self.request,
+                              _('Unable to retrieve domain record.'),
+                              redirect=redirect)
+        return context
+
+
+class UpdateDomainView(forms.ModalFormView):
+    form_class = DomainUpdate
+    template_name = 'project/dns_domains/update_domain.html'
+    success_url = reverse_lazy('horizon:project:dns_domains:index')
+
+    def get_object(self):
+        domain_id = self.kwargs['domain_id']
+        try:
+            return api.designate.domain_get(self.request, domain_id)
+        except Exception:
+            redirect = reverse('horizon:project:dns_domains:index')
+            exceptions.handle(self.request,
+                              _('Unable to retrieve domain record.'),
+                              redirect=redirect)
+
+    def get_initial(self):
+        self.domain = self.get_object()
+        return self.domain
+
+    def get_context_data(self, **kwargs):
+        context = super(UpdateDomainView, self).get_context_data(**kwargs)
+        context["domain"] = self.domain
+        return context
+
+
+class RecordsView(tables.DataTableView):
+    table_class = RecordsTable
+    template_name = 'project/dns_domains/records.html'
+
+    def get_data(self):
+        domain_id = self.kwargs['domain_id']
+        records = []
+        try:
+            self.domain = api.designate.domain_get(self.request, domain_id)
+            self.servers = api.designate.server_list(self.request, domain_id)
+            records = api.designate.record_list(self.request, domain_id)
+        except Exception:
+            redirect = reverse('horizon:project:dns_domains:index')
+            exceptions.handle(self.request,
+                              _('Unable to retrieve record list.'),
+                              redirect=redirect)
+
+        return records
+
+    def get_context_data(self, **kwargs):
+        context = super(RecordsView, self).get_context_data(**kwargs)
+        context['domain'] = self.domain
+        context['servers'] = self.servers
+
+        return context
+
+
+class BaseRecordFormView(forms.ModalFormView):
+    cancel_label = _("Cancel")
+
+    def get_success_url(self):
+        return reverse('horizon:project:dns_domains:records',
+                       args=(self.kwargs['domain_id'],))
+
+    def get_domain(self):
+        domain_id = self.kwargs['domain_id']
+        try:
+            return api.designate.domain_get(self.request, domain_id)
+        except Exception:
+            redirect = reverse('horizon:project:dns_domains:records',
+                               args=(self.kwargs['domain_id'],))
+            exceptions.handle(self.request,
+                              ('Unable to retrieve domain record.'),
+                              redirect=redirect)
+            # NotAuthorized errors won't be redirected automatically. Need
+            # to force the issue
+            raise exceptions.Http302(redirect)
+
+    def get_initial(self):
+        self.domain = self.get_domain()
+        results = {'domain_id': self.domain.id,
+                   'domain_name': self.domain.name, }
+        if limit_records_to_fips():
+            results.update({'fips': tenant_floating_ip_list(self.request),
+                            'instances': server_list(self.request)[0]})
+        return results
+
+    def get_context_data(self, **kwargs):
+        """Set the cancel url
+
+        the cancel_url needs a variable in it
+        so we cannot do this with a simple class attr
+        this is critical to perform before the super.get_context_data
+        """
+        self.cancel_url = reverse('horizon:project:dns_domains:records',
+                                  args=(self.kwargs['domain_id'],))
+        context = super(BaseRecordFormView, self).get_context_data(**kwargs)
+        context['domain'] = self.domain
+        return context
+
+
+class CreateRecordView(BaseRecordFormView):
+    form_class = RecordCreate
+    submit_label = _("Create Record")
+    template_name = 'project/dns_domains/create_record.html'
+
+
+class ViewRecordDetailsView(HorizonTemplateView):
+    template_name = 'project/dns_domains/record_detail.html'
+
+    def get_record(self):
+        domain_id = self.kwargs['domain_id']
+        record_id = self.kwargs['record_id']
+        try:
+            return api.designate.record_get(self.request, domain_id, record_id)
+        except Exception:
+            redirect = reverse('horizon:project:dns_domains:records',
+                               args=(self.kwargs['domain_id'],))
+            exceptions.handle(self.request,
+                              _('Unable to retrieve domain record.'),
+                              redirect=redirect)
+
+    def get_context_data(self, **kwargs):
+        context = super(ViewRecordDetailsView, self).get_context_data(**kwargs)
+        self.record = self.get_record()
+        context["record"] = self.record
+        context["domain_id"] = self.kwargs['domain_id']
+        return context
+
+
+class UpdateRecordView(BaseRecordFormView):
+    form_class = RecordUpdate
+    submit_label = _("Update Record")
+    template_name = 'project/dns_domains/update_record.html'
+
+    def get_record(self):
+        domain_id = self.kwargs['domain_id']
+        record_id = self.kwargs['record_id']
+
+        try:
+            return api.designate.record_get(self.request, domain_id, record_id)
+        except Exception:
+            redirect = reverse('horizon:project:dns_domains:records',
+                               args=(self.kwargs['domain_id'],))
+            exceptions.handle(self.request,
+                              _('Unable to retrieve domain record.'),
+                              redirect=redirect)
+
+    def get_initial(self):
+        initial = super(UpdateRecordView, self).get_initial()
+        self.record = self.get_record()
+
+        initial.update({
+            'id': self.record.id,
+            'name': self.record.name.replace("." + initial['domain_name'], ''),
+            'data': self.record.data,
+            'txt': self.record.data,
+            'priority': self.record.priority,
+            'ttl': self.record.ttl,
+            'type': self.record.type.lower(),
+            'description': self.record.description,
+        })
+        if limit_records_to_fips():
+            initial.update({'ip_addr': self.record.data})
+
+        return initial
+
+    def get_context_data(self, **kwargs):
+        context = super(UpdateRecordView, self).get_context_data(**kwargs)
+        context["record"] = self.record
+        return context
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/_1710_project_dns_panel_group.py b/designatedashboard/enabled/_1710_project_dns_panel_group.py
new file mode 100644
index 0000000..4473e40
--- /dev/null
+++ b/designatedashboard/enabled/_1710_project_dns_panel_group.py
@@ -0,0 +1,20 @@
+# 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.
+
+# The name of the panel group to be added to HORIZON_CONFIG. Required.
+PANEL_GROUP = 'dns'
+# The display name of the PANEL_GROUP. Required.
+PANEL_GROUP_NAME = 'DNS'
+# The name of the dashboard the PANEL_GROUP associated with. Required.
+PANEL_GROUP_DASHBOARD = 'project'
diff --git a/designatedashboard/enabled/_1720_project_dns_panel.py b/designatedashboard/enabled/_1720_project_dns_panel.py
new file mode 100644
index 0000000..c1494d8
--- /dev/null
+++ b/designatedashboard/enabled/_1720_project_dns_panel.py
@@ -0,0 +1,36 @@
+# 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 = 'domains'
+# 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_INSTALLED_APPS = ['designatedashboard']
+
+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.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/enabled/__init__.py b/designatedashboard/enabled/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/designatedashboard/exceptions.py b/designatedashboard/exceptions.py
new file mode 100644
index 0000000..677fba6
--- /dev/null
+++ b/designatedashboard/exceptions.py
@@ -0,0 +1,29 @@
+# Copyright (c) 2014 Rackspace Hosting.
+#
+# 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 designateclient import exceptions as designateclient
+
+from openstack_dashboard import exceptions
+
+NOT_FOUND = exceptions.NOT_FOUND + (
+    designateclient.ResourceNotFound,
+    designateclient.NotFound,
+    )
+RECOVERABLE = exceptions.RECOVERABLE + (
+    designateclient.BadRequest,
+    designateclient.Conflict,
+    )
+UNAUTHORIZED = exceptions.UNAUTHORIZED + (
+    designateclient.Forbidden,
+    )
diff --git a/designatedashboard/locale/cs/LC_MESSAGES/django.mo b/designatedashboard/locale/cs/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..b49d51a
Binary files /dev/null and b/designatedashboard/locale/cs/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/cs/LC_MESSAGES/django.po b/designatedashboard/locale/cs/LC_MESSAGES/django.po
new file mode 100644
index 0000000..4204093
--- /dev/null
+++ b/designatedashboard/locale/cs/LC_MESSAGES/django.po
@@ -0,0 +1,348 @@
+# Stanislav Ulrych <stanislav.ulrych@ultimum.io>, 2016. #zanata
+# Zbyněk Schwarz <zbynek.schwarz@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 6.0.0.0b2.dev3\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-11-21 15:00+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-11-17 07:24+0000\n"
+"Last-Translator: Zbyněk Schwarz <zbynek.schwarz@gmail.com>\n"
+"Language-Team: Czech\n"
+"Language: cs\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"     Zde můžete upravit emailovou adresu a TTL patřící k doméně.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"  Pole Email by mělo obsahovat platnou emailovou adresu, která je spojená\n"
+"   s doménou.\n"
+"   "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"     Pole Název by mělo obsahovat plně kvalifikovaný název domény (s \n"
+"     tečkou na \n"
+"     konci)."
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"    TTL (volitelné) může být hodnota mezi 1 a 2147483647\n"
+"     sekund.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    TTL je time-to-live záznamu, v sekundách.\n"
+"  </p>\n"
+"  <p>\n"
+"    Viz <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">více informací</a> o typech záznamů.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - adresní záznam"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - adresní záznam IPv6"
+
+msgid "All Records"
+msgstr "Všechny záznamy"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - záznam kanonického jména"
+
+msgid "Cancel"
+msgstr "Zrušit"
+
+msgid "Canonical Name"
+msgstr "Kanonické jméno"
+
+msgid "Create Domain"
+msgstr "Vytvořit doménu"
+
+msgid "Create Domain Record"
+msgstr "Vytvořit doménový záznam"
+
+msgid "Create Record"
+msgstr "Vytvořit záznam"
+
+msgid "Create Record for"
+msgstr "Vytvořit záznam pro"
+
+msgid "Created"
+msgstr "Vytvořeno"
+
+msgid "Created At"
+msgstr "Vytvořeno"
+
+msgid "Data"
+msgstr "Data"
+
+msgid "Delete"
+msgstr "Smazat"
+
+msgid "Deleted"
+msgstr "Smazáno"
+
+msgid "Description"
+msgstr "Popis"
+
+msgid "Domain"
+msgstr "Doména"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Doména %(name)s vytvořena."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Doména %(name)s aktualizována."
+
+msgid "Domain Detail"
+msgstr "Detail domény"
+
+msgid "Domain Name"
+msgstr "Název domény"
+
+msgid "Domain Overview"
+msgstr "Přehled domén"
+
+msgid "Domain Records"
+msgstr "Doménové záznamy"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Doménový záznam %(name)s vytvořen."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Doménový záznam %(name)s aktualizován."
+
+msgid "Domains"
+msgstr "Domény"
+
+msgid "Edit Domain"
+msgstr "Upravit doménu"
+
+msgid "Edit Record"
+msgstr "Upravit záznam"
+
+msgid "Email"
+msgstr "E-mail"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Zadejte platnou IPv4 adresu"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Zadejte platnou IPv6 adresu"
+
+msgid "Enter a valid SRV name"
+msgstr "Zadejte platné SRV jméno"
+
+msgid "Enter a valid SRV record"
+msgstr "Zadejte platný SRV záznam"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Zadejte platný SSHFP záznam"
+
+msgid "Enter a valid domain name."
+msgstr "Zadejte platný název domény."
+
+msgid "Enter a valid hostname"
+msgstr "Zadejte platné jméno hostitele"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Zadejte platný název hostitele. Název hostitele by měl obsahovat písmena a "
+"číslice a musí být maximálně 63 znaků."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP adresa"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - mailový záznam"
+
+msgid "Mail Server"
+msgstr "Mailový server"
+
+msgid "Manage Records"
+msgstr "Spravovat záznamy"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Název"
+
+msgid "Name Server"
+msgstr "Název serveru"
+
+msgid "Nameservers"
+msgstr "Jmenné servery"
+
+msgid "None"
+msgstr "Žádný"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - záznam ukazatele"
+
+msgid "PTR Domain Name"
+msgstr "PTR doménové jméno"
+
+msgid "Priority"
+msgstr "Priorita"
+
+msgid "Record"
+msgstr "Záznam"
+
+msgid "Record Data"
+msgstr "Zaznamenat data"
+
+msgid "Record Detail"
+msgstr "Zaznamenat detail"
+
+msgid "Record Type"
+msgstr "Typ záznamu"
+
+msgid "Records"
+msgstr "Záznamy"
+
+msgid "Reverse DNS"
+msgstr "Reverzní DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - lokátor služby"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - Otisk veřejného klíče SSH"
+
+msgid "Select an IP"
+msgstr "Vyberte IP adresu"
+
+msgid "Serial"
+msgstr "Sériový"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (ve vteřinách)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - textový záznam"
+
+msgid "Text"
+msgstr "Text"
+
+msgid "The quotas could not be retrieved."
+msgstr "Nelze získat kvóty."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Žádné plovoucí IP nejsou k dispozici pro výběr."
+
+msgid "This field is required"
+msgstr "Toto pole je povinné"
+
+msgid "Type"
+msgstr "Typ"
+
+msgid "Unable to create domain."
+msgstr "Nelze vytvořit doménu."
+
+msgid "Unable to create record."
+msgstr "Nelze vytvořit záznam."
+
+msgid "Unable to retrieve domain list."
+msgstr "Nelze získat seznam domén."
+
+msgid "Unable to retrieve domain record."
+msgstr "Nelze získat doménový záznam."
+
+msgid "Unable to retrieve record list."
+msgstr "Nelze získat záznamy."
+
+msgid "Unable to update domain."
+msgstr "Nelze aktualizovat doménu."
+
+msgid "Unknown"
+msgstr "Neznámé"
+
+msgid "Unknown instance name"
+msgstr "Neznámé jméno instance"
+
+msgid "Update Domain"
+msgstr "Aktualizovat doménu"
+
+msgid "Update Domain Record"
+msgstr "Aktualizovat záznam domény"
+
+msgid "Update Record"
+msgstr "Aktualizovat záznam"
+
+msgid "Updated"
+msgstr "Aktualizováno"
+
+msgid "Updated At"
+msgstr "Aktualizováno"
+
+msgid "Value"
+msgstr "Hodnota"
+
+msgid "Zones"
+msgstr "Zóny"
diff --git a/designatedashboard/locale/de/LC_MESSAGES/django.mo b/designatedashboard/locale/de/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..0f7d07e
Binary files /dev/null and b/designatedashboard/locale/de/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/de/LC_MESSAGES/django.po b/designatedashboard/locale/de/LC_MESSAGES/django.po
new file mode 100644
index 0000000..5c42e43
--- /dev/null
+++ b/designatedashboard/locale/de/LC_MESSAGES/django.po
@@ -0,0 +1,351 @@
+# Frank Kloeker <eumel@arcor.de>, 2016. #zanata
+# Robert Simai <robert.simai@suse.com>, 2016. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 3.0.0.0rc2.dev5\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2016-09-29 13:19+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2016-10-13 04:32+0000\n"
+"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"X-Generator: Zanata 3.7.3\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Hier können Sie die E-Mail-Adresse und TTL mit dazugehöriger Domäne "
+"editieren.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Das E-Mail-Feld sollte eine gültige E-Mail-Adresse enthalten,\n"
+"  die der Domäne zugewiesen wird.\n"
+"   "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      Das Namensfeld sollte einen vollqualifizierten Domänennamen enthalten "
+"(mit\n"
+"      abschließendem Punkt).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      Das optionale TTL Feld kann einen Wert zwischen 1 und 2147483647\n"
+"      Sekunden haben.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    Die TTL ist die time-to-live für den Datensatz,  in Sekunden.\n"
+"  </p>\n"
+"  <p>\n"
+"    Hier finden Sie <a href=\"http://en.wikipedia.org/wiki/"
+"List_of_DNS_record_types\" target=\"_designate_record_defs\">mehr "
+"Informationen</a> zu Datensatztypen.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - Adress Datensatz"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 Adress Datensatz"
+
+msgid "All Records"
+msgstr "Alle Datensätze"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - Canonical Name Datensatz"
+
+msgid "Cancel"
+msgstr "Abbrechen"
+
+msgid "Canonical Name"
+msgstr "Canonical Name"
+
+msgid "Create Domain"
+msgstr "Domäne erstellen"
+
+msgid "Create Domain Record"
+msgstr "Erzeuge Datensatz für Domäne"
+
+msgid "Create Record"
+msgstr "Datensatz erzeugen"
+
+msgid "Create Record for"
+msgstr "Erzeuge Datensatz für"
+
+msgid "Created"
+msgstr "Erstellt"
+
+msgid "Created At"
+msgstr "Erstellt am"
+
+msgid "Data"
+msgstr "Daten"
+
+msgid "Delete"
+msgstr "Löschen"
+
+msgid "Deleted"
+msgstr "Gelöscht"
+
+msgid "Description"
+msgstr "Beschreibung"
+
+msgid "Domain"
+msgstr "Domäne"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Domäne %(name)s erstellt."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Domäne %(name)s wurde geändert."
+
+msgid "Domain Detail"
+msgstr "Domänen Details"
+
+msgid "Domain Name"
+msgstr "Domänenname"
+
+msgid "Domain Overview"
+msgstr "Domänenübersicht"
+
+msgid "Domain Records"
+msgstr "Domänendatensatz"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Domändatensatz %(name)s erstellt."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Domänendatensatz %(name)s aktualisiert."
+
+msgid "Domains"
+msgstr "Domänen"
+
+msgid "Edit Domain"
+msgstr "Domäne bearbeiten"
+
+msgid "Edit Record"
+msgstr "Datensatze bearbeiten"
+
+msgid "Email"
+msgstr "E-Mail"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Geben Sie eine gültige IPv4 Addresse ein"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Geben Sie eine gültige IPv6 Addresse ein"
+
+msgid "Enter a valid SRV name"
+msgstr "Geben Sie einen gültigen SRV Namen ein"
+
+msgid "Enter a valid SRV record"
+msgstr "Geben Sie einen gültigen SRV Datensatz ein"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Geben Sie einen gültigen SSHFP Datensatz ein"
+
+msgid "Enter a valid domain name."
+msgstr "Geben Sie einen gültigen Domänennamen ein."
+
+msgid "Enter a valid hostname"
+msgstr "Geben Sie einen gültigen Hostnamen ein"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Geben Sie einen gültien Hostnamen ein. Der Hostname darf nur Buchstaben und "
+"Nummern enthalten und darf nicht mehr als 63 Zeichen lang sein."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP-Adresse"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Mail Austausch Datensatz"
+
+msgid "Mail Server"
+msgstr "Mailserver"
+
+msgid "Manage Records"
+msgstr "Datensätze verwalten"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Name"
+
+msgid "Name Server"
+msgstr "Nameserver"
+
+msgid "Nameservers"
+msgstr "Namensserver"
+
+msgid "None"
+msgstr "Keine"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - Pointer Datensatz"
+
+msgid "PTR Domain Name"
+msgstr "PTR Domänenname"
+
+msgid "Priority"
+msgstr "Priorität"
+
+msgid "Record"
+msgstr "Datensatz"
+
+msgid "Record Data"
+msgstr "Datensatz"
+
+msgid "Record Detail"
+msgstr "Datensatzdetails"
+
+msgid "Record Type"
+msgstr "Datensatztyp"
+
+msgid "Records"
+msgstr "Datensätze"
+
+msgid "Reverse DNS"
+msgstr "Reverse DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Dienstezeiger"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH Fingerprint für öffentlichen Schlüssel"
+
+msgid "Select an IP"
+msgstr "Wähle eine IP"
+
+msgid "Serial"
+msgstr "Seriennummer"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (Sekunden)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Text Datensatz"
+
+msgid "Text"
+msgstr "Text"
+
+msgid "The quotas could not be retrieved."
+msgstr "Die Kontingente konnten nicht abgerufen werden."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Es stehen derzeit keine Floating IP-Adressen zur Auswahl."
+
+msgid "This field is required"
+msgstr "Dieses Feld ist erforderlich"
+
+msgid "Type"
+msgstr "Typ"
+
+msgid "Unable to create domain."
+msgstr "Die Domäne kann nicht erstellt werden."
+
+msgid "Unable to create record."
+msgstr "Datensatz konnte nicht erzeugt werden."
+
+msgid "Unable to retrieve domain list."
+msgstr "Domänen-Liste kann nicht abgerufen werden."
+
+msgid "Unable to retrieve domain record."
+msgstr "Domänendatensatz kann nicht abgerufen werden."
+
+msgid "Unable to retrieve record list."
+msgstr "Die Datensatzliste kann nicht abgerufen werden."
+
+msgid "Unable to update domain."
+msgstr "Die Domäne kann nicht geändert werden."
+
+msgid "Unknown"
+msgstr "Unbekannt"
+
+msgid "Unknown instance name"
+msgstr "Unbekannter Instanzname"
+
+msgid "Update Domain"
+msgstr "Domain ändern"
+
+msgid "Update Domain Record"
+msgstr "Domänendatensatz aktualisieren"
+
+msgid "Update Record"
+msgstr "Datensatz aktualisieren"
+
+msgid "Updated"
+msgstr "Aktualisiert"
+
+msgid "Updated At"
+msgstr "Aktualisiert am"
+
+msgid "Value"
+msgstr "Wert"
+
+msgid "Zones"
+msgstr "Zonen"
diff --git a/designatedashboard/locale/en_GB/LC_MESSAGES/django.mo b/designatedashboard/locale/en_GB/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..5d86846
Binary files /dev/null and b/designatedashboard/locale/en_GB/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/en_GB/LC_MESSAGES/django.po b/designatedashboard/locale/en_GB/LC_MESSAGES/django.po
new file mode 100644
index 0000000..22fa28a
--- /dev/null
+++ b/designatedashboard/locale/en_GB/LC_MESSAGES/django.po
@@ -0,0 +1,351 @@
+# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
+# Rob Cresswell <robert.cresswell@outlook.com>, 2015. #zanata
+# Andi Chandler <andi@gowling.com>, 2016. #zanata
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 3.0.0.0rc2.dev6\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2016-10-14 10:51+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2016-10-23 09:03+0000\n"
+"Last-Translator: Andreas Jaeger <jaegerandi@gmail.com>\n"
+"Language-Team: English (United Kingdom)\n"
+"Language: en-GB\n"
+"X-Generator: Zanata 3.7.3\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - Address record"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 address record"
+
+msgid "All Records"
+msgstr "All Records"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - Canonical name record"
+
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Canonical Name"
+msgstr "Canonical Name"
+
+msgid "Create Domain"
+msgstr "Create Domain"
+
+msgid "Create Domain Record"
+msgstr "Create Domain Record"
+
+msgid "Create Record"
+msgstr "Create Record"
+
+msgid "Create Record for"
+msgstr "Create Record for"
+
+msgid "Created"
+msgstr "Created"
+
+msgid "Created At"
+msgstr "Created At"
+
+msgid "Data"
+msgstr "Data"
+
+msgid "Delete"
+msgstr "Delete"
+
+msgid "Deleted"
+msgstr "Deleted"
+
+msgid "Description"
+msgstr "Description"
+
+msgid "Domain"
+msgstr "Domain"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Domain %(name)s created."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Domain %(name)s updated."
+
+msgid "Domain Detail"
+msgstr "Domain Detail"
+
+msgid "Domain Name"
+msgstr "Domain Name"
+
+msgid "Domain Overview"
+msgstr "Domain Overview"
+
+msgid "Domain Records"
+msgstr "Domain Records"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Domain record %(name)s created."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Domain record %(name)s updated."
+
+msgid "Domains"
+msgstr "Domains"
+
+msgid "Edit Domain"
+msgstr "Edit Domain"
+
+msgid "Edit Record"
+msgstr "Edit Record"
+
+msgid "Email"
+msgstr "Email"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Enter a valid IPv4 address"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Enter a valid IPv6 address"
+
+msgid "Enter a valid SRV name"
+msgstr "Enter a valid SRV name"
+
+msgid "Enter a valid SRV record"
+msgstr "Enter a valid SRV record"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Enter a valid SSHFP record"
+
+msgid "Enter a valid domain name."
+msgstr "Enter a valid domain name."
+
+msgid "Enter a valid hostname"
+msgstr "Enter a valid hostname"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP Address"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Mail exchange record"
+
+msgid "Mail Server"
+msgstr "Mail Server"
+
+msgid "Manage Records"
+msgstr "Manage Records"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Name"
+
+msgid "Name Server"
+msgstr "Name Server"
+
+msgid "Nameservers"
+msgstr "Nameservers"
+
+msgid "None"
+msgstr "None"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - Pointer record"
+
+msgid "PTR Domain Name"
+msgstr "PTR Domain Name"
+
+msgid "Priority"
+msgstr "Priority"
+
+msgid "Record"
+msgstr "Record"
+
+msgid "Record Data"
+msgstr "Record Data"
+
+msgid "Record Detail"
+msgstr "Record Detail"
+
+msgid "Record Type"
+msgstr "Record Type"
+
+msgid "Records"
+msgstr "Records"
+
+msgid "Reverse DNS"
+msgstr "Reverse DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Service locator"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH Public Key Fingerprint"
+
+msgid "Select an IP"
+msgstr "Select an IP"
+
+msgid "Serial"
+msgstr "Serial"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (seconds)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Text record"
+
+msgid "Text"
+msgstr "Text"
+
+msgid "The quotas could not be retrieved."
+msgstr "The quotas could not be retrieved."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "There are no floating IP addresses currently in use to select from."
+
+msgid "This field is required"
+msgstr "This field is required"
+
+msgid "Type"
+msgstr "Type"
+
+msgid "Unable to create domain."
+msgstr "Unable to create domain."
+
+msgid "Unable to create record."
+msgstr "Unable to create record."
+
+msgid "Unable to retrieve domain list."
+msgstr "Unable to retrieve domain list."
+
+msgid "Unable to retrieve domain record."
+msgstr "Unable to retrieve domain record."
+
+msgid "Unable to retrieve record list."
+msgstr "Unable to retrieve record list."
+
+msgid "Unable to update domain."
+msgstr "Unable to update domain."
+
+msgid "Unknown"
+msgstr "Unknown"
+
+msgid "Unknown instance name"
+msgstr "Unknown instance name"
+
+msgid "Update Domain"
+msgstr "Update Domain"
+
+msgid "Update Domain Record"
+msgstr "Update Domain Record"
+
+msgid "Update Record"
+msgstr "Update Record"
+
+msgid "Updated"
+msgstr "Updated"
+
+msgid "Updated At"
+msgstr "Updated At"
+
+msgid "Value"
+msgstr "Value"
+
+msgid "Zones"
+msgstr "Zones"
diff --git a/designatedashboard/locale/es/LC_MESSAGES/django.mo b/designatedashboard/locale/es/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..a920e4e
Binary files /dev/null and b/designatedashboard/locale/es/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/es/LC_MESSAGES/django.po b/designatedashboard/locale/es/LC_MESSAGES/django.po
new file mode 100644
index 0000000..d647e74
--- /dev/null
+++ b/designatedashboard/locale/es/LC_MESSAGES/django.po
@@ -0,0 +1,342 @@
+# Marian Tort <marian.tort@gmail.com>, 2015. #zanata
+# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Eugènia Torrella <tester03@es.ibm.com>, 2016. #zanata
+# Alberto Molina Coballes <alb.molina@gmail.com>, 2017. #zanata
+# Zeus Arias Lucero <zeusariaslucero@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 5.0.0.0rc2.dev4\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-08-23 14:30+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-08-24 09:39+0000\n"
+"Last-Translator: Zeus Arias Lucero <zeusariaslucero@gmail.com>\n"
+"Language-Team: Spanish\n"
+"Language: es\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Desde aquí puede editar la dirección de correo y el TTL asociado a un "
+"dominio.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      El campo nombre debe contener un full-qualified domain name (con \n"
+"      punto final).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      El campo opcional TTL puede tener un valor entre 1 y 2147483647\n"
+"      segundos.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    El  TTL es el tiempo de vida del registro en segundos.\n"
+"  </p>\n"
+"  <p>\n"
+"    Vea <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">más info</a> sobre tipos de registros.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - Registro de dirección IPv4"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - Registro de dirección IPv6"
+
+msgid "All Records"
+msgstr "Todos los registros"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - Registro de nombre canónico"
+
+msgid "Cancel"
+msgstr "Cancelar "
+
+msgid "Canonical Name"
+msgstr "Nombre canónico"
+
+msgid "Create Domain"
+msgstr "Crear dominio"
+
+msgid "Create Domain Record"
+msgstr "Crear registro del dominio"
+
+msgid "Create Record"
+msgstr "Crear registro"
+
+msgid "Create Record for"
+msgstr "Crear registro para"
+
+msgid "Created"
+msgstr "Creado"
+
+msgid "Created At"
+msgstr "Creado el"
+
+msgid "Data"
+msgstr "Datos"
+
+msgid "Delete"
+msgstr "Eliminar"
+
+msgid "Deleted"
+msgstr "Eliminado"
+
+msgid "Description"
+msgstr "Descripción"
+
+msgid "Domain"
+msgstr "Dominio"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Dominio %(name)s creado."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Dominio %(name)s actualizado."
+
+msgid "Domain Detail"
+msgstr "Detalles del dominio"
+
+msgid "Domain Name"
+msgstr "Nombre de dominio"
+
+msgid "Domain Overview"
+msgstr "Visión general del dominio"
+
+msgid "Domain Records"
+msgstr "Registros del dominio"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Registro de dominio %(name)s creado"
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Registro de dominio %(name)s actualizado"
+
+msgid "Domains"
+msgstr "Dominios"
+
+msgid "Edit Domain"
+msgstr "Editar dominio"
+
+msgid "Edit Record"
+msgstr "Editar registro"
+
+msgid "Email"
+msgstr "Correo electrónico"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Introduzca una dirección IPv4 válida"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Introduzca una dirección IPv6 válida"
+
+msgid "Enter a valid SRV name"
+msgstr "Introduzca un nombre SRV válido"
+
+msgid "Enter a valid SRV record"
+msgstr "Introduzca un registro SRV válido"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Introduzca un registro SSHFP válido"
+
+msgid "Enter a valid domain name."
+msgstr "Introduzca un nombre de dominio válido."
+
+msgid "Enter a valid hostname"
+msgstr "Introduzca un hostname válido"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Introduzca un hostname válido. El hostname puede incluir letras, números y "
+"no tener más de 63 caracteres."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "Dirección IP"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Registro de Mail exchange"
+
+msgid "Mail Server"
+msgstr "Servidor de correo"
+
+msgid "Manage Records"
+msgstr "Gestionar registros"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Nombre"
+
+msgid "Name Server"
+msgstr "Servidor de nombres"
+
+msgid "Nameservers"
+msgstr "Servidores de nombres"
+
+msgid "None"
+msgstr "Ninguno"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - Registro de puntero"
+
+msgid "PTR Domain Name"
+msgstr "Nombre de dominio PTR"
+
+msgid "Priority"
+msgstr "Prioridad"
+
+msgid "Record"
+msgstr "Registro"
+
+msgid "Record Data"
+msgstr "Registro"
+
+msgid "Record Detail"
+msgstr "Detalles del registro"
+
+msgid "Record Type"
+msgstr "Tipo de registro"
+
+msgid "Records"
+msgstr "Registros"
+
+msgid "Reverse DNS"
+msgstr "DNS inverso"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Servicio locator"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - Huella digital de clave SSH pública"
+
+msgid "Select an IP"
+msgstr "Seleccione una IP"
+
+msgid "Serial"
+msgstr "Serial"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (segundos)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Registro de texto"
+
+msgid "Text"
+msgstr "Texto"
+
+msgid "The quotas could not be retrieved."
+msgstr "No ha sido posible obtener las cuotas."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Actualmente no hay direcciones IP flotantes en uso para seleccionar."
+
+msgid "This field is required"
+msgstr "Este campo es obligatorio"
+
+msgid "Type"
+msgstr "Tipo"
+
+msgid "Unable to create domain."
+msgstr "No ha sido posible crear el dominio."
+
+msgid "Unable to create record."
+msgstr "No ha sido posible crear el registro."
+
+msgid "Unable to retrieve domain list."
+msgstr "No ha sido posible obtener la lista de dominios."
+
+msgid "Unable to retrieve domain record."
+msgstr "No ha sido posible obtener el registro del dominio."
+
+msgid "Unable to retrieve record list."
+msgstr "No ha sido posible obtener la lista de registros."
+
+msgid "Unable to update domain."
+msgstr "No ha sido posible actualizar el dominio."
+
+msgid "Unknown"
+msgstr "Desconocido"
+
+msgid "Unknown instance name"
+msgstr "Nombre de instancia desconicodo"
+
+msgid "Update Domain"
+msgstr "Actualizar dominio"
+
+msgid "Update Domain Record"
+msgstr "Actualizar registro del dominio"
+
+msgid "Update Record"
+msgstr "Actualizar registro"
+
+msgid "Updated"
+msgstr "Actualizada"
+
+msgid "Updated At"
+msgstr "Actualizado el"
+
+msgid "Value"
+msgstr "Valor"
+
+msgid "Zones"
+msgstr "Zonas"
diff --git a/designatedashboard/locale/fr/LC_MESSAGES/django.mo b/designatedashboard/locale/fr/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..95b355d
Binary files /dev/null and b/designatedashboard/locale/fr/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/fr/LC_MESSAGES/django.po b/designatedashboard/locale/fr/LC_MESSAGES/django.po
new file mode 100644
index 0000000..95efa45
--- /dev/null
+++ b/designatedashboard/locale/fr/LC_MESSAGES/django.po
@@ -0,0 +1,362 @@
+# Translations template for designate-dashboard.
+# Copyright (C) 2015 ORGANIZATION
+# This file is distributed under the same license as the designate-dashboard
+# project.
+#
+# Translators:
+# Lucas Mascaro <mascaro.lucas@yahoo.fr>, 2015
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Gael Rehault <gael_rehault@dell.com>, 2016. #zanata
+# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
+# Gaelle <pattedeph@gmail.com>, 2017. #zanata
+# JF Taltavull <jftalta@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 6.0.0.0b2.dev3\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-11-21 15:00+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-11-14 07:52+0000\n"
+"Last-Translator: Gaelle <pattedeph@gmail.com>\n"
+"Language: fr\n"
+"Plural-Forms: nplurals=2; plural=(n > 1);\n"
+"Generated-By: Babel 2.0\n"
+"X-Generator: Zanata 3.9.6\n"
+"Language-Team: French\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Ici vous pouvez éditer l'adresse email e t le TTL associés à un "
+"domaine.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Le champ Email doit contenir un email valide qui sera associé \n"
+"      avec le domaine.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      Le champ Nom doit contenir un nom de domaine (FQDN avec le\n"
+"      point \".\" de terminaison).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      La valeur du champ optionnel TTL doit être comprise entre 1 et "
+"2147483647\n"
+"      secondes.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    Le TTL est le time-to-live de l'enregistrement, en secondes.\n"
+"  </p>\n"
+"  <p>\n"
+"    Voir <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">plus de détails</a> sur les types "
+"d'enregistrement.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - Enregistrement d'Adresse"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - Enregistrement d'Adresse IPv6"
+
+msgid "All Records"
+msgstr "Tous les enregistrements"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - Enregistrement de Nom Canonique"
+
+msgid "Cancel"
+msgstr "Annuler"
+
+msgid "Canonical Name"
+msgstr "Nom canonique"
+
+msgid "Create Domain"
+msgstr "Créer un domaine"
+
+msgid "Create Domain Record"
+msgstr "Créer un enregistrement de domaine"
+
+msgid "Create Record"
+msgstr "Créer un enregistrement"
+
+msgid "Create Record for"
+msgstr "Créer un enregistrement pour"
+
+msgid "Created"
+msgstr "Créé"
+
+msgid "Created At"
+msgstr "Créé le"
+
+msgid "Data"
+msgstr "Données"
+
+msgid "Delete"
+msgstr "Supprimer"
+
+msgid "Deleted"
+msgstr "Supprimé"
+
+msgid "Description"
+msgstr "Description"
+
+msgid "Domain"
+msgstr "Domaine"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Domaine %(name)s créé."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Domaine %(name)s mis à jour."
+
+msgid "Domain Detail"
+msgstr "Détail du domaine"
+
+msgid "Domain Name"
+msgstr "Nom de domaine"
+
+msgid "Domain Overview"
+msgstr "Vue d'ensemble du domaine"
+
+msgid "Domain Records"
+msgstr "Enregistrements de domaine"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Enregistrement Domaine %(name)s créé"
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Enregistrement Domaine %(name)s mis à jour."
+
+msgid "Domains"
+msgstr "Domaines"
+
+msgid "Edit Domain"
+msgstr "Editer le domaine"
+
+msgid "Edit Record"
+msgstr "Editer l'enregistrement"
+
+msgid "Email"
+msgstr "Courriel"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Entrer une adresse IPv4 valide"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Entrer une adresse IPv6 valide"
+
+msgid "Enter a valid SRV name"
+msgstr "Entrer un nom SRV valide"
+
+msgid "Enter a valid SRV record"
+msgstr "Entrer un enregistrement SRV valide"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Entrer un enregistrement SSHFP valide"
+
+msgid "Enter a valid domain name."
+msgstr "Entrer un nom de domaine valide."
+
+msgid "Enter a valid hostname"
+msgstr "Entrer un nom d'hôte valide"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Entrer un nom d'hôte valide. Le nom d'hôte ne doit contenir que des lettres "
+"et des chiffres, et ne doit pas dépasser 63 caractères."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "Adresse IP"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Enregistrement de Serveur de messagerie"
+
+msgid "Mail Server"
+msgstr "Serveur de messagerie"
+
+msgid "Manage Records"
+msgstr "Gérer les enregistrements"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Nom"
+
+msgid "Name Server"
+msgstr "Nom de serveur"
+
+msgid "Nameservers"
+msgstr "Serveurs de nom"
+
+msgid "None"
+msgstr "Aucun"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - Enregistrement de Pointeur"
+
+msgid "PTR Domain Name"
+msgstr "Nom de domaine PTR"
+
+msgid "Priority"
+msgstr "Priorité"
+
+msgid "Record"
+msgstr "Enregistrement"
+
+msgid "Record Data"
+msgstr "Enregistrer les données"
+
+msgid "Record Detail"
+msgstr "Détail de l'enregistrement"
+
+msgid "Record Type"
+msgstr "Type d'enregistrement"
+
+msgid "Records"
+msgstr "Enregistrements"
+
+msgid "Reverse DNS"
+msgstr "Reverse DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Politique d’émission"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Enregistrement de Service"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - Somme de contrôle de la clé SSH publique"
+
+msgid "Select an IP"
+msgstr "Sélectionner une IP"
+
+msgid "Serial"
+msgstr "Numéro de série"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (secondes)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Enregistrement Texte"
+
+msgid "Text"
+msgstr "Texte"
+
+msgid "The quotas could not be retrieved."
+msgstr "Les quotas n'ont pas pu être récupérés."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Aucune adresse IP flottante en cours d'utilisation à sélectionner."
+
+msgid "This field is required"
+msgstr "Ce champ est requis"
+
+msgid "Type"
+msgstr "Type"
+
+msgid "Unable to create domain."
+msgstr "Impossible de créer le domaine."
+
+msgid "Unable to create record."
+msgstr "Impossible de créer un enregistrement"
+
+msgid "Unable to retrieve domain list."
+msgstr "Impossible de récupérer la liste des domaines."
+
+msgid "Unable to retrieve domain record."
+msgstr "Impossible de récupérer l'enregistrement de domaine."
+
+msgid "Unable to retrieve record list."
+msgstr "Impossible de récupérer la liste des enregistrements."
+
+msgid "Unable to update domain."
+msgstr "Impossible de mettre à jour le domaine."
+
+msgid "Unknown"
+msgstr "Inconnu"
+
+msgid "Unknown instance name"
+msgstr "Nom d'instance inconnu"
+
+msgid "Update Domain"
+msgstr "Mettre à jour le domaine"
+
+msgid "Update Domain Record"
+msgstr "Mettre à jour  l'enregistrement de domaine"
+
+msgid "Update Record"
+msgstr "Mettre à jour l'enregistrement"
+
+msgid "Updated"
+msgstr "Mis à jour"
+
+msgid "Updated At"
+msgstr "Mis à jour le"
+
+msgid "Value"
+msgstr "Valeur"
+
+msgid "Zones"
+msgstr "Zones"
diff --git a/designatedashboard/locale/id/LC_MESSAGES/django.mo b/designatedashboard/locale/id/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..46ae670
Binary files /dev/null and b/designatedashboard/locale/id/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/id/LC_MESSAGES/django.po b/designatedashboard/locale/id/LC_MESSAGES/django.po
new file mode 100644
index 0000000..25ba70e
--- /dev/null
+++ b/designatedashboard/locale/id/LC_MESSAGES/django.po
@@ -0,0 +1,350 @@
+# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# suhartono <cloudsuhartono@gmail.com>, 2016. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 3.0.0.0rc2.dev5\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2016-09-29 13:19+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2016-10-13 04:32+0000\n"
+"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
+"Language-Team: Indonesian\n"
+"Language: id\n"
+"X-Generator: Zanata 3.7.3\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Dari sini Anda dapat mengedit alamat email dan TTL terkait dengan "
+"domain.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"Kolom email harus berisi alamat email yang valid terkait\n"
+"       dengan domain.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      Kolom TTL opsional dapat berupa nilai antara 1 dan 2147483647\n"
+"      detik.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    TTL adalah time-to-live untuk rekor, dalam hitungan detik.\n"
+"  </p>\n"
+"  <p>\n"
+"    Lihat <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> pada tipe rekor.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - Address record"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 address record"
+
+msgid "All Records"
+msgstr "All Records (semua rekor)"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - Canonical name record"
+
+msgid "Cancel"
+msgstr "Batal"
+
+msgid "Canonical Name"
+msgstr "Canonical Name (nama canonical)"
+
+msgid "Create Domain"
+msgstr "Membuat Domain"
+
+msgid "Create Domain Record"
+msgstr "Create Domain Record (buat rekor domain)"
+
+msgid "Create Record"
+msgstr "Buat rekor"
+
+msgid "Create Record for"
+msgstr "Buat rekor"
+
+msgid "Created"
+msgstr "Created (dibuat)"
+
+msgid "Created At"
+msgstr "Created At (dibuat pada)"
+
+msgid "Data"
+msgstr "Data"
+
+msgid "Delete"
+msgstr "Hapus"
+
+msgid "Deleted"
+msgstr "Terhapus"
+
+msgid "Description"
+msgstr "Deskripsi"
+
+msgid "Domain"
+msgstr "Domain"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Domain %(name)s dibuat."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Domain %(name)s diperbaharui."
+
+msgid "Domain Detail"
+msgstr "Domain Detail (rincian domain)"
+
+msgid "Domain Name"
+msgstr "Nama domain"
+
+msgid "Domain Overview"
+msgstr "Domain Overview (ikhtisar domain)"
+
+msgid "Domain Records"
+msgstr "Domain Records (rekor domain)"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Rekor domain %(name)s dibuat."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Rekor domain %(name)s diperbaharui"
+
+msgid "Domains"
+msgstr "Domain"
+
+msgid "Edit Domain"
+msgstr "Mengedit domain"
+
+msgid "Edit Record"
+msgstr "Mengedit recor"
+
+msgid "Email"
+msgstr "Surat elektronik"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Masukkan alamat IPv4 yang valid"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Masukkan alamat IPv6 yang valid"
+
+msgid "Enter a valid SRV name"
+msgstr "Masukkan nama SRV valid"
+
+msgid "Enter a valid SRV record"
+msgstr "Masukkan rekor SRV valid"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Masukkan rekor SSHFP valid"
+
+msgid "Enter a valid domain name."
+msgstr "Masukkan nama domain yang valid."
+
+msgid "Enter a valid hostname"
+msgstr "Masukkan hostname yang valid"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Masukkan hostname yang valid. Hostname harus berisi huruf dan angka, dan "
+"tidak lebih dari 63 karakter."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP Address (alamat IP)"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Mail exchange record"
+
+msgid "Mail Server"
+msgstr "Mail Server (server mail)"
+
+msgid "Manage Records"
+msgstr "Mengelola rekor"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Name (nama)"
+
+msgid "Name Server"
+msgstr "Name Server (server nama)"
+
+msgid "Nameservers"
+msgstr "Nameservers"
+
+msgid "None"
+msgstr "None (tak satupun)"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - Pointer record"
+
+msgid "PTR Domain Name"
+msgstr "PTR Domain Name (nama domain PTR)"
+
+msgid "Priority"
+msgstr "Priority (prioritas)"
+
+msgid "Record"
+msgstr "Record (rekor)"
+
+msgid "Record Data"
+msgstr "Record Data (data rekam)"
+
+msgid "Record Detail"
+msgstr "Record Detail (rincian rekor)"
+
+msgid "Record Type"
+msgstr "Record Type (tipe rekam)"
+
+msgid "Records"
+msgstr "Records (rekor)"
+
+msgid "Reverse DNS"
+msgstr "Reverse DNS "
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Service locator"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH Public Key Fingerprint"
+
+msgid "Select an IP"
+msgstr "Pilih IP"
+
+msgid "Serial"
+msgstr "Serial (serial)"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (detik)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Text record"
+
+msgid "Text"
+msgstr "Text"
+
+msgid "The quotas could not be retrieved."
+msgstr "Kuota tidak dapat diambil."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Tidak ada alamat IP mengambang saat ini yang digunakan untuk memilih."
+
+msgid "This field is required"
+msgstr "Bagian ini diperlukan"
+
+msgid "Type"
+msgstr "Tipe"
+
+msgid "Unable to create domain."
+msgstr "Tidak dapat membuat domain."
+
+msgid "Unable to create record."
+msgstr "Tidak dapat membuat rekor."
+
+msgid "Unable to retrieve domain list."
+msgstr "Tidak dapat mengambil daftar domain."
+
+msgid "Unable to retrieve domain record."
+msgstr "Tidak dapat mengambil rekor domain."
+
+msgid "Unable to retrieve record list."
+msgstr "Tidak dapat mengambil daftar rekor."
+
+msgid "Unable to update domain."
+msgstr "Tidak dapat memperbarui domain."
+
+msgid "Unknown"
+msgstr "Unknown (tidak diketahui)"
+
+msgid "Unknown instance name"
+msgstr " nama instance tidak diketahui"
+
+msgid "Update Domain"
+msgstr "Update Domain (pembaharui domain)"
+
+msgid "Update Domain Record"
+msgstr "Update Domain Record (pembaharuan rekor domain)"
+
+msgid "Update Record"
+msgstr "Pembaruan rekor"
+
+msgid "Updated"
+msgstr "Updated (sudah di perbaharui)"
+
+msgid "Updated At"
+msgstr "Diperbarui pada"
+
+msgid "Value"
+msgstr "Value (nilai)"
+
+msgid "Zones"
+msgstr "Zones (zona)"
diff --git a/designatedashboard/locale/ja/LC_MESSAGES/django.mo b/designatedashboard/locale/ja/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..1681c82
Binary files /dev/null and b/designatedashboard/locale/ja/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/ja/LC_MESSAGES/django.po b/designatedashboard/locale/ja/LC_MESSAGES/django.po
new file mode 100644
index 0000000..3c9f714
--- /dev/null
+++ b/designatedashboard/locale/ja/LC_MESSAGES/django.po
@@ -0,0 +1,347 @@
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Mie Yamamoto <myamamot@redhat.com>, 2016. #zanata
+# Yoshiki Eguchi <yoshiki.eguchi@gmail.com>, 2016. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 3.0.0.0rc2.dev5\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2016-09-29 13:19+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2016-10-13 04:32+0000\n"
+"Last-Translator: Akihiro Motoki <amotoki@gmail.com>\n"
+"Language-Team: Japanese\n"
+"Language: ja\n"
+"X-Generator: Zanata 3.7.3\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      ここでドメインに関連付けられたメールアドレスと TTL を編集できます。\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      メールフィールドには、ドメインに関連付ける\n"
+"      有効なメールアドレスを指定する必要があります。\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      名前フィールドには、\n"
+"      (末尾がピリオドの) 完全修飾ドメイン名を指定する必要があります。\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      オプションの TTL フィールドは\n"
+"      1 秒から 2147483647 秒までの任意の値にできます。\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    TTL はこのレコードの生存時間 (time-to-live) で、単位は秒です。\n"
+"  </p>\n"
+"  <p>\n"
+"    レコードタイプの詳細は<a href=\"http://en.wikipedia.org/wiki/"
+"List_of_DNS_record_types\" target=\"_designate_record_defs\">こちら</a>を参照"
+"してください。\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - アドレスレコード"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 アドレスレコード"
+
+msgid "All Records"
+msgstr "全レコード"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - 別名レコード"
+
+msgid "Cancel"
+msgstr "取り消し"
+
+msgid "Canonical Name"
+msgstr "正規名"
+
+msgid "Create Domain"
+msgstr "ドメインの作成"
+
+msgid "Create Domain Record"
+msgstr "ドメインレコードの作成"
+
+msgid "Create Record"
+msgstr "レコードの作成"
+
+msgid "Create Record for"
+msgstr "レコードの作成: ドメイン"
+
+msgid "Created"
+msgstr "作成時刻"
+
+msgid "Created At"
+msgstr "作成時刻"
+
+msgid "Data"
+msgstr "データ"
+
+msgid "Delete"
+msgstr "削除"
+
+msgid "Deleted"
+msgstr "削除"
+
+msgid "Description"
+msgstr "説明"
+
+msgid "Domain"
+msgstr "ドメイン"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "ドメイン %(name)s が作成されました。"
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "ドメイン %(name)s が更新されました。"
+
+msgid "Domain Detail"
+msgstr "ドメインの詳細"
+
+msgid "Domain Name"
+msgstr "ドメイン名"
+
+msgid "Domain Overview"
+msgstr "ドメインの概要"
+
+msgid "Domain Records"
+msgstr "ドメインレコード"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "ドメインレコード %(name)s が作成されました。"
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "ドメインレコード %(name)s が更新されました。"
+
+msgid "Domains"
+msgstr "ドメイン"
+
+msgid "Edit Domain"
+msgstr "ドメインの編集"
+
+msgid "Edit Record"
+msgstr "レコードの編集"
+
+msgid "Email"
+msgstr "メール"
+
+msgid "Enter a valid IPv4 address"
+msgstr "有効な IPv4 アドレスを入力してください。"
+
+msgid "Enter a valid IPv6 address"
+msgstr "有効な IPv6 アドレスを入力してください。"
+
+msgid "Enter a valid SRV name"
+msgstr "有効な SRV 名を入力してください。"
+
+msgid "Enter a valid SRV record"
+msgstr "有効な SRV レコードを入力してください。"
+
+msgid "Enter a valid SSHFP record"
+msgstr "有効な SSHFP レコードを入力してください。"
+
+msgid "Enter a valid domain name."
+msgstr "有効なドメイン名を入力してください。"
+
+msgid "Enter a valid hostname"
+msgstr "有効なホスト名を入力してください。"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"有効なホスト名を入力してください。ホスト名は、文字と数字で 63 文字以内に設定"
+"してください。"
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP アドレス"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Mail exchange レコード"
+
+msgid "Mail Server"
+msgstr "メールサーバー"
+
+msgid "Manage Records"
+msgstr "レコードの管理"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "名前"
+
+msgid "Name Server"
+msgstr "ネームサーバー"
+
+msgid "Nameservers"
+msgstr "ネームサーバー"
+
+msgid "None"
+msgstr "なし"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - ポインターレコード"
+
+msgid "PTR Domain Name"
+msgstr "PTR ドメイン名"
+
+msgid "Priority"
+msgstr "優先度"
+
+msgid "Record"
+msgstr "レコード"
+
+msgid "Record Data"
+msgstr "レコードデータ"
+
+msgid "Record Detail"
+msgstr "レコードの詳細"
+
+msgid "Record Type"
+msgstr "レコード種別"
+
+msgid "Records"
+msgstr "レコード"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - サービスロケーター"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH 公開鍵フィンガープリント"
+
+msgid "Select an IP"
+msgstr "IP を選択してください"
+
+msgid "Serial"
+msgstr "シリアル"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (秒)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - テキストレコード"
+
+msgid "Text"
+msgstr "テキスト"
+
+msgid "The quotas could not be retrieved."
+msgstr "クォータを取得できませんでした。"
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "選択可能な現在使用中の Floating IP がありません。"
+
+msgid "This field is required"
+msgstr "このフィールドは必須です"
+
+msgid "Type"
+msgstr "種別"
+
+msgid "Unable to create domain."
+msgstr "ドメインを作成できません。"
+
+msgid "Unable to create record."
+msgstr "レコードを作成できません。"
+
+msgid "Unable to retrieve domain list."
+msgstr "ドメインの一覧を取得できません。"
+
+msgid "Unable to retrieve domain record."
+msgstr "ドメインレコードを取得できません。"
+
+msgid "Unable to retrieve record list."
+msgstr "レコードの一覧を取得できません。"
+
+msgid "Unable to update domain."
+msgstr "ドメインを更新できません。"
+
+msgid "Unknown"
+msgstr "不明"
+
+msgid "Unknown instance name"
+msgstr "不明なインスタンス名"
+
+msgid "Update Domain"
+msgstr "ドメインの更新"
+
+msgid "Update Domain Record"
+msgstr "ドメインレコードの更新"
+
+msgid "Update Record"
+msgstr "レコードの更新"
+
+msgid "Updated"
+msgstr "更新時刻"
+
+msgid "Updated At"
+msgstr "最終更新"
+
+msgid "Value"
+msgstr "値"
+
+msgid "Zones"
+msgstr "ゾーン"
diff --git a/designatedashboard/locale/ko_KR/LC_MESSAGES/django.mo b/designatedashboard/locale/ko_KR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..854f3e7
Binary files /dev/null and b/designatedashboard/locale/ko_KR/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/ko_KR/LC_MESSAGES/django.po b/designatedashboard/locale/ko_KR/LC_MESSAGES/django.po
new file mode 100644
index 0000000..08577b0
--- /dev/null
+++ b/designatedashboard/locale/ko_KR/LC_MESSAGES/django.po
@@ -0,0 +1,350 @@
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Ian Y. Choi <ianyrchoi@gmail.com>, 2016. #zanata
+# Sungjin Kang <gang.sungjin@gmail.com>, 2016. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 3.0.0.0rc2.dev5\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2016-09-29 13:19+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2016-09-20 12:11+0000\n"
+"Last-Translator: Ian Y. Choi <ianyrchoi@gmail.com>\n"
+"Language-Team: Korean (South Korea)\n"
+"Language: ko-KR\n"
+"X-Generator: Zanata 3.7.3\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      여기서부터 도메인과 연결된 이메일 주소와 TTL을 편집할 수 있습니다.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      이메일 피드는 도메인에 연결된 유료한 이메일 주소 값을\n"
+"      포함해야 합니다.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      이름 필드는 정규화된 도메인 이름 (끝 마침표와 함께)을\n"
+"      포함해야 합니다.\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      옵션인 TTL 필드는 1 에서 2147483647 초 사이 임의의 값을\n"
+"      사용할 수 있습니다.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    TTL은 초 단위의 레코드에 대한 time-to-live 값입니다.\n"
+"  </p>\n"
+"  <p>\n"
+"    레코드 유형에 관한 <a href=\"http://en.wikipedia.org/wiki/"
+"List_of_DNS_record_types\" target=\"_designate_record_defs\">자세한 정보를</"
+"a> 살펴봅니다.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - 주소 레코드"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 주소 레코드"
+
+msgid "All Records"
+msgstr "모든 레코드"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - 대체 이름 레코드"
+
+msgid "Cancel"
+msgstr "취소"
+
+msgid "Canonical Name"
+msgstr "대체 이름"
+
+msgid "Create Domain"
+msgstr "도메인 생성"
+
+msgid "Create Domain Record"
+msgstr "도메인 레코드 생성"
+
+msgid "Create Record"
+msgstr "레코드 생성"
+
+msgid "Create Record for"
+msgstr "다음에 대한 레코드 생성"
+
+msgid "Created"
+msgstr "생성됨"
+
+msgid "Created At"
+msgstr "생성 시점"
+
+msgid "Data"
+msgstr "데이터"
+
+msgid "Delete"
+msgstr "삭제"
+
+msgid "Deleted"
+msgstr "삭제됨"
+
+msgid "Description"
+msgstr "설명"
+
+msgid "Domain"
+msgstr "도메인"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "도메인 %(name)s 이 생성되었습니다."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "도메인 %(name)s 이 수정되었습니다."
+
+msgid "Domain Detail"
+msgstr "도메인 세부 사항"
+
+msgid "Domain Name"
+msgstr "도메인 이름"
+
+msgid "Domain Overview"
+msgstr "도메인 개요"
+
+msgid "Domain Records"
+msgstr "도메인 레코드"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "도메인 레코드 %(name)s 가 생성되었습니다."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "도메인 레코드 %(name)s 가 업데이트되었습니다."
+
+msgid "Domains"
+msgstr "도메인"
+
+msgid "Edit Domain"
+msgstr "도메인 수정"
+
+msgid "Edit Record"
+msgstr "레코드 수정"
+
+msgid "Email"
+msgstr "이메일"
+
+msgid "Enter a valid IPv4 address"
+msgstr "유효한 IPv4 주소를 입력합니다"
+
+msgid "Enter a valid IPv6 address"
+msgstr "유요한 IPv6 주소를 입력합니다"
+
+msgid "Enter a valid SRV name"
+msgstr "유효한 SRV 이름을 입력합니다"
+
+msgid "Enter a valid SRV record"
+msgstr "유효한 SRV 레코드를 입력합니다"
+
+msgid "Enter a valid SSHFP record"
+msgstr "유효한 SSHFP 레코드를 입력합니다"
+
+msgid "Enter a valid domain name."
+msgstr "유효한 도메인 이름을 입력하시오."
+
+msgid "Enter a valid hostname"
+msgstr "유효한 호스트명을 입력합니다"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"유효한 호스트명을 입력합니다. 호스트명은 문자 및 숫자로 구성되어야 하며, 63 "
+"문자를 초과할 수 없습니다."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP 주소"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - 메일 교환 레코드"
+
+msgid "Mail Server"
+msgstr "메일 서버"
+
+msgid "Manage Records"
+msgstr "레코드 관리"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "이름"
+
+msgid "Name Server"
+msgstr "네임 서버"
+
+msgid "Nameservers"
+msgstr "이름 서버"
+
+msgid "None"
+msgstr "없음"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - 포인터 레코드"
+
+msgid "PTR Domain Name"
+msgstr "PTR 도메인 이름"
+
+msgid "Priority"
+msgstr "우선순위"
+
+msgid "Record"
+msgstr "레코드"
+
+msgid "Record Data"
+msgstr "레코드 데이터"
+
+msgid "Record Detail"
+msgstr "레코드 세부 사항"
+
+msgid "Record Type"
+msgstr "레코드 타입"
+
+msgid "Records"
+msgstr "레코드"
+
+msgid "Reverse DNS"
+msgstr "Reverse DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - 발송자 정책 프레임워크"
+
+msgid "SRV - Service locator"
+msgstr "SRV - 서비스 위치"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH 공개키 Fingerprint"
+
+msgid "Select an IP"
+msgstr "IP를 선택합니다"
+
+msgid "Serial"
+msgstr "시리얼"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (초)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - 텍스트 레코드"
+
+msgid "Text"
+msgstr "텍스트"
+
+msgid "The quotas could not be retrieved."
+msgstr "할당량을 가져올 수 없습니다."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "선택할 수 있는 사용 중인 floating IP 주소가 없습니다."
+
+msgid "This field is required"
+msgstr "해당 필드는 필수입니다"
+
+msgid "Type"
+msgstr "타입"
+
+msgid "Unable to create domain."
+msgstr "도메인을 생성할 수 없습니다."
+
+msgid "Unable to create record."
+msgstr "레코드를 생성할 수 없습니다."
+
+msgid "Unable to retrieve domain list."
+msgstr "도메인 목록을 가져올 수 없습니다."
+
+msgid "Unable to retrieve domain record."
+msgstr "도메인 레코드를 가져올 수 없습니다."
+
+msgid "Unable to retrieve record list."
+msgstr "레코드 목록을 가져올 수 없습니다."
+
+msgid "Unable to update domain."
+msgstr "도메인을 업데이트할 수 없습니다."
+
+msgid "Unknown"
+msgstr "알 수 없음"
+
+msgid "Unknown instance name"
+msgstr "알려지지 않은 인스턴스 이름"
+
+msgid "Update Domain"
+msgstr "도메인 업데이트"
+
+msgid "Update Domain Record"
+msgstr "도메인 레코드 업데이트"
+
+msgid "Update Record"
+msgstr "레코드 업데이트"
+
+msgid "Updated"
+msgstr "업데이트됨"
+
+msgid "Updated At"
+msgstr "갱신 시점"
+
+msgid "Value"
+msgstr "값"
+
+msgid "Zones"
+msgstr "존"
diff --git a/designatedashboard/locale/pt_BR/LC_MESSAGES/django.mo b/designatedashboard/locale/pt_BR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..45113d4
Binary files /dev/null and b/designatedashboard/locale/pt_BR/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/pt_BR/LC_MESSAGES/django.po b/designatedashboard/locale/pt_BR/LC_MESSAGES/django.po
new file mode 100644
index 0000000..c79632c
--- /dev/null
+++ b/designatedashboard/locale/pt_BR/LC_MESSAGES/django.po
@@ -0,0 +1,352 @@
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Eric Baum <ecbaum@gmail.com>, 2016. #zanata
+# Marcio <marciofoz@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 4.0.0.0rc2.dev10\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-03-10 19:52+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-03-15 07:16+0000\n"
+"Last-Translator: Marcio <marciofoz@gmail.com>\n"
+"Language-Team: Portuguese (Brazil)\n"
+"Language: pt-BR\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"    Aqui você pode editar o endereço de e-mail e o TTL associado ao "
+"domínio.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"    O campo de Email deve conter um endereço de e-mail válido para ser\n"
+"    associado ao domínio.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      O campo de nome deve conter um nome de domínio completamente "
+"qualificado\n"
+"      ( com ponto final).\n"
+"      "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"    O campo opcional de TTL pode ter qualquer valor entre 1 e 2147483647\n"
+"    segundos.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    O TTL é o tempo de vida do registro, em segundos.\n"
+"  </p>\n"
+"  <p>\n"
+"    Veja <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\"> para mais informações </a> nos tipos de "
+"registro.\n"
+" </p>\n"
+"    "
+
+msgid "A - Address record"
+msgstr "A - Registro do endereço"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - Registro do endereço IPv6"
+
+msgid "All Records"
+msgstr "Todos os Registros"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - Registro Canonical name "
+
+msgid "Cancel"
+msgstr "Cancelar"
+
+msgid "Canonical Name"
+msgstr "Canonical Name"
+
+msgid "Create Domain"
+msgstr "Criar Domínio"
+
+msgid "Create Domain Record"
+msgstr "Criar Registro do Domínio"
+
+msgid "Create Record"
+msgstr "Criar Registro"
+
+msgid "Create Record for"
+msgstr "Criar Registro para"
+
+msgid "Created"
+msgstr "Criado"
+
+msgid "Created At"
+msgstr "Criado em"
+
+msgid "Data"
+msgstr "Dados"
+
+msgid "Delete"
+msgstr "Remover"
+
+msgid "Deleted"
+msgstr "Removido"
+
+msgid "Description"
+msgstr "Descrição"
+
+msgid "Domain"
+msgstr "Domínio"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Domínio %(name)s criado."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Domínio %(name)s atualizado."
+
+msgid "Domain Detail"
+msgstr "Detalhes do Domínio"
+
+msgid "Domain Name"
+msgstr "Nome do Domínio"
+
+msgid "Domain Overview"
+msgstr "Visão Geral do Domínio"
+
+msgid "Domain Records"
+msgstr "Registros do Domínio"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Registro de domínio %(name)s criado."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Registro de domínio %(name)s atualizado."
+
+msgid "Domains"
+msgstr "Domínios"
+
+msgid "Edit Domain"
+msgstr "Editar Domínio"
+
+msgid "Edit Record"
+msgstr "Editar Registro"
+
+msgid "Email"
+msgstr "Email"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Entre com um endereço IPv4 válido"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Entre com um endereço IPv6 válido"
+
+msgid "Enter a valid SRV name"
+msgstr "Entre com um nome SRV válido."
+
+msgid "Enter a valid SRV record"
+msgstr "Entre com um registro SRV válido"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Entre com um registro SSHFP válido"
+
+msgid "Enter a valid domain name."
+msgstr "Entre com um nome de domínio válido."
+
+msgid "Enter a valid hostname"
+msgstr "Entre com um nome de host válido"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Insira um nome de host válido. O Nome de host deve conter letras e números, "
+"e não pode ter mais de 63 caracteres."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "Endereço IP"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Registro Mail exchange"
+
+msgid "Mail Server"
+msgstr "Servidor de Email"
+
+msgid "Manage Records"
+msgstr "Gerenciar Registros"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Nome"
+
+msgid "Name Server"
+msgstr "Servidor de Nome"
+
+msgid "Nameservers"
+msgstr "Servidores de Nome"
+
+msgid "None"
+msgstr "Nenhum"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - Registro Pointer record"
+
+msgid "PTR Domain Name"
+msgstr "PTR Nome do Domínio"
+
+msgid "Priority"
+msgstr "Prioridade"
+
+msgid "Record"
+msgstr "Registro"
+
+msgid "Record Data"
+msgstr "Dados do Registro"
+
+msgid "Record Detail"
+msgstr "Detalhes do Registro"
+
+msgid "Record Type"
+msgstr "Tipo de Registro"
+
+msgid "Records"
+msgstr "Registros"
+
+msgid "Reverse DNS"
+msgstr "DNS Reverso"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Sender Policy Framework"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Service locator"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH Public Key Fingerprint"
+
+msgid "Select an IP"
+msgstr "Selecione um IP"
+
+msgid "Serial"
+msgstr "Serial"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (segundos)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Text record"
+
+msgid "Text"
+msgstr "Text"
+
+msgid "The quotas could not be retrieved."
+msgstr "Não foi possível recuperar as cotas."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Nenhum endereço IP flutuante atualmente em uso para ser selecionado."
+
+msgid "This field is required"
+msgstr "Este campo é necessário"
+
+msgid "Type"
+msgstr "Tipo"
+
+msgid "Unable to create domain."
+msgstr "Não foi possível criar o domínio."
+
+msgid "Unable to create record."
+msgstr "Não é possível criar o registro."
+
+msgid "Unable to retrieve domain list."
+msgstr "Não foi possível recuperar a lista de domínios."
+
+msgid "Unable to retrieve domain record."
+msgstr "Não foi possível recuperar o registro de domínio."
+
+msgid "Unable to retrieve record list."
+msgstr "Não é possível recuperar lista de registros."
+
+msgid "Unable to update domain."
+msgstr "Não foi possível atualizar o domínio."
+
+msgid "Unknown"
+msgstr "Desconhecido"
+
+msgid "Unknown instance name"
+msgstr "Nome de Instância desconhecida"
+
+msgid "Update Domain"
+msgstr "Atualizar Domínio"
+
+msgid "Update Domain Record"
+msgstr "Atualizar Registro do Domínio"
+
+msgid "Update Record"
+msgstr "Atualizar Registro"
+
+msgid "Updated"
+msgstr "Atualizado"
+
+msgid "Updated At"
+msgstr "Atualizado em"
+
+msgid "Value"
+msgstr "Valor"
+
+msgid "Zones"
+msgstr "Zonas"
diff --git a/designatedashboard/locale/ru/LC_MESSAGES/django.mo b/designatedashboard/locale/ru/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..f4cf4ec
Binary files /dev/null and b/designatedashboard/locale/ru/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/ru/LC_MESSAGES/django.po b/designatedashboard/locale/ru/LC_MESSAGES/django.po
new file mode 100644
index 0000000..7352687
--- /dev/null
+++ b/designatedashboard/locale/ru/LC_MESSAGES/django.po
@@ -0,0 +1,361 @@
+# Translations template for designate-dashboard.
+# Copyright (C) 2015 ORGANIZATION
+# This file is distributed under the same license as the designate-dashboard
+# project.
+#
+# Translators:
+# Denis Gubanov <v12aml@gmail.com>, 2015
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Ivan Startsev <istartsev67@gmail.com>, 2016. #zanata
+# Ilya Alekseyev <ilyaalekseyev@acm.org>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 4.0.0.0rc2.dev10\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-03-10 19:52+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-03-23 08:04+0000\n"
+"Last-Translator: Ilya Alekseyev <ilyaalekseyev@acm.org>\n"
+"Language: ru\n"
+"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n"
+"%100>=11 && n%100<=14)? 2 : 3);\n"
+"Generated-By: Babel 2.0\n"
+"X-Generator: Zanata 3.9.6\n"
+"Language-Team: Russian\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Отсюда вы можете отредактировать адрес почты и ассоциированный с "
+"доменом TTL \n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Поле Почта должно содержать валидный почтовый адрес\n"
+"      который может быть ассоциирован с доменом.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      Поле Имя должно содержать полностью определённое имя домена (FQDN) "
+"( с\n"
+"      завершающей точкой).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      Опциональное поле TTL может принимать любое значение \n"
+"      между 1 и 2147483647 секундами.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    TTL - это time-to-live для записи, в секундах.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">больше информации</a> на типах записи.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - IPv4 адрес"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 адрес"
+
+msgid "All Records"
+msgstr "Все Записи"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME - каноническое имя"
+
+msgid "Cancel"
+msgstr "Отмена"
+
+msgid "Canonical Name"
+msgstr "Каноническое имя"
+
+msgid "Create Domain"
+msgstr "Создать домен"
+
+msgid "Create Domain Record"
+msgstr "Создать Доменную Запись"
+
+msgid "Create Record"
+msgstr "Создать запись"
+
+msgid "Create Record for"
+msgstr "Создать запись для"
+
+msgid "Created"
+msgstr "Создано"
+
+msgid "Created At"
+msgstr "Создано"
+
+msgid "Data"
+msgstr "Данные"
+
+msgid "Delete"
+msgstr "Удалить"
+
+msgid "Deleted"
+msgstr "Удалено"
+
+msgid "Description"
+msgstr "Описание"
+
+msgid "Domain"
+msgstr "Домен"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "Домен %(name)s создан."
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "Домен %(name)s обновлен."
+
+msgid "Domain Detail"
+msgstr "Детали Домена"
+
+msgid "Domain Name"
+msgstr "Имя домена"
+
+msgid "Domain Overview"
+msgstr "Обзор Домена"
+
+msgid "Domain Records"
+msgstr "Записи Домена"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "Доменная запись %(name)s создана."
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "Доменная запись %(name)s обновлена."
+
+msgid "Domains"
+msgstr "Домены"
+
+msgid "Edit Domain"
+msgstr "Редактировать домен"
+
+msgid "Edit Record"
+msgstr "Редактировать запись"
+
+msgid "Email"
+msgstr "Email"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Введите корректный IPv4 адрес"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Введите корректный IPv6 адрес"
+
+msgid "Enter a valid SRV name"
+msgstr "Введите верное SRV имя"
+
+msgid "Enter a valid SRV record"
+msgstr "Введите корректную SRV запись"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Введите корректныю SSHFP запись"
+
+msgid "Enter a valid domain name."
+msgstr "Введите корректное имя домена"
+
+msgid "Enter a valid hostname"
+msgstr "Введите корректное имя узла"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Введите верное имя узла. Имя узла должно содержать буквы, цифры и быть не "
+"длиннее 63 символов."
+
+msgid "ID"
+msgstr "ID"
+
+msgid "IP Address"
+msgstr "IP адрес"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - почтовый узел"
+
+msgid "Mail Server"
+msgstr "Почтовый сервер"
+
+msgid "Manage Records"
+msgstr "Управление записями"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "Имя"
+
+msgid "Name Server"
+msgstr "Сервер имен"
+
+msgid "Nameservers"
+msgstr "сервера"
+
+msgid "None"
+msgstr "Нет"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - указатель на каноническое имя"
+
+msgid "PTR Domain Name"
+msgstr "Имя домена PTR"
+
+msgid "Priority"
+msgstr "Приоритет"
+
+msgid "Record"
+msgstr "Запись"
+
+msgid "Record Data"
+msgstr "Запись Данных"
+
+msgid "Record Detail"
+msgstr "Детали Записи"
+
+msgid "Record Type"
+msgstr "Тип записи"
+
+msgid "Records"
+msgstr "Записи"
+
+msgid "Reverse DNS"
+msgstr "Обратный DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - инфраструктура политики отправителя"
+
+msgid "SRV - Service locator"
+msgstr "SRV - указатель сервиса"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - отпечаток публичного ключа SSH"
+
+msgid "Select an IP"
+msgstr "Выберите IP"
+
+msgid "Serial"
+msgstr "Серийный номер"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (секунды)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - тестовая запись"
+
+msgid "Text"
+msgstr "Текст"
+
+msgid "The quotas could not be retrieved."
+msgstr "Невозможно получить квоты."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Отсутствуют используемые плавающие IP-адреса для выбора."
+
+msgid "This field is required"
+msgstr "Это поле обязательно"
+
+msgid "Type"
+msgstr "Тип"
+
+msgid "Unable to create domain."
+msgstr "Невозможно создать домен."
+
+msgid "Unable to create record."
+msgstr "Невозможно создать запись."
+
+msgid "Unable to retrieve domain list."
+msgstr "Невозможно получить список доменов."
+
+msgid "Unable to retrieve domain record."
+msgstr "Невозможно получить список записей."
+
+msgid "Unable to retrieve record list."
+msgstr "Невозможно получить список записей."
+
+msgid "Unable to update domain."
+msgstr "Невозможно обновить домен."
+
+msgid "Unknown"
+msgstr "Неизвестно"
+
+msgid "Unknown instance name"
+msgstr "Неизвестное имя инстанса"
+
+msgid "Update Domain"
+msgstr "Обновить Домен"
+
+msgid "Update Domain Record"
+msgstr "Обновить Доменную Запись"
+
+msgid "Update Record"
+msgstr "Обновить запись"
+
+msgid "Updated"
+msgstr "Обновлено"
+
+msgid "Updated At"
+msgstr "Обновлено"
+
+msgid "Value"
+msgstr "Значение"
+
+msgid "Zones"
+msgstr "Зоны"
diff --git a/designatedashboard/locale/tr_TR/LC_MESSAGES/django.mo b/designatedashboard/locale/tr_TR/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..8a1bf5e
Binary files /dev/null and b/designatedashboard/locale/tr_TR/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/tr_TR/LC_MESSAGES/django.po b/designatedashboard/locale/tr_TR/LC_MESSAGES/django.po
new file mode 100644
index 0000000..29a1e1b
--- /dev/null
+++ b/designatedashboard/locale/tr_TR/LC_MESSAGES/django.po
@@ -0,0 +1,360 @@
+# Translations template for designate-dashboard.
+# Copyright (C) 2015 ORGANIZATION
+# This file is distributed under the same license as the designate-dashboard
+# project.
+#
+# Translators:
+# Alper Çiftçi <alprciftci@gmail.com>, 2015
+# Mücahit Büyükyılmaz <mucahit@deltanoc.com>, 2015. #zanata
+# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# işbaran akçayır <isbaran@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 5.0.0.0b2.dev8\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-05-18 22:12+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-05-22 08:57+0000\n"
+"Last-Translator: Copied by Zanata <copied-by-zanata@zanata.org>\n"
+"Language: tr-TR\n"
+"Plural-Forms: nplurals=1; plural=0;\n"
+"Generated-By: Babel 2.0\n"
+"X-Generator: Zanata 3.9.6\n"
+"Language-Team: Turkish (Turkey)\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Burdan alan ile ilişkili eposta adresini ve TTL değerini "
+"düzenleyebilirsiniz.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"      Eposta alanı alan ile ilişkilendirilecek geçerli bir\n"
+"      eposta adresi içermelidir.\n"
+"    "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"      İsim alanı tam-nitelikli alan adı içermelidir\n"
+"      (sonunda nokta ile).\n"
+"    "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"      İsteğe bağlı TTL alanı 1 ve 2147483647 saniye arasında\n"
+"      bir sayı olabilir.\n"
+"    "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    TTL kaydın yaşam süresidir, saniye olarak.\n"
+"  </p>\n"
+"  <p>\n"
+"    Kayıt türleri hakkında <a href=\"http://en.wikipedia.org/wiki/"
+"List_of_DNS_record_types\" target=\"_designate_record_defs\">daha fazla "
+"bilgi için</a> bakınız.\n"
+"  </p>\n"
+"  "
+
+msgid "A - Address record"
+msgstr "A - Adres kaydı"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA - IPv6 adres kaydı"
+
+msgid "All Records"
+msgstr "Tüm Kayıtlar"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME -  Meşru isim kaydı"
+
+msgid "Cancel"
+msgstr "İptal"
+
+msgid "Canonical Name"
+msgstr "Meşru isim"
+
+msgid "Create Domain"
+msgstr "Alan Oluştur"
+
+msgid "Create Domain Record"
+msgstr "Alan Kaydı Oluştur"
+
+msgid "Create Record"
+msgstr "Kayıt Oluştur"
+
+msgid "Create Record for"
+msgstr "Kayıt oluştur"
+
+msgid "Created"
+msgstr "Oluşturuldu"
+
+msgid "Created At"
+msgstr "Oluşturulduğu zaman"
+
+msgid "Data"
+msgstr "Veri"
+
+msgid "Delete"
+msgstr "Sil"
+
+msgid "Deleted"
+msgstr "Silindi"
+
+msgid "Description"
+msgstr "Açıklama"
+
+msgid "Domain"
+msgstr "Alan"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "%(name)s alanı oluşturuldu"
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "%(name)s alanı güncellendi"
+
+msgid "Domain Detail"
+msgstr "Alan Ayrıntısı"
+
+msgid "Domain Name"
+msgstr "Alan Adı"
+
+msgid "Domain Overview"
+msgstr "Alan Genel Görünümü"
+
+msgid "Domain Records"
+msgstr "Alan Kayıtları"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "%(name)s alan kaydı oluşturuldu"
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "%(name)s alan kaydı güncellendi"
+
+msgid "Domains"
+msgstr "Alanlar"
+
+msgid "Edit Domain"
+msgstr "Alan Değiştir"
+
+msgid "Edit Record"
+msgstr "Kaydı Düzenle"
+
+msgid "Email"
+msgstr "E-posta"
+
+msgid "Enter a valid IPv4 address"
+msgstr "Geçerli bir IPv4 adresi girin"
+
+msgid "Enter a valid IPv6 address"
+msgstr "Geçerli bir IPv6 adresi girin"
+
+msgid "Enter a valid SRV name"
+msgstr "Geçerli bir SRV adı girin"
+
+msgid "Enter a valid SRV record"
+msgstr "Geçerli bir SRV kaydı girin"
+
+msgid "Enter a valid SSHFP record"
+msgstr "Geçerli bir SSHFP kaydı girin"
+
+msgid "Enter a valid domain name."
+msgstr "Geçerli bir alan adı girin"
+
+msgid "Enter a valid hostname"
+msgstr "Geçerli bir sunucu adı girin"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr ""
+"Geçerli bir sunucu adı girin. Sunucu adı 63 karakterden az olmayan harf ve "
+"sayılar içermektedir."
+
+msgid "ID"
+msgstr "KİMLİK"
+
+msgid "IP Address"
+msgstr "IP Adresi"
+
+msgid "MX - Mail exchange record"
+msgstr "MX - Posta değişim kaydı"
+
+msgid "Mail Server"
+msgstr "Posta Sunucusu"
+
+msgid "Manage Records"
+msgstr "Kayıtları Yönet"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "İsim"
+
+msgid "Name Server"
+msgstr "İsim Sunucusu"
+
+msgid "Nameservers"
+msgstr "İsim sunucular"
+
+msgid "None"
+msgstr "Yok"
+
+msgid "PTR - Pointer record"
+msgstr "PTR - İşaretçi kaydı"
+
+msgid "PTR Domain Name"
+msgstr "PTR Alan Adı"
+
+msgid "Priority"
+msgstr "Öncelik"
+
+msgid "Record"
+msgstr "Kayıt"
+
+msgid "Record Data"
+msgstr "Kayıt Verisi"
+
+msgid "Record Detail"
+msgstr "Kayıt Ayrıntısı"
+
+msgid "Record Type"
+msgstr "Kayıt Tipi"
+
+msgid "Records"
+msgstr "Kayıtlar"
+
+msgid "Reverse DNS"
+msgstr "Ters DNS"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF - Gönderen İlkesi Çatısı"
+
+msgid "SRV - Service locator"
+msgstr "SRV - Servis konumlandırıcı"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP - SSH Açık Anahtar Parmakizi"
+
+msgid "Select an IP"
+msgstr "Bir IP seçiniz"
+
+msgid "Serial"
+msgstr "Seri"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "TTL (saniye)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT - Yazı Kaydı"
+
+msgid "Text"
+msgstr "Yazı"
+
+msgid "The quotas could not be retrieved."
+msgstr "Kotalar alınamadı."
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "Seçilecek kullanılabilir değişken IP adresi yok."
+
+msgid "This field is required"
+msgstr "Bu alanın doldurulması gerekmektedir."
+
+msgid "Type"
+msgstr "Tip"
+
+msgid "Unable to create domain."
+msgstr "Alan yaratılamıyor"
+
+msgid "Unable to create record."
+msgstr "Kayıt oluşturulamıyor"
+
+msgid "Unable to retrieve domain list."
+msgstr "Alan listesi alınamıyor."
+
+msgid "Unable to retrieve domain record."
+msgstr "Alan kaydı alınamadı."
+
+msgid "Unable to retrieve record list."
+msgstr "Kayıt listesi alınamadı."
+
+msgid "Unable to update domain."
+msgstr "Alan güncellenemiyor."
+
+msgid "Unknown"
+msgstr "Bilinmeyen"
+
+msgid "Unknown instance name"
+msgstr "Bilinmeyen mesafe adı"
+
+msgid "Update Domain"
+msgstr "Alanı Güncelle"
+
+msgid "Update Domain Record"
+msgstr "Alan Kaydını Güncelle"
+
+msgid "Update Record"
+msgstr "Kaydı Güncelle"
+
+msgid "Updated"
+msgstr "Güncellendi"
+
+msgid "Updated At"
+msgstr "Güncellendiği Zaman"
+
+msgid "Value"
+msgstr "Değer"
+
+msgid "Zones"
+msgstr "Bölgeler"
diff --git a/designatedashboard/locale/zh_CN/LC_MESSAGES/django.mo b/designatedashboard/locale/zh_CN/LC_MESSAGES/django.mo
new file mode 100644
index 0000000..34173bd
Binary files /dev/null and b/designatedashboard/locale/zh_CN/LC_MESSAGES/django.mo differ
diff --git a/designatedashboard/locale/zh_CN/LC_MESSAGES/django.po b/designatedashboard/locale/zh_CN/LC_MESSAGES/django.po
new file mode 100644
index 0000000..ff78785
--- /dev/null
+++ b/designatedashboard/locale/zh_CN/LC_MESSAGES/django.po
@@ -0,0 +1,353 @@
+# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
+# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
+# Gaoxiao Zhu <zhu.gaoxiao@h3c.com>, 2016. #zanata
+# Linda <duleish@cn.ibm.com>, 2016. #zanata
+# Wu Han <wu.han@h3c.com>, 2016. #zanata
+# ZHIYUAN SU <suzhiyuan@inspur.com>, 2016. #zanata
+# vuuv <froms2008@gmail.com>, 2016. #zanata
+# zzxwill <zzxwill@gmail.com>, 2016. #zanata
+# vuuv <froms2008@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: designate-dashboard 4.0.0.0rc2.dev10\n"
+"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
+"POT-Creation-Date: 2017-03-10 19:52+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-03-23 08:04+0000\n"
+"Last-Translator: vuuv <froms2008@gmail.com>\n"
+"Language-Team: Chinese (China)\n"
+"Language: zh-CN\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ""
+"\n"
+"      From here you can edit the email address and TTL associated with a "
+"domain.\n"
+"    "
+msgstr ""
+"\n"
+"从这里可以编辑邮件地址和关联到一个域的TTL\n"
+" "
+
+msgid ""
+"\n"
+"      The Email field should contain a valid email address to be associated\n"
+"      with the domain.\n"
+"    "
+msgstr ""
+"\n"
+"电子邮件项应包括一个有效的邮件地址来\n"
+"跟域名进行关联。\n"
+" "
+
+msgid ""
+"\n"
+"      The Name field should contain a full-qualified domain name (with\n"
+"      trailing period).\n"
+"    "
+msgstr ""
+"\n"
+"名称项应该包含一个完全合格的域名(有一个\n"
+"尾随句点)。\n"
+" "
+
+msgid ""
+"\n"
+"      The optional TTL field can be any value between 1 and 2147483647\n"
+"      seconds.\n"
+"    "
+msgstr ""
+"\n"
+"可选的TTL项可以是1到2147483647之间的任何\n"
+"秒数。\n"
+" "
+
+msgid ""
+"\n"
+"  <p>\n"
+"    <strong>TTL</strong>\n"
+"    The TTL is the time-to-live for the record, in seconds.\n"
+"  </p>\n"
+"  <p>\n"
+"    See <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types\" "
+"target=\"_designate_record_defs\">more info</a> on record types.\n"
+"  </p>\n"
+"  "
+msgstr ""
+"\n"
+"<p>\n"
+"<strong>TTL</strong>\n"
+"TTL是指 记录的存活时间或者存在时间,单位为秒。\n"
+"</p>\n"
+"<p>\n"
+"见记录类型的 <a href=\"http://en.wikipedia.org/wiki/List_of_DNS_record_types"
+"\" target=\"_designate_record_defs\">更多信息</a>。\n"
+"</p>\n"
+" "
+
+msgid "A - Address record"
+msgstr "A-地址记录"
+
+msgid "AAAA - IPv6 address record"
+msgstr "AAAA-IPv6地址记录"
+
+msgid "All Records"
+msgstr "所有记录"
+
+msgid "CNAME - Canonical name record"
+msgstr "CNAME-标准名称记录"
+
+msgid "Cancel"
+msgstr "取消"
+
+msgid "Canonical Name"
+msgstr "规范名称"
+
+msgid "Create Domain"
+msgstr "创建域"
+
+msgid "Create Domain Record"
+msgstr "创建域记录"
+
+msgid "Create Record"
+msgstr "创建记录"
+
+msgid "Create Record for"
+msgstr "创建记录为:"
+
+msgid "Created"
+msgstr "已创建"
+
+msgid "Created At"
+msgstr "创建于"
+
+msgid "Data"
+msgstr "数据"
+
+msgid "Delete"
+msgstr "删除"
+
+msgid "Deleted"
+msgstr "已删除"
+
+msgid "Description"
+msgstr "描述"
+
+msgid "Domain"
+msgstr "域"
+
+#, python-format
+msgid "Domain %(name)s created."
+msgstr "域 %(name)s 已创建"
+
+#, python-format
+msgid "Domain %(name)s updated."
+msgstr "域%(name)s已更新"
+
+msgid "Domain Detail"
+msgstr "域明细"
+
+msgid "Domain Name"
+msgstr "域名"
+
+msgid "Domain Overview"
+msgstr "域概览"
+
+msgid "Domain Records"
+msgstr "域记录"
+
+#, python-format
+msgid "Domain record %(name)s created."
+msgstr "创建的域记录数 %(name)s "
+
+#, python-format
+msgid "Domain record %(name)s updated."
+msgstr "更新的域记录数 %(name)s"
+
+msgid "Domains"
+msgstr "域"
+
+msgid "Edit Domain"
+msgstr "编辑域"
+
+msgid "Edit Record"
+msgstr "编辑记录"
+
+msgid "Email"
+msgstr "邮箱"
+
+msgid "Enter a valid IPv4 address"
+msgstr "输入一个有效的IPv4地址"
+
+msgid "Enter a valid IPv6 address"
+msgstr "输入一个有效的IPv6地址"
+
+msgid "Enter a valid SRV name"
+msgstr "输入一个有效的SRV名"
+
+msgid "Enter a valid SRV record"
+msgstr "输入一个有效的SRV记录"
+
+msgid "Enter a valid SSHFP record"
+msgstr "输入一个有效的SSHFP记录"
+
+msgid "Enter a valid domain name."
+msgstr "输入一个有效域名"
+
+msgid "Enter a valid hostname"
+msgstr "输入一个有效的主机名"
+
+msgid ""
+"Enter a valid hostname. The hostname should contain letters and numbers, and "
+"be no more than 63 characters."
+msgstr "输入一个有效的主机名。该主机名应包含字母和数字且不多余63个字符。"
+
+msgid "ID"
+msgstr "标识"
+
+msgid "IP Address"
+msgstr "IP 地址"
+
+msgid "MX - Mail exchange record"
+msgstr "MX-邮件交换记录"
+
+msgid "Mail Server"
+msgstr "邮件服务器"
+
+msgid "Manage Records"
+msgstr "管理记录"
+
+msgid "NS"
+msgstr "NS"
+
+msgid "Name"
+msgstr "名称"
+
+msgid "Name Server"
+msgstr "域名服务器"
+
+msgid "Nameservers"
+msgstr "名称服务器数"
+
+msgid "None"
+msgstr "无"
+
+msgid "PTR - Pointer record"
+msgstr "PTR-指针记录"
+
+msgid "PTR Domain Name"
+msgstr "域名"
+
+msgid "Priority"
+msgstr "优先级"
+
+msgid "Record"
+msgstr "记录"
+
+msgid "Record Data"
+msgstr "记录数据"
+
+msgid "Record Detail"
+msgstr "记录明细"
+
+msgid "Record Type"
+msgstr "记录类型"
+
+msgid "Records"
+msgstr "记录数"
+
+msgid "Reverse DNS"
+msgstr "反向解析域名"
+
+msgid "SOA"
+msgstr "SOA"
+
+msgid "SPF - Sender Policy Framework"
+msgstr "SPF-发送方策略框架"
+
+msgid "SRV - Service locator"
+msgstr "SRV-服务定位器"
+
+msgid "SSHFP - SSH Public Key Fingerprint"
+msgstr "SSHFP-SSH公钥指纹"
+
+msgid "Select an IP"
+msgstr "选择一个IP"
+
+msgid "Serial"
+msgstr "序列号"
+
+msgid "TTL"
+msgstr "TTL"
+
+msgid "TTL (seconds)"
+msgstr "生存时间(秒)"
+
+msgid "TXT"
+msgstr "TXT"
+
+msgid "TXT - Text record"
+msgstr "TXT-文本记录"
+
+msgid "Text"
+msgstr "文本"
+
+msgid "The quotas could not be retrieved."
+msgstr "无法获取配额"
+
+msgid "There are no floating IP addresses currently in use to select from."
+msgstr "没有可以从中选择的使用中的浮动IP地址"
+
+msgid "This field is required"
+msgstr "需要此字段"
+
+msgid "Type"
+msgstr "类型"
+
+msgid "Unable to create domain."
+msgstr "无法创建域"
+
+msgid "Unable to create record."
+msgstr "不能创建记录。"
+
+msgid "Unable to retrieve domain list."
+msgstr "无法获取域列表。"
+
+msgid "Unable to retrieve domain record."
+msgstr "无法获取域记录"
+
+msgid "Unable to retrieve record list."
+msgstr "无法获取记录列表"
+
+msgid "Unable to update domain."
+msgstr "无法更新域"
+
+msgid "Unknown"
+msgstr "未知"
+
+msgid "Unknown instance name"
+msgstr "未知实例名"
+
+msgid "Update Domain"
+msgstr "更新域"
+
+msgid "Update Domain Record"
+msgstr "更新域记录"
+
+msgid "Update Record"
+msgstr "更新记录"
+
+msgid "Updated"
+msgstr "已更新"
+
+msgid "Updated At"
+msgstr "已更新于"
+
+msgid "Value"
+msgstr "值"
+
+msgid "Zones"
+msgstr "区域"
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 @@
+<hz-resource-property-list
+    ng-if="item"
+    resource-type-name="OS::Designate::FloatingIp"
+    item="item"
+    cls="dl-horizontal"
+    property-groups="[
+      ['description'],
+      ['id']
+    ]">
+</hz-resource-property-list>
\ 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 @@
+<div ng-controller="designatedashboard.resources.os-designate-floatingip.details.overviewController as ctrl">
+  <hz-resource-property-list
+      ng-if="ctrl.item"
+      resource-type-name="OS::Designate::FloatingIp"
+      item="ctrl.item"
+      cls="dl-horizontal"
+      property-groups="[
+            ['ptrdname', 'description', 'id', 'ttl', 'status', 'action']
+          ]">
+  </hz-resource-property-list>
+</div>
\ 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..7872ad9
--- /dev/null
+++ b/designatedashboard/static/designatedashboard/resources/os-designate-floatingip/os-designate-floatingip.module.js
@@ -0,0 +1,156 @@
+/**
+ * (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.app.core.detailRoute',
+    'horizon.framework.conf.resource-type-registry.service',
+    'designatedashboard.resources.os-designate-floatingip.api',
+    'designatedashboard.resources.os-designate-floatingip.resourceType',
+    'designatedashboard.resources.util'
+  ];
+
+  function run(
+    detailRoute,
+    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: '<a ng-href="{$ \'' + detailRoute + 'OS::Designate::FloatingIp/\' + item.id $}">{$ item.address $}</a>'
+      })
+      .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 "<zone_id>/<recordset_id>"
+     */
+    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 @@
+<hz-resource-property-list
+    ng-if="item"
+    resource-type-name="OS::Designate::RecordSet"
+    item="item"
+    cls="dl-horizontal"
+    property-groups="[
+      ['records', 'notes', 'description'],
+      ['id']
+    ]">
+</hz-resource-property-list>
\ 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 @@
+<div ng-controller="designatedashboard.resources.os-designate-recordset.detailController as ctrl">
+  <div class="row">
+    <div class="col-md-6 detail">
+      <h3 translate>Details</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::RecordSet"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['id', 'type', 'name', 'records', 'notes', 'description', 'ttl', 'version', 'action', 'status']
+          ]">
+      </hz-resource-property-list>
+    </div>
+    <div class="col-md-6 detail">
+      <h3 translate>Associations</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::RecordSet"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['zone_id', 'zone_name', 'project_id']
+          ]">
+      </hz-resource-property-list>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-md-6 detail">
+      <h3 translate>Modification Times</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::RecordSet"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['created_at', 'updated_at']
+          ]">
+      </hz-resource-property-list>
+    </div>
+  </div>
+</div>
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 @@
+<div
+  ng-controller="designatedashboard.resources.os-designate-recordset.zoneRecordSetsController as ctrl">
+  <hz-resource-table
+    resource-type-name="{$ ctrl.resourceTypeName $}"
+    list-function-extra-params="ctrl.extraListParams"
+    track-by="_timestampId">
+  </hz-resource-table>
+</div>
\ 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..7feff60
--- /dev/null
+++ b/designatedashboard/static/designatedashboard/resources/os-designate-recordset/os-designate-recordset.module.js
@@ -0,0 +1,240 @@
+/**
+ * (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.app.core.detailRoute',
+    '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(detailRoute,
+               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: '<a ng-href="{$ \'' + detailRoute + 'OS::Designate::RecordSet/\' + item.zone_id + \'/\' + item.id $}">{$ item.name $}</a>'
+      })
+      .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/actions.module.spec.js b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/actions.module.spec.js
new file mode 100644
index 0000000..f4bcda2
--- /dev/null
+++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/actions.module.spec.js
@@ -0,0 +1,49 @@
+/**
+ *
+ * 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';
+
+  describe('zone actions module', function() {
+    var registry;
+    beforeEach(module('designatedashboard'));
+
+    beforeEach(inject(function($injector) {
+      registry = $injector.get('horizon.framework.conf.resource-type-registry.service');
+    }));
+
+    it('registers Create Zone as a global action', function() {
+      var actions = registry.getResourceType('OS::Designate::Zone').globalActions;
+      expect(actionHasId(actions, 'create')).toBe(true);
+    });
+
+    it('registers Update Zone as a item action', function() {
+      var actions = registry.getResourceType('OS::Designate::Zone').itemActions;
+      expect(actionHasId(actions, 'update')).toBe(true);
+    });
+
+    function actionHasId(list, value) {
+      return list.filter(matchesId).length === 1;
+
+      function matchesId(action) {
+        if (action.id === value) {
+          return true;
+        }
+      }
+    }
+
+  });
+
+})();
\ No newline at end of file
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..55df08f
--- /dev/null
+++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/actions/common-forms.service.js
@@ -0,0 +1,185 @@
+/**
+ *
+ * (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"),
+            "description": gettext("Email address to contact the zone owner."),
+            "validationMessage": gettext("Email address must contain a single '@' character"),
+            "type": "text",
+            "condition": "model.type == 'PRIMARY'",
+            "required": true
+          },
+          {
+            "key": "ttl",
+            "title": gettext("TTL"),
+            "description": gettext("Time To Live in seconds."),
+            "type": "number",
+            "condition": "model.type == 'PRIMARY'",
+            "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 @@
+<h1>CREATE TEMPLATE</h1>
\ 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 @@
+<hz-resource-property-list
+    ng-if="item"
+    resource-type-name="OS::Designate::Zone"
+    item="item"
+    cls="dl-horizontal"
+    property-groups="[
+      ['description', 'email'],
+      ['id', 'pool_id', 'project_id']
+    ]">
+</hz-resource-property-list>
\ 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..58c5601
--- /dev/null
+++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/details/overview.controller.js
@@ -0,0 +1,55 @@
+/**
+ * (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;
+
+      var attr = '';
+      var keys = Object.keys(response.data.attributes);
+
+      for (var i = keys.length - 1; i >= 0; i--) {
+        attr += keys[i] + ':' + response.data.attributes[keys[i]] + ', ';
+      }
+
+      ctrl.item.attributes = attr;
+    }
+  }
+
+})();
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 @@
+<div ng-controller="designatedashboard.resources.os-designate-zone.detailController as ctrl">
+  <div class="row">
+    <div class="col-md-6 detail">
+      <h3 translate>Details</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::Zone"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['id', 'description', 'type', 'status', 'action', 'email', 'serial', 'ttl', 'version']
+          ]">
+      </hz-resource-property-list>
+    </div>
+    <div class="col-md-6 detail">
+      <h3 translate>Attributes</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::Zone"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['attributes']
+          ]">
+      </hz-resource-property-list>
+    </div>
+  </div>
+  <div class="row">
+    <div class="col-md-6 detail">
+      <h3 translate>Modification Times</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::Zone"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['created_at', 'updated_at', 'transferred_at']
+          ]">
+      </hz-resource-property-list>
+    </div>
+    <div class="col-md-6 detail">
+      <h3 translate>Associations</h3>
+      <hr>
+      <hz-resource-property-list
+          ng-if="ctrl.item"
+          resource-type-name="OS::Designate::Zone"
+          item="ctrl.item"
+          cls="dl-horizontal"
+          property-groups="[
+            ['pool_id', 'project_id', 'masters']
+          ]">
+      </hz-resource-property-list>
+    </div>
+  </div>
+</div>
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..3c8bf61
--- /dev/null
+++ b/designatedashboard/static/designatedashboard/resources/os-designate-zone/os-designate-zone.module.js
@@ -0,0 +1,204 @@
+/**
+ * (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.app.core.detailRoute',
+    'horizon.framework.conf.resource-type-registry.service',
+    'designatedashboard.resources.os-designate-zone.api',
+    'designatedashboard.resources.os-designate-zone.resourceType',
+    'designatedashboard.resources.util'
+  ];
+
+  function run(detailRoute,
+               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: '<a ng-href="{$ \'' + detailRoute + 'OS::Designate::Zone/\' + item.id $}">{$ item.name $}</a>'
+      })
+      .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 @@
+<hz-resource-panel resource-type-name="OS::Designate::FloatingIp">
+  <hz-resource-table resource-type-name="OS::Designate::FloatingIp"
+                     track-by="_timestampId"></hz-resource-table>
+</hz-resource-panel>
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 @@
+<hz-resource-panel resource-type-name="OS::Designate::Zone">
+  <hz-resource-table resource-type-name="OS::Designate::Zone"
+                     track-by="_timestampId"></hz-resource-table>
+</hz-resource-panel>
diff --git a/designatedashboard/tests/.secret_key_store b/designatedashboard/tests/.secret_key_store
new file mode 100644
index 0000000..6e8db9b
--- /dev/null
+++ b/designatedashboard/tests/.secret_key_store
@@ -0,0 +1 @@
+5NMb6ZfXYBLClFGFYf6VkbiJ9TRNyU3w8NQHPG8LXJltU8EZFeB7I632vQ8MF5m6
\ No newline at end of file
diff --git a/designatedashboard/tests/__init__.py b/designatedashboard/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/designatedashboard/tests/base.py b/designatedashboard/tests/base.py
new file mode 100644
index 0000000..9062a3a
--- /dev/null
+++ b/designatedashboard/tests/base.py
@@ -0,0 +1,100 @@
+# -*- coding: utf-8 -*-
+
+# Copyright 2010-2011 OpenStack Foundation
+# Copyright (c) 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.
+
+import os
+
+import fixtures
+import testtools
+
+from openstack_dashboard.test import helpers as test
+
+from designatedashboard.dashboards.project.dns_domains import forms
+
+
+_TRUE_VALUES = ('True', 'true', '1', 'yes')
+
+
+class TestCase(testtools.TestCase):
+
+    """Test case base class for all unit tests."""
+
+    def setUp(self):
+        """Run before each test method to initialize test environment."""
+
+        super(TestCase, self).setUp()
+        test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0)
+        try:
+            test_timeout = int(test_timeout)
+        except ValueError:
+            # If timeout value is invalid do not set a timeout.
+            test_timeout = 0
+        if test_timeout > 0:
+            self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
+
+        self.useFixture(fixtures.NestedTempfile())
+        self.useFixture(fixtures.TempHomeDir())
+
+        if os.environ.get('OS_STDOUT_CAPTURE') in _TRUE_VALUES:
+            stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+            self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+        if os.environ.get('OS_STDERR_CAPTURE') in _TRUE_VALUES:
+            stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+            self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+
+        self.log_fixture = self.useFixture(fixtures.FakeLogger())
+
+
+class BaseRecordFormCleanTests(test.TestCase):
+
+    DOMAIN_NAME = 'foo.com.'
+    HOSTNAME = 'www'
+
+    MSG_FIELD_REQUIRED = 'This field is required'
+    MSG_INVALID_HOSTNAME = 'Enter a valid hostname. The '\
+                           'hostname should contain letters '\
+                           'and numbers, and be no more than '\
+                           '63 characters.'
+    MSG_INVALID_HOSTNAME_SHORT = 'Enter a valid hostname'
+
+    def setUp(self):
+        super(BaseRecordFormCleanTests, self).setUp()
+
+        # Request object with messages support
+        self.request = self.factory.get('', {})
+
+        # Set-up form instance
+        kwargs = {}
+        kwargs['initial'] = {'domain_name': self.DOMAIN_NAME}
+        self.form = forms.RecordCreate(self.request, **kwargs)
+        self.form._errors = {}
+        self.form.cleaned_data = {
+            'domain_name': self.DOMAIN_NAME,
+            'name': '',
+            'data': '',
+            'txt': '',
+            'priority': None,
+            'ttl': None,
+        }
+
+    def assert_no_errors(self):
+        self.assertEqual(self.form._errors, {})
+
+    def assert_error(self, field, msg):
+        self.assertIn(msg, self.form._errors[field])
+
+    def assert_required_error(self, field):
+        self.assert_error(field, self.MSG_FIELD_REQUIRED)
diff --git a/designatedashboard/tests/settings.py b/designatedashboard/tests/settings.py
new file mode 100644
index 0000000..afc2b87
--- /dev/null
+++ b/designatedashboard/tests/settings.py
@@ -0,0 +1,83 @@
+# 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.
+import socket
+
+SECRET_KEY = 'HELLA_SECRET!'
+
+DATABASES = {'default': {'ENGINE': 'django.db.backends.sqlite3',
+                         'NAME': 'test'}}
+
+from horizon.test.settings import *  # noqa
+
+socket.setdefaulttimeout(1)
+
+DEBUG = False
+TEMPLATE_DEBUG = DEBUG
+
+TESTSERVER = 'http://testserver'
+
+
+MESSAGE_STORAGE = 'django.contrib.messages.storage.cookie.CookieStorage'
+
+TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
+NOSE_ARGS = ['--nocapture',
+             '--nologcapture',
+             '--cover-package=windc']
+
+EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
+SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
+
+OPENSTACK_ADDRESS = "localhost"
+OPENSTACK_ADMIN_TOKEN = "openstack"
+OPENSTACK_KEYSTONE_URL = "http://%s:5000/v2.0" % OPENSTACK_ADDRESS
+OPENSTACK_KEYSTONE_ADMIN_URL = "http://%s:35357/v2.0" % OPENSTACK_ADDRESS
+OPENSTACK_KEYSTONE_DEFAULT_ROLE = "Member"
+
+# Silence logging output during tests.
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'handlers': {
+        'null': {
+            'level': 'DEBUG',
+            'class': 'logging.NullHandler'
+        },
+    },
+    'loggers': {
+        'django.db.backends': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'horizon': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'novaclient': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'keystoneclient': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'quantum': {
+            'handlers': ['null'],
+            'propagate': False,
+        },
+        'nose.plugins.manager': {
+            'handlers': ['null'],
+            'propagate': False,
+        }
+    }
+}
diff --git a/designatedashboard/tests/test_designatedashboard.py b/designatedashboard/tests/test_designatedashboard.py
new file mode 100644
index 0000000..12d2310
--- /dev/null
+++ b/designatedashboard/tests/test_designatedashboard.py
@@ -0,0 +1,406 @@
+# Copyright 2012 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# All Rights Reserved.
+#
+# Copyright 2012 Nebula, Inc.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+from __future__ import unicode_literals
+
+from django.core.urlresolvers import reverse  # noqa
+# from django import http
+
+from designatedashboard.tests import base
+
+DOMAIN_ID = '123'
+# INDEX_URL = reverse('horizon:project:dns_domains:index')
+# RECORDS_URL = reverse('horizon:project:dns_domains:records',
+#                       args=[DOMAIN_ID])
+
+
+# class DNSDomainsTests(test.TestCase):
+
+#     def setUp(self):
+#         super(DNSDomainsTests, self).setUp()
+
+#     @test.create_stubs(
+#         {api.designate: ('domain_list',)})
+#     def test_index(self):
+#         domains = self.dns_domains.list()
+#         api.designate.domain_list(
+#             IsA(http.HttpRequest)).AndReturn(domains)
+#         self.mox.ReplayAll()
+
+#         res = self.client.get(INDEX_URL)
+
+#         self.assertTemplateUsed(res, 'project/dns_domains/index.html')
+#         self.assertEqual(len(res.context['table'].data), len(domains))
+
+#     @test.create_stubs(
+#         {api.designate: ('domain_get', 'server_list', 'record_list')})
+#     def test_records(self):
+#         domain_id = '123'
+#         domain = self.dns_domains.first()
+#         servers = self.dns_servers.list()
+#         records = self.dns_records.list()
+
+#         api.designate.domain_get(
+#             IsA(http.HttpRequest),
+#             domain_id).AndReturn(domain)
+
+#         api.designate.server_list(
+#             IsA(http.HttpRequest),
+#             domain_id).AndReturn(servers)
+
+#         api.designate.record_list(
+#             IsA(http.HttpRequest),
+#             domain_id).AndReturn(records)
+
+#         self.mox.ReplayAll()
+
+#         res = self.client.get(RECORDS_URL)
+
+#         self.assertTemplateUsed(res, 'project/dns_domains/records.html')
+#         self.assertEqual(len(res.context['table'].data), len(records))
+
+
+class ARecordFormTests(base.BaseRecordFormCleanTests):
+
+    IPV4 = '1.1.1.1'
+
+    MSG_INVALID_IPV4 = 'Enter a valid IPv4 address'
+
+    def setUp(self):
+        super(ARecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'A'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['data'] = self.IPV4
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_no_errors()
+        self.assertIsNotNone(self.form.cleaned_data['name'])
+
+    def test_missing_data_field(self):
+        self.form.cleaned_data['data'] = ''
+        self.form.clean()
+        self.assert_required_error('data')
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = '$#%foo!!'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*.' + self.DOMAIN_NAME
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_data_field(self):
+        self.form.cleaned_data['data'] = 'foo'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_IPV4)
+
+
+class AAAARecordFormTests(base.BaseRecordFormCleanTests):
+
+    IPV6 = '1111:1111:1111:11::1'
+
+    MSG_INVALID_IPV6 = 'Enter a valid IPv6 address'
+
+    def setUp(self):
+        super(AAAARecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'AAAA'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['data'] = self.IPV6
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_no_errors()
+        self.assertIsNotNone(self.form.cleaned_data['name'])
+
+    def test_missing_data_field(self):
+        self.form.cleaned_data['data'] = ''
+        self.form.clean()
+        self.assert_required_error('data')
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = '#@$foo!!'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww.foo.com'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'www.foo.co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*.' + self.DOMAIN_NAME
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_data_field(self):
+        self.form.cleaned_data['data'] = 'foo'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_IPV6)
+
+
+class CNAMERecordFormTests(base.BaseRecordFormCleanTests):
+
+    CNAME = 'bar.foo.com.'
+
+    def setUp(self):
+        super(CNAMERecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'CNAME'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['data'] = self.CNAME
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_required_error('name')
+
+    def test_missing_data_field(self):
+        self.form.cleaned_data['data'] = ''
+        self.form.clean()
+        self.assert_required_error('data')
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = '$#%#$foo!!!'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww.foo.com'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'www.foo.co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*.' + self.DOMAIN_NAME
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_data_field(self):
+        self.form.cleaned_data['data'] = 'foo'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_HOSTNAME_SHORT)
+
+
+class MXRecordFormTests(base.BaseRecordFormCleanTests):
+
+    MAIL_SERVER = 'mail.foo.com.'
+    PRIORITY = 10
+
+    def setUp(self):
+        super(MXRecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'MX'
+        self.form.cleaned_data['data'] = self.MAIL_SERVER
+        self.form.cleaned_data['priority'] = self.PRIORITY
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_data_field(self):
+        self.form.cleaned_data['data'] = ''
+        self.form.clean()
+        self.assert_required_error('data')
+
+    def test_missing_priority_field(self):
+        self.form.cleaned_data['priority'] = None
+        self.form.clean()
+        self.assert_required_error('priority')
+
+    def test_invalid_data_field(self):
+        self.form.cleaned_data['data'] = 'foo'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_HOSTNAME_SHORT)
+
+    def test_default_assignment_name_field(self):
+        self.form.clean()
+        self.assertEqual(self.DOMAIN_NAME, self.form.cleaned_data['name'])
+
+
+class TXTRecordFormTests(base.BaseRecordFormCleanTests):
+
+    TEXT = 'Lorem ipsum'
+
+    def setUp(self):
+        super(TXTRecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'TXT'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['txt'] = self.TEXT
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_no_errors()
+        self.assertIsNotNone(self.form.cleaned_data['name'])
+
+    def test_missing_txt_field(self):
+        self.form.cleaned_data['txt'] = ''
+        self.form.clean()
+        self.assert_required_error('txt')
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = 'foo-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww.foo.com'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'www.foo.co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_default_assignment_data_field(self):
+        self.form.clean()
+        self.assertEqual(self.TEXT, self.form.cleaned_data['data'])
+
+
+class SRVRecordFormTests(base.BaseRecordFormCleanTests):
+
+    SRV_NAME = '_foo._tcp.'
+    SRV_DATA = '1 1 srv.foo.com.'
+    PRIORITY = 10
+
+    MSG_INVALID_SRV_NAME = 'Enter a valid SRV name'
+    MSG_INVALID_SRV_DATA = 'Enter a valid SRV record'
+
+    def setUp(self):
+        super(SRVRecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'SRV'
+        self.form.cleaned_data['name'] = self.SRV_NAME
+        self.form.cleaned_data['data'] = self.SRV_DATA
+        self.form.cleaned_data['priority'] = self.PRIORITY
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_required_error('name')
+
+    def test_missing_data_field(self):
+        self.form.cleaned_data['data'] = ''
+        self.form.clean()
+        self.assert_required_error('data')
+
+    def test_missing_priority_field(self):
+        self.form.cleaned_data['priority'] = None
+        self.form.clean()
+        self.assert_required_error('priority')
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = 'foo'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_SRV_NAME)
+
+    def test_invalid_data_field(self):
+        self.form.cleaned_data['data'] = 'foo'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_SRV_DATA)
+
+    def test_default_assignment_name_field(self):
+        self.form.clean()
+        self.assertEqual(self.SRV_NAME + self.DOMAIN_NAME,
+                         self.form.cleaned_data['name'])
diff --git a/designatedashboard/tests/test_ptr_record_form.py b/designatedashboard/tests/test_ptr_record_form.py
new file mode 100644
index 0000000..feafc98
--- /dev/null
+++ b/designatedashboard/tests/test_ptr_record_form.py
@@ -0,0 +1,85 @@
+# Copyright 2015 NEC Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from designatedashboard.tests import base
+
+
+class PTRRecordFormTests(base.BaseRecordFormCleanTests):
+
+    PTR = "6.0.0.10.in-addr.arpa."
+
+    def setUp(self):
+        super(PTRRecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'PTR'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['data'] = self.PTR
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_no_errors()
+        self.assertIsNotNone(self.form.cleaned_data['name'])
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = '#@$foo!!'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww.foo.com'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'www.foo.co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*.' + self.DOMAIN_NAME
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+
+    def test_invalid_data_field(self):
+        self.form.cleaned_data['data'] = '#@$' + self.PTR + '!!'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_HOSTNAME_SHORT)
+
+    def test_invalid_data_field_starting_dash(self):
+        self.form.cleaned_data['data'] = '-' + self.PTR
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_HOSTNAME_SHORT)
+
+    def test_invalid_data_field_trailing_dash(self):
+        self.form.cleaned_data['data'] = self.PTR + '-'
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_HOSTNAME_SHORT)
+
+    def test_invalid_data_field_bad_wild_card(self):
+        self.form.cleaned_data['data'] = 'derp.*.' + self.PTR
+        self.form.clean()
+        self.assert_error('data', self.MSG_INVALID_HOSTNAME_SHORT)
diff --git a/designatedashboard/tests/test_spf_record_form.py b/designatedashboard/tests/test_spf_record_form.py
new file mode 100644
index 0000000..7885c3f
--- /dev/null
+++ b/designatedashboard/tests/test_spf_record_form.py
@@ -0,0 +1,70 @@
+# Copyright 2015 NEC Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from designatedashboard.tests import base
+
+
+class SPFRecordFormTests(base.BaseRecordFormCleanTests):
+
+    TEXT = 'v=spf1 +all'
+
+    def setUp(self):
+        super(SPFRecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'SPF'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['txt'] = self.TEXT
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_name_field(self):
+        self.form.cleaned_data['name'] = ''
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_txt_field(self):
+        self.form.cleaned_data['txt'] = ''
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = 'foo-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww.foo.com'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'www.foo.co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
diff --git a/designatedashboard/tests/test_sshfp_record_form.py b/designatedashboard/tests/test_sshfp_record_form.py
new file mode 100644
index 0000000..32d7799
--- /dev/null
+++ b/designatedashboard/tests/test_sshfp_record_form.py
@@ -0,0 +1,92 @@
+# Copyright 2015 NEC Corporation.  All rights reserved.
+#
+#    Licensed under the Apache License, Version 2.0 (the "License"); you may
+#    not use this file except in compliance with the License. You may obtain
+#    a copy of the License at
+#
+#         http://www.apache.org/licenses/LICENSE-2.0
+#
+#    Unless required by applicable law or agreed to in writing, software
+#    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+#    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+#    License for the specific language governing permissions and limitations
+#    under the License.
+
+from designatedashboard.tests import base
+
+
+class SSHFPRecordFormTests(base.BaseRecordFormCleanTests):
+
+    TEXT = '2 1 d1eb0d876ec69d18bcefc4263ae43ec33ae14f4c'
+    MSG_INVALID_RECORD = "Enter a valid SSHFP record"
+
+    def setUp(self):
+        super(SSHFPRecordFormTests, self).setUp()
+        self.form.cleaned_data['type'] = 'SSHFP'
+        self.form.cleaned_data['name'] = self.HOSTNAME
+        self.form.cleaned_data['txt'] = self.TEXT
+
+    def test_valid_field_values(self):
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_valid_name_field_wild_card(self):
+        self.form.cleaned_data['name'] = '*'
+        self.form.clean()
+        self.assert_no_errors()
+
+    def test_missing_txt_field(self):
+        self.form.cleaned_data['txt'] = ''
+        self.form.clean()
+        self.assert_error('txt', self.MSG_INVALID_RECORD)
+
+    def test_invalid_txt_field(self):
+        self.form.cleaned_data['txt'] = 'foo'
+        self.form.clean()
+        self.assert_error('txt', self.MSG_INVALID_RECORD)
+
+    def test_invalid_text_field_starting_dash(self):
+        self.form.cleaned_data['txt'] = '-2 1 d1eb0d876ec69d18bcef\
+                                         c4263ae43ec33ae14f4c'
+        self.form.clean()
+        self.assert_error('txt', self.MSG_INVALID_RECORD)
+
+    def test_invalid_text_field_trailing_dash(self):
+        self.form.cleaned_data['txt'] = '2 1 d1eb0d876ec69d18bcef\
+                                         c4263ae43ec33ae14f4c-'
+        self.form.clean()
+        self.assert_error('txt', self.MSG_INVALID_RECORD)
+
+    def test_invalid_text_field_bad_wild_card(self):
+        self.form.cleaned_data['txt'] = '1 2 e0d5320e7e36dea8e369b*'
+        self.form.clean()
+        self.assert_error('txt', self.MSG_INVALID_RECORD)
+
+    def test_default_assignment_data_field(self):
+        self.form.clean()
+        self.assertEqual(self.TEXT, self.form.cleaned_data['data'])
+
+    def test_invalid_name_field(self):
+        self.form.cleaned_data['name'] = 'foo-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_starting_dash(self):
+        self.form.cleaned_data['name'] = '-ww.foo.com'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_trailing_dash(self):
+        self.form.cleaned_data['name'] = 'www.foo.co-'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_invalid_name_field_bad_wild_card(self):
+        self.form.cleaned_data['name'] = 'derp.*'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
+
+    def test_outside_of_domain_name_field(self):
+        self.form.cleaned_data['name'] = 'www.bar.com.'
+        self.form.clean()
+        self.assert_error('name', self.MSG_INVALID_HOSTNAME)
diff --git a/doc/source/conf.py b/doc/source/conf.py
new file mode 100755
index 0000000..67602e2
--- /dev/null
+++ b/doc/source/conf.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+# 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 reqdashboardred by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('../..'))
+# -- General configuration ----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [
+    'sphinx.ext.autodoc',
+    #'sphinx.ext.intersphinx',
+    'openstackdocstheme'
+]
+
+# openstackdocstheme options
+repository_name = 'openstack/designate-dashboard'
+bug_project = 'designate-dashboard'
+bug_tag = ''
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+html_theme = 'openstackdocs'
+
+# autodoc generation is a bit aggressive and a ndashboardsance when doing heavy
+# text edit cycles.
+# execute "export SPHINX_DEBUG=1" in your terminal to disable
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'designate_dashboard'
+copyright = u'2013, OpenStack Foundation'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output --------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+# html_theme_path = ["."]
+# html_theme = '_theme'
+# html_static_path = ['static']
+
+# Output file base name for HTML help bdashboardlder.
+htmlhelp_basename = '%sdoc' % project
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass
+# [howto/manual]).
+latex_documents = [
+    ('index',
+     '%s.tex' % project,
+     u'%s Documentation' % project,
+     u'OpenStack Foundation', 'manual'),
+]
+
+# Example configuration for intersphinx: refer to the Python standard library.
+#intersphinx_mapping = {'http://docs.python.org/': None}
diff --git a/doc/source/contributor/index.rst b/doc/source/contributor/index.rst
new file mode 100644
index 0000000..3d4ceb4
--- /dev/null
+++ b/doc/source/contributor/index.rst
@@ -0,0 +1,5 @@
+============
+Contributing
+============
+
+.. include:: ../../../CONTRIBUTING.rst
diff --git a/doc/source/index.rst b/doc/source/index.rst
new file mode 100644
index 0000000..2bced7e
--- /dev/null
+++ b/doc/source/index.rst
@@ -0,0 +1,21 @@
+.. designate_ui documentation master file, created by
+   sphinx-quickstart on Tue Jul  9 22:26:36 2013.
+   You can adapt this file completely to your liking, but it should at least
+   contain the root `toctree` directive.
+
+================================================
+ Welcome to designatedashboard's documentation!
+================================================
+
+.. toctree::
+   :maxdepth: 2
+
+   readme
+   install/index
+   user/index
+   contributor/index
+
+.. rubric:: Indices and tables
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/doc/source/install/index.rst b/doc/source/install/index.rst
new file mode 100644
index 0000000..af26cf0
--- /dev/null
+++ b/doc/source/install/index.rst
@@ -0,0 +1,12 @@
+============
+Installation
+============
+
+At the command line::
+
+    $ pip install designate_dashboard
+
+Or, if you have virtualenvwrapper installed::
+
+    $ mkvirtualenv designate_dashboard
+    $ pip install designate_dashboard
\ No newline at end of file
diff --git a/doc/source/readme.rst b/doc/source/readme.rst
new file mode 100644
index 0000000..38ba804
--- /dev/null
+++ b/doc/source/readme.rst
@@ -0,0 +1 @@
+.. include:: ../../README.rst
\ No newline at end of file
diff --git a/doc/source/user/index.rst b/doc/source/user/index.rst
new file mode 100644
index 0000000..8ca3de4
--- /dev/null
+++ b/doc/source/user/index.rst
@@ -0,0 +1,7 @@
+========
+Usage
+========
+
+To use designate_dashboard in a project::
+
+	import designate_dashboard
\ No newline at end of file
diff --git a/karma.conf.js b/karma.conf.js
new file mode 100644
index 0000000..f437037
--- /dev/null
+++ b/karma.conf.js
@@ -0,0 +1,141 @@
+/*
+ *
+ * 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.
+ */
+
+'use strict';
+
+var fs = require('fs');
+var path = require('path');
+
+module.exports = function (config) {
+  // This tox venv is setup in the post-install npm step
+  var toxPath = '.tox/py27/lib/python2.7/site-packages/';
+  var xstaticPath = toxPath + 'xstatic/pkg/';
+
+  config.set({
+    preprocessors: {
+      // Used to collect templates for preprocessing.
+      // NOTE: the templates must also be listed in the files section below.
+      './static/**/*.html': ['ng-html2js']
+    },
+
+    // Sets up module to process templates.
+    ngHtml2JsPreprocessor: {
+      prependPrefix: '/',
+      moduleName: 'templates'
+    },
+
+    basePath: './',
+
+    // Contains both source and test files.
+    files: [
+      /*
+       * shim, partly stolen from /i18n/js/horizon/
+       * Contains expected items not provided elsewhere (dynamically by
+       * Django or via jasmine template.
+       */
+      './test-shim.js',
+
+      // from jasmine.html
+      xstaticPath + 'jquery/data/jquery.js',
+      xstaticPath + 'angular/data/angular.js',
+      xstaticPath + 'angular/data/angular-route.js',
+      xstaticPath + 'angular/data/angular-mocks.js',
+      xstaticPath + 'angular/data/angular-cookies.js',
+      xstaticPath + 'angular_bootstrap/data/angular-bootstrap.js',
+      xstaticPath + 'angular_gettext/data/angular-gettext.js',
+      xstaticPath + 'angular_fileupload/data/ng-file-upload-all.js',
+      xstaticPath + 'angular/data/angular-sanitize.js',
+      xstaticPath + 'd3/data/d3.js',
+      xstaticPath + 'rickshaw/data/rickshaw.js',
+      xstaticPath + 'angular_smart_table/data/smart-table.js',
+      xstaticPath + 'angular_lrdragndrop/data/lrdragndrop.js',
+      xstaticPath + 'spin/data/spin.js',
+      xstaticPath + 'spin/data/spin.jquery.js',
+      xstaticPath + 'tv4/data/tv4.js',
+      xstaticPath + 'objectpath/data/ObjectPath.js',
+      xstaticPath + 'angular_schema_form/data/schema-form.js',
+
+      // TODO: These should be mocked.
+      toxPath + '/horizon/static/horizon/js/horizon.js',
+
+      /**
+       * Include framework source code from horizon that we need.
+       * Otherwise, karma will not be able to find them when testing.
+       * These files should be mocked in the foreseeable future.
+       */
+      toxPath + 'horizon/static/framework/**/*.module.js',
+      toxPath + 'horizon/static/framework/**/!(*.spec|*.mock).js',
+      toxPath + 'openstack_dashboard/static/**/*.module.js',
+      toxPath + 'openstack_dashboard/static/**/!(*.spec|*.mock).js',
+      toxPath + 'openstack_dashboard/dashboards/**/static/**/*.module.js',
+      toxPath + 'openstack_dashboard/dashboards/**/static/**/!(*.spec|*.mock).js',
+
+      /**
+       * First, list all the files that defines application's angular modules.
+       * Those files have extension of `.module.js`. The order among them is
+       * not significant.
+       */
+      './designatedashboard/static/**/*.module.js',
+
+      /**
+       * Followed by other JavaScript files that defines angular providers
+       * on the modules defined in files listed above. And they are not mock
+       * files or spec files defined below. The order among them is not
+       * significant.
+       */
+      './designatedashboard/static/**/!(*.spec|*.mock).js',
+
+      /**
+       * Then, list files for mocks with `mock.js` extension. The order
+       * among them should not be significant.
+       */
+      toxPath + 'openstack_dashboard/static/**/*.mock.js',
+      //'./static/**/*.mock.js',
+
+      /**
+       * Finally, list files for spec with `spec.js` extension. The order
+       * among them should not be significant.
+       */
+      './designatedashboard/static/**/*.spec.js',
+
+      /**
+       * Angular external templates
+       */
+      './designatedashboard/static/**/*.html',
+
+    ],
+
+    autoWatch: true,
+
+    frameworks: ['jasmine'],
+
+    browsers: ['Chrome'],
+
+    phantomjsLauncher: {
+      // Have phantomjs exit if a ResourceError is encountered
+      // (useful if karma exits without killing phantom)
+      exitOnResourceError: true
+    },
+
+    reporters: ['progress'],
+
+    plugins: [
+      'karma-chrome-launcher',
+      'karma-jasmine',
+      'karma-ng-html2js-preprocessor'
+    ]
+
+  });
+};
diff --git a/manage.py b/manage.py
new file mode 100755
index 0000000..3cb4f27
--- /dev/null
+++ b/manage.py
@@ -0,0 +1,23 @@
+#!/usr/bin/env python
+# Copyright 2014 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.
+import os
+import sys
+
+
+if __name__ == "__main__":
+    os.environ.setdefault(
+        "DJANGO_SETTINGS_MODULE", "designatedashboard.settings")
+    from django.core.management import execute_from_command_line  # noqa
+    execute_from_command_line(sys.argv)
diff --git a/openstack-common.conf b/openstack-common.conf
new file mode 100644
index 0000000..1d8dcca
--- /dev/null
+++ b/openstack-common.conf
@@ -0,0 +1,6 @@
+[DEFAULT]
+
+# The list of modules to copy from oslo-incubator.git
+
+# The base module to hold the copy of openstack.common
+base=designatedashboard
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6ee7760
--- /dev/null
+++ b/package.json
@@ -0,0 +1,26 @@
+{
+  "version": "0.0.0",
+  "private": true,
+  "name": "designate-dashboard",
+  "description": "Designate Dashboard",
+  "repository": "none",
+  "license": "Apache 2.0",
+  "devDependencies": {
+    "eslint": "1.10.3",
+    "eslint-config-openstack": "1.2.4",
+    "jasmine-core": "2.4.1",
+    "karma": "1.1.2",
+    "karma-chrome-launcher": "1.0.1",
+    "karma-cli": "1.0.1",
+    "karma-jasmine": "1.0.2",
+    "karma-ng-html2js-preprocessor": "1.0.0"
+  },
+  "scripts": {
+    "postinstall": "if [ ! -d .venv ]; then tox -epy27 --notest; fi",
+    "lint": "eslint --no-color designatedashboard/static",
+    "lintq": "eslint --quiet designatedashboard/static",
+    "test": "karma start karma.conf.js --single-run"
+  },
+  "dependencies": {}
+}
+
diff --git a/releasenotes/notes/.placeholder b/releasenotes/notes/.placeholder
new file mode 100644
index 0000000..e69de29
diff --git a/releasenotes/source/conf.py b/releasenotes/source/conf.py
new file mode 100644
index 0000000..fd620eb
--- /dev/null
+++ b/releasenotes/source/conf.py
@@ -0,0 +1,281 @@
+# -*- coding: utf-8 -*-
+#
+# Designate dashboard Release Notes documentation build configuration file.
+#
+# This file is execfile()d with the current directory set to its
+# containing dir.
+#
+# Note that not all possible configuration values are present in this
+# autogenerated file.
+#
+# All configuration values have a default; values that are commented out
+# serve to show the default.
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+# sys.path.insert(0, os.path.abspath('.'))
+
+# -- General configuration ------------------------------------------------
+
+# If your documentation needs a minimal Sphinx version, state it here.
+# needs_sphinx = '1.0'
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
+# ones.
+
+extensions = [
+    'openstackdocstheme',
+    'reno.sphinxext',
+]
+
+# openstackdocstheme options
+repository_name = 'openstack/designate-dashboard'
+bug_project = 'designate-dashboard'
+bug_tag = ''
+html_last_updated_fmt = '%Y-%m-%d %H:%M'
+html_theme = 'openstackdocs'
+
+# Add any paths that contain templates here, relative to this directory.
+
+templates_path = ['_templates']
+
+# The suffix of source filenames.
+
+source_suffix = '.rst'
+
+# The encoding of source files.
+# source_encoding = 'utf-8-sig'
+
+# The master toctree document.
+
+master_doc = 'index'
+
+# General information about the project.
+
+project = u'Designate dashboard Client Release Notes'
+copyright = u'2016, Designate dashboard developers'
+
+# Release notes are version independent
+# The short X.Y version.
+version = ''
+# The full version, including alpha/beta/rc tags.
+release = ''
+
+# The language for content autogenerated by Sphinx. Refer to documentation
+# for a list of supported languages.
+# language = None
+
+# There are two options for replacing |today|: either, you set today to some
+# non-false value, then it is used:
+# today = ''
+# Else, today_fmt is used as the format for a strftime call.
+# today_fmt = '%B %d, %Y'
+
+# List of patterns, relative to source directory, that match files and
+# directories to ignore when looking for source files.
+
+exclude_patterns = []
+
+# The reST default role (used for this markup: `text`) to use for all
+# documents.
+# default_role = None
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+# add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+# add_module_names = True
+
+# If true, sectionauthor and moduleauthor directives will be shown in the
+# output. They are ignored by default.
+# show_authors = False
+
+# The name of the Pygments (syntax highlighting) style to use.
+
+pygments_style = 'sphinx'
+
+# A list of ignored prefixes for module index sorting.
+# modindex_common_prefix = []
+
+# If true, keep warnings as "system message" paragraphs in the built documents.
+# keep_warnings = False
+
+
+# -- Options for HTML output ----------------------------------------------
+
+# The theme to use for HTML and HTML Help pages.  See the documentation for
+# a list of builtin themes.
+
+# html_theme = 'default'
+
+# Theme options are theme-specific and customize the look and feel of a theme
+# further.  For a list of options available for each theme, see the
+# documentation.
+# html_theme_options = {}
+
+# Add any paths that contain custom themes here, relative to this directory.
+# html_theme_path = []
+
+# The name for this set of Sphinx documents.  If None, it defaults to
+# "<project> v<release> documentation".
+# html_title = None
+
+# A shorter title for the navigation bar.  Default is the same as html_title.
+# html_short_title = None
+
+# The name of an image file (relative to this directory) to place at the top
+# of the sidebar.
+# html_logo = None
+
+# The name of an image file (within the static path) to use as favicon of the
+# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
+# pixels large.
+# html_favicon = None
+
+# Add any paths that contain custom static files (such as style sheets) here,
+# relative to this directory. They are copied after the builtin static files,
+# so a file named "default.css" will overwrite the builtin "default.css".
+
+html_static_path = ['_static']
+
+# Add any extra paths that contain custom files (such as robots.txt or
+# .htaccess) here, relative to this directory. These files are copied
+# directly to the root of the documentation.
+# html_extra_path = []
+
+# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
+# using the given strftime format.
+# html_last_updated_fmt = '%b %d, %Y'
+
+# If true, SmartyPants will be used to convert quotes and dashes to
+# typographically correct entities.
+# html_use_smartypants = True
+
+# Custom sidebar templates, maps document names to template names.
+# html_sidebars = {}
+
+# Additional templates that should be rendered to pages, maps page names to
+# template names.
+# html_additional_pages = {}
+
+# If false, no module index is generated.
+# html_domain_indices = True
+
+# If false, no index is generated.
+# html_use_index = True
+
+# If true, the index is split into individual pages for each letter.
+# html_split_index = False
+
+# If true, links to the reST sources are added to the pages.
+# tml_show_sourcelink = True
+
+# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
+# tml_show_sphinx = True
+
+# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
+# tml_show_copyright = True
+
+# If true, an OpenSearch description file will be output, and all pages will
+# contain a <link> tag referring to it.  The value of this option must be the
+# base URL from which the finished HTML is served.
+# tml_use_opensearch = ''
+
+# This is the file name suffix for HTML files (e.g. ".xhtml").
+# tml_file_suffix = None
+
+# Output file base name for HTML help builder.
+
+htmlhelp_basename = 'DesignatedashboardReleaseNotestdoc'
+
+
+# -- Options for LaTeX output ---------------------------------------------
+
+latex_elements = {
+
+    # The paper size ('letterpaper' or 'a4paper').
+    # 'papersize': 'letterpaper',
+
+    # The font size ('10pt', '11pt' or '12pt').
+    # 'pointsize': '10pt',
+
+    # Additional stuff for the LaTeX preamble.
+    # 'preamble': '',
+}
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title,
+# author, documentclass [howto, manual, or own class]).
+
+latex_documents = [
+    ('index', 'PythonDesignatedashboard.tex',
+     u'Designate dashboard Release Notes Documentation',
+     u'Designate dashboard developers', 'manual'),
+]
+
+# The name of an image file (relative to this directory) to place at the top of
+# the title page.
+# atex_logo = None
+
+# For "manual" documents, if this is true, then toplevel headings are parts,
+# not chapters.
+# atex_use_parts = False
+
+# If true, show page references after internal links.
+# atex_show_pagerefs = False
+
+# If true, show URL addresses after external links.
+# atex_show_urls = False
+
+# Documents to append as an appendix to all manuals.
+# atex_appendices = []
+
+# If false, no module index is generated.
+# atex_domain_indices = True
+
+
+# -- Options for manual page output ---------------------------------------
+
+# One entry per manual page. List of tuples
+# (source start file, name, description, authors, manual section).
+
+man_pages = [
+    ('index', 'designatedashboard',
+     u'Designate dashboard Release Notes Documentation',
+     [u'Designate dashboard developers'], 1)
+]
+
+# If true, show URL addresses after external links.
+# an_show_urls = False
+
+
+# -- Options for Texinfo output -------------------------------------------
+
+# Grouping the document tree into Texinfo files. List of tuples
+# (source start file, target name, title, author,
+# dir menu entry, description, category)
+
+texinfo_documents = [
+    ('index', 'Designate dashboard',
+     u'Designate dashboard Release Notes Documentation',
+     u'Designate dashboard developers', 'Designate dashboard',
+     'One line description of project.', 'Miscellaneous'),
+]
+
+# Documents to append as an appendix to all manuals.
+# exinfo_appendices = []
+
+# If false, no module index is generated.
+# exinfo_domain_indices = True
+
+# How to display URL addresses: 'footnote', 'no', or 'inline'.
+# exinfo_show_urls = 'footnote'
+
+# If true, do not generate a @detailmenu in the "Top" node's menu.
+# exinfo_no_detailmenu = False
+
+# -- Options for Internationalization output ------------------------------
+locale_dirs = ['locale/']
diff --git a/releasenotes/source/index.rst b/releasenotes/source/index.rst
new file mode 100644
index 0000000..5b98ad9
--- /dev/null
+++ b/releasenotes/source/index.rst
@@ -0,0 +1,20 @@
+Welcome to Designate dashboard Agent Release Notes documentation!
+=================================================================
+
+Contents
+========
+
+.. toctree::
+   :maxdepth: 2
+
+   unreleased
+   pike
+   ocata
+   newton
+   mitaka
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`search`
diff --git a/releasenotes/source/locale/cs/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/cs/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..7144149
--- /dev/null
+++ b/releasenotes/source/locale/cs/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,45 @@
+# Zbyněk Schwarz <zbynek.schwarz@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-11-21 15:00+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-11-17 09:17+0000\n"
+"Last-Translator: Zbyněk Schwarz <zbynek.schwarz@gmail.com>\n"
+"Language-Team: Czech\n"
+"Language: cs\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "Obsah"
+
+msgid "Current Series Release Notes"
+msgstr "Poznámky k vydání současné verze"
+
+msgid "Indices and tables"
+msgstr "Rejstříky a tabulky"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Poznámky k vydání verze Mitaka"
+
+msgid "Newton Series Release Notes"
+msgstr "Poznámky k vydání verze Newton"
+
+msgid "Ocata Series Release Notes"
+msgstr "Poznámky k vydání verze Ocata"
+
+msgid "Pike Series Release Notes"
+msgstr "Poznámky k vydání verze Pike"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr "Vítejte v dokumentaci poznámek k vydání agenta nástěnky Designate!"
diff --git a/releasenotes/source/locale/de/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/de/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..cebef79
--- /dev/null
+++ b/releasenotes/source/locale/de/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,46 @@
+# Adriano Perri <adriano.perri@telekom.de>, 2017. #zanata
+# Frank Kloeker <eumel@arcor.de>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 5.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-08-24 13:30+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-08-25 12:08+0000\n"
+"Last-Translator: Adriano Perri <adriano.perri@telekom.de>\n"
+"Language-Team: German\n"
+"Language: de\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "Inhalt"
+
+msgid "Current Series Release Notes"
+msgstr "Aktuelle Serie Releasenotes"
+
+msgid "Indices and tables"
+msgstr "Indizes und Tabellen"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka Serie Releasenotes"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton Serie Releasenotes"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata Serie Releasenotes"
+
+msgid "Pike Series Release Notes"
+msgstr "Pike Serien Versionshinweise"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr "Willkommen bei den Releasenotes für den Designate Dashboard Agent"
diff --git a/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..e30bd4c
--- /dev/null
+++ b/releasenotes/source/locale/en_GB/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,45 @@
+# Andi Chandler <andi@gowling.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 5.0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-09-21 16:35+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-09-28 02:18+0000\n"
+"Last-Translator: Andi Chandler <andi@gowling.com>\n"
+"Language-Team: English (United Kingdom)\n"
+"Language: en-GB\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "Contents"
+
+msgid "Current Series Release Notes"
+msgstr "Current Series Release Notes"
+
+msgid "Indices and tables"
+msgstr "Indices and tables"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka Series Release Notes"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton Series Release Notes"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata Series Release Notes"
+
+msgid "Pike Series Release Notes"
+msgstr "Pike Series Release Notes"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr "Welcome to Designate dashboard Agent Release Notes documentation!"
diff --git a/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..e732216
--- /dev/null
+++ b/releasenotes/source/locale/fr/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,21 @@
+# Gaelle <pattedeph@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-11-21 15:00+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-11-14 07:50+0000\n"
+"Last-Translator: Gaelle <pattedeph@gmail.com>\n"
+"Language-Team: French\n"
+"Language: fr\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n > 1)\n"
+
+msgid "Contents"
+msgstr "Contenus"
+
+msgid "Current Series Release Notes"
+msgstr "Notes sur la Release Actuelle"
diff --git a/releasenotes/source/locale/id/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/id/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..fe7e7fc
--- /dev/null
+++ b/releasenotes/source/locale/id/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,45 @@
+# suhartono <cloudsuhartono@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 5.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-08-23 14:30+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-08-23 02:39+0000\n"
+"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
+"Language-Team: Indonesian\n"
+"Language: id\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "Contents (Isi)"
+
+msgid "Current Series Release Notes"
+msgstr "Series Release Notes saat ini"
+
+msgid "Indices and tables"
+msgstr "Indices and tables (indeks dan tabel)"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka Series Release Notes"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton Series Release Notes"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata Series Release Notes"
+
+msgid "Pike Series Release Notes"
+msgstr "Catatan Rilis Seri Pike"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr "Selamat Datang di dokumentasi Designate dashboard Agent Release Notes!"
diff --git a/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..f8e4e36
--- /dev/null
+++ b/releasenotes/source/locale/ko_KR/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,47 @@
+# Ian Y. Choi <ianyrchoi@gmail.com>, 2017. #zanata
+# Sungjin Kang <gang.sungjin@gmail.com>, 2017. #zanata
+# minwook-shin <minwook0106@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 5.0.1\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-09-20 03:15+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-09-20 05:25+0000\n"
+"Last-Translator: minwook-shin <minwook0106@gmail.com>\n"
+"Language-Team: Korean (South Korea)\n"
+"Language: ko-KR\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "내용"
+
+msgid "Current Series Release Notes"
+msgstr "최신 시리즈에 대한 릴리즈 노트"
+
+msgid "Indices and tables"
+msgstr "인텍스 및 테이블"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka 시리즈에 대한 릴리즈 노트"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton 시리즈에 대한 릴리즈 노트"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata 시리즈에 대한 릴리즈 노트"
+
+msgid "Pike Series Release Notes"
+msgstr "Pike 시리즈에 대한 릴리즈 노트"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr "대시 보드 에이전트 릴리스 노트 문서에 오신 것을 환영합니다!"
diff --git a/releasenotes/source/locale/pt_BR/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/pt_BR/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..073a2e5
--- /dev/null
+++ b/releasenotes/source/locale/pt_BR/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,46 @@
+# André Franciosi <andre@franciosi.org>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 5.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-08-22 13:02+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-08-22 10:43+0000\n"
+"Last-Translator: André Franciosi <andre@franciosi.org>\n"
+"Language-Team: Portuguese (Brazil)\n"
+"Language: pt-BR\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "Conteúdo"
+
+msgid "Current Series Release Notes"
+msgstr "Atual - Série de Notas de Versão"
+
+msgid "Indices and tables"
+msgstr "Índices e tabelas"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka - Série de Notas de Versão"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton - Série de Notas de Versão"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata - Série de Notas de Versão"
+
+msgid "Pike Series Release Notes"
+msgstr "Pike - Série de Notas de Versão"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr ""
+"Bemvindo a documentação de Notas de Versão do Agente dashboard do Designate!"
diff --git a/releasenotes/source/locale/ru/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/ru/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..2d3e798
--- /dev/null
+++ b/releasenotes/source/locale/ru/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,37 @@
+# Fedor Tarasenko <feodor.tarasenko@gmail.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 4.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-02-03 00:10+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-02-03 08:41+0000\n"
+"Last-Translator: Fedor Tarasenko <feodor.tarasenko@gmail.com>\n"
+"Language-Team: Russian\n"
+"Language: ru\n"
+"X-Generator: Zanata 3.7.3\n"
+"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
+"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "Содержание"
+
+msgid "Current Series Release Notes"
+msgstr "Примечания к текущему релизу"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Примечания к релизу Mitaka"
+
+msgid "Newton Series Release Notes"
+msgstr "Примечания к релизу Newton"
+
+msgid "Ocata Series Release Notes"
+msgstr "Примечания к релизу Ocata"
diff --git a/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po
new file mode 100644
index 0000000..7ae05c7
--- /dev/null
+++ b/releasenotes/source/locale/zh_CN/LC_MESSAGES/releasenotes.po
@@ -0,0 +1,42 @@
+# TigerFang <tigerfun@126.com>, 2017. #zanata
+msgid ""
+msgstr ""
+"Project-Id-Version: Designate dashboard Client Release Notes 5.0.0\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2017-05-04 14:35+0000\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"PO-Revision-Date: 2017-05-15 09:55+0000\n"
+"Last-Translator: TigerFang <tigerfun@126.com>\n"
+"Language-Team: Chinese (China)\n"
+"Language: zh-CN\n"
+"X-Generator: Zanata 3.9.6\n"
+"Plural-Forms: nplurals=1; plural=0\n"
+
+msgid ":ref:`genindex`"
+msgstr ":ref:`genindex`"
+
+msgid ":ref:`search`"
+msgstr ":ref:`search`"
+
+msgid "Contents"
+msgstr "内容"
+
+msgid "Current Series Release Notes"
+msgstr "当前版本发布说明"
+
+msgid "Indices and tables"
+msgstr "索引和表格"
+
+msgid "Mitaka Series Release Notes"
+msgstr "Mitaka 版本发布说明"
+
+msgid "Newton Series Release Notes"
+msgstr "Newton版本发布说明"
+
+msgid "Ocata Series Release Notes"
+msgstr "Ocata版本发布说明"
+
+msgid "Welcome to Designate dashboard Agent Release Notes documentation!"
+msgstr "欢迎使用Designate控制台代理发布说明文档!"
diff --git a/releasenotes/source/mitaka.rst b/releasenotes/source/mitaka.rst
new file mode 100644
index 0000000..97ab8d1
--- /dev/null
+++ b/releasenotes/source/mitaka.rst
@@ -0,0 +1,6 @@
+============================
+ Mitaka Series Release Notes
+============================
+
+.. release-notes::
+   :branch: origin/stable/mitaka
diff --git a/releasenotes/source/newton.rst b/releasenotes/source/newton.rst
new file mode 100644
index 0000000..be21859
--- /dev/null
+++ b/releasenotes/source/newton.rst
@@ -0,0 +1,6 @@
+============================
+ Newton Series Release Notes
+============================
+
+.. release-notes::
+   :branch: origin/stable/newton
diff --git a/releasenotes/source/ocata.rst b/releasenotes/source/ocata.rst
new file mode 100644
index 0000000..ebe62f4
--- /dev/null
+++ b/releasenotes/source/ocata.rst
@@ -0,0 +1,6 @@
+===================================
+ Ocata Series Release Notes
+===================================
+
+.. release-notes::
+   :branch: origin/stable/ocata
diff --git a/releasenotes/source/pike.rst b/releasenotes/source/pike.rst
new file mode 100644
index 0000000..e43bfc0
--- /dev/null
+++ b/releasenotes/source/pike.rst
@@ -0,0 +1,6 @@
+===================================
+ Pike Series Release Notes
+===================================
+
+.. release-notes::
+   :branch: stable/pike
diff --git a/releasenotes/source/unreleased.rst b/releasenotes/source/unreleased.rst
new file mode 100644
index 0000000..875030f
--- /dev/null
+++ b/releasenotes/source/unreleased.rst
@@ -0,0 +1,5 @@
+============================
+Current Series Release Notes
+============================
+
+.. release-notes::
diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..e7b376a
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,7 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+oslo.log>=3.30.0 # Apache-2.0
+pbr!=2.1.0,>=2.0.0 # Apache-2.0
+Babel!=2.4.0,>=2.3.4 # BSD
+python-designateclient>=2.7.0 # Apache-2.0
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..0a48004
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,46 @@
+[metadata]
+name = designate-dashboard
+summary = Designate Horizon UI bits
+description-file =
+    README.rst
+author = OpenStack
+author-email = openstack-dev@lists.openstack.org
+home-page = http://docs.openstack.org/developer/designate-dashboard/
+classifier =
+    Environment :: OpenStack
+    Intended Audience :: Information Technology
+    Intended Audience :: System Administrators
+    License :: OSI Approved :: Apache Software License
+    Operating System :: POSIX :: Linux
+    Programming Language :: Python
+    Programming Language :: Python :: 2
+    Programming Language :: Python :: 2.7
+    Programming Language :: Python :: 3
+    Programming Language :: Python :: 3.5
+
+[files]
+packages =
+    designatedashboard
+
+[build_sphinx]
+source-dir = doc/source
+build-dir = doc/build
+all_files = 1
+warning-is-error = 1
+
+[upload_sphinx]
+upload-dir = doc/build/html
+
+[compile_catalog]
+directory = designatedashboard/locale
+domain = django
+
+[update_catalog]
+domain = django
+output_dir = designatedashboard/locale
+input_file = designatedashboard/locale/django.pot
+
+[extract_messages]
+keywords = _ gettext ngettext l_ lazy_gettext
+mapping_file = babel-django.cfg
+output_file = designatedashboard/locale/django.pot
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..566d844
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,29 @@
+# Copyright (c) 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.
+
+# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT
+import setuptools
+
+# In python < 2.7.4, a lazy loading of package `pbr` will break
+# setuptools if some other modules registered functions in `atexit`.
+# solution from: http://bugs.python.org/issue15881#msg170215
+try:
+    import multiprocessing  # noqa
+except ImportError:
+    pass
+
+setuptools.setup(
+    setup_requires=['pbr>=2.0.0'],
+    pbr=True)
diff --git a/test b/test
new file mode 100644
index 0000000..dd690e3
Binary files /dev/null and b/test differ
diff --git a/test-requirements.txt b/test-requirements.txt
new file mode 100644
index 0000000..3c3716d
--- /dev/null
+++ b/test-requirements.txt
@@ -0,0 +1,26 @@
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+hacking!=0.13.0,<0.14,>=0.12.0 # Apache-2.0
+
+coverage!=4.4,>=4.0 # Apache-2.0
+mock>=2.0.0 # BSD
+mox>=0.5.3 # Apache-2.0
+mox3>=0.20.0 # Apache-2.0
+oslo.config>=5.1.0 # Apache-2.0
+pylint==1.4.5 # GPLv2
+testrepository>=0.0.18 # Apache-2.0/BSD
+testtools>=2.2.0 # MIT
+unittest2>=1.1.0 # BSD
+sphinx>=1.6.2 # BSD
+openstackdocstheme>=1.17.0 # Apache-2.0
+reno>=2.5.0 # Apache-2.0
+nose>=1.3.7 # LGPL
+nosehtmloutput>=0.0.3 # Apache-2.0
+openstack.nose-plugin>=0.7 # Apache-2.0
+django-nose>=1.4.4 # BSD
+nosexcover>=1.0.10 # BSD
+
+# Horizon requirements
+Django<2.0,>=1.8 # BSD
+django-compressor>=2.0 # MIT
diff --git a/test-shim.js b/test-shim.js
new file mode 100644
index 0000000..176b1c3
--- /dev/null
+++ b/test-shim.js
@@ -0,0 +1,110 @@
+/**
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
+ * not use this file except in compliance with the License. You may obtain
+ * a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ *  Shim for Javascript unit tests; supplying expected global features.
+ *  This should be removed from the codebase once i18n services are provided.
+ *  Taken from default i18n file provided by Django.
+ */
+
+var horizonPlugInModules = [];
+
+
+(function (globals) {
+
+  var django = globals.django || (globals.django = {});
+
+
+  django.pluralidx = function (count) { return (count == 1) ? 0 : 1; };
+
+  /* gettext identity library */
+
+  django.gettext = function (msgid) { return msgid; };
+  django.ngettext = function (singular, plural, count) { return (count == 1) ? singular : plural; };
+  django.gettext_noop = function (msgid) { return msgid; };
+  django.pgettext = function (context, msgid) { return msgid; };
+  django.npgettext = function (context, singular, plural, count) { return (count == 1) ? singular : plural; };
+
+
+  django.interpolate = function (fmt, obj, named) {
+    if (named) {
+      return fmt.replace(/%\(\w+\)s/g, function(match){return String(obj[match.slice(2,-2)])});
+    } else {
+      return fmt.replace(/%s/g, function(match){return String(obj.shift())});
+    }
+  };
+
+
+  /* formatting library */
+
+  django.formats = {
+    "DATETIME_FORMAT": "N j, Y, P",
+    "DATETIME_INPUT_FORMATS": [
+      "%Y-%m-%d %H:%M:%S",
+      "%Y-%m-%d %H:%M:%S.%f",
+      "%Y-%m-%d %H:%M",
+      "%Y-%m-%d",
+      "%m/%d/%Y %H:%M:%S",
+      "%m/%d/%Y %H:%M:%S.%f",
+      "%m/%d/%Y %H:%M",
+      "%m/%d/%Y",
+      "%m/%d/%y %H:%M:%S",
+      "%m/%d/%y %H:%M:%S.%f",
+      "%m/%d/%y %H:%M",
+      "%m/%d/%y"
+    ],
+    "DATE_FORMAT": "N j, Y",
+    "DATE_INPUT_FORMATS": [
+      "%Y-%m-%d",
+      "%m/%d/%Y",
+      "%m/%d/%y"
+    ],
+    "DECIMAL_SEPARATOR": ".",
+    "FIRST_DAY_OF_WEEK": "0",
+    "MONTH_DAY_FORMAT": "F j",
+    "NUMBER_GROUPING": "3",
+    "SHORT_DATETIME_FORMAT": "m/d/Y P",
+    "SHORT_DATE_FORMAT": "m/d/Y",
+    "THOUSAND_SEPARATOR": ",",
+    "TIME_FORMAT": "P",
+    "TIME_INPUT_FORMATS": [
+      "%H:%M:%S",
+      "%H:%M:%S.%f",
+      "%H:%M"
+    ],
+    "YEAR_MONTH_FORMAT": "F Y"
+  };
+
+  django.get_format = function (format_type) {
+    var value = django.formats[format_type];
+    if (typeof(value) == 'undefined') {
+      return format_type;
+    } else {
+      return value;
+    }
+  };
+
+  /* add to global namespace */
+  globals.pluralidx = django.pluralidx;
+  globals.gettext = django.gettext;
+  globals.ngettext = django.ngettext;
+  globals.gettext_noop = django.gettext_noop;
+  globals.pgettext = django.pgettext;
+  globals.npgettext = django.npgettext;
+  globals.interpolate = django.interpolate;
+  globals.get_format = django.get_format;
+  globals.STATIC_URL = '/static/';
+  globals.WEBROOT = '/';
+
+}(this));
diff --git a/tox.ini b/tox.ini
new file mode 100644
index 0000000..97a23de
--- /dev/null
+++ b/tox.ini
@@ -0,0 +1,48 @@
+[tox]
+minversion = 1.6
+envlist = py35,py27,pep8
+skipsdist = True
+
+[testenv]
+usedevelop = True
+install_command = pip install -c{env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt?h=master} {opts} {packages}
+setenv = VIRTUAL_ENV={envdir}
+         NOSE_WITH_OPENSTACK=1
+         NOSE_OPENSTACK_COLOR=1
+         NOSE_OPENSTACK_RED=0.05
+         NOSE_OPENSTACK_YELLOW=0.025
+         NOSE_OPENSTACK_SHOW_ELAPSED=1
+         PYTHONDONTWRITEBYTECODE=1
+         DJANGO_SETTINGS_MODULE=designatedashboard.settings
+whitelist_externals = find
+deps = -r{toxinidir}/requirements.txt
+       -r{toxinidir}/test-requirements.txt
+       http://tarballs.openstack.org/horizon/horizon-master.tar.gz
+
+commands =
+    find . -type f -name "*.pyc" -delete
+    {toxinidir}/manage.py test designatedashboard --settings=designatedashboard.tests.settings
+passenv = http_proxy HTTP_PROXY https_proxy HTTPS_PROXY no_proxy NO_PROXY
+
+[testenv:pep8]
+commands = flake8
+
+[testenv:venv]
+commands = {posargs}
+
+[testenv:cover]
+commands = {toxinidir}/manage.py test designatedashboard --settings=designatedashboard.tests.settings --cover-xml
+
+[testenv:docs]
+commands = python setup.py build_sphinx
+
+[flake8]
+# E123, E125 skipped as they are invalid PEP-8.
+
+show-source = True
+ignore = E123,E125
+builtins = _
+exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build,releasenotes
+
+[testenv:releasenotes]
+commands = sphinx-build -a -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html