ironic-ui 2.0.0 release
meta:version: 2.0.0 meta:diff-start: - meta:series: newton meta:release-type: release meta:announce: openstack-announce@lists.openstack.org meta:pypi: yes meta:first: yes meta:release:Author: Elizabeth Elwell <e.r.elwell@gmail.com> meta:release:Commit: Elizabeth Elwell <e.r.elwell@gmail.com> meta:release:Change-Id: I7aca3d9fe3d97e1ab725dc52ace9a6a0de3ccf88 meta:release:Code-Review+1: Jim Rollenhagen <jim@jimrollenhagen.com> meta:release:Code-Review+2: Doug Hellmann <doug@doughellmann.com> meta:release:Workflow+1: Doug Hellmann <doug@doughellmann.com> -----BEGIN PGP SIGNATURE----- Comment: GPGTools - http://gpgtools.org iQEcBAABAgAGBQJXtfb1AAoJEDttBqDEKEN6W2AH/3g8hGg9t4AIH0IcxydBjCXE bvVzO8/c6Ib42z45+XUoZTLOu9tjivQ3hQpIEytwpaNnKWcELk1tEaQQdurMXcUn aPal7ljXTD1EbD1gmIuDPh9XgoDPHLg/1BbeYnCbE0TGbxect/jfWYfdyBGql793 cduPkA3yXFzhdHU3LFdofGoz+vnrzMJmLiiH4DLcZeuJBnoRWWipi8tlaanVNO9t jQIJfmMuSSO+IbYL3i4GdshcfJRn8roZDqY295tnZlOy5Nc2Dw3GAuliB68/X6sy dXGhQ5mncR2D6IO8RIQ0l260ZB+9e6KT9D8pXTDcqjk3TWyEWoQPYma8C/dxBZk= =bxCb -----END PGP SIGNATURE----- Merge tag '2.0.0' into debian/newton ironic-ui 2.0.0 release * New upstream release. Change-Id: If0ce5982efe48af6841e91c95b1520162727ac85
This commit is contained in:
commit
ca8a9eef3d
|
@ -60,3 +60,6 @@ ChangeLog
|
|||
package/
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
|
||||
# release notes build
|
||||
releasenotes/build
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
[gerrit]
|
||||
host=review.openstack.org
|
||||
port=29418
|
||||
project=openstack/ironic-ui.git
|
||||
defaultbranch=stable/mitaka
|
||||
project=openstack/deb-ironic-ui.git
|
||||
defaultbranch=debian/newton
|
||||
|
|
|
@ -34,7 +34,7 @@ installation please see http://docs.openstack.org/developer/horizon/quickstart.h
|
|||
|
||||
`source .venv/bin/activate`
|
||||
|
||||
3. Copy the _2200_ironic.py file from ironic-ui/enabled directory to
|
||||
3. Copy the _2200_ironic.py file from ironic_ui/enabled directory to
|
||||
horizon/openstack_dashboard/local/enabled
|
||||
|
||||
4. Change into the ironic-ui repository and package the plugin:
|
||||
|
|
|
@ -1,13 +1,5 @@
|
|||
ironic-ui (1.1.1-2) UNRELEASED; urgency=medium
|
||||
ironic-ui (2.0.0-1) experimental; urgency=medium
|
||||
|
||||
* Standards-Version is 3.9.8 now (no change)
|
||||
* d/rules: Removed UPSTREAM_GIT with default value
|
||||
* d/copyright: Changed source URL to https protocol
|
||||
* Initial release. (Closes: #839076)
|
||||
|
||||
-- Ondřej Nový <novy@ondrej.org> Sat, 09 Apr 2016 19:23:10 +0200
|
||||
|
||||
ironic-ui (1.1.1-1) unstable; urgency=medium
|
||||
|
||||
* Initial release. (Closes: #XXXXXX)
|
||||
|
||||
-- Thomas Goirand <zigo@debian.org> Mon, 21 Mar 2016 22:26:27 +0000
|
||||
-- Thomas Goirand <zigo@debian.org> Wed, 28 Sep 2016 17:06:05 +0200
|
||||
|
|
|
@ -10,15 +10,27 @@ Build-Depends: debhelper (>= 9),
|
|||
python-pbr (>= 1.8),
|
||||
python-setuptools,
|
||||
python-sphinx,
|
||||
Build-Depends-Indep: openstack-dashboard (>= 2:9.0.0~rc1),
|
||||
Build-Depends-Indep: openstack-dashboard (>= 3:10.0.0~rc2),
|
||||
python-coverage,
|
||||
python-django-nose (>= 1.2),
|
||||
python-hacking,
|
||||
python-ironicclient (>= 1.1.0),
|
||||
python-oslosphinx (>= 2.5.0),
|
||||
python-oslotest (>= 1:1.10.0),
|
||||
python-subunit,
|
||||
python-testscenarios,
|
||||
python-testtools (>= 1.4.0),
|
||||
subunit,
|
||||
testrepository,
|
||||
Standards-Version: 3.9.8
|
||||
Vcs-Git: https://anonscm.debian.org/git/openstack/ironic-ui.git
|
||||
Vcs-Browser: https://anonscm.debian.org/gitweb/?p=openstack/ironic-ui.git;a=summary
|
||||
Vcs-Browser: https://git.openstack.org/cgit/openstack/deb-ironic-ui?h=debian%2Fnewton
|
||||
Vcs-Git: https://git.openstack.org/openstack/deb-ironic-ui -b debian/newton
|
||||
Homepage: https://github.com/openstack/ironic-ui
|
||||
|
||||
Package: python-ironic-ui
|
||||
Architecture: all
|
||||
Depends: openstack-dashboard (>= 2:9.0.0~rc1),
|
||||
Depends: openstack-dashboard (>= 3:10.0.0~rc2),
|
||||
python-ironicclient (>= 1.1.0),
|
||||
python-pbr (>= 1.8),
|
||||
python-six (>= 1.7.0),
|
||||
${misc:Depends},
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
# This is a false positive from Lintian: this really is a source file
|
||||
# and not a minimized .js file: it just happens to have a long line.
|
||||
ironic-ui source: source-is-missing ironic_ui/static/dashboard/admin/ironic/enroll-node/enroll-node.service.js line length is 1173 characters (>512)
|
|
@ -0,0 +1,2 @@
|
|||
extend-diff-ignore = "^[^/]*[.]egg-info/"
|
||||
extend-diff-ignore = "^[.]gitreview$"
|
|
@ -1,4 +1,50 @@
|
|||
============
|
||||
Contributing
|
||||
============
|
||||
.. include:: ../../CONTRIBUTING.rst
|
||||
.. _contributing:
|
||||
|
||||
=========================
|
||||
Contributing to Ironic UI
|
||||
=========================
|
||||
|
||||
If you're interested in contributing to the Ironic UI project,
|
||||
the following will help get you started.
|
||||
|
||||
Contributor License Agreement
|
||||
-----------------------------
|
||||
|
||||
.. index::
|
||||
single: license; agreement
|
||||
|
||||
In order to contribute to the Ironic project, you need to have
|
||||
signed OpenStack's contributor's agreement.
|
||||
|
||||
.. seealso::
|
||||
|
||||
* http://docs.openstack.org/infra/manual/developers.html
|
||||
* http://wiki.openstack.org/CLA
|
||||
|
||||
LaunchPad Project
|
||||
-----------------
|
||||
|
||||
Most of the tools used for OpenStack depend on a launchpad.net ID for
|
||||
authentication.
|
||||
|
||||
.. seealso::
|
||||
|
||||
* https://launchpad.net
|
||||
* https://launchpad.net/ironic
|
||||
|
||||
Project Hosting Details
|
||||
-------------------------
|
||||
|
||||
Bug tracker
|
||||
http://launchpad.net/ironic-ui
|
||||
|
||||
Mailing list (prefix subjects with ``[ironic-ui]`` for faster responses)
|
||||
http://lists.openstack.org/cgi-bin/mailman/listinfo/openstack-dev
|
||||
|
||||
Code Hosting
|
||||
https://github.com/openstack/ironic-ui
|
||||
|
||||
Code Review
|
||||
https://review.openstack.org/#/q/status:open+project:openstack/ironic-ui,n,z
|
||||
|
||||
|
||||
|
|
|
@ -1,25 +1,33 @@
|
|||
.. ironic-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 Ironic UI's developer documentation!
|
||||
===============================================
|
||||
|
||||
Welcome to ironic-ui's documentation!
|
||||
========================================================
|
||||
Introduction
|
||||
============
|
||||
|
||||
Contents:
|
||||
The ironic UI is an OpenStack Horizon plugin that will allow users to view and
|
||||
manage their ironic bare metal nodes, ports and drivers.
|
||||
|
||||
The documentation provided here is continually kept up-to-date based
|
||||
on the latest code that has been committed, and may not represent the state of
|
||||
the project at any specific prior release.
|
||||
|
||||
For information on any current or prior version of Ironic, see `the release
|
||||
notes`_.
|
||||
|
||||
.. _the release notes: http://docs.openstack.org/releasenotes/ironic-ui/
|
||||
|
||||
For more information on ironic, see `the ironic documentation`_.
|
||||
|
||||
.. _the ironic documentation: http://docs.openstack.org/developer/ironic/
|
||||
|
||||
Administrator's Guide
|
||||
=====================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
readme
|
||||
installation
|
||||
usage
|
||||
contributing
|
||||
|
||||
Indices and tables
|
||||
==================
|
||||
|
||||
* :ref:`genindex`
|
||||
* :ref:`modindex`
|
||||
* :ref:`search`
|
||||
:maxdepth: 1
|
||||
|
||||
Introduction to ironic UI <readme>
|
||||
Introduction to ironic <http://docs.openstack.org/developer/ironic/deploy/user-guide.html>
|
||||
Installing the ironic UI <installation>
|
||||
Contributing <contributing>
|
||||
|
|
|
@ -1,12 +1,40 @@
|
|||
============
|
||||
Installation
|
||||
============
|
||||
.. _installation:
|
||||
|
||||
At the command line::
|
||||
======================
|
||||
Ironic-UI Installation
|
||||
======================
|
||||
|
||||
$ pip install ironic-ui
|
||||
Please note that the following instructions assume that you have an existing
|
||||
installation of the OpenStack Horizon dashboard application. For Horizon
|
||||
installation please see http://docs.openstack.org/developer/horizon/quickstart.html
|
||||
|
||||
Or, if you have virtualenvwrapper installed::
|
||||
1. Clone Ironic UI repository:
|
||||
|
||||
$ mkvirtualenv ironic-ui
|
||||
$ pip install ironic-ui
|
||||
`git clone https://git.openstack.org/openstack/ironic-ui`
|
||||
|
||||
2. Change into the root directory of your horizon installation and run the venv.
|
||||
NOTE: this has been preinstalled when horizon was setup with ./run_tests.sh -
|
||||
do not reinstall venv
|
||||
|
||||
`source .venv/bin/activate`
|
||||
|
||||
3. Copy the _2200_ironic.py file from ironic_ui/enabled directory to
|
||||
horizon/openstack_dashboard/local/enabled
|
||||
|
||||
4. Change into the ironic-ui repository and package the plugin:
|
||||
|
||||
`pip install -r requirements.txt -e .`
|
||||
|
||||
This will build and install the ironic-ui plugin into the active virtual
|
||||
environment associated with your horizon installation. The plugin is installed
|
||||
in "editable" mode as a link back to your ironic-ui plugin directory.
|
||||
|
||||
5. Change back into the horizon repository and bring up your environment:
|
||||
|
||||
`./run_tests.sh --runserver`
|
||||
|
||||
The Bare Metal service should now be visible in the Horizon navigation.
|
||||
|
||||
To uninstall, use pip uninstall (find the name of the package to uninstall by
|
||||
running pip list from inside the horizon .venv). You will also need to remove
|
||||
the enabled file from the openstack_dashboard/enabled folder.
|
||||
|
|
|
@ -117,3 +117,73 @@ def node_set_maintenance(request, node_id, state, maint_reason=None):
|
|||
node_id,
|
||||
state,
|
||||
maint_reason=maint_reason)
|
||||
|
||||
|
||||
def node_create(request, params):
|
||||
"""Create a node
|
||||
|
||||
:param request: HTTP request.
|
||||
:param params: Dictionary of node parameters
|
||||
"""
|
||||
node_manager = ironicclient(request).node
|
||||
node = node_manager.create(**params)
|
||||
field_list = ['chassis_uuid',
|
||||
'driver',
|
||||
'driver_info',
|
||||
'properties',
|
||||
'extra',
|
||||
'uuid',
|
||||
'name']
|
||||
return dict([(f, getattr(node, f, '')) for f in field_list])
|
||||
|
||||
|
||||
def node_delete(request, node_id):
|
||||
"""Delete a node from inventory.
|
||||
|
||||
:param request: HTTP request.
|
||||
:param node_id: The UUID of the node.
|
||||
:return: node.
|
||||
|
||||
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.delete
|
||||
"""
|
||||
return ironicclient(request).node.delete(node_id)
|
||||
|
||||
|
||||
def driver_list(request):
|
||||
"""Retrieve a list of drivers.
|
||||
|
||||
:param request: HTTP request.
|
||||
:return: A list of drivers.
|
||||
"""
|
||||
return ironicclient(request).driver.list()
|
||||
|
||||
|
||||
def driver_properties(request, driver_name):
|
||||
"""Retrieve the properties of a specified driver
|
||||
|
||||
:param request: HTTP request
|
||||
:param driver_name: Name of the driver
|
||||
:return: Property list
|
||||
"""
|
||||
return ironicclient(request).driver.properties(driver_name)
|
||||
|
||||
|
||||
def port_create(request, params):
|
||||
"""Create network port
|
||||
|
||||
:param request: HTTP request
|
||||
:param params: Port creation parameters
|
||||
:return: Port
|
||||
"""
|
||||
port_manager = ironicclient(request).port
|
||||
return port_manager.create(**params)
|
||||
|
||||
|
||||
def port_delete(request, port_uuid):
|
||||
"""Delete a network port
|
||||
|
||||
:param request: HTTP request
|
||||
:param port_uuid: Port uuid
|
||||
:return: Port
|
||||
"""
|
||||
return ironicclient(request).port.delete(port_uuid)
|
||||
|
|
|
@ -40,6 +40,24 @@ class Nodes(generic.View):
|
|||
'items': [i.to_dict() for i in items],
|
||||
}
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def post(self, request):
|
||||
"""Create an Ironic node
|
||||
|
||||
:param request: HTTP request
|
||||
"""
|
||||
params = request.DATA.get('node')
|
||||
return ironic.node_create(request, params)
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def delete(self, request):
|
||||
"""Delete an Ironic node from inventory
|
||||
|
||||
:param request: HTTP request
|
||||
"""
|
||||
params = request.DATA.get('node')
|
||||
return ironic.node_delete(request, params)
|
||||
|
||||
|
||||
@urls.register
|
||||
class Node(generic.View):
|
||||
|
@ -75,6 +93,25 @@ class Ports(generic.View):
|
|||
'items': [i.to_dict() for i in items],
|
||||
}
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def post(self, request):
|
||||
"""Create a network port
|
||||
|
||||
:param request: HTTP request
|
||||
:return: Port
|
||||
"""
|
||||
port = request.DATA.get('port')
|
||||
return ironic.port_create(request, port).to_dict()
|
||||
|
||||
@rest_utils.ajax(data_required=True)
|
||||
def delete(self, request):
|
||||
"""Delete a network port
|
||||
|
||||
:param request: HTTP request
|
||||
"""
|
||||
params = request.DATA.get('port_uuid')
|
||||
return ironic.port_delete(request, params)
|
||||
|
||||
|
||||
@urls.register
|
||||
class StatesPower(generic.View):
|
||||
|
@ -122,3 +159,37 @@ class Maintenance(generic.View):
|
|||
:return: Return code
|
||||
"""
|
||||
return ironic.node_set_maintenance(request, node_id, 'off')
|
||||
|
||||
|
||||
@urls.register
|
||||
class Drivers(generic.View):
|
||||
|
||||
url_regex = r'ironic/drivers/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get the list of drivers
|
||||
|
||||
:param request: HTTP request
|
||||
:return: drivers
|
||||
"""
|
||||
items = ironic.driver_list(request)
|
||||
return {
|
||||
'items': [i.to_dict() for i in items]
|
||||
}
|
||||
|
||||
|
||||
@urls.register
|
||||
class DriverProperties(generic.View):
|
||||
|
||||
url_regex = r'ironic/drivers/(?P<driver_name>[0-9a-zA-Z_-]+)/properties$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request, driver_name):
|
||||
"""Get the properties associated with a specified driver
|
||||
|
||||
:param request: HTTP request
|
||||
:param driver_name: Driver name
|
||||
:return: Dictionary of properties
|
||||
"""
|
||||
return ironic.driver_properties(request, driver_name)
|
||||
|
|
|
@ -17,7 +17,27 @@ from django.utils.translation import ugettext_lazy as _
|
|||
|
||||
import horizon
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.dashboards.admin import dashboard
|
||||
|
||||
|
||||
class Ironic(horizon.Panel):
|
||||
name = _("Ironic Bare Metal Provisioning")
|
||||
slug = 'ironic'
|
||||
permissions = ('openstack.roles.admin',)
|
||||
|
||||
def allowed(self, context):
|
||||
request = context['request']
|
||||
if not base.is_service_enabled(request, 'baremetal'):
|
||||
return False
|
||||
else:
|
||||
return super(Ironic, self).allowed(context)
|
||||
|
||||
def nav(self, context):
|
||||
request = context['request']
|
||||
if not base.is_service_enabled(request, 'baremetal'):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
dashboard.Admin.register(Ironic)
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
{% load i18n %}
|
||||
{% block title %}{% trans "Ironic Bare Metal Provisioning" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class="breadcrumb">
|
||||
<li>{% trans "Admin" %}</li>
|
||||
<li>{% trans "System" %}</li>
|
||||
<li class="active">{% trans "Ironic Bare Metal Provisioning" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Ironic Bare Metal Provisioning" %}"></hz-page-header>
|
||||
{% endblock %}
|
||||
|
|
|
@ -2,6 +2,15 @@
|
|||
{% load i18n %}
|
||||
{% block title %}{% trans "Node Details" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class="breadcrumb">
|
||||
<li>{% trans "Admin" %}</li>
|
||||
<li>{% trans "System" %}</li>
|
||||
<li>{% trans "Ironic Bare Metal Provisioning" %}</li>
|
||||
<li class="active">{% trans "Node Details" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<hz-page-header header="{% trans "Node Details" %}"></hz-page-header>
|
||||
{% endblock %}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
# Andi Chandler <andi@gowling.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev28\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-07-13 21:09+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-06-29 01:16+0000\n"
|
||||
"Last-Translator: Andi Chandler <andi@gowling.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 "Ironic Bare Metal Provisioning"
|
||||
msgstr "Ironic Bare Metal Provisioning"
|
|
@ -0,0 +1,311 @@
|
|||
# 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
|
||||
# Rob Cresswell <robert.cresswell@outlook.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev28\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-07-13 21:09+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-06-29 01:25+0000\n"
|
||||
"Last-Translator: Andi Chandler <andi@gowling.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 "A unique node name. Optional."
|
||||
msgstr "A unique node name. Optional."
|
||||
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
msgid "Add Extra:"
|
||||
msgstr "Add Extra:"
|
||||
|
||||
msgid "Add New Property:"
|
||||
msgstr "Add New Property:"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
|
||||
msgstr ""
|
||||
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
|
||||
msgstr ""
|
||||
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Cancel"
|
||||
|
||||
msgid "Capabilities"
|
||||
msgstr "Capabilities"
|
||||
|
||||
msgid "Chassis ID"
|
||||
msgstr "Chassis ID"
|
||||
|
||||
msgid "Choose an Image"
|
||||
msgstr "Choose an Image"
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "Configuration"
|
||||
|
||||
msgid "Console Enabled"
|
||||
msgstr "Console Enabled"
|
||||
|
||||
msgid "Created At"
|
||||
msgstr "Created At"
|
||||
|
||||
msgid "Delete Node"
|
||||
msgstr "Delete Node"
|
||||
|
||||
msgid "Delete Nodes"
|
||||
msgstr "Delete Nodes"
|
||||
|
||||
msgid "Delete node"
|
||||
msgstr "Delete node"
|
||||
|
||||
msgid "Delete nodes"
|
||||
msgstr "Delete nodes"
|
||||
|
||||
msgid "Deploy Kernel"
|
||||
msgstr "Deploy Kernel"
|
||||
|
||||
msgid "Deploy Ramdisk"
|
||||
msgstr "Deploy Ramdisk"
|
||||
|
||||
msgid "Driver"
|
||||
msgstr "Driver"
|
||||
|
||||
msgid "Driver Details"
|
||||
msgstr "Driver Details"
|
||||
|
||||
msgid "Driver Info"
|
||||
msgstr "Driver Info"
|
||||
|
||||
msgid "Enroll Node"
|
||||
msgstr "Enroll Node"
|
||||
|
||||
#, python-format
|
||||
msgid "Error deleting nodes \"%s\""
|
||||
msgstr "Error deleting nodes \"%s\""
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "Extra"
|
||||
|
||||
msgid "Extra Property Name"
|
||||
msgstr "Extra Property Name"
|
||||
|
||||
msgid "Extras"
|
||||
msgstr "Extras"
|
||||
|
||||
msgid "General"
|
||||
msgstr "General"
|
||||
|
||||
msgid "Inspection Finished At"
|
||||
msgstr "Inspection Finished At"
|
||||
|
||||
msgid "Inspection Started At"
|
||||
msgstr "Inspection Started At"
|
||||
|
||||
msgid "Instance ID"
|
||||
msgstr "Instance ID"
|
||||
|
||||
msgid "Instance Info"
|
||||
msgstr "Instance Info"
|
||||
|
||||
msgid "Instance Name"
|
||||
msgstr "Instance Name"
|
||||
|
||||
msgid "Kernel"
|
||||
msgstr "Kernel"
|
||||
|
||||
msgid "Last Error"
|
||||
msgstr "Last Error"
|
||||
|
||||
msgid "Maintenance"
|
||||
msgstr "Maintenance"
|
||||
|
||||
msgid "Maintenance Reason"
|
||||
msgstr "Maintenance Reason"
|
||||
|
||||
msgid "Maintenance off"
|
||||
msgstr "Maintenance off"
|
||||
|
||||
msgid "Maintenance on"
|
||||
msgstr "Maintenance on"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "Name"
|
||||
|
||||
msgid "No Instance"
|
||||
msgstr "No Instance"
|
||||
|
||||
msgid "No maintenance reason given."
|
||||
msgstr "No maintenance reason given."
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is already in maintenance mode."
|
||||
msgstr "Node %s is already in maintenance mode."
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not in maintenance mode."
|
||||
msgstr "Node %s is not in maintenance mode."
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not powered off."
|
||||
msgstr "Node %s is not powered off."
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not powered on."
|
||||
msgstr "Node %s is not powered on."
|
||||
|
||||
msgid "Node Driver"
|
||||
msgstr "Node Driver"
|
||||
|
||||
msgid "Node ID"
|
||||
msgstr "Node ID"
|
||||
|
||||
msgid "Node Info"
|
||||
msgstr "Node Info"
|
||||
|
||||
msgid "Node Name"
|
||||
msgstr "Node Name"
|
||||
|
||||
msgid "Overview"
|
||||
msgstr "Overview"
|
||||
|
||||
msgid "Ports"
|
||||
msgstr "Ports"
|
||||
|
||||
msgid "Power State"
|
||||
msgstr "Power State"
|
||||
|
||||
msgid "Power off"
|
||||
msgstr "Power off"
|
||||
|
||||
msgid "Power on"
|
||||
msgstr "Power on"
|
||||
|
||||
msgid "Properties"
|
||||
msgstr "Properties"
|
||||
|
||||
msgid "Property Name"
|
||||
msgstr "Property Name"
|
||||
|
||||
msgid ""
|
||||
"Provide a reason for why you are putting the selected node(s) into "
|
||||
"maintenance mode (optional)"
|
||||
msgstr ""
|
||||
"Provide a reason for why you are putting the selected node(s) into "
|
||||
"maintenance mode (optional)"
|
||||
|
||||
msgid "Provision State"
|
||||
msgstr "Provision State"
|
||||
|
||||
msgid "Provisioning State"
|
||||
msgstr "Provisioning State"
|
||||
|
||||
msgid "Provisioning Status"
|
||||
msgstr "Provisioning Status"
|
||||
|
||||
msgid "Put Node(s) Into Maintenance Mode"
|
||||
msgstr "Put Node(s) Into Maintenance Mode"
|
||||
|
||||
msgid "Ramdisk"
|
||||
msgstr "Ramdisk"
|
||||
|
||||
msgid "Refresh page to see updated power status"
|
||||
msgstr "Refresh page to see updated power status"
|
||||
|
||||
msgid "Required"
|
||||
msgstr "Required"
|
||||
|
||||
msgid "Reservation"
|
||||
msgstr "Reservation"
|
||||
|
||||
msgid "SSH Port"
|
||||
msgstr "SSH Port"
|
||||
|
||||
msgid "SSH Username"
|
||||
msgstr "SSH Username"
|
||||
|
||||
msgid "Select a Driver"
|
||||
msgstr "Select a Driver"
|
||||
|
||||
#, python-format
|
||||
msgid "Successfully deleted node \"%s\""
|
||||
msgstr "Successfully deleted node \"%s\""
|
||||
|
||||
#, python-format
|
||||
msgid "Successfully deleted nodes \"%s\""
|
||||
msgstr "Successfully deleted nodes \"%s\""
|
||||
|
||||
msgid "Target Power State"
|
||||
msgstr "Target Power State"
|
||||
|
||||
msgid "Target Provision State"
|
||||
msgstr "Target Provision State"
|
||||
|
||||
msgid "UUID"
|
||||
msgstr "UUID"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to create node: %s"
|
||||
msgstr "Unable to create node: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to delete node \"%s\""
|
||||
msgstr "Unable to delete node \"%s\""
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to delete node %s: %s"
|
||||
msgstr "Unable to delete node %s: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to power off the node: %s"
|
||||
msgstr "Unable to power off the node: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to power on the node: %s"
|
||||
msgstr "Unable to power on the node: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to put the Ironic node in maintenance mode: %s"
|
||||
msgstr "Unable to put the Ironic node in maintenance mode: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to remove the Ironic node from maintenance mode: %s"
|
||||
msgstr "Unable to remove the Ironic node from maintenance mode: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve Ironic drivers: %s"
|
||||
msgstr "Unable to retrieve Ironic drivers: %s"
|
||||
|
||||
msgid "Unable to retrieve Ironic nodes."
|
||||
msgstr "Unable to retrieve Ironic nodes."
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve driver properties: %s"
|
||||
msgstr "Unable to retrieve driver properties: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve the Ironic node ports: %s"
|
||||
msgstr "Unable to retrieve the Ironic node ports: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve the Ironic node: %s"
|
||||
msgstr "Unable to retrieve the Ironic node: %s"
|
||||
|
||||
msgid "Updated At"
|
||||
msgstr "Updated At"
|
||||
|
||||
msgid "{$ property.getDescription() $}"
|
||||
msgstr "{$ property.getDescription() $}"
|
|
@ -0,0 +1,18 @@
|
|||
# Nicolas Fournier <nicolas.fournier3@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev9\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-05-05 12:53+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-04-30 05:13+0000\n"
|
||||
"Last-Translator: Nicolas Fournier <nicolas.fournier3@gmail.com>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||
|
||||
msgid "Ironic Bare Metal Provisioning"
|
||||
msgstr "Déploiement Bare Metal Ironic"
|
|
@ -0,0 +1,173 @@
|
|||
# Gael Rehault <gael01@gmail.com>, 2015. #zanata
|
||||
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
# Martine Marin <mmarin@fr.ibm.com>, 2016. #zanata
|
||||
# Nicolas Fournier <nicolas.fournier3@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev13\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-05-23 20:35+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-04-30 05:12+0000\n"
|
||||
"Last-Translator: Nicolas Fournier <nicolas.fournier3@gmail.com>\n"
|
||||
"Language-Team: French\n"
|
||||
"Language: fr\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||
|
||||
msgid "Actions"
|
||||
msgstr "Actions"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "Annuler"
|
||||
|
||||
msgid "Capabilities"
|
||||
msgstr "Capacités"
|
||||
|
||||
msgid "Chassis ID"
|
||||
msgstr "ID du chassis"
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "Configuration"
|
||||
|
||||
msgid "Console Enabled"
|
||||
msgstr "Console Activée"
|
||||
|
||||
msgid "Created At"
|
||||
msgstr "Créé le"
|
||||
|
||||
msgid "Deploy Kernel"
|
||||
msgstr "Kernel de déploiement"
|
||||
|
||||
msgid "Deploy Ramdisk"
|
||||
msgstr "Ramdisk de déploiement"
|
||||
|
||||
msgid "Driver"
|
||||
msgstr "Driver"
|
||||
|
||||
msgid "Driver Info"
|
||||
msgstr "Info du Driver"
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "Extra"
|
||||
|
||||
msgid "General"
|
||||
msgstr "Général"
|
||||
|
||||
msgid "Inspection Finished At"
|
||||
msgstr "Inspection terminée à"
|
||||
|
||||
msgid "Inspection Started At"
|
||||
msgstr "Inspection démarrée à"
|
||||
|
||||
msgid "Instance ID"
|
||||
msgstr "ID de l'instance"
|
||||
|
||||
msgid "Instance Info"
|
||||
msgstr "Infos de l'instance"
|
||||
|
||||
msgid "Instance Name"
|
||||
msgstr "Nom de l'instance"
|
||||
|
||||
msgid "Kernel"
|
||||
msgstr "Noyau"
|
||||
|
||||
msgid "Last Error"
|
||||
msgstr "Dernière Erreur"
|
||||
|
||||
msgid "Maintenance"
|
||||
msgstr "Maintenance"
|
||||
|
||||
msgid "Maintenance Reason"
|
||||
msgstr "Raison de la maintenance"
|
||||
|
||||
msgid "Maintenance off"
|
||||
msgstr "Maintenance désactivée"
|
||||
|
||||
msgid "Maintenance on"
|
||||
msgstr "Maintenance activée"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "Nom"
|
||||
|
||||
msgid "No Instance"
|
||||
msgstr "Pas d'instance"
|
||||
|
||||
msgid "No maintenance reason given."
|
||||
msgstr "Aucune raison de maintenance fournie."
|
||||
|
||||
msgid "Node ID"
|
||||
msgstr "ID du noeud"
|
||||
|
||||
msgid "Node Name"
|
||||
msgstr "Nom du noeud"
|
||||
|
||||
msgid "Overview"
|
||||
msgstr "Vue d'ensemble"
|
||||
|
||||
msgid "Ports"
|
||||
msgstr "Ports"
|
||||
|
||||
msgid "Power State"
|
||||
msgstr "État de l'alimentation"
|
||||
|
||||
msgid "Power off"
|
||||
msgstr "Éteindre"
|
||||
|
||||
msgid "Power on"
|
||||
msgstr "Allumer"
|
||||
|
||||
msgid "Properties"
|
||||
msgstr "Propriétés"
|
||||
|
||||
msgid ""
|
||||
"Provide a reason for why you are putting the selected node(s) into "
|
||||
"maintenance mode (optional)"
|
||||
msgstr ""
|
||||
"Fournir une raison pour avoir mis le(s) noeud(s) sélectionné(s) en mode "
|
||||
"maintenance (optionnel)"
|
||||
|
||||
msgid "Provision State"
|
||||
msgstr "État de déploiement"
|
||||
|
||||
msgid "Provisioning State"
|
||||
msgstr "État de déploiement"
|
||||
|
||||
msgid "Provisioning Status"
|
||||
msgstr "Statut de Déploiement"
|
||||
|
||||
msgid "Put Node(s) Into Maintenance Mode"
|
||||
msgstr "Mettre le(s) noeud(s) en mode maintenance"
|
||||
|
||||
msgid "Ramdisk"
|
||||
msgstr "Ramdisk"
|
||||
|
||||
msgid "Refresh page to see updated power status"
|
||||
msgstr "Rafraichir la page pour voir les statuts d'alimentation à jour"
|
||||
|
||||
msgid "Reservation"
|
||||
msgstr "Réservation"
|
||||
|
||||
msgid "SSH Port"
|
||||
msgstr "Port SSH"
|
||||
|
||||
msgid "SSH Username"
|
||||
msgstr "Utilisateur SSH"
|
||||
|
||||
msgid "Target Power State"
|
||||
msgstr "État de l'alimentation de la cible"
|
||||
|
||||
msgid "Target Provision State"
|
||||
msgstr "État de déploiement de la cible"
|
||||
|
||||
msgid "UUID"
|
||||
msgstr "UUID"
|
||||
|
||||
msgid "Unable to retrieve Ironic nodes."
|
||||
msgstr "Impossible de récupérer les noeud Ironic."
|
||||
|
||||
msgid "Updated At"
|
||||
msgstr "Mis à jour à"
|
|
@ -0,0 +1,18 @@
|
|||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev9\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-05-05 12:53+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-04-01 04:04+0000\n"
|
||||
"Last-Translator: Shu Muto <shu-mutou@rf.jp.nec.com>\n"
|
||||
"Language-Team: Japanese\n"
|
||||
"Language: ja\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Plural-Forms: nplurals=1; plural=0\n"
|
||||
|
||||
msgid "Ironic Bare Metal Provisioning"
|
||||
msgstr "Ironic ベアメタルプロビジョニング"
|
|
@ -0,0 +1,297 @@
|
|||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
# Shu Muto <shu-mutou@rf.jp.nec.com>, 2016. #zanata
|
||||
# Yoshiki Eguchi <yoshiki.eguchi@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev33\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-08-15 22:06+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-08-06 02:49+0000\n"
|
||||
"Last-Translator: Yoshiki Eguchi <yoshiki.eguchi@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 "A unique node name. Optional."
|
||||
msgstr "一意のノード名。オプション。"
|
||||
|
||||
msgid "Actions"
|
||||
msgstr "アクション"
|
||||
|
||||
msgid "Add New Property:"
|
||||
msgstr "新しいプロパティーの追加:"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
|
||||
msgstr "ノード「%s」 を削除してよろしいですか?この操作は取り消せません。"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
|
||||
msgstr "ノード「%s」 を削除してよろしいですか?この操作は取り消せません。"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "取り消し"
|
||||
|
||||
msgid "Capabilities"
|
||||
msgstr "機能"
|
||||
|
||||
msgid "Chassis ID"
|
||||
msgstr "シャーシ ID"
|
||||
|
||||
msgid "Choose an Image"
|
||||
msgstr "イメージの選択"
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "設定"
|
||||
|
||||
msgid "Console Enabled"
|
||||
msgstr "コンソールの有効化"
|
||||
|
||||
msgid "Created At"
|
||||
msgstr "作成時刻"
|
||||
|
||||
msgid "Delete Node"
|
||||
msgstr "ノードの削除"
|
||||
|
||||
msgid "Delete Nodes"
|
||||
msgstr "ノードの削除"
|
||||
|
||||
msgid "Delete node"
|
||||
msgstr "ノードの削除"
|
||||
|
||||
msgid "Delete nodes"
|
||||
msgstr "ノードの削除"
|
||||
|
||||
msgid "Deploy Kernel"
|
||||
msgstr "カーネルのデプロイ"
|
||||
|
||||
msgid "Deploy Ramdisk"
|
||||
msgstr "RAM ディスクのデプロイ"
|
||||
|
||||
msgid "Driver"
|
||||
msgstr "ドライバー"
|
||||
|
||||
msgid "Driver Details"
|
||||
msgstr "ドライバー詳細"
|
||||
|
||||
msgid "Driver Info"
|
||||
msgstr "ドライバー情報"
|
||||
|
||||
msgid "Enroll Node"
|
||||
msgstr "ノードの登録"
|
||||
|
||||
#, python-format
|
||||
msgid "Error deleting nodes \"%s\""
|
||||
msgstr "ノード「%s」の削除中にエラーが発生しました"
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "拡張"
|
||||
|
||||
msgid "General"
|
||||
msgstr "一般"
|
||||
|
||||
msgid "Inspection Finished At"
|
||||
msgstr "検査終了時刻"
|
||||
|
||||
msgid "Inspection Started At"
|
||||
msgstr "検査開始時刻"
|
||||
|
||||
msgid "Instance ID"
|
||||
msgstr "インスタンス ID"
|
||||
|
||||
msgid "Instance Info"
|
||||
msgstr "インスタンス情報"
|
||||
|
||||
msgid "Instance Name"
|
||||
msgstr "インスタンス名"
|
||||
|
||||
msgid "Kernel"
|
||||
msgstr "カーネル"
|
||||
|
||||
msgid "Last Error"
|
||||
msgstr "最後のエラー"
|
||||
|
||||
msgid "Maintenance"
|
||||
msgstr "メンテナンス"
|
||||
|
||||
msgid "Maintenance Reason"
|
||||
msgstr "メンテナンスの理由"
|
||||
|
||||
msgid "Maintenance off"
|
||||
msgstr "メンテナンスオフ"
|
||||
|
||||
msgid "Maintenance on"
|
||||
msgstr "メンテナンスオン"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "名前"
|
||||
|
||||
msgid "No Instance"
|
||||
msgstr "インスタンスなし"
|
||||
|
||||
msgid "No maintenance reason given."
|
||||
msgstr "メンテナンスの理由がありません。"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is already in maintenance mode."
|
||||
msgstr "ノード %s は既にメンテナンスモードです。"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not in maintenance mode."
|
||||
msgstr "ノード %s はメンテナンスモードになっていません。"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not powered off."
|
||||
msgstr "ノード %s の電源がオフになっていません。"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not powered on."
|
||||
msgstr "ノード %s の電源がオンになっていません。"
|
||||
|
||||
msgid "Node Driver"
|
||||
msgstr "ノードドライバー"
|
||||
|
||||
msgid "Node ID"
|
||||
msgstr "ノード ID"
|
||||
|
||||
msgid "Node Info"
|
||||
msgstr "ノード情報"
|
||||
|
||||
msgid "Node Name"
|
||||
msgstr "ノード名"
|
||||
|
||||
msgid "Overview"
|
||||
msgstr "概要"
|
||||
|
||||
msgid "Ports"
|
||||
msgstr "ポート"
|
||||
|
||||
msgid "Power State"
|
||||
msgstr "電源状態"
|
||||
|
||||
msgid "Power off"
|
||||
msgstr "電源オフ"
|
||||
|
||||
msgid "Power on"
|
||||
msgstr "電源オン"
|
||||
|
||||
msgid "Properties"
|
||||
msgstr "プロパティー"
|
||||
|
||||
msgid "Property Name"
|
||||
msgstr "プロパティ名"
|
||||
|
||||
msgid ""
|
||||
"Provide a reason for why you are putting the selected node(s) into "
|
||||
"maintenance mode (optional)"
|
||||
msgstr ""
|
||||
"選択したノードをメンテナンスモードにする理由を設定してください(オプション)"
|
||||
|
||||
msgid "Provision State"
|
||||
msgstr "プロビジョニング状態"
|
||||
|
||||
msgid "Provisioning State"
|
||||
msgstr "プロビジョニング状態"
|
||||
|
||||
msgid "Provisioning Status"
|
||||
msgstr "プロビジョニング状態"
|
||||
|
||||
msgid "Put Node(s) Into Maintenance Mode"
|
||||
msgstr "ノードをメンテナンスモードにします"
|
||||
|
||||
msgid "Ramdisk"
|
||||
msgstr "RAM ディスク"
|
||||
|
||||
msgid "Refresh page to see updated power status"
|
||||
msgstr "電源状態を更新するにはページをリフレッシュしてください"
|
||||
|
||||
msgid "Required"
|
||||
msgstr "必須"
|
||||
|
||||
msgid "Reservation"
|
||||
msgstr "予約"
|
||||
|
||||
msgid "SSH Port"
|
||||
msgstr "SSH ポート"
|
||||
|
||||
msgid "SSH Username"
|
||||
msgstr "SSH ユーザー名"
|
||||
|
||||
msgid "Select a Driver"
|
||||
msgstr "ドライバーを選択してください"
|
||||
|
||||
#, python-format
|
||||
msgid "Successfully deleted node \"%s\""
|
||||
msgstr "ノード「%s」を正常に削除しました"
|
||||
|
||||
#, python-format
|
||||
msgid "Successfully deleted nodes \"%s\""
|
||||
msgstr "ノード「%s」を正常に削除しました"
|
||||
|
||||
msgid "Target Power State"
|
||||
msgstr "ターゲット電源状態"
|
||||
|
||||
msgid "Target Provision State"
|
||||
msgstr "ターゲットプロビジョニング状態"
|
||||
|
||||
msgid "UUID"
|
||||
msgstr "UUID"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to create node: %s"
|
||||
msgstr "ノードを作成できません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to delete node \"%s\""
|
||||
msgstr "ノード 「%s」を削除できません"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to delete node %s: %s"
|
||||
msgstr "ノード %s を削除できません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to power off the node: %s"
|
||||
msgstr "ノードを電源OFFにできません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to power on the node: %s"
|
||||
msgstr "ノードを電源オンにできません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to put the Ironic node in maintenance mode: %s"
|
||||
msgstr "Ironic ノードをメンテナンスモードにできません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to remove the Ironic node from maintenance mode: %s"
|
||||
msgstr "Ironic ノードをメンテナンスモードから削除できません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve Ironic drivers: %s"
|
||||
msgstr "Ironic ドライバーの一覧を取得できません: %s"
|
||||
|
||||
msgid "Unable to retrieve Ironic nodes."
|
||||
msgstr "Ironic ノードの一覧を取得できません"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve driver properties: %s"
|
||||
msgstr "ドライバープロパティーの一覧を取得できません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve the Ironic node ports: %s"
|
||||
msgstr "Ironic ノードのポート一覧を取得できません: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve the Ironic node: %s"
|
||||
msgstr "Ironic ノードを取得できません: %s"
|
||||
|
||||
msgid "Updated At"
|
||||
msgstr "最終更新"
|
||||
|
||||
msgid "{$ property.getDescription() $}"
|
||||
msgstr "{$ property.getDescription() $}"
|
|
@ -0,0 +1,18 @@
|
|||
# Shengjing Zhu <zsj950618@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev33\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-08-15 22:06+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-08-09 08:49+0000\n"
|
||||
"Last-Translator: Shengjing Zhu <zsj950618@gmail.com>\n"
|
||||
"Language-Team: Chinese (China)\n"
|
||||
"Language: zh-CN\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Plural-Forms: nplurals=1; plural=0\n"
|
||||
|
||||
msgid "Ironic Bare Metal Provisioning"
|
||||
msgstr "裸金属 Ironic 配置向导"
|
|
@ -0,0 +1,323 @@
|
|||
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
|
||||
# zhangjingwen <zhangjingwen@cn.fujitsu.com>, 2015. #zanata
|
||||
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
|
||||
# Shengjing Zhu <zsj950618@gmail.com>, 2016. #zanata
|
||||
# sunanchen <KF.sunanchen@h3c.com>, 2016. #zanata
|
||||
# vuuv <froms2008@gmail.com>, 2016. #zanata
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: ironic-ui 1.1.1.dev33\n"
|
||||
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
|
||||
"POT-Creation-Date: 2016-08-15 22:06+0000\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"PO-Revision-Date: 2016-08-09 08:52+0000\n"
|
||||
"Last-Translator: Shengjing Zhu <zsj950618@gmail.com>\n"
|
||||
"Language-Team: Chinese (China)\n"
|
||||
"Language: zh-CN\n"
|
||||
"X-Generator: Zanata 3.7.3\n"
|
||||
"Plural-Forms: nplurals=1; plural=0\n"
|
||||
|
||||
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
|
||||
msgstr "([^\" ]+|\"[^\"]+\") \\(Default标签\\)"
|
||||
|
||||
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
|
||||
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
|
||||
|
||||
msgid "A unique node name. Optional."
|
||||
msgstr "独一无二的节点名称。可选。"
|
||||
|
||||
msgid "Actions"
|
||||
msgstr "动作"
|
||||
|
||||
msgid "Add Extra:"
|
||||
msgstr "增加额外信息"
|
||||
|
||||
msgid "Add New Property:"
|
||||
msgstr "增加新的属性"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
|
||||
msgstr "你确认要删除节点\"%s\"嘛?此操作将不可恢复"
|
||||
|
||||
#, python-format
|
||||
msgid ""
|
||||
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
|
||||
msgstr "你确认要删除节点\"%s\"嘛?此操作将不可恢复"
|
||||
|
||||
msgid "Cancel"
|
||||
msgstr "取消"
|
||||
|
||||
msgid "Capabilities"
|
||||
msgstr "能力"
|
||||
|
||||
msgid "Chassis ID"
|
||||
msgstr "机架ID"
|
||||
|
||||
msgid "Choose an Image"
|
||||
msgstr "选择一个镜像"
|
||||
|
||||
msgid "Configuration"
|
||||
msgstr "配置"
|
||||
|
||||
msgid "Console Enabled"
|
||||
msgstr "允许控制台"
|
||||
|
||||
msgid "Created At"
|
||||
msgstr "创建于"
|
||||
|
||||
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
|
||||
msgstr "默认为 ([^\"\\. ]+|\"[^\"]+\")"
|
||||
|
||||
msgid "Delete Node"
|
||||
msgstr "删除节点"
|
||||
|
||||
msgid "Delete Nodes"
|
||||
msgstr "删除多个节点"
|
||||
|
||||
msgid "Delete node"
|
||||
msgstr "删除节点"
|
||||
|
||||
msgid "Delete nodes"
|
||||
msgstr "删除多个节点"
|
||||
|
||||
msgid "Deploy Kernel"
|
||||
msgstr "部署内核"
|
||||
|
||||
msgid "Deploy Ramdisk"
|
||||
msgstr "部署虚拟内存盘"
|
||||
|
||||
msgid "Driver"
|
||||
msgstr "驱动"
|
||||
|
||||
msgid "Driver Details"
|
||||
msgstr "驱动详情"
|
||||
|
||||
msgid "Driver Info"
|
||||
msgstr "驱动信息"
|
||||
|
||||
msgid "Enroll Node"
|
||||
msgstr "注册节点"
|
||||
|
||||
#, python-format
|
||||
msgid "Error deleting nodes \"%s\""
|
||||
msgstr "删除多个节点\"%s\"错误"
|
||||
|
||||
msgid "Extra"
|
||||
msgstr "额外信息"
|
||||
|
||||
msgid "Extra Property Name"
|
||||
msgstr "额外属性名称"
|
||||
|
||||
msgid "Extras"
|
||||
msgstr "额外信息"
|
||||
|
||||
msgid "General"
|
||||
msgstr "概要信息"
|
||||
|
||||
msgid "Inspection Finished At"
|
||||
msgstr "检查结束于"
|
||||
|
||||
msgid "Inspection Started At"
|
||||
msgstr "检查开始于"
|
||||
|
||||
msgid "Instance ID"
|
||||
msgstr "云主机ID"
|
||||
|
||||
msgid "Instance Info"
|
||||
msgstr "实例信息"
|
||||
|
||||
msgid "Instance Name"
|
||||
msgstr "实例名字"
|
||||
|
||||
msgid "Kernel"
|
||||
msgstr "内核"
|
||||
|
||||
msgid "Last Error"
|
||||
msgstr "最近的一次错误"
|
||||
|
||||
msgid "Maintenance"
|
||||
msgstr "维护"
|
||||
|
||||
msgid "Maintenance Reason"
|
||||
msgstr "维护原因"
|
||||
|
||||
msgid "Maintenance off"
|
||||
msgstr "退出维护模式"
|
||||
|
||||
msgid "Maintenance on"
|
||||
msgstr "处于维护状态"
|
||||
|
||||
msgid "Name"
|
||||
msgstr "名称"
|
||||
|
||||
msgid "No Instance"
|
||||
msgstr "没有实例"
|
||||
|
||||
msgid "No maintenance reason given."
|
||||
msgstr "缺少提供维护原因"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is already in maintenance mode."
|
||||
msgstr "节点\"%s\"已经处于维护模式"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not in maintenance mode."
|
||||
msgstr "节点\"%s\"没有处于维护模式"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not powered off."
|
||||
msgstr "节点\"%s\"电源没有关闭"
|
||||
|
||||
#, python-format
|
||||
msgid "Node %s is not powered on."
|
||||
msgstr "节点\"%s\"电源没有开启"
|
||||
|
||||
msgid "Node Driver"
|
||||
msgstr "节点驱动"
|
||||
|
||||
msgid "Node ID"
|
||||
msgstr "节点ID"
|
||||
|
||||
msgid "Node Info"
|
||||
msgstr "节点信息"
|
||||
|
||||
msgid "Node Name"
|
||||
msgstr "节点名称"
|
||||
|
||||
msgid "One of this, (.*) must be specified\\."
|
||||
msgstr "必须指定其中的一个 (.*)"
|
||||
|
||||
msgid "Overview"
|
||||
msgstr "概览"
|
||||
|
||||
msgid "Ports"
|
||||
msgstr "端口"
|
||||
|
||||
msgid "Power State"
|
||||
msgstr "电源状态"
|
||||
|
||||
msgid "Power off"
|
||||
msgstr "关闭电源"
|
||||
|
||||
msgid "Power on"
|
||||
msgstr "打开电源"
|
||||
|
||||
msgid "Properties"
|
||||
msgstr "属性"
|
||||
|
||||
msgid "Property Name"
|
||||
msgstr "属性名称"
|
||||
|
||||
msgid ""
|
||||
"Provide a reason for why you are putting the selected node(s) into "
|
||||
"maintenance mode (optional)"
|
||||
msgstr "提供一个你为什么选择将节点置于维护模式的原因(可选)"
|
||||
|
||||
msgid "Provision State"
|
||||
msgstr "配置状态"
|
||||
|
||||
msgid "Provisioning State"
|
||||
msgstr "配置状态"
|
||||
|
||||
msgid "Provisioning Status"
|
||||
msgstr "配置状态"
|
||||
|
||||
msgid "Put Node(s) Into Maintenance Mode"
|
||||
msgstr "将节点置于维护模式"
|
||||
|
||||
msgid "Ramdisk"
|
||||
msgstr "内存盘"
|
||||
|
||||
msgid "Refresh page to see updated power status"
|
||||
msgstr "刷新页面来确认电源状态是否更新"
|
||||
|
||||
msgid "Required"
|
||||
msgstr "必需的"
|
||||
|
||||
msgid "Reservation"
|
||||
msgstr "预留"
|
||||
|
||||
msgid "SSH Port"
|
||||
msgstr "SSH端口"
|
||||
|
||||
msgid "SSH Username"
|
||||
msgstr "SSH用户名"
|
||||
|
||||
msgid "Select a Driver"
|
||||
msgstr "选择一种驱动"
|
||||
|
||||
#, python-format
|
||||
msgid "Successfully deleted node \"%s\""
|
||||
msgstr "成功删除节点\"%s\""
|
||||
|
||||
#, python-format
|
||||
msgid "Successfully deleted nodes \"%s\""
|
||||
msgstr "成功删除多个节点\"%s\""
|
||||
|
||||
msgid "Target Power State"
|
||||
msgstr "标记电源状态"
|
||||
|
||||
msgid "Target Provision State"
|
||||
msgstr "标记配置状态"
|
||||
|
||||
msgid "UUID"
|
||||
msgstr "UUID"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to create node: %s"
|
||||
msgstr "无法创建Ironic节点: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to delete node \"%s\""
|
||||
msgstr "无法删除节点\"%s\""
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to delete node %s: %s"
|
||||
msgstr "无法删除Ironic节点\"%s\": %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to power off the node: %s"
|
||||
msgstr "无法关闭节点电源: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to power on the node: %s"
|
||||
msgstr "无法启动节点电源: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to put the Ironic node in maintenance mode: %s"
|
||||
msgstr "无法将Ironic节点置于维护模式: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to remove the Ironic node from maintenance mode: %s"
|
||||
msgstr "无法将Ironic节点退出维护模式: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve Ironic drivers: %s"
|
||||
msgstr "无法获取Ironic驱动: %s"
|
||||
|
||||
msgid "Unable to retrieve Ironic nodes."
|
||||
msgstr "无法获取Ironic节点信息"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve driver properties: %s"
|
||||
msgstr "无法获取驱动属性: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve the Ironic node ports: %s"
|
||||
msgstr "无法获取Ironic节点端口信息: %s"
|
||||
|
||||
#, python-format
|
||||
msgid "Unable to retrieve the Ironic node: %s"
|
||||
msgstr "无法获取Ironic节点信息: %s"
|
||||
|
||||
msgid "Updated At"
|
||||
msgstr "已更新于"
|
||||
|
||||
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
|
||||
msgstr "默认值 (?:value )?是 ([^\"\\. ]+|\"[^\"]+\")"
|
||||
|
||||
msgid "{$ property.getDescription() $}"
|
||||
msgstr "{$ property.getDescription() $}"
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.directive('autoFocus', AutoFocus);
|
||||
|
||||
AutoFocus.$inject = ['$timeout'];
|
||||
|
||||
function AutoFocus($timeout) {
|
||||
return {
|
||||
restrict: 'AC',
|
||||
link: function(scope, elem) {
|
||||
$timeout(function() {
|
||||
elem[0].focus();
|
||||
}, 1000);
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Controller used to create a network port on a specified node
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.controller('CreatePortController', CreatePortController);
|
||||
|
||||
CreatePortController.$inject = [
|
||||
'$rootScope',
|
||||
'$modalInstance',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'$log',
|
||||
'node'
|
||||
];
|
||||
|
||||
function CreatePortController($rootScope,
|
||||
$modalInstance,
|
||||
ironic,
|
||||
ironicEvents,
|
||||
$log,
|
||||
node) {
|
||||
var ctrl = this;
|
||||
|
||||
// Paramater object that defines the port to be created
|
||||
ctrl.port = {
|
||||
node_uuid: node.id,
|
||||
address: null,
|
||||
extra: {}
|
||||
};
|
||||
|
||||
/**
|
||||
* Cancel the port creation process
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.cancel = function() {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
/**
|
||||
* Create the defined port
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.createPort = function() {
|
||||
ironic.createPort(ctrl.port).then(
|
||||
function() {
|
||||
$modalInstance.close();
|
||||
$rootScope.$emit(ironicEvents.CREATE_PORT_SUCCESS);
|
||||
},
|
||||
function() {
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Delete a port metadata property
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.deleteExtra = function(propertyName) {
|
||||
delete ctrl.port.extra[propertyName];
|
||||
};
|
||||
|
||||
/**
|
||||
* Check whether the specified port metadata property already exists
|
||||
*
|
||||
* @param {string} propertyName - Name of the metadata property
|
||||
* @return {boolean} True if the property already exists,
|
||||
* otherwise false
|
||||
*/
|
||||
ctrl.checkExtraUnique = function(propertyName) {
|
||||
return !(propertyName in ctrl.port.extra);
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,90 @@
|
|||
<div class="modal-header" modal-draggable>
|
||||
<button type="button"
|
||||
class="close"
|
||||
ng-click="$dismiss()"
|
||||
aria-hidden="true"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true" class="fa fa-times"></span>
|
||||
</button>
|
||||
<h3 class="modal-title" translate>Create Port</h3>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form id="CreatePortForm" name="CreatePortForm">
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': CreatePortForm.macAddress.$invalid &&
|
||||
CreatePortForm.macAddress.$dirty}">
|
||||
<label for="macAddress"
|
||||
class="control-label"
|
||||
translate>MAC address</label>
|
||||
<div>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="ctrl.port.address"
|
||||
id="macAddress"
|
||||
name="macAddress"
|
||||
ng-required="true"
|
||||
ng-pattern="'([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})'"
|
||||
auto-focus
|
||||
placeholder="{$ 'MAC address for this port. Required.' | translate $}"/>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form id="AddExtraForm" name="AddExtraForm" style="margin-bottom:10px;">
|
||||
<label for="extras" class="control-label" translate>Extras</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
Add Extra:</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
ng-model="extraName"
|
||||
validate-unique="ctrl.checkExtraUnique"
|
||||
placeholder="{$ 'Property Name' | translate $}"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
ng-disabled="!extraName || AddExtraForm.$invalid"
|
||||
ng-click="ctrl.port.extra[extraName] = null;
|
||||
extraName = null">
|
||||
<span class="fa fa-plus"> </span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<form class="form-horizontal" id="ExtraForm" name="ExtraForm">
|
||||
<div class="input-group input-group-sm"
|
||||
ng-repeat="(propertyName, propertyValue) in ctrl.port.extra">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
{$ propertyName $}
|
||||
</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="{$ propertyName $}"
|
||||
ng-model="ctrl.port.extra[propertyName]"
|
||||
ng-required="true"/>
|
||||
<div class="input-group-btn">
|
||||
<a class="btn btn-default"
|
||||
ng-click="ctrl.deleteExtra(propertyName)">
|
||||
<span class="fa fa-minus"> </span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<!--modal footer-->
|
||||
<div class="modal-footer ng-scope">
|
||||
<button class="btn btn-default" ng-click="ctrl.cancel()">
|
||||
<span class="fa fa-close"></span>
|
||||
<span class="ng-scope" translate>Cancel</span>
|
||||
</button>
|
||||
<button type="submit"
|
||||
ng-disabled="CreatePortForm.$invalid || ExtraForm.$invalid"
|
||||
ng-click="ctrl.createPort()"
|
||||
class="btn btn-primary"
|
||||
translate>
|
||||
Create Port
|
||||
</button>
|
||||
</div>
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.factory('horizon.dashboard.admin.ironic.create-port.service',
|
||||
createPortService);
|
||||
|
||||
createPortService.$inject = [
|
||||
'$modal',
|
||||
'horizon.dashboard.admin.basePath'
|
||||
];
|
||||
|
||||
function createPortService($modal, basePath) {
|
||||
var service = {
|
||||
modal: modal
|
||||
};
|
||||
return service;
|
||||
|
||||
function modal(node) {
|
||||
var options = {
|
||||
controller: 'CreatePortController as ctrl',
|
||||
backdrop: 'static',
|
||||
resolve: {
|
||||
node: function() {
|
||||
return node;
|
||||
}
|
||||
},
|
||||
templateUrl: basePath + '/ironic/create-port/create-port.html'
|
||||
};
|
||||
return $modal.open(options);
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.directive('emptyToPristine', EmptyToPristine);
|
||||
|
||||
function EmptyToPristine() {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: function(scope, elem, attrs, ctrl) {
|
||||
ctrl.$parsers.push(function(viewValue) {
|
||||
if (viewValue === "") {
|
||||
ctrl.$setPristine();
|
||||
}
|
||||
return viewValue;
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,343 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
/**
|
||||
* Controller used to enroll a node in the Ironic database
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.controller('EnrollNodeController', EnrollNodeController);
|
||||
|
||||
EnrollNodeController.$inject = [
|
||||
'$rootScope',
|
||||
'$modalInstance',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'horizon.app.core.openstack-service-api.glance',
|
||||
'horizon.dashboard.admin.ironic.enroll-node.service',
|
||||
'horizon.dashboard.admin.ironic.validHostNamePattern',
|
||||
'$log'
|
||||
];
|
||||
|
||||
function EnrollNodeController($rootScope,
|
||||
$modalInstance,
|
||||
ironic,
|
||||
ironicEvents,
|
||||
glance,
|
||||
enrollNodeService,
|
||||
validHostNamePattern,
|
||||
$log) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.validHostNameRegex = new RegExp(validHostNamePattern);
|
||||
ctrl.drivers = null;
|
||||
ctrl.images = null;
|
||||
ctrl.loadingDriverProperties = false;
|
||||
// Object containing the set of properties associated with the currently
|
||||
// selected driver
|
||||
ctrl.driverProperties = null;
|
||||
ctrl.driverPropertyGroups = null;
|
||||
|
||||
// Parameter object that defines the node to be enrolled
|
||||
ctrl.node = {
|
||||
name: null,
|
||||
driver: null,
|
||||
driver_info: {},
|
||||
properties: {},
|
||||
extra: {}
|
||||
};
|
||||
|
||||
init();
|
||||
|
||||
function init() {
|
||||
loadDrivers();
|
||||
getImages();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the list of currently active Ironic drivers
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function loadDrivers() {
|
||||
ironic.getDrivers().then(function(response) {
|
||||
ctrl.drivers = response.data.items;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the list of images from Glance
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function getImages() {
|
||||
glance.getImages().then(function(response) {
|
||||
ctrl.images = response.data.items;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Check whether a group contains required properties
|
||||
*
|
||||
* @param {DriverProperty[]} group - Property group
|
||||
* @return {boolean} Return true if the group contains required
|
||||
* properties, false otherwise
|
||||
*/
|
||||
function driverPropertyGroupHasRequired(group) {
|
||||
var hasRequired = false;
|
||||
for (var i = 0; i < group.length; i++) {
|
||||
if (group[i].required) {
|
||||
hasRequired = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return hasRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Convert array of driver property groups to a string
|
||||
*
|
||||
* @param {array[]} groups - Array for driver property groups
|
||||
* @return {string} Output string
|
||||
*/
|
||||
function driverPropertyGroupsToString(groups) {
|
||||
var output = [];
|
||||
angular.forEach(groups, function(group) {
|
||||
var groupStr = [];
|
||||
angular.forEach(group, function(property) {
|
||||
groupStr.push(property.name);
|
||||
});
|
||||
groupStr = groupStr.join(", ");
|
||||
output.push(['[', groupStr, ']'].join(""));
|
||||
});
|
||||
output = output.join(", ");
|
||||
return ['[', output, ']'].join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Comaprison function used to sort driver property groups
|
||||
*
|
||||
* @param {DriverProperty[]} group1 - First group
|
||||
* @param {DriverProperty[]} group2 - Second group
|
||||
* @return {integer} Return:
|
||||
* < 0 if group1 should precede group2 in an ascending ordering
|
||||
* > 0 if group2 should precede group1
|
||||
* 0 if group1 and group2 are considered equal from ordering perpsective
|
||||
*/
|
||||
function compareDriverPropertyGroups(group1, group2) {
|
||||
var group1HasRequired = driverPropertyGroupHasRequired(group1);
|
||||
var group2HasRequired = driverPropertyGroupHasRequired(group2);
|
||||
|
||||
if (group1HasRequired === group2HasRequired) {
|
||||
if (group1.length === group2.length) {
|
||||
return group1[0].name.localeCompare(group2[0].name);
|
||||
} else {
|
||||
return group1.length - group2.length;
|
||||
}
|
||||
} else {
|
||||
return group1HasRequired ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Order driver properties in the form using the following
|
||||
* rules:
|
||||
*
|
||||
* (1) Properties that are related to one another should occupy adjacent
|
||||
* locations in the form
|
||||
*
|
||||
* (2) Required properties with no dependents should be located at the
|
||||
* top of the form
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl._sortDriverProperties = function() {
|
||||
// Build dependency graph between driver properties
|
||||
var graph = new enrollNodeService.Graph();
|
||||
|
||||
// Create vertices
|
||||
angular.forEach(ctrl.driverProperties, function(property, name) {
|
||||
graph.addVertex(name, property);
|
||||
});
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
|
||||
// Create edges
|
||||
angular.forEach(ctrl.driverProperties,
|
||||
function(property, name) {
|
||||
var activators = property.getActivators();
|
||||
if (activators) {
|
||||
angular.forEach(activators,
|
||||
function(unused, activatorName) {
|
||||
graph.addEdge(name, activatorName);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
/* eslint-enable no-unused-vars */
|
||||
|
||||
// Perform depth-first-search to find groups of related properties
|
||||
var groups = [];
|
||||
graph.dfs(
|
||||
function(vertexList, components) {
|
||||
// Sort properties so that those with the largest number of
|
||||
// immediate dependents are the top of the list
|
||||
vertexList.sort(function(vertex1, vertex2) {
|
||||
return vertex2.adjacents.length - vertex1.adjacents.length;
|
||||
});
|
||||
|
||||
// Build component and add to list
|
||||
var component = new Array(vertexList.length);
|
||||
angular.forEach(vertexList, function(vertex, index) {
|
||||
component[index] = vertex.data;
|
||||
});
|
||||
components.push(component);
|
||||
},
|
||||
groups);
|
||||
groups.sort(compareDriverPropertyGroups);
|
||||
|
||||
$log.debug("Found the following property groups: " +
|
||||
driverPropertyGroupsToString(groups));
|
||||
return groups;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get the properties associated with a specified driver
|
||||
*
|
||||
* @param {string} driverName - Name of driver
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.loadDriverProperties = function(driverName) {
|
||||
ctrl.node.driver = driverName;
|
||||
ctrl.node.driver_info = {};
|
||||
|
||||
ctrl.loadingDriverProperties = true;
|
||||
ctrl.driverProperties = null;
|
||||
ctrl.driverPropertyGroups = null;
|
||||
|
||||
ironic.getDriverProperties(driverName).then(function(response) {
|
||||
ctrl.driverProperties = {};
|
||||
angular.forEach(response.data, function(desc, property) {
|
||||
ctrl.driverProperties[property] =
|
||||
new enrollNodeService.DriverProperty(property,
|
||||
desc,
|
||||
ctrl.driverProperties);
|
||||
});
|
||||
ctrl.driverPropertyGroups = ctrl._sortDriverProperties();
|
||||
ctrl.loadingDriverProperties = false;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Cancel the node enrollment process
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.cancel = function() {
|
||||
$modalInstance.dismiss('cancel');
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Enroll the defined node
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.enroll = function() {
|
||||
$log.debug(">> EnrollNodeController.enroll()");
|
||||
angular.forEach(ctrl.driverProperties, function(property, name) {
|
||||
$log.debug(name +
|
||||
", required = " + property.isRequired() +
|
||||
", active = " + property.isActive() +
|
||||
", input-value = " + property.getInputValue() +
|
||||
", default-value = " + property.getDefaultValue());
|
||||
if (property.isActive() &&
|
||||
property.getInputValue() &&
|
||||
property.getInputValue() !== property.getDefaultValue()) {
|
||||
$log.debug("Setting driver property " + name + " to " +
|
||||
property.inputValue);
|
||||
ctrl.node.driver_info[name] = property.inputValue;
|
||||
}
|
||||
});
|
||||
|
||||
ironic.createNode(ctrl.node).then(
|
||||
function() {
|
||||
$modalInstance.close();
|
||||
$rootScope.$emit(ironicEvents.ENROLL_NODE_SUCCESS);
|
||||
},
|
||||
function() {
|
||||
// No additional error processing for now
|
||||
});
|
||||
$log.debug("<< EnrollNodeController.enroll()");
|
||||
};
|
||||
|
||||
/**
|
||||
* @desription Delete a node property
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.deleteProperty = function(propertyName) {
|
||||
delete ctrl.node.properties[propertyName];
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Check whether the specified node property already exists
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {boolean} True if the property already exists,
|
||||
* otherwise false
|
||||
*/
|
||||
ctrl.checkPropertyUnique = function(propertyName) {
|
||||
return !(propertyName in ctrl.node.properties);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Delete a node metadata property
|
||||
*
|
||||
* @param {string} propertyName - Name of the property
|
||||
* @return {void}
|
||||
*/
|
||||
ctrl.deleteExtra = function(propertyName) {
|
||||
delete ctrl.node.extra[propertyName];
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Check whether the specified node metadata property
|
||||
* already exists
|
||||
*
|
||||
* @param {string} propertyName - Name of the metadata property
|
||||
* @return {boolean} True if the property already exists,
|
||||
* otherwise false
|
||||
*/
|
||||
ctrl.checkExtraUnique = function(propertyName) {
|
||||
return !(propertyName in ctrl.node.extra);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Check whether a specified driver property is
|
||||
* currently active
|
||||
*
|
||||
* @param {string} property - Driver property
|
||||
* @return {boolean} True if the property is active, false otherwise
|
||||
*/
|
||||
ctrl.isDriverPropertyActive = function(property) {
|
||||
return property.isActive();
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,278 @@
|
|||
<div class="modal-header" modal-draggable>
|
||||
<button type="button"
|
||||
class="close"
|
||||
ng-click="$dismiss()"
|
||||
aria-hidden="true"
|
||||
aria-label="Close">
|
||||
<span aria-hidden="true" class="fa fa-times"></span>
|
||||
</button>
|
||||
<h3 class="modal-title" translate>Enroll Node</h3>
|
||||
</div>
|
||||
<!-- begin general node info modal -->
|
||||
<div class="modal-body">
|
||||
<div class="tabbable"> <!-- Only required for left/right tabs -->
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="required active">
|
||||
<a href=""
|
||||
data-target="#nodeInfo"
|
||||
data-toggle="tab"
|
||||
translate>Node Info</a></li>
|
||||
<li ng-if="!ctrl.driverProperties"
|
||||
class="disabled">
|
||||
<a data-target="#driverDetails"
|
||||
translate>Driver Details</a></li>
|
||||
<li ng-if="ctrl.driverProperties">
|
||||
<a href=""
|
||||
data-target="#driverDetails"
|
||||
data-toggle="tab"
|
||||
translate>Driver Details</a></li>
|
||||
</ul>
|
||||
|
||||
<!--enroll node form-->
|
||||
<form id="enrollNodeForm"
|
||||
name="enrollNodeForm">
|
||||
|
||||
<!--tabbed content-->
|
||||
<div class="tab-content">
|
||||
<!-- node info tab-->
|
||||
<div class="tab-pane active" id="nodeInfo">
|
||||
<!--node name-->
|
||||
<div class="form-group"
|
||||
ng-class="{'has-error': enrollNodeForm.name.$invalid &&
|
||||
enrollNodeForm.name.$dirty}">
|
||||
<label for="name"
|
||||
class="control-label"
|
||||
translate>Node Name</label>
|
||||
<div>
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
ng-model="ctrl.node.name"
|
||||
id="name"
|
||||
name="name"
|
||||
ng-pattern="ctrl.validHostNameRegex"
|
||||
placeholder="{$ 'A unique node name. Optional.' | translate $}"/>
|
||||
</div>
|
||||
</div>
|
||||
<!--node driver-->
|
||||
<div class="form-group required">
|
||||
<label for="driver"
|
||||
class="control-label"
|
||||
translate>Node Driver</label>
|
||||
<span class="hz-icon-required fa fa-asterisk"></span>
|
||||
<div>
|
||||
<select id="driver"
|
||||
class="form-control"
|
||||
ng-options="driver as driver.name for driver in ctrl.drivers"
|
||||
ng-model="ctrl.selectedDriver"
|
||||
ng-change="ctrl.loadDriverProperties(ctrl.selectedDriver.name)">
|
||||
<option value="" disabled selected translate>Select a Driver</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<!--properties-->
|
||||
<div class="form-group">
|
||||
<label for="properties"
|
||||
class="control-label"
|
||||
translate>Properties</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right"
|
||||
translate>
|
||||
Add New Property:</span>
|
||||
<input class="form-control"
|
||||
id="properties"
|
||||
type="text"
|
||||
ng-model="propertyName"
|
||||
validate-unique="ctrl.checkPropertyUnique"
|
||||
placeholder="{$ 'Property Name' | translate $}"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
ng-disabled="!propertyName || AddPropertyForm.$invalid"
|
||||
ng-click="ctrl.node.properties[propertyName] = null;
|
||||
propertyName = null">
|
||||
<span class="fa fa-plus"> </span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!--properties list-->
|
||||
<div class="form-group">
|
||||
<div class="input-group input-group-sm"
|
||||
ng-repeat="(propertyName, propertyValue) in ctrl.node.properties">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
{$ propertyName $}
|
||||
</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="{$ propertyName $}"
|
||||
ng-model="ctrl.node.properties[propertyName]"
|
||||
ng-required="true"/>
|
||||
<div class="input-group-btn">
|
||||
<a class="btn btn-default"
|
||||
ng-click="ctrl.deleteProperty(propertyName)">
|
||||
<span class="fa fa-minus"> </span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--extras-->
|
||||
<div class="form-group">
|
||||
<label for="extras"
|
||||
class="control-label"
|
||||
translate>Extras</label>
|
||||
<div class="input-group input-group-sm">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right"
|
||||
translate>
|
||||
Add Extra:</span>
|
||||
<input class="form-control"
|
||||
id="extras"
|
||||
type="text"
|
||||
ng-model="extraName"
|
||||
validate-unique="ctrl.checkExtraUnique"
|
||||
placeholder="{$ 'Extra Property Name' | translate $}"/>
|
||||
<span class="input-group-btn">
|
||||
<button class="btn btn-primary"
|
||||
type="button"
|
||||
ng-disabled="!extraName || AddExtraForm.$invalid"
|
||||
ng-click="ctrl.node.extra[extraName] = null;
|
||||
extraName = null">
|
||||
<span class="fa fa-plus"> </span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<!--extras list-->
|
||||
<div class="input-group input-group-sm"
|
||||
ng-repeat="(propertyName, propertyValue) in ctrl.node.extra">
|
||||
<span class="input-group-addon"
|
||||
style="width:25%;text-align:right">
|
||||
{$ propertyName $}
|
||||
</span>
|
||||
<input class="form-control"
|
||||
type="text"
|
||||
name="{$ propertyName $}"
|
||||
ng-model="ctrl.node.extra[propertyName]"
|
||||
ng-required="true"/>
|
||||
<div class="input-group-btn">
|
||||
<a class="btn btn-default"
|
||||
ng-click="ctrl.deleteExtra(propertyName)">
|
||||
<span class="fa fa-minus"> </span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end node info tab-->
|
||||
|
||||
<!--driver details tab-->
|
||||
<div class="tab-pane" id="driverDetails">
|
||||
<p class="text-center"
|
||||
ng-if="ctrl.loadingDriverProperties">
|
||||
<small><em><i class="fa fa-spin fa-refresh"></i></em></small>
|
||||
</p>
|
||||
<div ng-repeat="propertyGroup in ctrl.driverPropertyGroups"
|
||||
ng-class="{'well': propertyGroup.length > 1}">
|
||||
<div class="form-group"
|
||||
ng-repeat="property in propertyGroup | filter:ctrl.isDriverPropertyActive"
|
||||
ng-init="name = property.name;
|
||||
selectOptions = property.getSelectOptions()"
|
||||
ng-class="{'has-error': enrollNodeForm.{$ name $}.$invalid &&
|
||||
enrollNodeForm.{$ name $}.$dirty}">
|
||||
<label for="{$ name $}"
|
||||
class="control-label"
|
||||
style="white-space: nowrap">
|
||||
{$ name $}
|
||||
<span ng-if="property.isRequired()"
|
||||
class="hz-icon-required fa fa-asterisk"></span>
|
||||
<span class="help-icon"
|
||||
data-container="body"
|
||||
title=""
|
||||
data-toggle="tooltip"
|
||||
data-original-title="{$ property.getDescription() | translate $}">
|
||||
<span class="fa fa-question-circle"></span>
|
||||
</span>
|
||||
</label>
|
||||
<div ng-if="!selectOptions"
|
||||
ng-class="{'input-group': name === 'deploy_kernel' ||
|
||||
name === 'deploy_ramdisk'}">
|
||||
<input type="text"
|
||||
class="form-control"
|
||||
id="{$ name $}"
|
||||
name="{$ name $}"
|
||||
ng-model="property.inputValue"
|
||||
ng-pattern="property.getValidValueRegex()"
|
||||
placeholder="{$ property.defaultValue !== undefined ?
|
||||
property.defaultValue :
|
||||
property.getDescription() $}"
|
||||
ng-required="property.isRequired()"
|
||||
empty-to-pristine/>
|
||||
<div ng-if="name === 'deploy_kernel' ||
|
||||
name === 'deploy_ramdisk'"
|
||||
class="input-group-btn">
|
||||
<button type="button"
|
||||
class="btn btn-primary dropdown-toggle"
|
||||
data-toggle="dropdown"
|
||||
translate>
|
||||
Choose an Image
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right">
|
||||
<li>
|
||||
<a class="dropdown-item"
|
||||
ng-repeat="imageObj in ctrl.images"
|
||||
href="#"
|
||||
ng-click="property.inputValue = imageObj.id">{$ imageObj.name + ' [' + imageObj.id + ']' $}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div ng-if="selectOptions" class="">
|
||||
<select ng-if="selectOptions.length > 4"
|
||||
id="{$ name $}"
|
||||
class="form-control"
|
||||
ng-options="opt for opt in selectOptions"
|
||||
ng-model="property.inputValue"
|
||||
ng-required="property.isRequired()">
|
||||
<option ng-if="property.defaultValue === undefined"
|
||||
value=""
|
||||
disabled
|
||||
selected
|
||||
translate>{$ property.getDescription() $}</option>
|
||||
</select>
|
||||
<div ng-if="selectOptions.length <= 4"
|
||||
class="btn-group">
|
||||
<label class="btn btn-default"
|
||||
ng-repeat="opt in selectOptions"
|
||||
ng-model="property.inputValue"
|
||||
btn-radio="opt">{$ opt $}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--end driver details tab-->
|
||||
</div>
|
||||
<!--end tabbed content-->
|
||||
|
||||
</form>
|
||||
<!--end enroll node form-->
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<!--modal footer-->
|
||||
<div class="modal-footer ng-scope">
|
||||
<button class="btn btn-default"
|
||||
ng-click="ctrl.cancel()">
|
||||
<span class="fa fa-close"></span>
|
||||
<span class="ng-scope" translate>Cancel</span>
|
||||
</button>
|
||||
<button type="submit"
|
||||
ng-disabled="!ctrl.driverProperties ||
|
||||
enrollNodeForm.$invalid"
|
||||
ng-click="ctrl.enroll()"
|
||||
class="btn btn-primary"
|
||||
translate>
|
||||
Enroll Node
|
||||
</button>
|
||||
</div>
|
|
@ -0,0 +1,685 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function() {
|
||||
'use strict';
|
||||
|
||||
var REQUIRED = " " + gettext("Required") + ".";
|
||||
|
||||
var SELECT_OPTIONS_REGEX =
|
||||
new RegExp(
|
||||
gettext('(?:[Oo]ne of )(?!this)((?:(?:"[^"]+"|[^,\\. ]+)(?:, |\\.))+)'));
|
||||
|
||||
var DEFAULT_IS_REGEX =
|
||||
new RegExp(gettext('default (?:value )?is ([^"\\. ]+|"[^"]+")'));
|
||||
|
||||
var DEFAULTS_TO_REGEX =
|
||||
new RegExp(gettext('Defaults to ([^"\\. ]+|"[^"]+")'));
|
||||
|
||||
var DEFAULT_IN_PARENS_REGEX =
|
||||
new RegExp(gettext(' ([^" ]+|"[^"]+") \\(Default\\)'));
|
||||
|
||||
var DEFAULT_REGEX_LIST = [DEFAULT_IS_REGEX,
|
||||
DEFAULTS_TO_REGEX,
|
||||
DEFAULT_IN_PARENS_REGEX];
|
||||
var ONE_OF_REGEX =
|
||||
new RegExp(gettext('One of this, (.*) must be specified\\.'));
|
||||
|
||||
var NOT_INSIDE_MATCH = -1;
|
||||
|
||||
var VALID_PORT_REGEX = new RegExp('^\\d+$');
|
||||
|
||||
var VALID_IPV4_ADDRESS = "^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"; // eslint-disable-line max-len
|
||||
|
||||
var VALID_IPV6_ADDRESS = "^\\s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)(\\.(25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]?\\d)){3}))|:)))(%.+)?\\s*$"; // eslint-disable-line max-len
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.factory('horizon.dashboard.admin.ironic.enroll-node.service',
|
||||
enrollNodeService);
|
||||
|
||||
enrollNodeService.$inject = [
|
||||
'$modal',
|
||||
'horizon.dashboard.admin.basePath',
|
||||
'$log',
|
||||
'horizon.dashboard.admin.ironic.validHostNamePattern',
|
||||
'horizon.dashboard.admin.ironic.validUuidPattern'
|
||||
];
|
||||
|
||||
function enrollNodeService($modal,
|
||||
basePath,
|
||||
$log,
|
||||
validHostNamePattern,
|
||||
validUuidPattern) {
|
||||
var service = {
|
||||
modal: modal,
|
||||
DriverProperty: DriverProperty,
|
||||
Graph: Graph
|
||||
};
|
||||
|
||||
var VALID_ADDRESS_HOSTNAME_REGEX = new RegExp(VALID_IPV4_ADDRESS + "|" +
|
||||
VALID_IPV6_ADDRESS + "|" +
|
||||
validHostNamePattern);
|
||||
|
||||
var VALID_IMAGE_REGEX = new RegExp(validUuidPattern + "|" +
|
||||
"^(https?|file)://.+$");
|
||||
|
||||
function modal() {
|
||||
var options = {
|
||||
controller: 'EnrollNodeController as ctrl',
|
||||
backdrop: 'static',
|
||||
templateUrl: basePath + '/ironic/enroll-node/enroll-node.html'
|
||||
};
|
||||
return $modal.open(options);
|
||||
}
|
||||
|
||||
/**
|
||||
The DriverProperty class is used to represent an ironic driver
|
||||
property. It is currently used by the enroll-node form to
|
||||
support property display, value assignment and validation.
|
||||
|
||||
The following rules are used to extract information about a property
|
||||
from the description returned by the driver.
|
||||
|
||||
1. If the description ends with " Required." a value must be
|
||||
supplied for the property.
|
||||
|
||||
2. The following syntax is used to extract default values
|
||||
from property descriptions.
|
||||
|
||||
Default is <value>(<space>|.)
|
||||
default is “<value>”
|
||||
default value is <value>(<space>|.)
|
||||
default value is “<value>”
|
||||
Defaults to <value>(<space>|.)
|
||||
Defaults to “<value>”
|
||||
<value> (Default)
|
||||
|
||||
3. The following syntax is used to determine whether a property
|
||||
is considered active. In the example below if the user specifies
|
||||
a value for <property-name-1>, properties 2 to n will be tagged
|
||||
inactive, and hidden from view. All properties are considered
|
||||
to be required.
|
||||
|
||||
One of this, <property-name-1>, <property-name-2>, …, or
|
||||
<property-name-n> must be specified.
|
||||
|
||||
4. The following syntax is used to determine whether a property
|
||||
is restricted to a set of enumerated values. The property will
|
||||
be displayed as an HTML select element.
|
||||
|
||||
[Oo]ne of <value-1>, "<value-2>", …, <value-n>.
|
||||
|
||||
5. The following syntax is used to determine whether a property is
|
||||
active and required based on the value of another property.
|
||||
If the property is not active it will not be displayed.
|
||||
|
||||
Required|Used only if <property-name> is set to <value-1>
|
||||
(or "<value-2>")*.
|
||||
|
||||
Notes:
|
||||
1. The properties "deploy_kernel" and "deploy_ramdisk" are
|
||||
assumed to accept Glance image uuids as valid values.
|
||||
|
||||
2. Property names ending in _port are assumed to only accept
|
||||
postive integer values
|
||||
|
||||
3. Property names ending in _address are assumed to only accept
|
||||
valid IPv4 and IPv6 addresses; and hostnames
|
||||
*/
|
||||
|
||||
/**
|
||||
* @description Construct a new driver property
|
||||
*
|
||||
* @class DriverProperty
|
||||
* @param {string} name - Name of property
|
||||
* @param {string} desc - Description of property
|
||||
* @param {object} propertySet - Set of properties to which this one belongs
|
||||
*
|
||||
* @property {string} defaultValue - Default value of the property
|
||||
* @property {string[]} selectOptions - If the property is limited to a
|
||||
* set of enumerated values then selectOptions will be an array of those
|
||||
* values, otherwise null
|
||||
* @property {boolean} required - Boolean value indicating whether a value
|
||||
* must be supplied for this property if it is active
|
||||
* @property {PostfixExpr} isActiveExpr - Null if this property is always
|
||||
* active; otherwise, a boolean expression that when evaluated will
|
||||
* return whether this variable is active. A property is considered
|
||||
* active if its role is not eliminated by the values of other
|
||||
* properties in the property-set.
|
||||
* @property {string} inputValue - User assigned value for this property
|
||||
* @property {regexp} validValueRegex - Regular expression used to
|
||||
* determine whether an input value is valid.
|
||||
* @returns {object} Driver property
|
||||
*/
|
||||
function DriverProperty(name, desc, propertySet) {
|
||||
this.name = name;
|
||||
this.desc = desc;
|
||||
this.propertySet = propertySet;
|
||||
|
||||
// Determine whether this property should be presented as a selection
|
||||
this.selectOptions = this._analyzeSelectOptions();
|
||||
|
||||
this.required = null; // Initialize to unknown
|
||||
// Expression to be evaluated to determine whether property is active.
|
||||
// By default the property is considered active.
|
||||
this.isActiveExpr = null;
|
||||
var result = this._analyzeRequiredOnlyDependencies();
|
||||
if (result) {
|
||||
this.required = result[0];
|
||||
this.isActiveExpr = result[1];
|
||||
}
|
||||
if (!this.isActiveExpr) {
|
||||
result = this._analyzeOneOfDependencies();
|
||||
if (result) {
|
||||
this.required = result[0];
|
||||
this.isActiveExpr = result[1];
|
||||
}
|
||||
}
|
||||
if (this.required === null) {
|
||||
this.required = desc.endsWith(REQUIRED);
|
||||
}
|
||||
|
||||
this.defaultValue = this._getDefaultValue();
|
||||
this.inputValue = this.defaultValue;
|
||||
|
||||
// Infer that property is a boolean that can be represented as a
|
||||
// True/False selection
|
||||
if (this.selectOptions === null &&
|
||||
(this.defaultValue === "True" || this.defaultValue === "False")) {
|
||||
this.selectOptions = ["True", "False"];
|
||||
}
|
||||
|
||||
this.validValueRegex = _determineValidValueRegex(this.name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Return a regular expression that can be used to
|
||||
* validate the value of a specified property
|
||||
*
|
||||
* @param {string} propertyName - Name of property
|
||||
* @return {regexp} Regular expression object or undefined
|
||||
*/
|
||||
function _determineValidValueRegex(propertyName) {
|
||||
var regex;
|
||||
if (propertyName.endsWith("_port")) {
|
||||
regex = VALID_PORT_REGEX;
|
||||
} else if (propertyName.endsWith("_address")) {
|
||||
regex = VALID_ADDRESS_HOSTNAME_REGEX;
|
||||
} else if (propertyName === "deploy_kernel") {
|
||||
regex = VALID_IMAGE_REGEX;
|
||||
} else if (propertyName === "deploy_ramdisk") {
|
||||
regex = VALID_IMAGE_REGEX;
|
||||
}
|
||||
|
||||
return regex;
|
||||
}
|
||||
|
||||
DriverProperty.prototype.isActive = function() {
|
||||
if (!this.isActiveExpr) {
|
||||
return true;
|
||||
}
|
||||
var ret = this.isActiveExpr.evaluate(this.propertySet);
|
||||
return ret[0] === PostfixExpr.status.OK &&
|
||||
typeof ret[1] === "boolean" ? ret[1] : true;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get a regular expression object that can be used to
|
||||
* determine whether a value is valid for this property
|
||||
*
|
||||
* @return {regexp} Regular expression object or undefined
|
||||
*/
|
||||
DriverProperty.prototype.getValidValueRegex = function() {
|
||||
return this.validValueRegex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Must a value be provided for this property
|
||||
*
|
||||
* @return {boolean} True if a value must be provided for this property
|
||||
*/
|
||||
DriverProperty.prototype.isRequired = function() {
|
||||
return this.required;
|
||||
};
|
||||
|
||||
DriverProperty.prototype._analyzeSelectOptions = function() {
|
||||
var match = this.desc.match(SELECT_OPTIONS_REGEX);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var matches = match[1].substring(0, match[1].length - 1).split(", ");
|
||||
var options = [];
|
||||
angular.forEach(matches, function(match) {
|
||||
options.push(trimQuotes(match));
|
||||
});
|
||||
return options;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get the list of select options for this property
|
||||
*
|
||||
* @return {string[]} null if this property is not selectable; else,
|
||||
* an array of selectable options
|
||||
*/
|
||||
DriverProperty.prototype.getSelectOptions = function() {
|
||||
return this.selectOptions;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Remove leading/trailing double-quotes from a string
|
||||
*
|
||||
* @param {string} str - String to be trimmed
|
||||
* @return {string} trim'd string
|
||||
*/
|
||||
function trimQuotes(str) {
|
||||
return str.charAt(0) === '"'
|
||||
? str.substring(1, str.length - 1) : str;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Get the default value of this property
|
||||
*
|
||||
* @return {string} Default value of this property
|
||||
*/
|
||||
DriverProperty.prototype._getDefaultValue = function() {
|
||||
var value;
|
||||
for (var i = 0; i < DEFAULT_REGEX_LIST.length; i++) {
|
||||
var match = this.desc.match(DEFAULT_REGEX_LIST[i]);
|
||||
if (match) {
|
||||
value = trimQuotes(match[1]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$log.debug("_getDefaultValue | " + this.desc + " | " + value);
|
||||
return value;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get the input value of this property
|
||||
*
|
||||
* @return {string} the input value of this property
|
||||
*/
|
||||
DriverProperty.prototype.getInputValue = function() {
|
||||
return this.inputValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get the default value of this property
|
||||
*
|
||||
* @return {string} the default value of this property
|
||||
*/
|
||||
DriverProperty.prototype.getDefaultValue = function() {
|
||||
return this.defaultValue;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get the description of this property
|
||||
*
|
||||
* @return {string} Description of this property
|
||||
*/
|
||||
DriverProperty.prototype.getDescription = function() {
|
||||
return this.desc;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Use the property description to build an expression
|
||||
* that will evaluate to a boolean result indicating whether the
|
||||
* property is active
|
||||
*
|
||||
* @return {array} null if this property is not dependent on any others;
|
||||
* otherwise,
|
||||
* [0] boolean indicating whether if active a value must be
|
||||
* supplied for this property.
|
||||
* [1] an expression that when evaluated will return a boolean
|
||||
* result indicating whether this property is active
|
||||
*/
|
||||
DriverProperty.prototype._analyzeRequiredOnlyDependencies = function() {
|
||||
var re = /(Required|Used) only if ([^ ]+) is set to /g;
|
||||
var match = re.exec(this.desc);
|
||||
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build logical expression to describe under what conditions this
|
||||
// property is active
|
||||
var expr = new PostfixExpr();
|
||||
var numAdds = 0;
|
||||
|
||||
var i = NOT_INSIDE_MATCH;
|
||||
var j = re.lastIndex;
|
||||
while (j < this.desc.length) {
|
||||
if (i === NOT_INSIDE_MATCH && this.desc.charAt(j) === ".") {
|
||||
break;
|
||||
}
|
||||
|
||||
if (this.desc.charAt(j) === '"') {
|
||||
if (i === NOT_INSIDE_MATCH) {
|
||||
i = j + 1;
|
||||
} else {
|
||||
expr.addProperty(match[2]);
|
||||
expr.addValue(this.desc.substring(i, j));
|
||||
expr.addOperator(PostfixExpr.op.EQ);
|
||||
numAdds++;
|
||||
if (numAdds > 1) {
|
||||
expr.addOperator(PostfixExpr.op.OR);
|
||||
}
|
||||
i = NOT_INSIDE_MATCH;
|
||||
}
|
||||
}
|
||||
j++;
|
||||
}
|
||||
$log.debug("_analyzeRequiredOnlyDependencies | " +
|
||||
this.desc + " | " +
|
||||
match[2] + ", " +
|
||||
JSON.stringify(expr));
|
||||
return [match[1] === "Required", expr];
|
||||
};
|
||||
|
||||
DriverProperty.prototype._analyzeOneOfDependencies = function() {
|
||||
var match = this.desc.match(ONE_OF_REGEX);
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build logical expression to describe under what conditions this
|
||||
// property is active
|
||||
var expr = new PostfixExpr();
|
||||
|
||||
var parts = match[1].split(", or ");
|
||||
expr.addProperty(parts[1]);
|
||||
expr.addValue(undefined);
|
||||
expr.addOperator(PostfixExpr.op.EQ);
|
||||
|
||||
parts = parts[0].split(", ");
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
expr.addProperty(parts[i]);
|
||||
expr.addValue(undefined);
|
||||
expr.addOperator(PostfixExpr.op.EQ);
|
||||
expr.addOperator(PostfixExpr.op.AND);
|
||||
}
|
||||
$log.debug("_analyzeOneOfDependencies | " +
|
||||
this.desc + " | " +
|
||||
JSON.stringify(match) + ", " +
|
||||
JSON.stringify(expr));
|
||||
return [true, expr];
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get the names of the driver-properties whose values
|
||||
* determine whether this property is active
|
||||
*
|
||||
* @return {object} Object the properties of which are names of
|
||||
* activating driver-properties or null
|
||||
*/
|
||||
DriverProperty.prototype.getActivators = function() {
|
||||
return this.isActiveExpr ? this.isActiveExpr.getProperties() : null;
|
||||
};
|
||||
|
||||
/**
|
||||
* PostFixExpr is a class primarily developed to support the
|
||||
* evaluation of boolean expressions that determine whether a
|
||||
* particular property is active.
|
||||
*
|
||||
* The expression is stored as a postfix sequence of operands and
|
||||
* operators. Operands are currently limited to the literal values
|
||||
* and the values of properties in a specified set. Currently
|
||||
* supported operands are ==, or, and.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function PostfixExpr() {
|
||||
this.elem = [];
|
||||
}
|
||||
|
||||
PostfixExpr.op = {
|
||||
EQ: "==",
|
||||
AND: "and",
|
||||
OR: "or"
|
||||
};
|
||||
|
||||
PostfixExpr.UNDEFINED = undefined;
|
||||
|
||||
PostfixExpr.status = {
|
||||
OK: 0,
|
||||
ERROR: 1,
|
||||
BAD_ARG: 2,
|
||||
UNKNOWN_OP: 3,
|
||||
MALFORMED: 4
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Add a property to the expression
|
||||
*
|
||||
* @param {string} propertyName - Property name
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
PostfixExpr.prototype.addProperty = function(propertyName) {
|
||||
this.elem.push({name: propertyName});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Add a value to the expression
|
||||
*
|
||||
* @param {object} value - value
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
PostfixExpr.prototype.addValue = function(value) {
|
||||
this.elem.push({value: value});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Add an operator to the expression
|
||||
*
|
||||
* @param {PostfixExpr.op} opId - operator
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
PostfixExpr.prototype.addOperator = function(opId) {
|
||||
this.elem.push({op: opId});
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Get a list of property names referenced by this
|
||||
* expression
|
||||
*
|
||||
* @return {object} An object each property of which corresponds to
|
||||
* a property in the expression
|
||||
*/
|
||||
PostfixExpr.prototype.getProperties = function() {
|
||||
var properties = {};
|
||||
angular.forEach(this.elem, function(elem) {
|
||||
if (angular.isDefined(elem.name)) {
|
||||
properties[elem.name] = true;
|
||||
}
|
||||
});
|
||||
return properties;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Evaluate a boolean binary operation
|
||||
*
|
||||
* @param {array} valStack - Stack of values to operate on
|
||||
* @param {string} opId - operator id
|
||||
*
|
||||
* @return {integer} Return code
|
||||
*/
|
||||
function _evaluateBoolBinaryOp(valStack, opId) {
|
||||
var retCode = PostfixExpr.status.OK;
|
||||
var val1 = valStack.pop();
|
||||
var val2 = valStack.pop();
|
||||
if (typeof val1 === "boolean" &&
|
||||
typeof val2 === "boolean") {
|
||||
switch (opId) {
|
||||
case PostfixExpr.op.AND:
|
||||
valStack.push(val1 && val2);
|
||||
break;
|
||||
case PostfixExpr.op.OR:
|
||||
valStack.push(val1 || val2);
|
||||
break;
|
||||
default:
|
||||
retCode = PostfixExpr.status.UNKNOWN_OP;
|
||||
}
|
||||
} else {
|
||||
retCode = PostfixExpr.status.BAD_ARG;
|
||||
}
|
||||
return retCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Evaluate the experssion using property values from
|
||||
* a specified set
|
||||
*
|
||||
* @param {object} propertySet - Dictionary of DriverProperty instances
|
||||
*
|
||||
* @return {array} Return code and Value of the expression
|
||||
*/
|
||||
PostfixExpr.prototype.evaluate = function(propertySet) {
|
||||
var resultStack = [];
|
||||
for (var i = 0, len = this.elem.length; i < len; i++) {
|
||||
var elem = this.elem[i];
|
||||
if (elem.hasOwnProperty("name")) {
|
||||
resultStack.push(propertySet[elem.name].getInputValue());
|
||||
} else if (elem.hasOwnProperty("value")) {
|
||||
resultStack.push(elem.value);
|
||||
} else if (elem.hasOwnProperty("op")) {
|
||||
if (elem.op === PostfixExpr.op.EQ) {
|
||||
var val1 = resultStack.pop();
|
||||
var val2 = resultStack.pop();
|
||||
resultStack.push(val1 === val2);
|
||||
} else {
|
||||
var ret = _evaluateBoolBinaryOp(resultStack, elem.op);
|
||||
if (ret !== PostfixExpr.status.OK) {
|
||||
return [ret, PostfixExpr.UNDEFINED];
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return [PostfixExpr.status.UNKNOWN_ELEMENT, PostfixExpr.UNDEFINED];
|
||||
}
|
||||
}
|
||||
return resultStack.length === 1
|
||||
? [PostfixExpr.status.OK, resultStack.pop()]
|
||||
: [PostfixExpr.status.MALFORMED, PostfixExpr.UNDEFINED];
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Class for representing and manipulating undirected
|
||||
* graphs
|
||||
*
|
||||
* @property {object} vertices - Associative array of vertex objects
|
||||
* indexed by property name
|
||||
* @return {object} Graph
|
||||
*/
|
||||
function Graph() {
|
||||
this.vertices = {};
|
||||
}
|
||||
|
||||
Graph.prototype.getVertex = function(vertexName) {
|
||||
var vertex = null;
|
||||
if (this.vertices.hasOwnProperty(vertexName)) {
|
||||
vertex = this.vertices[vertexName];
|
||||
}
|
||||
return vertex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Add a vertex to this graph
|
||||
*
|
||||
* @param {string} name - Vertex name
|
||||
* @param {object} data - Vertex data
|
||||
* @returns {object} - Newly created vertex
|
||||
*/
|
||||
Graph.prototype.addVertex = function(name, data) {
|
||||
var vertex = {name: name, data: data, adjacents: []};
|
||||
this.vertices[name] = vertex;
|
||||
return vertex;
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Add an undirected edge between two vertices
|
||||
*
|
||||
* @param {string} vertexName1 - Name of first vertex
|
||||
* @param {string} vertexName2 - Name of second vertex
|
||||
* @returns {void}
|
||||
*/
|
||||
Graph.prototype.addEdge = function(vertexName1, vertexName2) {
|
||||
this.vertices[vertexName1].adjacents.push(vertexName2);
|
||||
this.vertices[vertexName2].adjacents.push(vertexName1);
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Depth-first-search graph traversal utility function
|
||||
*
|
||||
* @param {object} vertex - Root vertex from which traveral will begin.
|
||||
* It is assumed that this vertex has not alreday been visited as part
|
||||
* of this traversal.
|
||||
* @param {object} visited - Associative array. Each named property
|
||||
* corresponds to a vertex with the same name, and has boolean value
|
||||
* indicating whether the vertex has been alreday visited.
|
||||
* @param {object[]} component - Array of vertices that define a strongly
|
||||
* connected component.
|
||||
* @returns {void}
|
||||
*/
|
||||
Graph.prototype._dfsTraverse = function(vertex, visited, component) {
|
||||
var graph = this;
|
||||
visited[vertex.name] = true;
|
||||
component.push(vertex);
|
||||
|
||||
/* eslint-disable no-unused-vars */
|
||||
angular.forEach(vertex.adjacents, function(vertexName) {
|
||||
if (!visited[vertexName]) {
|
||||
graph._dfsTraverse(graph.vertices[vertexName], visited, component);
|
||||
}
|
||||
});
|
||||
/* eslint-enable no-unused-vars */
|
||||
};
|
||||
|
||||
/**
|
||||
* @description Perform a depth-first-search on a specified graph to
|
||||
* find strongly connected components. A user provided function will
|
||||
* be called to process each component.
|
||||
*
|
||||
* @param {function} componentFunc - Function called on each strongly
|
||||
* connected component. Accepts aruments: array of vertex objects, and
|
||||
* user-provided extra data that can be used in processing the component.
|
||||
* @param {object} extra - Extra data that is passed into the component
|
||||
* processing function.
|
||||
* @returns {void}
|
||||
*/
|
||||
Graph.prototype.dfs = function(componentFunc, extra) {
|
||||
var graph = this;
|
||||
var visited = {};
|
||||
angular.forEach(
|
||||
graph.vertices,
|
||||
function(unused, name) {
|
||||
visited[name] = false;
|
||||
});
|
||||
|
||||
angular.forEach(this.vertices, function(vertex, vertexName) {
|
||||
if (!visited[vertexName]) {
|
||||
var component = [];
|
||||
graph._dfsTraverse(vertex, visited, component);
|
||||
componentFunc(component, extra);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
return service;
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,115 @@
|
|||
/**
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
|
||||
(function() {
|
||||
"use strict";
|
||||
|
||||
describe(
|
||||
'horizon.dashboard.admin.ironic.enroll-node.service',
|
||||
function() {
|
||||
var service;
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin'));
|
||||
|
||||
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('$modal', jasmine.createSpy());
|
||||
}));
|
||||
|
||||
beforeEach(inject(function($injector) {
|
||||
service =
|
||||
$injector.get('horizon.dashboard.admin.ironic.enroll-node.service');
|
||||
}));
|
||||
|
||||
it('defines the service', function() {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
describe('DriverProperty', function() {
|
||||
it('Base construction', function() {
|
||||
var propertyName = 'propertyName';
|
||||
var description = '';
|
||||
var propertySet = [];
|
||||
var property = new service.DriverProperty(propertyName,
|
||||
description,
|
||||
propertySet);
|
||||
expect(property.name).toBe(propertyName);
|
||||
expect(property.desc).toBe(description);
|
||||
expect(property.propertySet).toBe(propertySet);
|
||||
expect(property.getSelectOptions()).toBe(null);
|
||||
expect(property.required).toBe(false);
|
||||
expect(property.defaultValue).toBe(null);
|
||||
expect(property.inputValue).toBe(null);
|
||||
expect(property.getInputValue()).toBe(null);
|
||||
expect(property.isActive()).toBe(true);
|
||||
});
|
||||
|
||||
it('Required - ends with', function() {
|
||||
var property = new service.DriverProperty('propertyName',
|
||||
' Required.',
|
||||
[]);
|
||||
expect(property.required).toBe(true);
|
||||
});
|
||||
|
||||
it('Not required - missing space', function() {
|
||||
var property = new service.DriverProperty('propertyName',
|
||||
'Required.',
|
||||
[]);
|
||||
expect(property.required).toBe(false);
|
||||
});
|
||||
|
||||
it('Not required - missing period', function() {
|
||||
var property = new service.DriverProperty('propertyName',
|
||||
' Required',
|
||||
[]);
|
||||
expect(property.required).toBe(false);
|
||||
});
|
||||
|
||||
it('Select options', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
'One of "foo", bar.',
|
||||
[]);
|
||||
expect(property.getSelectOptions()).toEqual(['foo', 'bar']);
|
||||
});
|
||||
|
||||
it('Select options - No single quotes', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
"One of 'foo', bar.",
|
||||
[]);
|
||||
expect(property.getSelectOptions()).toEqual(["'foo'", 'bar']);
|
||||
});
|
||||
|
||||
it('default - is string', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
'default is "5.1".',
|
||||
[]);
|
||||
expect(property._getDefaultValue()).toEqual('5.1');
|
||||
});
|
||||
|
||||
it('default - period processing', function() {
|
||||
var property = new service.DriverProperty(
|
||||
'propertyName',
|
||||
'default is 5.1.',
|
||||
[]);
|
||||
expect(property._getDefaultValue()).toEqual('5');
|
||||
});
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -26,6 +26,21 @@
|
|||
* to support and display Ironic related content.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic', []);
|
||||
.module('horizon.dashboard.admin.ironic', [])
|
||||
.constant('horizon.dashboard.admin.ironic.validHostNamePattern',
|
||||
'^(([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)*([A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9\\-]*[A-Za-z0-9])$') // eslint-disable-line max-len
|
||||
|
||||
.constant('horizon.dashboard.admin.ironic.validUuidPattern',
|
||||
'^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$') // eslint-disable-line max-len
|
||||
.constant('horizon.dashboard.admin.ironic.events', events());
|
||||
|
||||
function events() {
|
||||
return {
|
||||
ENROLL_NODE_SUCCESS:'horizon.dashboard.admin.ironic.ENROLL_NODE_SUCCESS',
|
||||
DELETE_NODE_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_NODE_SUCCESS',
|
||||
CREATE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.CREATE_PORT_SUCCESS',
|
||||
DELETE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_PORT_SUCCESS'
|
||||
};
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
@ -28,157 +28,295 @@
|
|||
];
|
||||
|
||||
/**
|
||||
* @ngdoc service
|
||||
* @name horizon.app.core.openstack-service-api.ironic
|
||||
* @description Provides access to Ironic API
|
||||
* @description Service that provides access to the Ironic client API
|
||||
*
|
||||
* @param {object} apiService - HTTP service
|
||||
* @param {object} toastService - User message service
|
||||
* @return {object} Ironic API service
|
||||
*/
|
||||
|
||||
function ironicAPI(apiService, toastService) {
|
||||
var service = {
|
||||
getNodes: getNodes,
|
||||
createNode: createNode,
|
||||
createPort: createPort,
|
||||
deleteNode: deleteNode,
|
||||
deletePort: deletePort,
|
||||
getDrivers: getDrivers,
|
||||
getDriverProperties: getDriverProperties,
|
||||
getNode: getNode,
|
||||
getNodes: getNodes,
|
||||
getPortsWithNode: getPortsWithNode,
|
||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode,
|
||||
powerOffNode: powerOffNode,
|
||||
powerOnNode: powerOnNode,
|
||||
powerOffNode: powerOffNode
|
||||
putNodeInMaintenanceMode: putNodeInMaintenanceMode,
|
||||
removeNodeFromMaintenanceMode: removeNodeFromMaintenanceMode
|
||||
};
|
||||
|
||||
return service;
|
||||
|
||||
///////////
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.getNodes
|
||||
* @description Retrieve a list of nodes
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes
|
||||
*
|
||||
* @return Node collection in JSON
|
||||
* @return {promise} Node collection in JSON
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#NodeCollection
|
||||
*/
|
||||
|
||||
function getNodes() {
|
||||
return apiService.get('/api/ironic/nodes/')
|
||||
.error(function() {
|
||||
toastService.add('error', gettext('Unable to retrieve Ironic nodes.'));
|
||||
toastService.add('error',
|
||||
gettext('Unable to retrieve Ironic nodes.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.getNode
|
||||
* @description Retrieve information about the given node.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-nodes-(node_ident)
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-
|
||||
* nodes-(node_ident)
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Node
|
||||
*/
|
||||
|
||||
function getNode(uuid) {
|
||||
return apiService.get('/api/ironic/nodes/' + uuid).error(function() {
|
||||
toastService.add('error', gettext('Unable to retrieve the Ironic node.'));
|
||||
});
|
||||
return apiService.get('/api/ironic/nodes/' + uuid)
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to retrieve the Ironic node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.getPortsWithNode
|
||||
* @description Retrieve a list of ports associated with a node.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-ports
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} List of ports
|
||||
*/
|
||||
|
||||
function getPortsWithNode(uuid) {
|
||||
var config = {
|
||||
params : {
|
||||
node_id: uuid
|
||||
}
|
||||
};
|
||||
return apiService.get('/api/ironic/ports/', config).error(function() {
|
||||
toastService.add('error', gettext('Unable to retrieve the Ironic node ports.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.putNodeInMaintenanceMode
|
||||
* @description Put the node in maintenance mode.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-maintenance}
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
*/
|
||||
|
||||
function putNodeInMaintenanceMode(uuid, reason) {
|
||||
var data = {
|
||||
maint_reason: reason ? reason : gettext("No maintenance reason given.")
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance', data).error(function() {
|
||||
toastService.add('error',
|
||||
gettext('Unable to put the Ironic node in maintenance mode.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.removeNodeFromMaintenanceMode
|
||||
* @description Remove the node from maintenance mode.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* delete--v1-nodes-(node_ident)-maintenance}
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
*/
|
||||
|
||||
function removeNodeFromMaintenanceMode(uuid) {
|
||||
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance').error(function() {
|
||||
toastService.add('error',
|
||||
gettext('Unable to remove the Ironic node from maintenance mode.'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.powerOnNode
|
||||
* @description Set the power state of the node.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power}
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
*/
|
||||
|
||||
function powerOnNode(uuid) {
|
||||
var data = {
|
||||
state: 'on'
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power', data)
|
||||
.success(function () {
|
||||
toastService.add('success', gettext('Refresh page to see updated power status'));
|
||||
})
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to power on the node'));
|
||||
return apiService.get('/api/ironic/ports/', config)
|
||||
.error(function(reason) {
|
||||
var msg = gettext(
|
||||
'Unable to retrieve the Ironic node ports: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.app.core.openstack-service-api.ironic.powerOffNode
|
||||
* @description Set the power state of the node.
|
||||
* @description Put the node in maintenance mode.
|
||||
*
|
||||
* \href{http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power}
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-maintenance
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @param {string} reason – Reason for why node is being put into
|
||||
* maintenance mode
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function putNodeInMaintenanceMode(uuid, reason) {
|
||||
var data = {
|
||||
maint_reason: reason ? reason : gettext("No maintenance reason given.")
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/maintenance',
|
||||
data)
|
||||
.error(function(reason) {
|
||||
var msg = gettext(
|
||||
'Unable to put the Ironic node in maintenance mode: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Remove the node from maintenance mode.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* delete--v1-nodes-(node_ident)-maintenance
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function removeNodeFromMaintenanceMode(uuid) {
|
||||
return apiService.delete('/api/ironic/nodes/' + uuid + '/maintenance')
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to remove the Ironic node ' +
|
||||
'from maintenance mode: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Set the power state of the node.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function powerOnNode(uuid) {
|
||||
var data = {
|
||||
state: 'on'
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
|
||||
data)
|
||||
.success(function() {
|
||||
toastService.add('success',
|
||||
gettext('Refresh page to see updated power status'));
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to power on the node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Set the power state of the node.
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* put--v1-nodes-(node_ident)-states-power
|
||||
*
|
||||
* @param {string} uuid – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function powerOffNode(uuid) {
|
||||
var data = {
|
||||
state: 'off'
|
||||
};
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power', data)
|
||||
.success(function () {
|
||||
toastService.add('success', gettext('Refresh page to see updated power status'));
|
||||
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
|
||||
data)
|
||||
.success(function() {
|
||||
toastService.add('success',
|
||||
gettext('Refresh page to see updated power status'));
|
||||
})
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to power off the node'));
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to power off the node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Create an Ironic node
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#post--v1-nodes
|
||||
*
|
||||
* @param {object} params – Object containing parameters that define
|
||||
* the node to be created
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function createNode(params) {
|
||||
var data = {
|
||||
node: params
|
||||
};
|
||||
return apiService.post('/api/ironic/nodes/', data)
|
||||
.success(function() {
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to create node: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Delete the specified node from inventory
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* delete--v1-nodes
|
||||
*
|
||||
* @param {string} nodeIdent – UUID or logical name of a node.
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function deleteNode(nodeIdent) {
|
||||
var data = {
|
||||
node: nodeIdent
|
||||
};
|
||||
return apiService.delete('/api/ironic/nodes/', data)
|
||||
.success(function() {
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to delete node %s: %s');
|
||||
toastService.add(
|
||||
'error',
|
||||
interpolate(msg, [nodeIdent, reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Retrieve the list of Ironic drivers
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#get--v1-drivers
|
||||
*
|
||||
* @return {promise} Driver collection in JSON
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#DriverList
|
||||
*/
|
||||
function getDrivers() {
|
||||
return apiService.get('/api/ironic/drivers/').error(function(reason) {
|
||||
var msg = gettext('Unable to retrieve Ironic drivers: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Retrieve properities of a specified driver
|
||||
*
|
||||
* http://docs.openstack.org/developer/ironic/webapi/v1.html#
|
||||
* get--v1-drivers-properties
|
||||
*
|
||||
* @param {string} driverName - Driver name
|
||||
* @returns {promise} Property list
|
||||
*/
|
||||
function getDriverProperties(driverName) {
|
||||
return apiService.get(
|
||||
'/api/ironic/drivers/' + driverName + '/properties').error(
|
||||
function(reason) {
|
||||
var msg = gettext(
|
||||
'Unable to retrieve driver properties: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Create a network port
|
||||
*
|
||||
* @param {object} port – Object containing parameters that define
|
||||
* the port to be created
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function createPort(port) {
|
||||
var data = {
|
||||
port: port
|
||||
};
|
||||
return apiService.post('/api/ironic/ports/', data)
|
||||
.success(function() {
|
||||
toastService.add('success',
|
||||
gettext('Port successfully created'));
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to create port: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Delete a network port
|
||||
*
|
||||
* @param {string} portUuid – UUID of the port to be deleted
|
||||
* @return {promise} Promise
|
||||
*/
|
||||
function deletePort(portUuid) {
|
||||
var data = {
|
||||
port_uuid: portUuid
|
||||
};
|
||||
return apiService.delete('/api/ironic/ports/', data)
|
||||
.success(function() {
|
||||
})
|
||||
.error(function(reason) {
|
||||
var msg = gettext('Unable to delete port: %s');
|
||||
toastService.add('error', interpolate(msg, [reason], false));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-default secondary"
|
||||
<button class="btn btn-default secondary"
|
||||
type="button"
|
||||
ng-click="ctrl.cancel()"
|
||||
translate>
|
||||
|
@ -28,3 +28,4 @@
|
|||
Put Node(s) Into Maintenance Mode
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* Copyright 2016 Cray 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.
|
||||
*/
|
||||
(function () {
|
||||
'use strict';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.directive('modalDraggable', ModalDraggable);
|
||||
|
||||
ModalDraggable.$inject = ['$document', '$log'];
|
||||
|
||||
function ModalDraggable($document, $log) {
|
||||
|
||||
return function (scope, element) {
|
||||
var modalContent = null;
|
||||
while (element) {
|
||||
if (element.hasClass("modal-content")) {
|
||||
modalContent = element;
|
||||
break;
|
||||
}
|
||||
element = element.parent();
|
||||
}
|
||||
|
||||
if (modalContent) {
|
||||
modalContent.draggable({
|
||||
handle: ".modal-header"
|
||||
});
|
||||
} else {
|
||||
$log.error("Unable to find parent dialog");
|
||||
}
|
||||
};
|
||||
}
|
||||
})();
|
|
@ -20,6 +20,34 @@
|
|||
var POWER_STATE_ON = 'power on';
|
||||
var POWER_STATE_OFF = 'power off';
|
||||
|
||||
var DELETE_NODE_TITLE = gettext("Delete Node");
|
||||
var DELETE_NODE_MSG =
|
||||
gettext('Are you sure you want to delete node "%s"? ' +
|
||||
'This action cannot be undone.');
|
||||
var DELETE_NODE_SUCCESS = gettext('Successfully deleted node "%s"');
|
||||
var DELETE_NODE_ERROR = gettext('Unable to delete node "%s"');
|
||||
|
||||
var DELETE_NODES_TITLE = gettext("Delete Nodes");
|
||||
var DELETE_NODES_MSG =
|
||||
gettext('Are you sure you want to delete nodes "%s"? ' +
|
||||
'This action cannot be undone.');
|
||||
var DELETE_NODES_SUCCESS = gettext('Successfully deleted nodes "%s"');
|
||||
var DELETE_NODES_ERROR = gettext('Error deleting nodes "%s"');
|
||||
|
||||
var DELETE_PORT_TITLE = gettext("Delete Port");
|
||||
var DELETE_PORT_MSG =
|
||||
gettext('Are you sure you want to delete port "%s"? ' +
|
||||
'This action cannot be undone.');
|
||||
var DELETE_PORT_SUCCESS = gettext('Successfully deleted port "%s"');
|
||||
var DELETE_PORT_ERROR = gettext('Unable to delete port "%s"');
|
||||
|
||||
var DELETE_PORTS_TITLE = gettext("Delete Ports");
|
||||
var DELETE_PORTS_MSG =
|
||||
gettext('Are you sure you want to delete ports "%s"? ' +
|
||||
'This action cannot be undone.');
|
||||
var DELETE_PORTS_SUCCESS = gettext('Successfully deleted ports "%s"');
|
||||
var DELETE_PORTS_ERROR = gettext('Error deleting ports "%s"');
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.admin.ironic')
|
||||
.factory('horizon.dashboard.admin.ironic.actions', actions);
|
||||
|
@ -27,11 +55,26 @@
|
|||
actions.$inject = [
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.framework.widgets.toast.service',
|
||||
'$q'
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'horizon.framework.widgets.modal.deleteModalService',
|
||||
'horizon.dashboard.admin.ironic.create-port.service',
|
||||
'$q',
|
||||
'$rootScope'
|
||||
];
|
||||
|
||||
function actions(ironic, toastService, $q) {
|
||||
function actions(ironic,
|
||||
toastService,
|
||||
ironicEvents,
|
||||
deleteModalService,
|
||||
createPortService,
|
||||
$q,
|
||||
$rootScope) {
|
||||
var service = {
|
||||
createPort: createPort,
|
||||
deleteNode: deleteNode,
|
||||
deleteNodes: deleteNodes,
|
||||
deletePort: deletePort,
|
||||
deletePorts: deletePorts,
|
||||
powerOn: powerOn,
|
||||
powerOff: powerOff,
|
||||
powerOnAll: powerOnNodes,
|
||||
|
@ -44,33 +87,62 @@
|
|||
|
||||
return service;
|
||||
|
||||
function deleteNode(node) {
|
||||
var labels = {
|
||||
title: DELETE_NODE_TITLE,
|
||||
message: DELETE_NODE_MSG,
|
||||
submit: DELETE_NODE_TITLE,
|
||||
success: DELETE_NODE_SUCCESS,
|
||||
error: DELETE_NODE_ERROR
|
||||
};
|
||||
var context = {
|
||||
labels: labels,
|
||||
deleteEntity: ironic.deleteNode,
|
||||
successEvent: ironicEvents.DELETE_NODE_SUCCESS
|
||||
};
|
||||
return deleteModalService.open($rootScope, [node], context);
|
||||
}
|
||||
|
||||
function deleteNodes(nodes) {
|
||||
var labels = {
|
||||
title: DELETE_NODES_TITLE,
|
||||
message: DELETE_NODES_MSG,
|
||||
submit: DELETE_NODES_TITLE,
|
||||
success: DELETE_NODES_SUCCESS,
|
||||
error: DELETE_NODES_ERROR
|
||||
};
|
||||
var context = {
|
||||
labels: labels,
|
||||
deleteEntity: ironic.deleteNode,
|
||||
successEvent: ironicEvents.DELETE_NODE_SUCCESS
|
||||
};
|
||||
return deleteModalService.open($rootScope, nodes, context);
|
||||
}
|
||||
|
||||
// power state
|
||||
|
||||
function powerOn(node) {
|
||||
if (node.power_state !== POWER_STATE_OFF) {
|
||||
return $q.reject(gettext("Node is not powered off."));
|
||||
var msg = gettext("Node %s is not powered off.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.powerOnNode(node.uuid).then(
|
||||
function() {
|
||||
// Set power state to be indeterminate
|
||||
node.power_state = null;
|
||||
},
|
||||
function(reason) {
|
||||
toastService.add('error', reason);
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function powerOff(node) {
|
||||
if (node.power_state !== POWER_STATE_ON) {
|
||||
return $q.reject(gettext("Node is not powered on."));
|
||||
var msg = gettext("Node %s is not powered on.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.powerOffNode(node.uuid).then(
|
||||
function() {
|
||||
// Set power state to be indeterminate
|
||||
node.power_state = null;
|
||||
},
|
||||
function(reason) {
|
||||
toastService.add('error', reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -87,30 +159,26 @@
|
|||
|
||||
function putInMaintenanceMode(node, maintReason) {
|
||||
if (node.maintenance !== false) {
|
||||
return $q.reject(gettext("Node is already in maintenance mode."));
|
||||
var msg = gettext("Node %s is already in maintenance mode.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.putNodeInMaintenanceMode(node.uuid, maintReason).then(
|
||||
function () {
|
||||
node.maintenance = true;
|
||||
node.maintenance_reason = maintReason;
|
||||
},
|
||||
function(reason) {
|
||||
toastService.add('error', reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
function removeFromMaintenanceMode(node) {
|
||||
if (node.maintenance !== true) {
|
||||
return $q.reject(gettext("Node is not in maintenance mode."));
|
||||
var msg = gettext("Node %s is not in maintenance mode.");
|
||||
return $q.reject(interpolate(msg, [node], false));
|
||||
}
|
||||
return ironic.removeNodeFromMaintenanceMode(node.uuid).then(
|
||||
function () {
|
||||
node.maintenance = false;
|
||||
node.maintenance_reason = "";
|
||||
},
|
||||
function (reason) {
|
||||
toastService.add('error', reason);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
@ -123,6 +191,42 @@
|
|||
return applyFuncToNodes(removeFromMaintenanceMode, nodes);
|
||||
}
|
||||
|
||||
function createPort(node) {
|
||||
return createPortService.modal(node);
|
||||
}
|
||||
|
||||
function deletePort(port) {
|
||||
var labels = {
|
||||
title: DELETE_PORT_TITLE,
|
||||
message: DELETE_PORT_MSG,
|
||||
submit: DELETE_PORT_TITLE,
|
||||
success: DELETE_PORT_SUCCESS,
|
||||
error: DELETE_PORT_ERROR
|
||||
};
|
||||
var context = {
|
||||
labels: labels,
|
||||
deleteEntity: ironic.deletePort,
|
||||
successEvent: ironicEvents.DELETE_PORT_SUCCESS
|
||||
};
|
||||
return deleteModalService.open($rootScope, [port], context);
|
||||
}
|
||||
|
||||
function deletePorts(ports) {
|
||||
var labels = {
|
||||
title: DELETE_PORTS_TITLE,
|
||||
message: DELETE_PORTS_MSG,
|
||||
submit: DELETE_PORTS_TITLE,
|
||||
success: DELETE_PORTS_SUCCESS,
|
||||
error: DELETE_PORTS_ERROR
|
||||
};
|
||||
var context = {
|
||||
labels: labels,
|
||||
deleteEntity: ironic.deletePort,
|
||||
successEvent: ironicEvents.DELETE_PORT_SUCCESS
|
||||
};
|
||||
return deleteModalService.open($rootScope, ports, context);
|
||||
}
|
||||
|
||||
/*
|
||||
* @name horizon.dashboard.admin.ironic.actions.applyFuncToNodes
|
||||
* @description Apply a specified function to each member of a
|
||||
|
|
|
@ -23,21 +23,31 @@
|
|||
IronicNodeDetailsController);
|
||||
|
||||
IronicNodeDetailsController.$inject = [
|
||||
'$scope',
|
||||
'$rootScope',
|
||||
'$location',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'horizon.dashboard.admin.ironic.actions',
|
||||
'horizon.dashboard.admin.basePath',
|
||||
'horizon.dashboard.admin.ironic.maintenance.service'
|
||||
'horizon.dashboard.admin.ironic.maintenance.service',
|
||||
'horizon.dashboard.admin.ironic.validUuidPattern'
|
||||
];
|
||||
|
||||
function IronicNodeDetailsController($location,
|
||||
function IronicNodeDetailsController($scope,
|
||||
$rootScope,
|
||||
$location,
|
||||
ironic,
|
||||
ironicEvents,
|
||||
actions,
|
||||
basePath,
|
||||
maintenanceService) {
|
||||
maintenanceService,
|
||||
validUuidPattern) {
|
||||
var ctrl = this;
|
||||
var path = basePath + 'ironic/node-details/sections/';
|
||||
|
||||
ctrl.noPortsText = gettext('No network ports have been defined');
|
||||
|
||||
ctrl.actions = actions;
|
||||
|
||||
ctrl.sections = [
|
||||
|
@ -51,18 +61,42 @@
|
|||
}
|
||||
];
|
||||
|
||||
ctrl.ports = [];
|
||||
ctrl.portsSrc = [];
|
||||
ctrl.basePath = basePath;
|
||||
ctrl.re_uuid = /^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/;
|
||||
ctrl.re_uuid = new RegExp(validUuidPattern);
|
||||
ctrl.isUuid = isUuid;
|
||||
ctrl.getVifPortId = getVifPortId;
|
||||
ctrl.putNodeInMaintenanceMode = putNodeInMaintenanceMode;
|
||||
ctrl.removeNodeFromMaintenanceMode = removeNodeFromMaintenanceMode;
|
||||
ctrl.createPort = createPort;
|
||||
ctrl.deletePort = deletePort;
|
||||
ctrl.deletePorts = deletePorts;
|
||||
|
||||
var createPortHandler =
|
||||
$rootScope.$on(ironicEvents.CREATE_PORT_SUCCESS,
|
||||
function() {
|
||||
init();
|
||||
});
|
||||
|
||||
var deletePortHandler =
|
||||
$rootScope.$on(ironicEvents.DELETE_PORT_SUCCESS,
|
||||
function() {
|
||||
init();
|
||||
$scope.$broadcast('hzTable:clearSelected');
|
||||
});
|
||||
|
||||
$scope.$on('$destroy', function() {
|
||||
createPortHandler();
|
||||
deletePortHandler();
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.ironic.NodeDetailsController.init
|
||||
* @description Initialize the controller instance based on the current page url.
|
||||
* @description Initialize the controller instance based on the
|
||||
* current page url.
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
|
@ -87,20 +121,24 @@
|
|||
function retrieveNode(uuid) {
|
||||
return ironic.getNode(uuid).then(function (response) {
|
||||
ctrl.node = response.data;
|
||||
ctrl.node.id = uuid;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.ironic.NodeDetailsController.retrievePorts
|
||||
* @description Retrieve the ports associated with a specified node, and store
|
||||
* them in the controller instance.
|
||||
* @description Retrieve the ports associated with a specified node,
|
||||
* and store them in the controller instance.
|
||||
*
|
||||
* @param {string} nodeId – Node name or UUID
|
||||
* @return {void}
|
||||
*/
|
||||
function retrievePorts(nodeId) {
|
||||
ironic.getPortsWithNode(nodeId).then(function (response) {
|
||||
ctrl.ports = response.data.items;
|
||||
ctrl.portsSrc = response.data.items;
|
||||
ctrl.portsSrc.forEach(function(port) {
|
||||
port.id = port.uuid;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -109,7 +147,8 @@
|
|||
* @description Test whether a string is an OpenStack UUID
|
||||
*
|
||||
* @param {string} str – string
|
||||
* @return {boolean} True if the string is an OpenStack UUID, otherwise false
|
||||
* @return {boolean} True if the string is an OpenStack UUID,
|
||||
* otherwise false
|
||||
*/
|
||||
function isUuid(str) {
|
||||
return !!str.match(ctrl.re_uuid);
|
||||
|
@ -120,7 +159,8 @@
|
|||
* @description Get the vif_port_id property of a specified port
|
||||
*
|
||||
* @param {object} port – instance of port
|
||||
* @return {string} Value of vif_port_id property or "" if the property does not exist
|
||||
* @return {string} Value of vif_port_id property or
|
||||
* "" if the property does not exist
|
||||
*/
|
||||
function getVifPortId(port) {
|
||||
return angular.isDefined(port.extra) &&
|
||||
|
@ -135,5 +175,42 @@
|
|||
function removeNodeFromMaintenanceMode() {
|
||||
maintenanceService.removeNodeFromMaintenanceMode(ctrl.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.ironic.NodeDetailsController.createPort
|
||||
* @description Initiate creation of a newtwork port for the current
|
||||
* node
|
||||
*
|
||||
* @return {void}
|
||||
*/
|
||||
function createPort() {
|
||||
ctrl.actions.createPort(ctrl.node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.ironic.NodeDetailsController.deletePort
|
||||
* @description Delete a specified port
|
||||
*
|
||||
* @param {port []} port – port to be deleted
|
||||
* @return {void}
|
||||
*/
|
||||
function deletePort(port) {
|
||||
ctrl.actions.deletePort({id: port.uuid, name: port.address});
|
||||
}
|
||||
|
||||
/**
|
||||
* @name horizon.dashboard.admin.ironic.NodeDetailsController.deletePorts
|
||||
* @description Delete a specified list of ports
|
||||
*
|
||||
* @param {port []} ports – list of ports to be deleted
|
||||
* @return {void}
|
||||
*/
|
||||
function deletePorts(ports) {
|
||||
var selectedPorts = [];
|
||||
angular.forEach(ports, function(port) {
|
||||
selectedPorts.push({id: port.uuid, name: port.address});
|
||||
});
|
||||
ctrl.actions.deletePorts(selectedPorts);
|
||||
}
|
||||
}
|
||||
})();
|
||||
|
|
|
@ -56,8 +56,14 @@
|
|||
|
||||
beforeEach(module('horizon.dashboard.admin.ironic'));
|
||||
|
||||
beforeEach(module('horizon.app.core.openstack-service-api', function($provide) {
|
||||
$provide.value('horizon.app.core.openstack-service-api.ironic', ironicAPI);
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('horizon.app.core.openstack-service-api.ironic',
|
||||
ironicAPI);
|
||||
}));
|
||||
|
||||
beforeEach(module(function($provide) {
|
||||
$provide.value('horizon.dashboard.admin.ironic.maintenance.service',
|
||||
{});
|
||||
}));
|
||||
|
||||
beforeEach(inject(function ($injector, _$rootScope_, _$location_) {
|
||||
|
@ -67,11 +73,11 @@
|
|||
var $location = _$location_;
|
||||
$location.path('/admin/ironic/' + nodeUuid + '/');
|
||||
|
||||
ctrl = controller('horizon.dashboard.admin.ironic.NodeDetailsController', {
|
||||
$scope: scope,
|
||||
$location: $location,
|
||||
'horizon.dashboard.admin.ironic.actions': {},
|
||||
'horizon.dashboard.admin.basePath': '/static'});
|
||||
ctrl = controller(
|
||||
'horizon.dashboard.admin.ironic.NodeDetailsController',
|
||||
{$location: $location,
|
||||
'horizon.dashboard.admin.ironic.actions': {},
|
||||
'horizon.dashboard.admin.basePath': '/static'});
|
||||
|
||||
scope.$apply();
|
||||
}));
|
||||
|
|
|
@ -8,9 +8,9 @@
|
|||
<dt translate>Node ID</dt>
|
||||
<dd>{$ ctrl.node.uuid $}</dd>
|
||||
<dt translate>Chassis ID</dt>
|
||||
<dd>{$ ctrl.node.chassis_uuid $}</dd>
|
||||
<dd>{$ ctrl.node.chassis_uuid | noValue $}</dd>
|
||||
<dt translate>Created At</dt>
|
||||
<dd>{$ ctrl.node.created_at $}</dd>
|
||||
<dd>{$ ctrl.node.created_at | date:'medium' $}</dd>
|
||||
<dt translate>Extra</dt>
|
||||
<dd>{$ ctrl.node.extra $}</dd>
|
||||
</dl>
|
||||
|
@ -20,19 +20,89 @@
|
|||
<div class="col-md-6 status detail">
|
||||
<h4 translate>Ports</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt ng-repeat-start="port in ctrl.ports">
|
||||
{$ 'MAC ' + (1 + $index) $}</dt>
|
||||
<dd ng-if="vif_port_id = ctrl.getVifPortId(port)">
|
||||
<a href="/admin/networks/ports/{$ vif_port_id $}/detail">
|
||||
{$ port.address $}
|
||||
</a>
|
||||
</dd>
|
||||
<dd ng-if="!vif_port_id">
|
||||
{$ port.address $}
|
||||
</dd>
|
||||
<p ng-repeat-end></p>
|
||||
</dl>
|
||||
<table hz-table ng-cloak
|
||||
st-table="ctrl.ports"
|
||||
st-safe-src="ctrl.portsSrc"
|
||||
class="table table-striped table-rsp table-detail">
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="4" class="action-col">
|
||||
<action-list dropdown class="pull-right">
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
callback="ctrl.createPort">
|
||||
{$ 'Create port' | translate $}
|
||||
</action>
|
||||
<menu>
|
||||
<action button-type="menu-item"
|
||||
callback="ctrl.deletePorts"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
<span class="fa fa-trash"></span>
|
||||
{$ 'Delete ports' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th class="multi_select_column">
|
||||
<input type="checkbox"
|
||||
hz-select-all="ctrl.ports"/>
|
||||
</th>
|
||||
<th translate class="rsp-p1" style="white-space:nowrap">
|
||||
MAC Address
|
||||
</th>
|
||||
<th translate class="rsp-p2" style="width:100%;">
|
||||
Extra
|
||||
</th>
|
||||
<th translate class="actions_column">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr ng-repeat="port in ctrl.ports">
|
||||
<td class="multi_select_column">
|
||||
<input type="checkbox"
|
||||
hz-select="port"
|
||||
ng-model="tCtrl.selections[port.id].checked"/>
|
||||
<td ng-if="vif_port_id = ctrl.getVifPortId(port)"
|
||||
class="rsp-p1">
|
||||
<a href="/dashboard/admin/networks/ports/{$ vif_port_id $}/detail">
|
||||
{$ port.address $}
|
||||
</a>
|
||||
</td>
|
||||
<td ng-if="!vif_port_id" class="rsp-p1">
|
||||
{$ port.address $}
|
||||
</td>
|
||||
<td>
|
||||
<dl class="dl-horizontal">
|
||||
<dt style="width:auto;" ng-repeat-start="(id, value) in port.extra">
|
||||
{$ id $}
|
||||
</dt>
|
||||
<dd>
|
||||
{$ value $}
|
||||
</dd>
|
||||
<p ng-repeat-end></p>
|
||||
</dl>
|
||||
</td>
|
||||
<td class="actions_column">
|
||||
<action-list>
|
||||
<action action-classes="'btn btn-default btn-sm'"
|
||||
callback="ctrl.deletePort"
|
||||
item="port">
|
||||
<span class="fa fa-trash"></span>
|
||||
</action>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
<tr hz-no-items
|
||||
items="ctrl.ports"
|
||||
message="ctrl.noPortsText">
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -42,14 +112,9 @@
|
|||
<h4 translate>Properties</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt translate>Memory</dt>
|
||||
<dd>{$ ctrl.node.properties.memory_mb + ' MB' $}</dd>
|
||||
<dt translate>CPU Arch</dt>
|
||||
<dd>{$ ctrl.node.properties.cpu_arch $}</dd>
|
||||
<dt translate>Local GB</dt>
|
||||
<dd>{$ ctrl.node.properties.local_gb $}</dd>
|
||||
<dt translate>CPUs</dt>
|
||||
<dd>{$ ctrl.node.properties.cpus $}</dd>
|
||||
<dt ng-repeat-start="(propertyName, propertyValue) in ctrl.node.properties">
|
||||
{$ propertyName $}</dt>
|
||||
<dd ng-repeat-end>{$ propertyValue $}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
|
||||
|
@ -68,7 +133,7 @@
|
|||
<dt translate>Deploy Kernel</dt>
|
||||
<dd>
|
||||
<a ng-if="deploy_kernel_is_uuid = ctrl.isUuid(ctrl.node.driver_info.deploy_kernel)"
|
||||
href="/admin/images/{$ ctrl.node.driver_info.deploy_kernel $}/detail">
|
||||
href="/dashboard/admin/images/{$ ctrl.node.driver_info.deploy_kernel $}/detail">
|
||||
{$ ctrl.node.driver_info.deploy_kernel $}
|
||||
</a>
|
||||
<span ng-if="!deploy_kernel_is_uuid">
|
||||
|
@ -78,7 +143,7 @@
|
|||
<dt translate>Deploy Ramdisk</dt>
|
||||
<dd>
|
||||
<a ng-if="deploy_ramdisk_is_uuid = ctrl.isUuid(ctrl.node.driver_info.deploy_ramdisk)"
|
||||
href="/admin/images/{$ ctrl.node.driver_info.deploy_ramdisk $}/detail">
|
||||
href="/dashboard/admin/images/{$ ctrl.node.driver_info.deploy_ramdisk $}/detail">
|
||||
{$ ctrl.node.driver_info.deploy_ramdisk $}
|
||||
</a>
|
||||
<span ng-if="!deploy_ramdisk_is_uuid">
|
||||
|
@ -107,22 +172,29 @@
|
|||
<!-- Instance Info -->
|
||||
<div class="col-md-6 status detail">
|
||||
<h4 translate>Instance Info</h4>
|
||||
<hr class="header_rule">
|
||||
<dl class="dl-horizontal">
|
||||
<dt translate>Instance Name</dt>
|
||||
<dd>{$ ctrl.node.instance_info.display_name $}</dd>
|
||||
<dt translate>Ramdisk</dt>
|
||||
<dd>
|
||||
<a href="/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
|
||||
{$ ctrl.node.instance_info.ramdisk $}
|
||||
</a>
|
||||
</dd>
|
||||
<dt translate>Kernel</dt>
|
||||
<dd>
|
||||
<a href="/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
|
||||
{$ ctrl.node.instance_info.kernel $}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<hr class="header_rule"/>
|
||||
<div ng-switch="ctrl.node.driver">
|
||||
<dl ng-switch-when="pxe_ssh" class="dl-horizontal">
|
||||
<dt translate>Instance Name</dt>
|
||||
<dd>{$ ctrl.node.instance_info.display_name $}</dd>
|
||||
<dt translate>Ramdisk</dt>
|
||||
<dd>
|
||||
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
|
||||
{$ ctrl.node.instance_info.ramdisk $}
|
||||
</a>
|
||||
</dd>
|
||||
<dt translate>Kernel</dt>
|
||||
<dd>
|
||||
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
|
||||
{$ ctrl.node.instance_info.kernel $}
|
||||
</a>
|
||||
</dd>
|
||||
</dl>
|
||||
<dl ng-switch-default class="dl-horizontal">
|
||||
<dt ng-repeat-start="(id, value) in ctrl.node.instance_info">{$ id $}</dt>
|
||||
<dd ng-repeat-end>{$ value $}</dd>
|
||||
</dl>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
|
|
@ -22,16 +22,22 @@
|
|||
.controller('IronicNodeListController', IronicNodeListController);
|
||||
|
||||
IronicNodeListController.$inject = [
|
||||
'$rootScope',
|
||||
'horizon.app.core.openstack-service-api.ironic',
|
||||
'horizon.dashboard.admin.ironic.events',
|
||||
'horizon.dashboard.admin.ironic.actions',
|
||||
'horizon.dashboard.admin.basePath',
|
||||
'horizon.dashboard.admin.ironic.maintenance.service'
|
||||
'horizon.dashboard.admin.ironic.maintenance.service',
|
||||
'horizon.dashboard.admin.ironic.enroll-node.service'
|
||||
];
|
||||
|
||||
function IronicNodeListController(ironic,
|
||||
function IronicNodeListController($rootScope,
|
||||
ironic,
|
||||
ironicEvents,
|
||||
actions,
|
||||
basePath,
|
||||
maintenanceService) {
|
||||
maintenanceService,
|
||||
enrollNodeService) {
|
||||
var ctrl = this;
|
||||
|
||||
ctrl.nodes = [];
|
||||
|
@ -43,6 +49,7 @@
|
|||
ctrl.putNodesInMaintenanceMode = putNodesInMaintenanceMode;
|
||||
ctrl.removeNodeFromMaintenanceMode = removeNodeFromMaintenanceMode;
|
||||
ctrl.removeNodesFromMaintenanceMode = removeNodesFromMaintenanceMode;
|
||||
ctrl.enrollNode = enrollNode;
|
||||
|
||||
/**
|
||||
* Filtering - client-side MagicSearch
|
||||
|
@ -81,6 +88,23 @@
|
|||
}
|
||||
];
|
||||
|
||||
// Listen for the creation of new nodes, and update the node list
|
||||
$rootScope.$on(ironicEvents.ENROLL_NODE_SUCCESS, function() {
|
||||
init();
|
||||
});
|
||||
|
||||
$rootScope.$on(ironicEvents.DELETE_NODE_SUCCESS, function() {
|
||||
init();
|
||||
});
|
||||
|
||||
$rootScope.$on(ironicEvents.CREATE_PORT_SUCCESS, function() {
|
||||
init();
|
||||
});
|
||||
|
||||
$rootScope.$on(ironicEvents.DELETE_PORT_SUCCESS, function() {
|
||||
init();
|
||||
});
|
||||
|
||||
init();
|
||||
|
||||
// RETRIVE NODES AND PORTS
|
||||
|
@ -124,6 +148,10 @@
|
|||
function removeNodesFromMaintenanceMode(nodes) {
|
||||
maintenanceService.removeNodesFromMaintenanceMode(nodes);
|
||||
}
|
||||
|
||||
function enrollNode() {
|
||||
enrollNodeService.modal();
|
||||
}
|
||||
}
|
||||
|
||||
})();
|
||||
|
|
|
@ -1,150 +1,176 @@
|
|||
<table ng-controller="IronicNodeListController as table"
|
||||
hz-table ng-cloak
|
||||
st-table="table.nodes"
|
||||
st-safe-src="table.nodesSrc"
|
||||
default-sort="name"
|
||||
default-sort-reverse="false"
|
||||
class="table table-striped table-rsp table-detail">
|
||||
<div ng-controller="IronicNodeListController as table">
|
||||
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="100" class="search-header">
|
||||
<hz-magic-search-bar group-classes="input-group-sm" icon-classes="fa-search"
|
||||
filter-facets="table.nodeFacets">
|
||||
</hz-magic-search-bar>
|
||||
</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="100" class="action-col">
|
||||
<action-list dropdown class="pull-right">
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
callback="table.actions.powerOnAll"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Power on' | translate $}
|
||||
</action>
|
||||
<menu>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.powerOffAll"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Power off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.putNodesInMaintenanceMode"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Maintenance on' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.removeNodesFromMaintenanceMode"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Maintenance off' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
</th>
|
||||
</tr>
|
||||
<hz-magic-search-context filter-facets="table.nodeFacets">
|
||||
<hz-magic-search-bar>
|
||||
</hz-magic-search-bar>
|
||||
|
||||
<tr>
|
||||
<th class="multi_select_column">
|
||||
<input type="checkbox"
|
||||
hz-select-all="table.nodes"/>
|
||||
</th>
|
||||
<th translate class="rsp-p1" st-sort="name" st-sort-default="name">
|
||||
Node Name
|
||||
</th>
|
||||
<th translate class="rsp-p1" st-sort="instance_uuid">
|
||||
Instance ID
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="power_state">
|
||||
Power State
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="provision_state">
|
||||
Provisioning State
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="maintenance">
|
||||
Maintenance
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="ports">
|
||||
Ports
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="driver">
|
||||
Driver
|
||||
</th>
|
||||
<th translate class="actions_column">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<table st-magic-search
|
||||
hz-table ng-cloak
|
||||
st-table="table.nodes"
|
||||
st-safe-src="table.nodesSrc"
|
||||
default-sort="name"
|
||||
default-sort-reverse="false"
|
||||
class="table table-striped table-rsp table-detail">
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="node in table.nodes">
|
||||
<td class="multi_select_column">
|
||||
<input type="checkbox"
|
||||
hz-select="node"
|
||||
ng-model="tCtrl.selections[node.id].checked"/>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<a href="{$ node.uuid $}">
|
||||
{$ node.name || node.uuid $}
|
||||
</a>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<!-- multiple tags used purposefully to ensure that the link
|
||||
is removed if node uuid == no instance. Using ng-attr-href
|
||||
only removed the href and the <a> styling remained which could
|
||||
cause confusion for users. -->
|
||||
<a href="/admin/instances/{$ node.instance_uuid $}/detail"
|
||||
ng-if="node.instance_uuid">
|
||||
{$ node.instance_uuid $}
|
||||
</a>
|
||||
<span ng-if="!node.instance_uuid">{$ 'No Instance' | translate $}</span>
|
||||
</td>
|
||||
<td class="rsp-p2" >
|
||||
<div ng-class="{'running': node['target_power_state']}">
|
||||
{$ node.power_state $}
|
||||
</div>
|
||||
</td>
|
||||
<td class="rsp-p2">{$ node.provision_state $}</td>
|
||||
<td class="rsp-p2">{$ node.maintenance $}</td>
|
||||
<td class="rsp-p2">{$ node.ports.length $}</td>
|
||||
<td class="rsp-p2">{$ node.driver $}</td>
|
||||
<td class="actions_column">
|
||||
<action-list dropdown>
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
callback="table.actions.powerOn"
|
||||
disabled="node['power_state']!=='power off'"
|
||||
item="node">
|
||||
{$ 'Power on' | translate $}
|
||||
</action>
|
||||
<menu>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.powerOff"
|
||||
disabled="node['power_state']!=='power on'"
|
||||
item="node">
|
||||
{$ 'Power off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.putNodeInMaintenanceMode"
|
||||
disabled="node['maintenance']"
|
||||
item="node">
|
||||
{$ 'Maintenance on' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.removeNodeFromMaintenanceMode"
|
||||
disabled="!node['maintenance']"
|
||||
item="node">
|
||||
{$ 'Maintenance off' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
<thead>
|
||||
<tr>
|
||||
<th colspan="8">
|
||||
<button class="btn btn-default btn-sm pull-right"
|
||||
ng-click="table.enrollNode()">
|
||||
<span class="fa fa-plus"></span>
|
||||
<span translate>Enroll Node</span>
|
||||
</button>
|
||||
</th>
|
||||
<th class="action-col">
|
||||
<action-list dropdown class="pull-right">
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
callback="table.actions.powerOnAll"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Power on' | translate $}
|
||||
</action>
|
||||
<menu>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.powerOffAll"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Power off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.putNodesInMaintenanceMode"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Maintenance on' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.removeNodesFromMaintenanceMode"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
{$ 'Maintenance off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.deleteNodes"
|
||||
item="tCtrl.selected"
|
||||
disabled="tCtrl.selected.length === 0">
|
||||
<span class="fa fa-trash"></span>
|
||||
{$ 'Delete nodes' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
</th>
|
||||
</tr>
|
||||
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot hz-table-footer items="table.nodes"></tfoot>
|
||||
</table>
|
||||
<tr>
|
||||
<th class="multi_select_column">
|
||||
<input type="checkbox"
|
||||
hz-select-all="table.nodes"/>
|
||||
</th>
|
||||
<th translate class="rsp-p1" st-sort="name" st-sort-default="name">
|
||||
Node Name
|
||||
</th>
|
||||
<th translate class="rsp-p1" st-sort="instance_uuid">
|
||||
Instance ID
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="power_state">
|
||||
Power State
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="provision_state">
|
||||
Provisioning State
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="maintenance">
|
||||
Maintenance
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="ports">
|
||||
Ports
|
||||
</th>
|
||||
<th translate class="rsp-p2" st-sort="driver">
|
||||
Driver
|
||||
</th>
|
||||
<th translate class="actions_column">
|
||||
Actions
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
<tr ng-repeat="node in table.nodes">
|
||||
<td class="multi_select_column">
|
||||
<input type="checkbox"
|
||||
hz-select="node"
|
||||
ng-model="tCtrl.selections[node.id].checked"/>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<a href="{$ node.uuid $}">
|
||||
{$ node.name || node.uuid $}
|
||||
</a>
|
||||
</td>
|
||||
<td class="rsp-p1">
|
||||
<!-- multiple tags used purposefully to ensure that the link
|
||||
is removed if node uuid == no instance. Using ng-attr-href
|
||||
only removed the href and the <a> styling remained which could
|
||||
cause confusion for users. -->
|
||||
<a href="/admin/instances/{$ node.instance_uuid $}/detail"
|
||||
ng-if="node.instance_uuid">
|
||||
{$ node.instance_uuid $}
|
||||
</a>
|
||||
<span ng-if="!node.instance_uuid">{$ 'No Instance' | translate $}</span>
|
||||
</td>
|
||||
<td class="rsp-p2" >
|
||||
<div ng-class="{'running': node['target_power_state']}">
|
||||
{$ node.power_state $}
|
||||
</div>
|
||||
</td>
|
||||
<td class="rsp-p2">{$ node.provision_state $}</td>
|
||||
<td class="rsp-p2">{$ node.maintenance | yesno $}</td>
|
||||
<td class="rsp-p2">{$ node.ports.length $}</td>
|
||||
<td class="rsp-p2">{$ node.driver $}</td>
|
||||
<td class="actions_column">
|
||||
<action-list dropdown>
|
||||
<action button-type="split-button"
|
||||
action-classes="'btn btn-default btn-sm'"
|
||||
callback="table.actions.powerOn"
|
||||
disabled="node['power_state']!=='power off'"
|
||||
item="node">
|
||||
{$ 'Power on' | translate $}
|
||||
</action>
|
||||
<menu>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.powerOff"
|
||||
disabled="node['power_state']!=='power on'"
|
||||
item="node">
|
||||
{$ 'Power off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.putNodeInMaintenanceMode"
|
||||
disabled="node['maintenance']"
|
||||
item="node">
|
||||
{$ 'Maintenance on' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.removeNodeFromMaintenanceMode"
|
||||
disabled="!node['maintenance']"
|
||||
item="node">
|
||||
{$ 'Maintenance off' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.deleteNode"
|
||||
disabled="!(node['provision_state']==='available' || node['provision_state']==='nostate' || node['provision_state']==='manageable' || node['provision_state']==='enroll')"
|
||||
item="node">
|
||||
<span class="fa fa-trash"></span>
|
||||
{$ 'Delete node' | translate $}
|
||||
</action>
|
||||
<action button-type="menu-item"
|
||||
callback="table.actions.createPort"
|
||||
item="node">
|
||||
{$ 'Create port' | translate $}
|
||||
</action>
|
||||
</menu>
|
||||
</action-list>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
<tfoot hz-table-footer items="table.nodes"></tfoot>
|
||||
</table>
|
||||
</hz-magic-search-context>
|
||||
</div>
|
||||
|
|
|
@ -13,177 +13,28 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import importlib
|
||||
import os
|
||||
import six
|
||||
|
||||
# Default to Horizons test settings to avoid any missing keys
|
||||
from horizon.test.settings import * # noqa
|
||||
from horizon.utils import secret_key
|
||||
from openstack_dashboard import exceptions
|
||||
from openstack_dashboard.test.settings import * # noqa
|
||||
|
||||
DEBUG = True
|
||||
TEMPLATE_DEBUG = DEBUG
|
||||
# pop these keys to avoid log warnings about deprecation
|
||||
# update_dashboards will populate them anyway
|
||||
HORIZON_CONFIG.pop('dashboards', None)
|
||||
HORIZON_CONFIG.pop('default_dashboard', None)
|
||||
|
||||
TEST_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
ROOT_PATH = os.path.abspath(os.path.join(TEST_DIR, ".."))
|
||||
|
||||
MEDIA_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'media'))
|
||||
MEDIA_URL = '/media/'
|
||||
STATIC_ROOT = os.path.abspath(os.path.join(ROOT_PATH, '..', 'static'))
|
||||
STATIC_URL = '/static/'
|
||||
|
||||
SECRET_KEY = secret_key.generate_or_read_from_file(
|
||||
os.path.join(TEST_DIR, '.secret_key_store'))
|
||||
ROOT_URLCONF = 'ironic_ui.test.urls'
|
||||
TEMPLATE_DIRS = (
|
||||
os.path.join(TEST_DIR, 'templates'),
|
||||
)
|
||||
|
||||
TEMPLATE_CONTEXT_PROCESSORS += (
|
||||
'openstack_dashboard.context_processors.openstack',
|
||||
)
|
||||
|
||||
INSTALLED_APPS = (
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.humanize',
|
||||
'django_nose',
|
||||
'openstack_auth',
|
||||
'compressor',
|
||||
'horizon',
|
||||
'openstack_dashboard',
|
||||
'openstack_dashboard.dashboards',
|
||||
)
|
||||
|
||||
AUTHENTICATION_BACKENDS = ('openstack_auth.backend.KeystoneBackend',)
|
||||
|
||||
SITE_BRANDING = 'OpenStack'
|
||||
|
||||
HORIZON_CONFIG = {
|
||||
"password_validator": {
|
||||
"regex": '^.{8,18}$',
|
||||
"help_text": "Password must be between 8 and 18 characters."
|
||||
},
|
||||
'user_home': None,
|
||||
'help_url': "http://docs.openstack.org",
|
||||
'exceptions': {'recoverable': exceptions.RECOVERABLE,
|
||||
'not_found': exceptions.NOT_FOUND,
|
||||
'unauthorized': exceptions.UNAUTHORIZED},
|
||||
'angular_modules': [],
|
||||
'js_files': [],
|
||||
}
|
||||
|
||||
# Load the pluggable dashboard settings
|
||||
# Update the dashboards with ironic_ui
|
||||
import ironic_ui.enabled
|
||||
import openstack_dashboard.enabled
|
||||
from openstack_dashboard.utils import settings
|
||||
dashboard_module_names = [
|
||||
'openstack_dashboard.enabled',
|
||||
'openstack_dashboard.local.enabled',
|
||||
'ironic_ui.enabled',
|
||||
]
|
||||
dashboard_modules = []
|
||||
# All dashboards must be enabled for the namespace to get registered, which is
|
||||
# needed by the unit tests.
|
||||
for module_name in dashboard_module_names:
|
||||
module = importlib.import_module(module_name)
|
||||
dashboard_modules.append(module)
|
||||
for submodule in six.itervalues(settings.import_submodules(module)):
|
||||
if getattr(submodule, 'DISABLED', None):
|
||||
delattr(submodule, 'DISABLED')
|
||||
INSTALLED_APPS = list(INSTALLED_APPS) # Make sure it's mutable
|
||||
settings.update_dashboards(dashboard_modules, HORIZON_CONFIG, INSTALLED_APPS)
|
||||
|
||||
settings.update_dashboards(
|
||||
[
|
||||
ironic_ui.enabled,
|
||||
openstack_dashboard.enabled,
|
||||
],
|
||||
HORIZON_CONFIG,
|
||||
INSTALLED_APPS
|
||||
)
|
||||
|
||||
# Set to True to allow users to upload images to glance via Horizon server.
|
||||
# When enabled, a file form field will appear on the create image form.
|
||||
# See documentation for deployment considerations.
|
||||
HORIZON_IMAGES_ALLOW_UPLOAD = True
|
||||
|
||||
AVAILABLE_REGIONS = [
|
||||
('http://localhost:5000/v2.0', 'local'),
|
||||
('http://remote:5000/v2.0', 'remote'),
|
||||
]
|
||||
|
||||
OPENSTACK_API_VERSIONS = {
|
||||
"identity": 3
|
||||
}
|
||||
|
||||
OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0"
|
||||
OPENSTACK_KEYSTONE_DEFAULT_ROLE = "_member_"
|
||||
|
||||
OPENSTACK_KEYSTONE_MULTIDOMAIN_SUPPORT = True
|
||||
OPENSTACK_KEYSTONE_DEFAULT_DOMAIN = 'test_domain'
|
||||
|
||||
OPENSTACK_KEYSTONE_BACKEND = {
|
||||
'name': 'native',
|
||||
'can_edit_user': True,
|
||||
'can_edit_group': True,
|
||||
'can_edit_project': True,
|
||||
'can_edit_domain': True,
|
||||
'can_edit_role': True
|
||||
}
|
||||
|
||||
OPENSTACK_CINDER_FEATURES = {
|
||||
'enable_backup': True,
|
||||
}
|
||||
|
||||
OPENSTACK_NEUTRON_NETWORK = {
|
||||
'enable_lb': False,
|
||||
'enable_firewall': False,
|
||||
'enable_vpn': False,
|
||||
}
|
||||
|
||||
OPENSTACK_HYPERVISOR_FEATURES = {
|
||||
'can_set_mount_point': True,
|
||||
|
||||
# NOTE: as of Grizzly this is not yet supported in Nova so enabling this
|
||||
# setting will not do anything useful
|
||||
'can_encrypt_volumes': False
|
||||
}
|
||||
|
||||
LOGGING['loggers']['openstack_dashboard'] = {
|
||||
'handlers': ['test'],
|
||||
'propagate': False,
|
||||
}
|
||||
|
||||
LOGGING['loggers']['selenium'] = {
|
||||
'handlers': ['test'],
|
||||
'propagate': False,
|
||||
}
|
||||
|
||||
LOGGING['loggers']['ironic_ui'] = {
|
||||
'handlers': ['test'],
|
||||
'propagate': False,
|
||||
}
|
||||
|
||||
SECURITY_GROUP_RULES = {
|
||||
'all_tcp': {
|
||||
'name': 'ALL TCP',
|
||||
'ip_protocol': 'tcp',
|
||||
'from_port': '1',
|
||||
'to_port': '65535',
|
||||
},
|
||||
'http': {
|
||||
'name': 'HTTP',
|
||||
'ip_protocol': 'tcp',
|
||||
'from_port': '80',
|
||||
'to_port': '80',
|
||||
},
|
||||
}
|
||||
|
||||
NOSE_ARGS = ['--nocapture',
|
||||
'--nologcapture',
|
||||
'--cover-package=openstack_dashboard',
|
||||
'--cover-inclusive',
|
||||
'--all-modules']
|
||||
|
||||
POLICY_FILES_PATH = os.path.join(ROOT_PATH, "conf")
|
||||
POLICY_FILES = {
|
||||
'identity': 'keystone_policy.json',
|
||||
'compute': 'nova_policy.json'
|
||||
}
|
||||
|
||||
# The openstack_auth.user.Token object isn't JSON-serializable ATM
|
||||
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
|
||||
# Ensure any duplicate apps are removed after the update_dashboards call
|
||||
INSTALLED_APPS = list(set(INSTALLED_APPS))
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
---
|
||||
prelude: >
|
||||
This release adds support for adding and deleting
|
||||
nodes. Support has also been added for adding and
|
||||
deleting ports. The panel will now be hidden if the
|
||||
baremetal service is not present in the scenario where
|
||||
the collection of running services differs between
|
||||
multiple keystone regions.
|
||||
features:
|
||||
- Add and delete nodes
|
||||
- Add and delete ports
|
||||
- Panel hidden if baremetal service or admin rights are not present
|
||||
- UX improvements across the interface
|
||||
- Breadcrumbs have been added
|
||||
issues:
|
||||
- Currently it is not possible to edit a node via the UI
|
||||
once it has been enrolled. Therefore, the enrollment must
|
||||
be done accurately to ensure the node is enrolled accurately
|
||||
and can then be made available. At present, any errors
|
||||
made during enrollment can only be corrected by deleting
|
||||
the node and enrolling it again.
|
|
@ -0,0 +1,225 @@
|
|||
# Makefile for Sphinx documentation
|
||||
#
|
||||
|
||||
# You can set these variables from the command line.
|
||||
SPHINXOPTS =
|
||||
SPHINXBUILD = sphinx-build
|
||||
PAPER =
|
||||
BUILDDIR = _build
|
||||
|
||||
# Internal variables.
|
||||
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||
PAPEROPT_letter = -D latex_paper_size=letter
|
||||
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
# the i18n builder cannot share the environment and doctrees with the others
|
||||
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
|
||||
|
||||
.PHONY: help
|
||||
help:
|
||||
@echo "Please use \`make <target>' where <target> is one of"
|
||||
@echo " html to make standalone HTML files"
|
||||
@echo " dirhtml to make HTML files named index.html in directories"
|
||||
@echo " singlehtml to make a single large HTML file"
|
||||
@echo " pickle to make pickle files"
|
||||
@echo " json to make JSON files"
|
||||
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||
@echo " qthelp to make HTML files and a qthelp project"
|
||||
@echo " applehelp to make an Apple Help Book"
|
||||
@echo " devhelp to make HTML files and a Devhelp project"
|
||||
@echo " epub to make an epub"
|
||||
@echo " epub3 to make an epub3"
|
||||
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||
@echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
|
||||
@echo " text to make text files"
|
||||
@echo " man to make manual pages"
|
||||
@echo " texinfo to make Texinfo files"
|
||||
@echo " info to make Texinfo files and run them through makeinfo"
|
||||
@echo " gettext to make PO message catalogs"
|
||||
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||
@echo " xml to make Docutils-native XML files"
|
||||
@echo " pseudoxml to make pseudoxml-XML files for display purposes"
|
||||
@echo " linkcheck to check all external links for integrity"
|
||||
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||
@echo " coverage to run coverage check of the documentation (if enabled)"
|
||||
@echo " dummy to check syntax errors of document sources"
|
||||
|
||||
.PHONY: clean
|
||||
clean:
|
||||
rm -rf $(BUILDDIR)/*
|
||||
|
||||
.PHONY: html
|
||||
html:
|
||||
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||
|
||||
.PHONY: dirhtml
|
||||
dirhtml:
|
||||
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||
|
||||
.PHONY: singlehtml
|
||||
singlehtml:
|
||||
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||
@echo
|
||||
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||
|
||||
.PHONY: pickle
|
||||
pickle:
|
||||
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||
@echo
|
||||
@echo "Build finished; now you can process the pickle files."
|
||||
|
||||
.PHONY: json
|
||||
json:
|
||||
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||
@echo
|
||||
@echo "Build finished; now you can process the JSON files."
|
||||
|
||||
.PHONY: htmlhelp
|
||||
htmlhelp:
|
||||
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||
|
||||
.PHONY: qthelp
|
||||
qthelp:
|
||||
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||
@echo
|
||||
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/ironic-ui.qhcp"
|
||||
@echo "To view the help file:"
|
||||
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/ironic-ui.qhc"
|
||||
|
||||
.PHONY: applehelp
|
||||
applehelp:
|
||||
$(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp
|
||||
@echo
|
||||
@echo "Build finished. The help book is in $(BUILDDIR)/applehelp."
|
||||
@echo "N.B. You won't be able to view it unless you put it in" \
|
||||
"~/Library/Documentation/Help or install it in your application" \
|
||||
"bundle."
|
||||
|
||||
.PHONY: devhelp
|
||||
devhelp:
|
||||
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||
@echo
|
||||
@echo "Build finished."
|
||||
@echo "To view the help file:"
|
||||
@echo "# mkdir -p $$HOME/.local/share/devhelp/ironic-ui"
|
||||
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/ironic-ui"
|
||||
@echo "# devhelp"
|
||||
|
||||
.PHONY: epub
|
||||
epub:
|
||||
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||
@echo
|
||||
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||
|
||||
.PHONY: epub3
|
||||
epub3:
|
||||
$(SPHINXBUILD) -b epub3 $(ALLSPHINXOPTS) $(BUILDDIR)/epub3
|
||||
@echo
|
||||
@echo "Build finished. The epub3 file is in $(BUILDDIR)/epub3."
|
||||
|
||||
.PHONY: latex
|
||||
latex:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo
|
||||
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||
"(use \`make latexpdf' here to do that automatically)."
|
||||
|
||||
.PHONY: latexpdf
|
||||
latexpdf:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through pdflatex..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: latexpdfja
|
||||
latexpdfja:
|
||||
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||
@echo "Running LaTeX files through platex and dvipdfmx..."
|
||||
$(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
|
||||
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||
|
||||
.PHONY: text
|
||||
text:
|
||||
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||
@echo
|
||||
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||
|
||||
.PHONY: man
|
||||
man:
|
||||
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||
@echo
|
||||
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||
|
||||
.PHONY: texinfo
|
||||
texinfo:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo
|
||||
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||
"(use \`make info' here to do that automatically)."
|
||||
|
||||
.PHONY: info
|
||||
info:
|
||||
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||
@echo "Running Texinfo files through makeinfo..."
|
||||
make -C $(BUILDDIR)/texinfo info
|
||||
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||
|
||||
.PHONY: gettext
|
||||
gettext:
|
||||
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||
@echo
|
||||
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||
|
||||
.PHONY: changes
|
||||
changes:
|
||||
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||
@echo
|
||||
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||
|
||||
.PHONY: linkcheck
|
||||
linkcheck:
|
||||
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||
@echo
|
||||
@echo "Link check complete; look for any errors in the above output " \
|
||||
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||
|
||||
.PHONY: doctest
|
||||
doctest:
|
||||
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||
@echo "Testing of doctests in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/doctest/output.txt."
|
||||
|
||||
.PHONY: coverage
|
||||
coverage:
|
||||
$(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage
|
||||
@echo "Testing of coverage in the sources finished, look at the " \
|
||||
"results in $(BUILDDIR)/coverage/python.txt."
|
||||
|
||||
.PHONY: xml
|
||||
xml:
|
||||
$(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
|
||||
@echo
|
||||
@echo "Build finished. The XML files are in $(BUILDDIR)/xml."
|
||||
|
||||
.PHONY: pseudoxml
|
||||
pseudoxml:
|
||||
$(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
|
||||
@echo
|
||||
@echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
|
||||
|
||||
.PHONY: dummy
|
||||
dummy:
|
||||
$(SPHINXBUILD) -b dummy $(ALLSPHINXOPTS) $(BUILDDIR)/dummy
|
||||
@echo
|
||||
@echo "Build finished. Dummy builder generates no files."
|
|
@ -0,0 +1,351 @@
|
|||
# -*- 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.
|
||||
#
|
||||
# ironic-ui documentation build configuration file, created by
|
||||
# sphinx-quickstart on Tue Aug 16 16:29:30 2016.
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
# import os
|
||||
# import sys
|
||||
# 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 = [
|
||||
'reno.sphinxext',
|
||||
]
|
||||
# Add any paths that contain templates here, relative to this directory.
|
||||
templates_path = ['_templates']
|
||||
|
||||
# The suffix(es) of source filenames.
|
||||
# You can specify multiple suffix as a list of string:
|
||||
#
|
||||
# source_suffix = ['.rst', '.md']
|
||||
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'ironic-ui'
|
||||
copyright = u'2016, OpenStack'
|
||||
author = u'OpenStack'
|
||||
|
||||
# The version info for the project you're documenting, acts as replacement for
|
||||
# |version| and |release|, also used in various other places throughout the
|
||||
# built documents.
|
||||
#
|
||||
# The short X.Y version.
|
||||
version = u'2.0.0'
|
||||
# The full version, including alpha/beta/rc tags.
|
||||
release = u'2.0.0'
|
||||
|
||||
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||
# for a list of supported languages.
|
||||
#
|
||||
# This is also used if you do content translation via gettext catalogs.
|
||||
# Usually you set "language" from the command line for these cases.
|
||||
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.
|
||||
# This patterns also effect to html_static_path and html_extra_path
|
||||
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']
|
||||
|
||||
# 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
|
||||
|
||||
# If true, `todo` and `todoList` produce output, else they produce nothing.
|
||||
todo_include_todos = 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.
|
||||
# "<project> v<release> documentation" by default.
|
||||
#
|
||||
# html_title = u'ironic-ui v2.0.0'
|
||||
|
||||
# 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 (relative to this directory) to use as a 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 None, a 'Last updated on:' timestamp is inserted at every page
|
||||
# bottom, using the given strftime format.
|
||||
# The empty string is equivalent to '%b %d, %Y'.
|
||||
#
|
||||
# html_last_updated_fmt = None
|
||||
|
||||
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||
# typographically correct entities.
|
||||
#
|
||||
# html_use_smartypants = True
|
||||
|
||||
# Custom sidebar templates, maps document names to template names.
|
||||
#
|
||||
# html_sidebars = {}
|
||||
|
||||
# Additional templates that should be rendered to pages, maps page names to
|
||||
# template names.
|
||||
#
|
||||
# html_additional_pages = {}
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# html_domain_indices = True
|
||||
|
||||
# If false, no index is generated.
|
||||
#
|
||||
# html_use_index = True
|
||||
|
||||
# If true, the index is split into individual pages for each letter.
|
||||
#
|
||||
# html_split_index = False
|
||||
|
||||
# If true, links to the reST sources are added to the pages.
|
||||
#
|
||||
# html_show_sourcelink = True
|
||||
|
||||
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||
#
|
||||
# html_show_sphinx = True
|
||||
|
||||
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||
#
|
||||
# html_show_copyright = True
|
||||
|
||||
# If true, an OpenSearch description file will be output, and all pages will
|
||||
# contain a <link> tag referring to it. The value of this option must be the
|
||||
# base URL from which the finished HTML is served.
|
||||
#
|
||||
# html_use_opensearch = ''
|
||||
|
||||
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||
# html_file_suffix = None
|
||||
|
||||
# Language to be used for generating the HTML full-text search index.
|
||||
# Sphinx supports the following languages:
|
||||
# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja'
|
||||
# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr', 'zh'
|
||||
#
|
||||
# html_search_language = 'en'
|
||||
|
||||
# A dictionary with options for the search language support, empty by default.
|
||||
# 'ja' uses this config value.
|
||||
# 'zh' user can custom change `jieba` dictionary path.
|
||||
#
|
||||
# html_search_options = {'type': 'default'}
|
||||
|
||||
# The name of a javascript file (relative to the configuration directory) that
|
||||
# implements a search results scorer. If empty, the default will be used.
|
||||
#
|
||||
# html_search_scorer = 'scorer.js'
|
||||
|
||||
# Output file base name for HTML help builder.
|
||||
htmlhelp_basename = 'ironic-uidoc'
|
||||
|
||||
# -- 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': '',
|
||||
|
||||
# Latex figure (float) alignment
|
||||
#
|
||||
# 'figure_align': 'htbp',
|
||||
}
|
||||
|
||||
# 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 = [
|
||||
(master_doc, 'ironic-ui.tex', u'ironic-ui Documentation',
|
||||
u'OpenStack', 'manual'),
|
||||
]
|
||||
|
||||
# The name of an image file (relative to this directory) to place at the top of
|
||||
# the title page.
|
||||
#
|
||||
# latex_logo = None
|
||||
|
||||
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||
# not chapters.
|
||||
#
|
||||
# latex_use_parts = False
|
||||
|
||||
# If true, show page references after internal links.
|
||||
#
|
||||
# latex_show_pagerefs = False
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#
|
||||
# latex_show_urls = False
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#
|
||||
# latex_appendices = []
|
||||
|
||||
# It false, will not define \strong, \code, itleref, \crossref ... but only
|
||||
# \sphinxstrong, ..., \sphinxtitleref, ... To help avoid clash with user added
|
||||
# packages.
|
||||
#
|
||||
# latex_keep_old_macro_names = True
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# latex_domain_indices = True
|
||||
|
||||
|
||||
# -- Options for manual page output ---------------------------------------
|
||||
|
||||
# One entry per manual page. List of tuples
|
||||
# (source start file, name, description, authors, manual section).
|
||||
man_pages = [
|
||||
(master_doc, 'ironic-ui', u'ironic-ui Documentation',
|
||||
[author], 1)
|
||||
]
|
||||
|
||||
# If true, show URL addresses after external links.
|
||||
#
|
||||
# man_show_urls = False
|
||||
|
||||
|
||||
# -- Options for Texinfo output -------------------------------------------
|
||||
|
||||
# Grouping the document tree into Texinfo files. List of tuples
|
||||
# (source start file, target name, title, author,
|
||||
# dir menu entry, description, category)
|
||||
texinfo_documents = [
|
||||
(master_doc, 'ironic-ui', u'ironic-ui Documentation',
|
||||
author, 'ironic-ui', 'One line description of project.',
|
||||
'Miscellaneous'),
|
||||
]
|
||||
|
||||
# Documents to append as an appendix to all manuals.
|
||||
#
|
||||
# texinfo_appendices = []
|
||||
|
||||
# If false, no module index is generated.
|
||||
#
|
||||
# texinfo_domain_indices = True
|
||||
|
||||
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||
#
|
||||
# texinfo_show_urls = 'footnote'
|
||||
|
||||
# If true, do not generate a @detailmenu in the "Top" node's menu.
|
||||
#
|
||||
# texinfo_no_detailmenu = False
|
|
@ -0,0 +1,5 @@
|
|||
============================
|
||||
Current Series Release Notes
|
||||
============================
|
||||
|
||||
.. release-notes::
|
|
@ -0,0 +1,14 @@
|
|||
========================
|
||||
Ironic UI Release Notes
|
||||
========================
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
Current (unreleased) <current-series>
|
||||
Newton (unreleased) <newton>
|
||||
|
||||
.. toctree::
|
||||
:hidden:
|
||||
|
||||
unreleased
|
|
@ -0,0 +1,281 @@
|
|||
@ECHO OFF
|
||||
|
||||
REM Command file for Sphinx documentation
|
||||
|
||||
if "%SPHINXBUILD%" == "" (
|
||||
set SPHINXBUILD=sphinx-build
|
||||
)
|
||||
set BUILDDIR=_build
|
||||
set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
|
||||
set I18NSPHINXOPTS=%SPHINXOPTS% .
|
||||
if NOT "%PAPER%" == "" (
|
||||
set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
|
||||
set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
|
||||
)
|
||||
|
||||
if "%1" == "" goto help
|
||||
|
||||
if "%1" == "help" (
|
||||
:help
|
||||
echo.Please use `make ^<target^>` where ^<target^> is one of
|
||||
echo. html to make standalone HTML files
|
||||
echo. dirhtml to make HTML files named index.html in directories
|
||||
echo. singlehtml to make a single large HTML file
|
||||
echo. pickle to make pickle files
|
||||
echo. json to make JSON files
|
||||
echo. htmlhelp to make HTML files and a HTML help project
|
||||
echo. qthelp to make HTML files and a qthelp project
|
||||
echo. devhelp to make HTML files and a Devhelp project
|
||||
echo. epub to make an epub
|
||||
echo. epub3 to make an epub3
|
||||
echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
|
||||
echo. text to make text files
|
||||
echo. man to make manual pages
|
||||
echo. texinfo to make Texinfo files
|
||||
echo. gettext to make PO message catalogs
|
||||
echo. changes to make an overview over all changed/added/deprecated items
|
||||
echo. xml to make Docutils-native XML files
|
||||
echo. pseudoxml to make pseudoxml-XML files for display purposes
|
||||
echo. linkcheck to check all external links for integrity
|
||||
echo. doctest to run all doctests embedded in the documentation if enabled
|
||||
echo. coverage to run coverage check of the documentation if enabled
|
||||
echo. dummy to check syntax errors of document sources
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "clean" (
|
||||
for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
|
||||
del /q /s %BUILDDIR%\*
|
||||
goto end
|
||||
)
|
||||
|
||||
|
||||
REM Check if sphinx-build is available and fallback to Python version if any
|
||||
%SPHINXBUILD% 1>NUL 2>NUL
|
||||
if errorlevel 9009 goto sphinx_python
|
||||
goto sphinx_ok
|
||||
|
||||
:sphinx_python
|
||||
|
||||
set SPHINXBUILD=python -m sphinx.__init__
|
||||
%SPHINXBUILD% 2> nul
|
||||
if errorlevel 9009 (
|
||||
echo.
|
||||
echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
|
||||
echo.installed, then set the SPHINXBUILD environment variable to point
|
||||
echo.to the full path of the 'sphinx-build' executable. Alternatively you
|
||||
echo.may add the Sphinx directory to PATH.
|
||||
echo.
|
||||
echo.If you don't have Sphinx installed, grab it from
|
||||
echo.http://sphinx-doc.org/
|
||||
exit /b 1
|
||||
)
|
||||
|
||||
:sphinx_ok
|
||||
|
||||
|
||||
if "%1" == "html" (
|
||||
%SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/html.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dirhtml" (
|
||||
%SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "singlehtml" (
|
||||
%SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pickle" (
|
||||
%SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the pickle files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "json" (
|
||||
%SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can process the JSON files.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "htmlhelp" (
|
||||
%SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run HTML Help Workshop with the ^
|
||||
.hhp project file in %BUILDDIR%/htmlhelp.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "qthelp" (
|
||||
%SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; now you can run "qcollectiongenerator" with the ^
|
||||
.qhcp project file in %BUILDDIR%/qthelp, like this:
|
||||
echo.^> qcollectiongenerator %BUILDDIR%\qthelp\ironic-ui.qhcp
|
||||
echo.To view the help file:
|
||||
echo.^> assistant -collectionFile %BUILDDIR%\qthelp\ironic-ui.ghc
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "devhelp" (
|
||||
%SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub" (
|
||||
%SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub file is in %BUILDDIR%/epub.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "epub3" (
|
||||
%SPHINXBUILD% -b epub3 %ALLSPHINXOPTS% %BUILDDIR%/epub3
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The epub3 file is in %BUILDDIR%/epub3.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latex" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdf" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "latexpdfja" (
|
||||
%SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
|
||||
cd %BUILDDIR%/latex
|
||||
make all-pdf-ja
|
||||
cd %~dp0
|
||||
echo.
|
||||
echo.Build finished; the PDF files are in %BUILDDIR%/latex.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "text" (
|
||||
%SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The text files are in %BUILDDIR%/text.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "man" (
|
||||
%SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The manual pages are in %BUILDDIR%/man.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "texinfo" (
|
||||
%SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "gettext" (
|
||||
%SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "changes" (
|
||||
%SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.The overview file is in %BUILDDIR%/changes.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "linkcheck" (
|
||||
%SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Link check complete; look for any errors in the above output ^
|
||||
or in %BUILDDIR%/linkcheck/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "doctest" (
|
||||
%SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of doctests in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/doctest/output.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "coverage" (
|
||||
%SPHINXBUILD% -b coverage %ALLSPHINXOPTS% %BUILDDIR%/coverage
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Testing of coverage in the sources finished, look at the ^
|
||||
results in %BUILDDIR%/coverage/python.txt.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "xml" (
|
||||
%SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The XML files are in %BUILDDIR%/xml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "pseudoxml" (
|
||||
%SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
|
||||
goto end
|
||||
)
|
||||
|
||||
if "%1" == "dummy" (
|
||||
%SPHINXBUILD% -b dummy %ALLSPHINXOPTS% %BUILDDIR%/dummy
|
||||
if errorlevel 1 exit /b 1
|
||||
echo.
|
||||
echo.Build finished. Dummy builder generates no files.
|
||||
goto end
|
||||
)
|
||||
|
||||
:end
|
|
@ -0,0 +1,6 @@
|
|||
===========================
|
||||
Newton Series Release Notes
|
||||
===========================
|
||||
|
||||
.. release-notes::
|
||||
:branch: origin/master
|
|
@ -0,0 +1 @@
|
|||
.. include:: current-series.rst
|
|
@ -10,9 +10,12 @@ hacking>=0.10.2,<0.11 # Apache-2.0
|
|||
coverage>=3.6 # Apache-2.0
|
||||
django-nose>=1.2 # BSD
|
||||
python-subunit>=0.0.18 # Apache-2.0/BSD
|
||||
sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 # BSD
|
||||
oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0
|
||||
oslotest>=1.10.0 # Apache-2.0
|
||||
testrepository>=0.0.18 # Apache-2.0/BSD
|
||||
testscenarios>=0.4 # Apache-2.0/BSD
|
||||
testtools>=1.4.0 # MIT
|
||||
|
||||
# this is required for the docs build jobs
|
||||
sphinx>=1.2.1,!=1.3b1,<1.3 # BSD
|
||||
oslosphinx>=2.5.0,!=3.4.0 # Apache-2.0
|
||||
reno>=1.8.0 # Apache2
|
||||
|
|
3
tox.ini
3
tox.ini
|
@ -59,3 +59,6 @@ commands = oslo_debug_helper {posargs}
|
|||
show-source = True
|
||||
builtins = _
|
||||
exclude=.venv,.git,.tox,dist,doc,*openstack/common*,*lib/python*,*egg,build
|
||||
|
||||
[testenv:releasenotes]
|
||||
commands = sphinx-build -a -W -E -d releasenotes/build/doctrees -b html releasenotes/source releasenotes/build/html
|
||||
|
|
Loading…
Reference in New Issue