Retire Packaging Deb project repos

This commit is part of a series to retire the Packaging Deb
project. Step 2 is to remove all content from the project
repos, replacing it with a README notification where to find
ongoing work, and how to recover the repo if needed at some
future point (as in
https://docs.openstack.org/infra/manual/drivers.html#retiring-a-project).

Change-Id: I6319cb6aa2b8c5847ede4372e222b02a23b02ee3
This commit is contained in:
Tony Breeds 2017-09-12 15:39:55 -06:00
parent 4315f1e64a
commit 89490553d0
147 changed files with 14 additions and 16714 deletions

View File

@ -1,6 +0,0 @@
[run]
branch = True
source = ironic-ui
[report]
ignore_errors = True

View File

@ -1,2 +0,0 @@
node_modules
dist

View File

@ -1,23 +0,0 @@
# Enable eslint-plugin-angular
plugins:
- angular
extends: openstack
# Set up globals
globals:
angular: false
module: false
env:
browser: true
jasmine: true
rules:
angular/no-private-call: 0
angular/no-services:
- 2
- - $http
- $resource
- Restangular
angular/no-service-method: 0

66
.gitignore vendored
View File

@ -1,66 +0,0 @@
*.py[cod]
# C extensions
*.so
# Packages
*.egg
*.egg-info
dist
build
cover
.eggs
eggs
parts
bin
var
sdist
develop-eggs
.installed.cfg
lib
lib64
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
nosetests.xml
.testrepository
.venv
# Translations
*.mo
# Mr Developer
.mr.developer.cfg
.project
.pydevproject
# Complexity
output/*.html
output/*/index.html
# Sphinx
doc/build
# pbr generates these
AUTHORS
ChangeLog
# Editors
*~
.*.swp
.*sw?
.secret_key_store
*.lock
# Node generated files
package/
node_modules/
npm-debug.log
# release notes build
releasenotes/build

View File

@ -1,4 +0,0 @@
[gerrit]
host=review.openstack.org
port=29418
project=openstack/ironic-ui.git

View File

@ -1,3 +0,0 @@
# Format is:
# <preferred e-mail> <other e-mail 1>
# <preferred e-mail> <other e-mail 2>

View File

@ -1,7 +0,0 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} \
${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -1,17 +0,0 @@
If you would like to contribute to the development of OpenStack, you must
follow the steps in this page:
http://docs.openstack.org/infra/manual/developers.html
If you already have a good understanding of how the system works and your
OpenStack accounts are set up, you can skip to the development workflow
section of this documentation to learn how changes to OpenStack should be
submitted for review via the Gerrit tool:
http://docs.openstack.org/infra/manual/developers.html#development-workflow
Pull requests submitted through GitHub will be ignored.
Bugs should be filed on Launchpad, not GitHub:
https://bugs.launchpad.net/ironic-ui

View File

@ -1,4 +0,0 @@
ironic-ui Style Commandments
===============================================
Read the OpenStack Style Commandments http://docs.openstack.org/developer/hacking/

176
LICENSE
View File

@ -1,176 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.

View File

@ -1,9 +0,0 @@
include AUTHORS
include ChangeLog
exclude .gitignore
exclude .gitreview
include setup.py
recursive-include ironic_ui *.js *.html *.scss
global-exclude *.pyc

14
README Normal file
View File

@ -0,0 +1,14 @@
This project is no longer maintained.
The contents of this repository are still available in the Git
source code management system. To see the contents of this
repository before it reached its end of life, please check out the
previous commit with "git checkout HEAD^1".
For ongoing work on maintaining OpenStack packages in the Debian
distribution, please see the Debian OpenStack packaging team at
https://wiki.debian.org/OpenStack/.
For any further questions, please email
openstack-dev@lists.openstack.org or join #openstack-dev on
Freenode.

View File

@ -1,27 +0,0 @@
========================
Team and repository tags
========================
.. image:: http://governance.openstack.org/badges/ironic-ui.svg
:target: http://governance.openstack.org/reference/tags/index.html
.. Change things from this point on
=========
Ironic UI
=========
The Ironic UI is a Horizon plugin that will allow users to view and manage bare
metal nodes, ports and drivers.
* Free software: Apache license
* Documentation: http://docs.openstack.org/ironic-ui/latest
* Source: https://git.openstack.org/cgit/openstack/ironic-ui
* Bugs: http://bugs.launchpad.net/ironic-ui
Features
--------
* View bare metal nodes
* View node details
* Apply maintenance and power on/off actions to the nodes

View File

@ -1,6 +0,0 @@
[extractors]
django = django_babel.extract:extract_django
[python: **.py]
[django: templates/**.html]
[django: **/templates/**.csv]

View File

@ -1,14 +0,0 @@
[extractors]
# We use a custom extractor to find translatable strings in AngularJS
# templates. The extractor is included in horizon.utils for now.
# See http://babel.pocoo.org/docs/messages/#referencing-extraction-methods for
# details on how this works.
angular = horizon.utils.babel_extract_angular:extract_angular
[javascript: **.js]
# We need to look into all static folders for HTML files.
# The **/static ensures that we also search within
# /openstack_dashboard/dashboards/XYZ/static which will ensure
# that plugins are also translated.
[angular: **/static/**.html]

View File

@ -1,46 +0,0 @@
# plugin.sh - DevStack plugin.sh dispatch script ironic-ui
function install_ironic_ui {
# NOTE(crushil): workaround for devstack bug: 1540328
# where devstack installs 'test-requirements' but should not do it
# for ironic-ui project as it installs Horizon from url.
# Remove following two 'mv' commands when mentioned bug is fixed.
mv $IRONIC_UI_DIR/test-requirements.txt $IRONIC_UI_DIR/_test-requirements.txt
setup_develop ${IRONIC_UI_DIR}
mv $IRONIC_UI_DIR/_test-requirements.txt $IRONIC_UI_DIR/test-requirements.txt
}
# check for service enabled
if is_service_enabled horizon && is_service_enabled ironic && is_service_enabled ironic-ui; then
if [[ "$1" == "stack" && "$2" == "pre-install" ]]; then
# Set up system services
# no-op
:
elif [[ "$1" == "stack" && "$2" == "install" ]]; then
# Perform installation of service source
echo_summary "Installing Ironic UI"
install_ironic_ui
elif [[ "$1" == "stack" && "$2" == "post-config" ]]; then
# Configure after the other layer 1 and 2 services have been configured
echo_summary "Configuring Ironic UI"
cp -a ${IRONIC_UI_DIR}/ironic_ui/enabled/* ${DEST}/horizon/openstack_dashboard/local/enabled/
elif [[ "$1" == "stack" && "$2" == "extra" ]]; then
# no-op
:
fi
if [[ "$1" == "unstack" ]]; then
# no-op
:
fi
if [[ "$1" == "clean" ]]; then
# Remove state and transient data
# Remember clean.sh first calls unstack.sh
# no-op
:
fi
fi

View File

@ -1,5 +0,0 @@
# settings file for ironic-ui plugin
enable_service ironic-ui
# set up default directories
IRONIC_UI_DIR=${IRONIC_UI_DIR:=$DEST/ironic-ui}

View File

@ -1,81 +0,0 @@
# -*- coding: utf-8 -*-
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import sys
sys.path.insert(0, os.path.abspath('../..'))
# -- General configuration ----------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
extensions = [
'sphinx.ext.autodoc',
#'sphinx.ext.intersphinx',
'openstackdocstheme',
]
# autodoc generation is a bit aggressive and a nuisance when doing heavy
# text edit cycles.
# execute "export SPHINX_DEBUG=1" in your terminal to disable
# The suffix of source filenames.
source_suffix = '.rst'
# The master toctree document.
master_doc = 'index'
# General information about the project.
project = u'ironic-ui'
copyright = u'2016, OpenStack Foundation'
# If true, '()' will be appended to :func: etc. cross-reference text.
add_function_parentheses = True
# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
add_module_names = True
# The name of the Pygments (syntax highlighting) style to use.
pygments_style = 'sphinx'
# -- Options for HTML output --------------------------------------------------
# The theme to use for HTML and HTML Help pages. Major themes that come with
# Sphinx are currently 'default' and 'sphinxdoc'.
html_theme = 'openstackdocs'
# openstackdocstheme options
repository_name = 'openstack/ironic-ui'
bug_project = 'ironic-ui'
bug_tag = ''
# Must set this variable to include year, month, day, hours, and minutes.
html_last_updated_fmt = '%Y-%m-%d %H:%M'
# Output file base name for HTML help builder.
htmlhelp_basename = '%sdoc' % project
# Grouping the document tree into LaTeX files. List of tuples
# (source start file, target name, title, author, documentclass
# [howto/manual]).
latex_documents = [
('index',
'%s.tex' % project,
u'%s Documentation' % project,
u'OpenStack Foundation', 'manual'),
]
# Example configuration for intersphinx: refer to the Python standard library.
#intersphinx_mapping = {'http://docs.python.org/': None}

View File

@ -1,45 +0,0 @@
=================
How to Contribute
=================
Contributor License Agreement
-----------------------------
.. index::
single: license; agreement
In order to contribute to the Ironic UI 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-ui
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://git.openstack.org/cgit/openstack/ironic-ui
Code Review
https://review.openstack.org/#/q/status:open+project:openstack/ironic-ui,n,z

View File

@ -1,11 +0,0 @@
=========================
Contributing to Ironic UI
=========================
If you're interested in contributing to the Ironic UI project,
the following will help get you started.
.. toctree::
:maxdepth: 1
contributing

View File

@ -1,30 +0,0 @@
=====================================
Welcome to Ironic UI's documentation!
=====================================
Introduction
============
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/
.. toctree::
:maxdepth: 1
Introduction to ironic <http://docs.openstack.org/developer/ironic/deploy/user-guide.html>
Installing the ironic UI <install/index>
Contributing <contributor/index>
Release notes <http://docs.openstack.org/releasenotes/ironic-ui>

View File

@ -1,9 +0,0 @@
============================
ironic-ui installation guide
============================
.. toctree::
:maxdepth: 1
installation
uninstallation

View File

@ -1,68 +0,0 @@
.. _installation:
Ironic-UI Installation
======================
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
1. Clone the Ironic UI repository::
git clone https://git.openstack.org/openstack/ironic-ui
2. Change into the root directory of your horizon installation and
activate the python virtualenv. Example::
source .venv/bin/activate
.. NOTE:: The ``.venv`` folder is pre-installed when horizon is setup with
``./run_tests.sh``. Do not attempt to reinstall the virtual
environment.
3. Copy the ``_2200_ironic.py`` file from ``ironic_ui/enabled/_2200_ironic.py``
file to ``horizon/openstack_dashboard/local/enabled`` directory. Example,
set as if being executed from the root of the ironic-ui repository::
cp ./ironic_ui/enabled/_2200_ironic.py ../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.
Ironic-UI Installation with DevStack
------------------------------------
In order to use the Ironic UI with devstack, you will need to enable
the UI plugin separately in your installation local.conf file.
This is done in a similar fashion to enabling Ironic for devstack.
Make sure you have horizon enabled, which is the default in devstack.
Then, enable the Ironic UI plugin appending the following line to the end of the local.conf file,
just after Ironic plugin enablement:
enable_plugin ironic-ui https://github.com/openstack/ironic-ui
After this, you can run ./stack.sh from the devstack directory.
The Ironic Bare Metal Provisioning plugin should now be visible in the Horizon
navigation.
6. Run JavaScript unit tests by either:
Running the tests locally with npm run test.
Visiting http://localhost:8000/jasmine/?spec=horizon.dashboard.admin.ironic in your
browser.

View File

@ -1,9 +0,0 @@
.. _unstallation:
Uninstallation
==============
To uninstall, use ``pip uninstall ironic-ui`` from with-in the horizon
virtual environment. You will also need to remove the
``openstack_dashboard/enabled/_2200_ironic.py`` file from the horizon
installation.

View File

@ -1,17 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo(
'ironic-ui').version_string()

View File

@ -1,371 +0,0 @@
#
# Copyright 2015, 2016 Hewlett Packard Enterprise Development Company LP
# Copyright 2016 Cray Inc.
# Copyright 2017 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.conf import settings
from ironicclient import client
from ironicclient.v1 import resource_fields as res_fields
from horizon.utils.memoized import memoized # noqa
from openstack_dashboard.api import base
DEFAULT_IRONIC_API_VERSION = '1.27'
DEFAULT_INSECURE = False
DEFAULT_CACERT = None
IRONIC_CLIENT_CLASS_NAME = 'baremetal'
@memoized
def ironicclient(request):
"""Returns a client connected to the Ironic backend.
:param request: HTTP request.
:return: Ironic client.
"""
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', DEFAULT_INSECURE)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', DEFAULT_CACERT)
ironic_url = base.url_for(request, IRONIC_CLIENT_CLASS_NAME)
return client.Client(1,
ironic_url,
os_ironic_api_version=DEFAULT_IRONIC_API_VERSION,
project_id=request.user.project_id,
token=request.user.token.id,
insecure=insecure,
cacert=cacert)
def node_list(request):
"""Retrieve a list of nodes.
:param request: HTTP request.
:return: A list of nodes.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.list
"""
node_manager = ironicclient(request).node
return node_manager.list(detail=True, limit=0)
def node_get(request, node_id):
"""Retrieve a node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: node.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get
"""
return ironicclient(request).node.get(node_id)
def node_list_ports(request, node_id):
"""List all the ports on a given node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: A full list of ports. (limit=0)
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.list_ports
"""
return ironicclient(request).node.list_ports(node_id, limit=0, detail=True)
def node_set_power_state(request, node_id, state, soft=False):
"""Set power state for a given node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:param state: the power state to set ['on', 'off', 'reboot'].
:param soft: flag for graceful power 'off' or reboot
:return: node.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_power_state
"""
return ironicclient(request).node.set_power_state(node_id,
state,
soft)
def node_set_provision_state(request, node_id, state, cleansteps=None):
"""Set the target provision state for a given node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:param state: the target provision state to set.
:param cleansteps: Optional list of cleaning steps
:return: node.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_provision_state
"""
node_manager = ironicclient(request).node
return node_manager.set_provision_state(node_id,
state,
cleansteps=cleansteps)
def node_set_console_mode(request, node_id, enabled):
"""Start or stop the serial console for a given node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:param enabled: True to start the console, False to stop it
:return: node.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_console_mode
"""
return ironicclient(request).node.set_console_mode(node_id, enabled)
def node_get_console(request, node_id):
"""Get connection information for a node's console.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: Console connection information
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get_console
"""
return ironicclient(request).node.get_console(node_id)
def node_set_maintenance(request, node_id, state, maint_reason=None):
"""Set the maintenance mode on a given node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:param state: The maintenance state to set.
:return: node.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_maintenance
"""
return ironicclient(request).node.set_maintenance(
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)
return dict([(f, getattr(node, f, ''))
for f in res_fields.NODE_DETAILED_RESOURCE.fields])
def node_delete(request, node_id):
"""Delete a node from inventory.
:param request: HTTP request.
:param node_id: The UUID or name 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 node_update(request, node_id, patch):
"""Update a specified node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:param patch: Sequence of update operations
:return: node.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.update
"""
node = ironicclient(request).node.update(node_id, patch)
return dict([(f, getattr(node, f, ''))
for f in res_fields.NODE_DETAILED_RESOURCE.fields])
def node_validate(request, node_id):
"""Validate a specified node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: List of dictionaries, each containing an interface status
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.validate
"""
ifaces = ironicclient(request).node.validate(node_id)
result = []
for interface, status in ifaces.to_dict().items():
data = {'interface': interface}
data.update(status)
result.append(data)
return result
def node_get_boot_device(request, node_id):
"""Get the boot device for a specified node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: Dictionary with keys "boot_device" and "persistent"
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get_boot_device
"""
return ironicclient(request).node.get_boot_device(node_id)
def node_set_boot_device(request, node_id, device, persistent):
"""Set the boot device for a specified node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:param device: boot device.
:param persistent: True or False.
:return: null.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.set_boot_device
"""
return ironicclient(request).node.set_boot_device(node_id,
device,
persistent)
def node_get_supported_boot_devices(request, node_id):
"""Get the list of supported boot devices for a specified node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: List of supported boot devices (strings)
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.node.html#ironicclient.v1.node.NodeManager.get_boot_device
"""
result = ironicclient(request).node.get_supported_boot_devices(node_id)
return result.get('supported_boot_devices', [])
def driver_list(request):
"""Retrieve a list of drivers.
:param request: HTTP request.
:return: A list of drivers.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.driver.html#ironicclient.v1.driver.DriverManager.list
"""
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
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.driver.html#ironicclient.v1.driver.DriverManager.properties
"""
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
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.port.html#ironicclient.v1.port.PortManager.create
"""
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
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.port.html#ironicclient.v1.port.PortManager.delete
"""
return ironicclient(request).port.delete(port_uuid)
def port_update(request, port_uuid, patch):
"""Update a specified port.
:param request: HTTP request.
:param port_id: The UUID of the port.
:param patch: Sequence of update operations
:return: Port.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.port.html#ironicclient.v1.port.PortManager.update
"""
port = ironicclient(request).port.update(port_uuid, patch)
return dict([(f, getattr(port, f, ''))
for f in res_fields.PORT_DETAILED_RESOURCE.fields])
def portgroup_list(request, node_id):
"""List the portgroups associated with a given node.
:param request: HTTP request.
:param node_id: The UUID or name of the node.
:return: A full list of portgroups. (limit=0)
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.portgroup.html#ironicclient.v1.portgroup.PortgroupManager.list_portgroups
"""
return ironicclient(request).portgroup.list(node_id, limit=0, detail=True)
def portgroup_create(request, params):
"""Create a portgroup.
:param request: HTTP request.
:param params: Portgroup creation parameters.
:return: Portgroup.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.portgroup.html#ironicclient.v1.portgroup.PortgroupManager.create
"""
portgroup_manager = ironicclient(request).portgroup
return portgroup_manager.create(**params)
def portgroup_delete(request, portgroup_id):
"""Delete a portgroup from the DB.
:param request: HTTP request.
:param portgroup_id: The UUID or name of the portgroup.
:return: Portgroup.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.portgroup.html#ironicclient.v1.portgroup.PortgroupManager.delete
"""
return ironicclient(request).portgroup.delete(portgroup_id)
def portgroup_get_ports(request, portgroup_id):
"""Get the ports associated with a specified portgroup.
:param request: HTTP request.
:param portgroup_id: The UUID or name of the portgroup.
:return: List of ports.
http://docs.openstack.org/developer/python-ironicclient/api/ironicclient.v1.portgroup.html#ironicclient.v1.portgroup.PortgroupManager.list_ports
"""
return ironicclient(request).portgroup.list_ports(portgroup_id)

View File

@ -1,401 +0,0 @@
#
# Copyright 2015, 2016 Hewlett Packard Enterprise Development Company LP
# Copyright 2016 Cray Inc.
# Copyright 2017 Intel Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from django.views import generic
from ironic_ui.api import ironic
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
LOGICAL_NAME_PATTERN = '[a-zA-Z0-9-._~]+'
@urls.register
class Nodes(generic.View):
url_regex = r'ironic/nodes/$'
@rest_utils.ajax()
def get(self, request):
"""Get the list of nodes.
:param request: HTTP request.
:return: nodes.
"""
nodes = ironic.node_list(request)
return {
'nodes': [i.to_dict() for i in nodes]
}
@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):
url_regex = r'ironic/nodes/(?P<node_id>{})$'.format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def get(self, request, node_id):
"""Get information on a specific node.
:param request: HTTP request.
:param node_id: Node id.
:return: node.
"""
return ironic.node_get(request, node_id).to_dict()
@rest_utils.ajax(data_required=True)
def patch(self, request, node_id):
"""Update an Ironic node
:param request: HTTP request
:param node_uuid: Node uuid.
"""
patch = request.DATA.get('patch')
return ironic.node_update(request, node_id, patch)
@urls.register
class Ports(generic.View):
url_regex = r'ironic/ports/$'
@rest_utils.ajax()
def get(self, request):
"""Get the list of ports associated with a specified node.
:param request: HTTP request
:return: List of ports.
"""
node_id = request.GET.get('node_id')
ports = ironic.node_list_ports(request, node_id)
return {
'ports': [i.to_dict() for i in ports]
}
@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 Port(generic.View):
url_regex = r'ironic/ports/(?P<port_id>[0-9a-f-]+)$'
@rest_utils.ajax(data_required=True)
def patch(self, request, port_id):
"""Update an Ironic port
:param request: HTTP request
:param port_id: Port id.
"""
patch = request.DATA.get('patch')
return ironic.port_update(request, port_id, patch)
@urls.register
class StatesPower(generic.View):
url_regex = r'ironic/nodes/(?P<node_id>{})/states/power$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax(data_required=True)
def patch(self, request, node_id):
"""Set the power state for a specified node.
:param request: HTTP request.
:param node_id: Node name or uuid
:return: Return code
"""
return ironic.node_set_power_state(request,
node_id,
request.DATA.get('state'),
request.DATA.get('soft'))
@urls.register
class StatesProvision(generic.View):
url_regex = r'ironic/nodes/(?P<node_uuid>{})/states/provision$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax(data_required=True)
def put(self, request, node_uuid):
"""Set the provision state for a specified node.
:param request: HTTP request.
:param node_id: Node uuid
:return: Return code
"""
verb = request.DATA.get('verb')
clean_steps = request.DATA.get('clean_steps')
return ironic.node_set_provision_state(request,
node_uuid,
verb,
clean_steps)
@urls.register
class StatesConsole(generic.View):
url_regex = r'ironic/nodes/(?P<node_uuid>{})/states/console$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def get(self, request, node_uuid):
"""Get connection information for the node's console
:param request: HTTP request.
:param node_id: Node uuid
:return: Connection information
"""
return ironic.node_get_console(request, node_uuid)
@rest_utils.ajax(data_required=True)
def put(self, request, node_uuid):
"""Start or stop the serial console.
:param request: HTTP request.
:param node_id: Node uuid
:return: Return code
"""
return ironic.node_set_console_mode(request,
node_uuid,
request.DATA.get('enabled'))
@urls.register
class Maintenance(generic.View):
url_regex = r'ironic/nodes/(?P<node_id>{})/maintenance$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def patch(self, request, node_id):
"""Put a specified node into maintenance state
:param request: HTTP request.
:param node_id: Node name or uuid
:return: Return code
"""
maint_reason = request.DATA.get('maint_reason')
return ironic.node_set_maintenance(
request,
node_id,
'on',
maint_reason=maint_reason)
@rest_utils.ajax()
def delete(self, request, node_id):
"""Take a specified node out of the maintenance state
:param request: HTTP request.
:param node_id: Node name or uuid
:return: Return code
"""
return ironic.node_set_maintenance(request, node_id, 'off')
@urls.register
class Validate(generic.View):
url_regex = r'ironic/nodes/(?P<node_id>{})/validate$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def get(self, request, node_id):
"""Validate a specified node
:param request: HTTP request.
:param node_id: Node name or uuid
:return: List of dictionaries of interface statuses
"""
return ironic.node_validate(request, node_id)
@urls.register
class BootDevice(generic.View):
url_regex = r'ironic/nodes/(?P<node_id>{})/boot_device$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def get(self, request, node_id):
"""Get the boot device for a specified node
:param request: HTTP request.
:param node_id: Node name or uuid
:return: Dictionary with keys "boot_device" and "persistent"
"""
return ironic.node_get_boot_device(request, node_id)
@rest_utils.ajax(data_required=True)
def put(self, request, node_id):
"""Set the boot device for a specific node
:param request: HTTP request.
:param node_id: Node name or uuid
:return: null.
"""
return ironic.node_set_boot_device(
request,
node_id,
request.DATA.get('boot_device'),
persistent=request.DATA.get('persistent'))
@urls.register
class SupportedBootDevices(generic.View):
url_regex = r'ironic/nodes/(?P<node_id>{})/boot_device/supported$' . \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def get(self, request, node_id):
"""Get the list of supported boot devices for a specified node
:param request: HTTP request.
:param node_id: Node name or uuid
:return: List of supported boot devices
"""
return ironic.node_get_supported_boot_devices(request, node_id)
@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
"""
drivers = ironic.driver_list(request)
return {
'drivers': [i.to_dict() for i in drivers]
}
@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)
@urls.register
class Portgroups(generic.View):
url_regex = r'ironic/portgroups/$'
@rest_utils.ajax()
def get(self, request):
"""Get the list of portgroups associated with a specified node.
:param request: HTTP request.
:return: List of portgroups.
"""
portgroups = ironic.portgroup_list(request,
request.GET.get('node_id'))
return {
'portgroups': [i.to_dict() for i in portgroups]
}
@rest_utils.ajax(data_required=True)
def post(self, request):
"""Create a portgroup.
:param request: HTTP request.
:return: Portgroup.
"""
return ironic.portgroup_create(request, request.DATA).to_dict()
@rest_utils.ajax(data_required=True)
def delete(self, request):
"""Delete a portgroup.
:param request: HTTP request.
"""
return ironic.portgroup_delete(request,
request.DATA.get('portgroup_id'))
@urls.register
class PortgroupPorts(generic.View):
url_regex = r'ironic/portgroups/(?P<portgroup_id>{})/ports$'. \
format(LOGICAL_NAME_PATTERN)
@rest_utils.ajax()
def get(self, request, portgroup_id):
"""Get the ports for a specified portgroup.
:param request: HTTP request.
:param node_id: UUID or name of portgroup.
:return: List of port objects.
"""
ports = ironic.portgroup_get_ports(request, portgroup_id)
return {
'ports': [i.to_dict() for i in ports]
}

View File

@ -1,17 +0,0 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import pbr.version
__version__ = pbr.version.VersionInfo(
'ironic-ui').version_string()

View File

@ -1,43 +0,0 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
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)

View File

@ -1,19 +0,0 @@
{% extends 'base.html' %}
{% 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 %}
{% block main %}
<ng-include src="'{{ STATIC_URL }}dashboard/admin/ironic/node-list/node-list.html'"></ng-include>
{% endblock %}

View File

@ -1,20 +0,0 @@
{% extends 'base.html' %}
{% 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 %}
{% block main %}
<ng-include src="'{{ STATIC_URL }}dashboard/admin/ironic/node-details/node-details.html'"></ng-include>
{% endblock %}

View File

@ -1,25 +0,0 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
# Copyright (c) 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.
from django.conf.urls import url
import ironic_ui.api.ironic_rest_api # noqa
from ironic_ui.content.ironic import views
urlpatterns = [
url(r'^$', views.IndexView.as_view(), name='index'),
url(r'^([^/]+)/$', views.DetailView.as_view(), name='detail'),
]

View File

@ -1,25 +0,0 @@
# Copyright 2016 Cisco Systems, Inc.
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
# Copyright (c) 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.
from django.views import generic
class IndexView(generic.TemplateView):
template_name = 'admin/ironic/index.html'
class DetailView(generic.TemplateView):
template_name = 'admin/ironic/node_detail.html'

View File

@ -1,30 +0,0 @@
# Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
# Copyright (c) 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.
# The name of the panel to be added to HORIZON_CONFIG. Required.
PANEL = 'ironic'
# The name of the dashboard the PANEL associated with. Required.
PANEL_DASHBOARD = 'admin'
# The name of the panel group the PANEL is associated with.
PANEL_GROUP = 'admin'
# Python panel class of the PANEL to be added.
ADD_PANEL = 'ironic_ui.content.ironic.panel.Ironic'
# A list of applications to be prepended to INSTALLED_APPS
ADD_INSTALLED_APPS = ['ironic_ui', ]
# A list of AngularJS modules to be loaded when Angular bootstraps.
ADD_ANGULAR_MODULES = ['horizon.dashboard.admin.ironic']
# Automatically discover static resources in installed apps
AUTO_DISCOVER_STATIC_FILES = True

View File

@ -1,154 +0,0 @@
/*
* 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';
module.exports = function (config) {
// This tox venv is setup in the post-install npm step
var toxPath = '../.tox/py27/lib/python2.7/site-packages/';
config.set({
preprocessors: {
// Used to collect templates for preprocessing.
// NOTE: the templates must also be listed in the files section below.
'./static/**/*.html': ['ng-html2js'],
// Used to indicate files requiring coverage reports.
'./static/**/!(*.spec).js': ['coverage']
},
// Sets up module to process templates.
ngHtml2JsPreprocessor: {
prependPrefix: '/',
moduleName: 'templates'
},
basePath: './',
// Contains both source and test files.
files: [
/*
* shim, partly stolen from /i18n/js/horizon/
* Contains expected items not provided elsewhere (dynamically by
* Django or via jasmine template.
*/
'../test-shim.js',
// from jasmine.html
toxPath + 'xstatic/pkg/jquery/data/jquery.js',
toxPath + 'xstatic/pkg/angular/data/angular.js',
toxPath + 'xstatic/pkg/angular/data/angular-route.js',
toxPath + 'xstatic/pkg/angular/data/angular-mocks.js',
toxPath + 'xstatic/pkg/angular/data/angular-cookies.js',
toxPath + 'xstatic/pkg/angular_bootstrap/data/angular-bootstrap.js',
toxPath + 'xstatic/pkg/angular_gettext/data/angular-gettext.js',
toxPath + 'xstatic/pkg/angular/data/angular-sanitize.js',
toxPath + 'xstatic/pkg/d3/data/d3.js',
toxPath + 'xstatic/pkg/rickshaw/data/rickshaw.js',
toxPath + 'xstatic/pkg/angular_smart_table/data/smart-table.js',
toxPath + 'xstatic/pkg/angular_lrdragndrop/data/lrdragndrop.js',
toxPath + 'xstatic/pkg/angular_fileupload/data/ng-file-upload-all.js',
toxPath + 'xstatic/pkg/spin/data/spin.js',
toxPath + 'xstatic/pkg/spin/data/spin.jquery.js',
toxPath + 'xstatic/pkg/tv4/data/tv4.js',
toxPath + 'xstatic/pkg/objectpath/data/ObjectPath.js',
toxPath + 'xstatic/pkg/angular_schema_form/data/schema-form.js',
toxPath + '/horizon/static/horizon/js/horizon.js',
/**
* Include framework source code from horizon that we need.
* Otherwise, karma will not be able to find them when testing.
* These files should be mocked in the foreseeable future.
*/
toxPath + 'horizon/static/framework/**/*.module.js',
toxPath + 'horizon/static/framework/**/!(*.spec|*.mock).js',
toxPath + 'openstack_dashboard/static/**/*.module.js',
toxPath + 'openstack_dashboard/static/**/!(*.spec|*.mock).js',
toxPath + 'openstack_dashboard/dashboards/**/static/**/*.module.js',
toxPath + 'openstack_dashboard/dashboards/**/static/**/!(*.spec|*.mock).js',
/**
* First, list all the files that defines application's angular modules.
* Those files have extension of `.module.js`. The order among them is
* not significant.
*/
'./static/**/*.module.js',
/**
* Followed by other JavaScript files that defines angular providers
* on the modules defined in files listed above. And they are not mock
* files or spec files defined below. The order among them is not
* significant.
*/
'./static/**/!(*.spec|*.mock).js',
/**
* Then, list files for mocks with `mock.js` extension. The order
* among them should not be significant.
*/
toxPath + 'openstack_dashboard/static/**/*.mock.js',
/**
* Finally, list files for spec with `spec.js` extension. The order
* among them should not be significant.
*/
'./static/**/*.spec.js',
/**
* Angular external templates
*/
'./static/**/*.html'
],
autoWatch: true,
frameworks: ['jasmine'],
browsers: ['Chrome'],
browserNoActivityTimeout: 60000,
phantomjsLauncher: {
// Have phantomjs exit if a ResourceError is encountered
// (useful if karma exits without killing phantom)
exitOnResourceError: true
},
reporters: ['progress', 'coverage', 'threshold'],
plugins: [
'karma-chrome-launcher',
'karma-jasmine',
'karma-ng-html2js-preprocessor',
'karma-coverage',
'karma-threshold-reporter'
],
// Places coverage report in HTML format in the subdirectory below.
coverageReporter: {
type: 'html',
dir: '../cover/karma/'
},
// Coverage threshold values.
thresholdReporter: {
statements: 10, // target 100
branches: 0, // target 100
functions: 10, // target 100
lines: 10 // target 100
}
});
};
}());

View File

@ -1,18 +0,0 @@
# Robert Simai <robert.simai@suse.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.1.1.dev9\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-10-07 16:48+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-10-17 01:04+0000\n"
"Last-Translator: Robert Simai <robert.simai@suse.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid "Ironic Bare Metal Provisioning"
msgstr "Ironic Bare Metal Provisionierung"

View File

@ -1,601 +0,0 @@
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Frank Kloeker <eumel@arcor.de>, 2016. #zanata
# Robert Simai <robert.simai@suse.com>, 2016. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2017. #zanata
# Robert Simai <robert.simai@suse.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev70\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-18 16:08+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-04 10:43+0000\n"
"Last-Translator: Robert Simai <robert.simai@suse.com>\n"
"Language-Team: German\n"
"Language: de\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr ""
"Eine Anfrage wurde erstellt, um den Provisionierungszustand des Knotens %s "
"zu ändern"
msgid "Abort cleaning"
msgstr "Bereinigung abbrechen"
msgid "Actions"
msgstr "Aktionen"
msgid "Add Extra"
msgstr "Extra hinzufügen"
msgid "Add Instance Property"
msgstr "Instanz-Eigenschaft hinzufügen"
msgid "Add Property"
msgstr "Eigenschaft hinzufügen"
#, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] ""
"Sind Sie sicher, dass Sie den Knoten \"%s\" löschen wollen? Diese Aktion "
"kann nicht rückgängig gemacht werden."
msgstr[1] ""
"Sind Sie sicher, dass Sie die Knoten \"%s\" löschen wollen? Diese Aktion "
"kann nicht rückgängig gemacht werden."
#, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] ""
"Sind Sie sicher, dass Sie den Port \"%s\" löschen wollen? Diese Aktion kann "
"nicht rückgängig gemacht werden."
msgstr[1] ""
"Sind Sie sicher, dass Sie die Ports \"%s\" löschen wollen? Diese Aktion kann "
"nicht rückgängig gemacht werden."
#, python-format
msgid ""
"Are you sure you want to delete portgroup \"%s\"? This action cannot be "
"undone."
msgid_plural ""
"Are you sure you want to delete portgroups \"%s\"? This action cannot be "
"undone."
msgstr[0] ""
"Sind Sie sicher, dass Sie die Portgruppe \"%s\" löschen wollen? Diese Aktion "
"kann nicht rückgängig gemacht werden."
msgstr[1] ""
"Sind Sie sicher, dass Sie die Portgruppen \"%s\" löschen wollen? Diese "
"Aktion kann nicht rückgängig gemacht werden."
msgid "Attributes"
msgstr "Attribute"
msgid "Boot Device"
msgstr "Boot-Gerät"
msgid "Cancel"
msgstr "Abbrechen"
msgid "Chassis ID"
msgstr "Chassis-ID"
msgid "Choose an Image"
msgstr "Wählen Sie ein Abbild"
msgid "Clean"
msgstr "Bereinigen"
msgid "Clean Node"
msgstr "Knoten bereinigen"
msgid "Clean Step"
msgstr "Bereinigungsschritt"
msgid "Clean node"
msgstr "Knoten bereinigen"
msgid "Clean steps should be an non-empty array"
msgstr "Reinigungsschritte sollten ein nicht-leeres Array sein"
msgid "Click link to view console"
msgstr "Klicken Sie auf den Link, um nur die Konsole anzuzeigen."
msgid "Configuration"
msgstr "Konfiguration"
msgid "Console Enabled"
msgstr "Konsole aktiviert"
msgid "Console Info."
msgstr "Konsoleninformation."
msgid "Create Port"
msgstr "Port erstellen"
msgid "Created At"
msgstr "Erstellt am"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "Standard ist ([^\"\\. ]+|\"[^\"]+\")"
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "Knoten löschen"
msgstr[1] "Knoten löschen"
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "Port löschen"
msgstr[1] "Ports löschen"
msgid "Delete Portgroup"
msgid_plural "Delete Portgroups"
msgstr[0] "Portgruppe löschen"
msgstr[1] "Portgruppen löschen"
msgid "Deploy Kernel"
msgstr "Kernel bereitstellen"
msgid "Deploy Ramdisk"
msgstr "Ramdisk bereitstellen"
msgid "Device"
msgstr "Gerät"
msgid "Driver"
msgstr "Treiber"
msgid "Driver Details"
msgstr "Treiberdetails"
msgid "Driver Info"
msgstr "Treiberinformationen"
msgid "Driver Validation"
msgstr "Treibervalidierung"
msgid ""
"Each cleaning step must be an object that contains \"interface\" and \"step"
"\" properties"
msgstr ""
"Jeder Reinigunsschritt muss ein Objekt sein, das \"interface\" und \"step\" "
"Eigenschaften enthält"
msgid "Edit Node"
msgstr "Knoten bearbeiten"
msgid "Edit Port"
msgstr "Port bearbeiten"
msgid "Enroll Node"
msgstr "Knoten ausrollen"
msgid "Extra"
msgstr "Extra"
msgid "Extra Property Name"
msgstr "Extra Eigenschaftname"
msgid "Extras"
msgstr "Extras"
msgid "General"
msgstr "Allgemein"
msgid "Image Source"
msgstr "Abbildquelle"
msgid "Inspect"
msgstr "Inspizieren"
msgid "Inspection Finished At"
msgstr "Inspektion beenden um"
msgid "Inspection Started At"
msgstr "Inspektion gestartet um"
msgid "Instance ID"
msgstr "Instanz-ID"
msgid "Instance Info"
msgstr "Instanzinformation"
msgid "Instance Name"
msgstr "Instanzname"
msgid "Instance Property Name"
msgstr "Instanzeigenschaftname"
msgid "Interface"
msgstr "Interface"
msgid "Kernel"
msgstr "Kernel"
msgid "Last Error"
msgstr "Letzter Fehler"
msgid "Local Link Connection"
msgstr "Local Link Verbindung"
msgid "Local link connection"
msgstr "Local Link Verbindung"
msgid "MAC Address"
msgstr "MAC-Adresse"
msgid "MAC address"
msgstr "MAC-Adresse"
msgid "MAC address for this port. Required."
msgstr "MAC-Adresse für diesen Port. Erforderlich."
msgid "MAC address or OpenFlow datapath ID"
msgstr "MAC Adresse oder OpenFlow datapath ID"
msgid "Maintenance"
msgstr "Wartung"
msgid "Maintenance Reason"
msgstr "Grund der Wartung"
msgid "Move to"
msgstr "Verschieben nach"
msgid "Name"
msgstr "Name"
msgid "Network Interface"
msgstr "Netzwerk Schnittstelle"
msgid "No Instance"
msgstr "Keine Instanz"
msgid "No network ports have been defined"
msgstr "Es wurden keine Netzwerkports definiert"
msgid "No portgroups have been defined"
msgstr "Es wurden keine Portgruppen definiert"
msgid "No reason given."
msgstr "Kein Grund angegeben."
msgid "Node"
msgstr "Knoten"
#, python-format
msgid "Node %s is already in target maintenance state."
msgstr "Knoten %s ist bereits im beabsichtigten Wartungszustand."
msgid "Node Driver"
msgstr "Knotentreiber"
msgid "Node ID"
msgstr "Knoten-ID"
msgid "Node Info"
msgstr "Knoteninformation"
msgid "Node Name"
msgstr "Knotenname"
msgid "One of this, (.*) must be specified\\."
msgstr "Einer von diesen, (.*) muss spezifiziert sein\\."
msgid "Overview"
msgstr "Übersicht"
msgid "PXE Enabled"
msgstr "PXE aktiviert"
msgid "PXE enabled"
msgstr "PXE aktiviert"
msgid "Persistent"
msgstr "Persistent"
msgid "Port successfully created"
msgstr "Port erfolgreich erstellt"
msgid "Portgroup"
msgstr "Portgruppe"
msgid "Portgroup successfully created"
msgstr "Portgruppe erfolgreich erstellt"
msgid "Portgroups"
msgstr "Portgruppen"
msgid "Ports"
msgstr "Ports"
msgid "Power State"
msgstr "Zustand"
msgid "Power off"
msgstr "Ausschalten"
msgid "Power on"
msgstr "Einschalten"
msgid "Properties"
msgstr "Eigenschaften"
msgid "Property Name"
msgstr "Eigenschaftname"
msgid "Provide a list of cleaning steps in JSON format"
msgstr "Eine Liste der Reinigungsschritte im JSON-Format angeben"
msgid ""
"Provide a reason for why you are putting the selected node(s) into "
"maintenance mode (optional)"
msgstr ""
"Geben Sie einen Grund an, warum Sie den/die ausgewählten Knoten in den "
"Wartungsmodus setzen (optional)"
msgid "Provision State"
msgstr "Bereitstellungszustand"
msgid "Provisioning State"
msgstr "Provisionierungszustand"
msgid "Provisioning Status"
msgstr "Provisionierungsstatus"
msgid "Put Node(s) Into Maintenance Mode"
msgstr "Knoten in Wartungsmodus setzen"
msgid "Ramdisk"
msgstr "Ramdisk"
msgid "Reason"
msgstr "Grund"
msgid "Reboot"
msgstr "Neustart"
msgid "Refresh"
msgstr "Aktualisieren"
msgid "Refresh page to see updated console details"
msgstr "Seite neu laden, um aktualisierte Konsolendetails anzuzeigen"
msgid "Refresh page to see updated power status"
msgstr "Aktualisiere Seite, um den Power Status zu sehen"
msgid "Required"
msgstr "Erforderlich"
msgid "Reservation"
msgstr "Reservierung"
msgid "Resource Class"
msgstr "Ressourcen-Klasse"
msgid "Root GB"
msgstr "Root GB"
msgid "SSH Address"
msgstr "SSH Adresse"
msgid "SSH Key Contents"
msgstr "SSH Schlüsselinhalt"
msgid "SSH Key File"
msgstr "SSH Schlüsseldatei"
msgid "SSH Password"
msgstr "SSH Passwort"
msgid "SSH Port"
msgstr "SSH-Port"
msgid "SSH Username"
msgstr "SSH-Benutzername"
msgid "SSH terminal port"
msgstr "SSH Terminal Port"
msgid "Select a Driver"
msgstr "Wählen Sie einen Treiber"
msgid "Soft power off"
msgstr "Sanftes Ausschalten"
msgid "Soft reboot"
msgstr "Sanfter Neustart"
msgid "Submit"
msgstr "Abschicken"
#, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "Knoten \"%s\" erfolgreich gelöscht"
msgstr[1] "Knoten \"%s\" erfolgreich gelöscht"
#, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "Port \"%s\" erfolgreich gelöscht"
msgstr[1] "Port \"%s\" erfolgreich gelöscht"
#, python-format
msgid "Successfully deleted portgroup \"%s\""
msgid_plural "Successfully deleted portgroups \"%s\""
msgstr[0] "Portgruppe \"%s\" erfolgreich gelöscht"
msgstr[1] "Portgruppen \"%s\" erfolgreich gelöscht"
#, python-format
msgid "Successfully updated node %s"
msgstr "Knoten %s erfolgreich aktualisiert"
#, python-format
msgid "Successfully updated port %s"
msgstr "Port %s erfolgreich aktualisiert"
msgid "Target Power State"
msgstr "Ziel Power-Zustand"
msgid "Target Provision State"
msgstr "Ziel Bereitstellungszustand"
msgid ""
"This field is disabled because a port cannot have any connectivity "
"attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless "
"its associated node is in an enroll, inspecting, mangeable state; or in "
"maintenance mode."
msgstr ""
"Dieses Feld ist deaktiviert, denn bei einem Port können Verbindungsattribute "
"(pxe_enabled, local_link_connection, portgroup_id) nur aktualisiert werden, "
"wenn der zugewiesene Knoten im Enroll, Inspecting oder Manageable Status "
"oder im Wartungsmodus ist."
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "Knoten-Aktualisierungspatch kann nicht erstellt werden."
#, python-format
msgid "Unable to create node: %s"
msgstr "Knoten kann nicht erstellt werden: %s"
msgid "Unable to create port update patch."
msgstr "Port Update Patch kann nicht erstellt werden."
#, python-format
msgid "Unable to create port: %s"
msgstr "Port kann nicht erstellt werden: %s"
#, python-format
msgid "Unable to create portgroup: %s"
msgstr "Portgruppe kann nicht erstellt werden: %s"
#, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "Knoten \"%s\" kann nicht gelöscht werden"
msgstr[1] "Knoten \"%s\" können nicht gelöscht werden"
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "Knoten kann nicht gelöscht werden: %s: %s"
#, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "Port \"%s\" kann nicht gelöscht werden"
msgstr[1] "Ports \"%s\" können nicht gelöscht werden"
#, python-format
msgid "Unable to delete port: %s"
msgstr "Port kann nicht gelöscht werden: %s"
#, python-format
msgid "Unable to delete portgroup \"%s\""
msgid_plural "Unable to delete portgroups \"%s\""
msgstr[0] "Portgruppe \"%s\" kann nicht gelöscht werden"
msgstr[1] "Portgruppen \"%s\" können nicht gelöscht werden"
#, python-format
msgid "Unable to delete portgroup: %s"
msgstr "Portgruppe kann nicht gelöscht werden: %s"
#, python-format
msgid "Unable to get console for node %s: %s"
msgstr "Konsole für Node %s kann nicht abgerufen werden: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "Konnte den Knoten nicht ausschalten: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Ironic Treiber können nicht abgerufen werden: %s"
#, python-format
msgid "Unable to retrieve Ironic node portgroups: %s"
msgstr "Ironic Knotenportgruppen können nicht abgerufen werden: %s"
#, python-format
msgid "Unable to retrieve Ironic nodes. %s"
msgstr "Konnte die Ironic Knoten nicht abrufen: %s"
#, python-format
msgid "Unable to retrieve boot device for Ironic node. %s"
msgstr "Boot-Gerät für den Ironic-Knoten kann nicht abgerufen werden. %s"
#, python-format
msgid "Unable to retrieve driver properties: %s"
msgstr "Treibereigenschaften können nicht abgerufen werden: %s"
#, python-format
msgid "Unable to retrieve the Ironic node ports: %s"
msgstr "Konnte die Ironic-Knotenports nicht abrufen: %s"
#, python-format
msgid "Unable to retrieve the Ironic node: %s"
msgstr "Konnte den Ironic-Knoten nicht abrufen: %s"
#, python-format
msgid "Unable to set Ironic node %s maintenance state: %s"
msgstr "Konnte den Ironic Knoten %s nicht in den Wartungszustand setzen: %s"
#, python-format
msgid "Unable to set console mode: %s"
msgstr "Konsolenmodus kann nicht gesetzt werden: %s"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "Kann Provisionierungszustand nicht setzen: %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "Knoten %s kann nicht aktualisiert werden: %s"
#, python-format
msgid "Unable to update port %s: %s"
msgstr "Port %s kann nicht aktualisiert werden: %s"
#, python-format
msgid "Unable to validate node %s: %s"
msgstr "Knoten %s kann nicht validiert werden: %s"
msgid "Unable to validate the JSON input"
msgstr "JSON-Eingabe kann nicht validiert werden"
msgid "Update Node"
msgstr "Knoten aktualisieren"
msgid "Update Port"
msgstr "Port aktualisieren"
msgid "Updated At"
msgstr "Aktualisiert am"
msgid "Valid"
msgstr "Gültig"
msgid "Virtualization Software"
msgstr "Virtualisierungssoftware"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "Standard (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,18 +0,0 @@
# 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"

View File

@ -1,18 +0,0 @@
# 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"

View File

@ -1,347 +0,0 @@
# Gael Rehault <gael01@gmail.com>, 2015. #zanata
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Gérald LONLAS <g.lonlas@gmail.com>, 2016. #zanata
# Martine Marin <mmarin@fr.ibm.com>, 2016. #zanata
# Nicolas Fournier <nicolas.fournier3@gmail.com>, 2016. #zanata
# Gérald LONLAS <g.lonlas@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev70\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-18 16:08+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-02-03 10:49+0000\n"
"Last-Translator: Gérald LONLAS <g.lonlas@gmail.com>\n"
"Language-Team: French\n"
"Language: fr\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr ""
"Une requête a été envoyé pour changer l'état du provisionnement du nœud %s"
msgid "Actions"
msgstr "Actions"
#, fuzzy, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] ""
"Etes-vous certain de vouloir supprimer le nœud \"%s\" ? Cette action est "
"irréversible."
msgstr[1] ""
#, fuzzy, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] ""
"Etes-vous certain de vouloir supprimer le port \"%s\" ? Cette action est "
"irréversible."
msgstr[1] ""
msgid "Cancel"
msgstr "Annuler"
msgid "Chassis ID"
msgstr "ID du chassis"
msgid "Choose an Image"
msgstr "Choisir une image"
msgid "Configuration"
msgstr "Configuration"
msgid "Console Enabled"
msgstr "Console Activée"
msgid "Create Port"
msgstr "Créer un port"
msgid "Created At"
msgstr "Créé le"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "Par défaut ([^\"\\. ]+|\"[^\"]+\")"
#, fuzzy
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "Supprimer le nœud"
msgstr[1] ""
#, fuzzy
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "Supprimer le port"
msgstr[1] ""
msgid "Deploy Kernel"
msgstr "Kernel de déploiement"
msgid "Deploy Ramdisk"
msgstr "Ramdisk de déploiement"
msgid "Driver"
msgstr "Pilote"
msgid "Driver Details"
msgstr "Détails du pilote"
msgid "Driver Info"
msgstr "Info du pilote"
msgid "Edit Node"
msgstr "Editer le nœud"
msgid "Enroll Node"
msgstr "Enroller le nœud"
msgid "Extra"
msgstr "Extra"
msgid "Extras"
msgstr "Extras"
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 "MAC Address"
msgstr "Adresse MAC"
msgid "MAC address"
msgstr "Adresse MAC"
msgid "MAC address for this port. Required."
msgstr "Adresse MAC pour ce port. Requis."
msgid "Maintenance"
msgstr "Maintenance"
msgid "Maintenance Reason"
msgstr "Raison de la maintenance"
msgid "Name"
msgstr "Nom"
msgid "No Instance"
msgstr "Pas d'instance"
msgid "No network ports have been defined"
msgstr "Aucun port réseau n'a été défini"
msgid "Node"
msgstr "Nœud"
msgid "Node Driver"
msgstr "Pilote du nœud"
msgid "Node ID"
msgstr "ID du nœud"
msgid "Node Info"
msgstr "Info du nœud"
msgid "Node Name"
msgstr "Nom du nœud"
msgid "One of this, (.*) must be specified\\."
msgstr "Une des leurs (.*) doit être spécifié\\."
msgid "Overview"
msgstr "Vue d'ensemble"
msgid "Port successfully created"
msgstr "Port crée avec succès"
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 "Property Name"
msgstr "Nom de la propriété"
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) nœud(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) nœud(s) en mode maintenance"
msgid "Ramdisk"
msgstr "Ramdisk"
msgid "Refresh"
msgstr "Rafraichir"
msgid "Refresh page to see updated power status"
msgstr "Rafraichir la page pour voir les statuts d'alimentation à jour"
msgid "Required"
msgstr "Obligatoire"
msgid "Reservation"
msgstr "Réservation"
msgid "SSH Port"
msgstr "Port SSH"
msgid "SSH Username"
msgstr "Utilisateur SSH"
msgid "Select a Driver"
msgstr "Sélectionner un pilote"
msgid "Submit"
msgstr "Envoyer"
#, fuzzy, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "Suppression avec succès du nœud \"%s\" "
msgstr[1] ""
#, fuzzy, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "Suppression avec succès du port \"%s\" "
msgstr[1] ""
#, python-format
msgid "Successfully updated node %s"
msgstr "Mise à jour avec succès du nœud %s"
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 create node update patch."
msgstr "Impossible de créer le patch de mise à jour du nœud."
#, python-format
msgid "Unable to create node: %s"
msgstr "Impossible de créer le nœud : %s"
#, python-format
msgid "Unable to create port: %s"
msgstr "Impossible de créer le port : %s"
#, fuzzy, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "Impossible de supprimer le nœud \"%s\""
msgstr[1] ""
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "Impossible de supprimer le nœud %s : %s"
#, fuzzy, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "Impossible de supprimer le port \"%s\""
msgstr[1] ""
#, python-format
msgid "Unable to delete port: %s"
msgstr "Impossible de supprimer le port : %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "Impossible d'éteindre le nœud : %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Impossible de récupérer les pilotes Ironic : %s"
#, python-format
msgid "Unable to retrieve driver properties: %s"
msgstr "Impossible de récupérer les propriétés du pilote : %s"
#, python-format
msgid "Unable to retrieve the Ironic node ports: %s"
msgstr "Impossible de récupérer les ports du nœud Ironic : %s"
#, python-format
msgid "Unable to retrieve the Ironic node: %s"
msgstr "Impossible de récupérer le nœud Ironic : %s"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "Impossible de configurer l'état du provisionnement : %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "Impossible de mettre à jour le nœud %s : %s"
msgid "Update Node"
msgstr "Mettre à jour le nœud"
msgid "Updated At"
msgstr "Mis à jour à"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,18 +0,0 @@
# suhartono <cloudsuhartono@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.1.1.dev45\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-01-17 12:34+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-01-10 02:16+0000\n"
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Ironic Bare Metal Provisioning"
msgstr "Ironic Bare Metal Provisioning"

View File

@ -1,615 +0,0 @@
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# suhartono <cloudsuhartono@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev73\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-21 15:07+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-23 05:11+0000\n"
"Last-Translator: suhartono <cloudsuhartono@gmail.com>\n"
"Language-Team: Indonesian\n"
"Language: id\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr "Permintaan telah dibuat untuk mengubah keadaan provisioning node %s"
msgid "Abort cleaning"
msgstr "Membatalkan pembersihan"
msgid "Actions"
msgstr "Aksi"
msgid "Add Extra"
msgstr "Menambahkan Extra"
msgid "Add Instance Property"
msgstr "Menambahkan Instance Property"
msgid "Add Property"
msgstr "Menambahkan Property"
#, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] ""
"Apakah Anda yakin ingin menghapus node \"%s\"? Tindakan ini tidak bisa "
"dibatalkan."
#, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] ""
"Apakah Anda yakin ingin menghapus port \"%s\"? Tindakan ini tidak bisa "
"dibatalkan."
#, python-format
msgid ""
"Are you sure you want to delete portgroup \"%s\"? This action cannot be "
"undone."
msgid_plural ""
"Are you sure you want to delete portgroups \"%s\"? This action cannot be "
"undone."
msgstr[0] ""
"Are you sure you want to delete portgroup \"%s\"? This action cannot be "
"undone."
msgid "Attributes"
msgstr "Attributes"
msgid "Boot Device"
msgstr "Boot Device"
msgid "Cancel"
msgstr "Batal"
msgid "Chassis ID"
msgstr "Chassis ID"
msgid "Choose an Image"
msgstr "Pilih Image"
msgid "Clean"
msgstr "Bersih"
msgid "Clean Node"
msgstr "Clean Node"
msgid "Clean Step"
msgstr "Membersihkan langkah"
msgid "Clean node"
msgstr "Clean node"
msgid "Clean steps should be an non-empty array"
msgstr "Langkah pembersihan harus array non-empty"
msgid "Click link to view console"
msgstr "Klik tautan untuk melihat konsol"
msgid "Configuration"
msgstr "Konfigurasi"
msgid "Console Enabled"
msgstr "Console Enabled (konsol diaktifkan)"
msgid "Console Info."
msgstr "Console Info."
msgid "Create Port"
msgstr "Buat Port"
msgid "Created At"
msgstr "Created At"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "Defaults ke ([^\"\\. ]+|\"[^\"]+\")"
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "Delete Node (hapus node)"
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "Delete Port (hapus port)"
msgid "Delete Portgroup"
msgid_plural "Delete Portgroups"
msgstr[0] "Delete Portgroup"
msgid "Deploy Kernel"
msgstr "Deploy Kernel (mengerahkan kernel)"
msgid "Deploy Ramdisk"
msgstr "Deploy Ramdisk (mengerahkan ramdisk)"
msgid "Device"
msgstr "Device"
msgid "Driver"
msgstr "Driver"
msgid "Driver Details"
msgstr "Driver Details (rincian driver)"
msgid "Driver Info"
msgstr "Driver Info"
msgid "Driver Validation"
msgstr "Driver Validation (validasi driver)"
msgid ""
"Each cleaning step must be an object that contains \"interface\" and \"step"
"\" properties"
msgstr ""
"Setiap langkah pembersihan harus sebagai obyek yang berisi properti "
"\"interface\" dan \"step\"."
msgid "Edit Node"
msgstr "Edit Node"
msgid "Edit Port"
msgstr "Edit Port"
msgid "Enroll Node"
msgstr "Enroll Node"
msgid "Extra"
msgstr "Extra"
msgid "Extra Property Name"
msgstr "Extra Property Name"
msgid "Extras"
msgstr "Extras"
msgid "General"
msgstr "General (umum)"
msgid "Image Source"
msgstr "Image Source"
msgid "Indicates whether this port should be used when PXE booting this node"
msgstr ""
"Menunjukkan apakah port ini harus digunakan saat PXE melakukan booting node "
"ini"
msgid "Inspect"
msgstr "Inspect (periksa)"
msgid "Inspection Finished At"
msgstr "Inspection Finished At (pemeriksaan selesai)"
msgid "Inspection Started At"
msgstr "Inspection Started At (pemeriksaan dimulai)"
msgid "Instance ID"
msgstr "ID Instances"
msgid "Instance Info"
msgstr "Instance Info (info instance)"
msgid "Instance Name"
msgstr "Nama Instance"
msgid "Instance Property Name"
msgstr "Instance Property Name"
msgid "Interface"
msgstr "Interface (antarmuka)"
msgid "Kernel"
msgstr "Kernel"
msgid "Last Error"
msgstr "Last Error (kesalahan terakhir)"
msgid "Local Link Connection"
msgstr "Local Link Connection"
msgid "Local link connection"
msgstr "Koneksi link lokal"
msgid "MAC Address"
msgstr "MAC Address"
msgid "MAC address"
msgstr "MAC address"
msgid "MAC address for this port. Required."
msgstr "MAC address untuk port ini. Wajib."
msgid "MAC address or OpenFlow datapath ID"
msgstr "Alamat MAC atau OpenFlow datapath ID"
msgid "Maintenance"
msgstr "Maintenance (pemeliharaan)"
msgid "Maintenance Reason"
msgstr "Maintenance Reason (alasan pemeliharaan)"
msgid "Move to"
msgstr "Move to (pindah ke)"
msgid "Name"
msgstr "Nama"
msgid "Network Interface"
msgstr "Network Interface"
msgid "No Instance"
msgstr "Tak ada Instance"
msgid "No network ports have been defined"
msgstr "Tidak ada port jaringan telah didefinisikan"
msgid "No portgroups have been defined"
msgstr "No portgroups telah didefinisikan"
msgid "No reason given."
msgstr "Tidak ada alasan yang diberikan."
msgid "Node"
msgstr "Node (simpul)"
#, python-format
msgid "Node %s is already in target maintenance state."
msgstr "Node %s sudah dalam target maintenance state."
msgid "Node Driver"
msgstr "Node Driver"
msgid "Node ID"
msgstr "Node ID"
msgid "Node Info"
msgstr "Node Info (info node)"
msgid "Node Name"
msgstr "Node Name (nama node)"
msgid "One of this, (.*) must be specified\\."
msgstr "One of this, (.*) must be specified\\."
msgid "Overview"
msgstr "Iktisar"
msgid "PXE Enabled"
msgstr "PXE Diaktifkan"
msgid "PXE enabled"
msgstr "PXE enabled"
msgid "Persistent"
msgstr "Persistent"
msgid "Port successfully created"
msgstr "Port berhasil dibuat"
msgid "Portgroup"
msgstr "Portgroup"
msgid "Portgroup successfully created"
msgstr "Portgroup berhasil dibuat"
msgid "Portgroup that this port belongs to"
msgstr "Portgroup yang dimiliki port ini"
msgid "Portgroups"
msgstr "Portgroups"
msgid "Ports"
msgstr "Port-Port"
msgid "Power State"
msgstr "Power State"
msgid "Power off"
msgstr "Power off"
msgid "Power on"
msgstr "Power on"
msgid "Properties"
msgstr "Property (sifat)"
msgid "Property Name"
msgstr "Property Name (nama property)"
msgid "Provide a list of cleaning steps in JSON format"
msgstr "Berikan daftar langkah-langkah pembersihan dalam format JSON"
msgid ""
"Provide a reason for why you are putting the selected node(s) into "
"maintenance mode (optional)"
msgstr ""
"Berikan alasan mengapa Anda menempatkan node yang dipilih ke dalam modus "
"pemeliharaan (opsional)"
msgid "Provision State"
msgstr "Provision State (keadaan penyediaan)"
msgid "Provisioning State"
msgstr "Provisioning State"
msgid "Provisioning Status"
msgstr "Provisioning Status (status penyediaan)"
msgid "Put Node(s) Into Maintenance Mode"
msgstr "Letakan Node(s) dalam Maintenance Mode"
msgid "Ramdisk"
msgstr "Ramdisk"
msgid "Reason"
msgstr "Reason (alasan)"
msgid "Reboot"
msgstr "Reboot"
msgid "Refresh"
msgstr "Refresh"
msgid "Refresh page to see set boot device"
msgstr "Segarkan halaman untuk melihat perangkat booting yang ditetapkan"
msgid "Refresh page to see updated console details"
msgstr "Segarkan halaman untuk melihat rincian konsol yang diperbarui"
msgid "Refresh page to see updated power status"
msgstr "Refresh halaman untuk melihat status daya diperbarui"
msgid "Required"
msgstr "Wajib"
msgid "Reservation"
msgstr "Reservation"
msgid "Resource Class"
msgstr "Resource Class"
msgid "Root GB"
msgstr "Root GB"
msgid "SSH Address"
msgstr "SSH Address"
msgid "SSH Key Contents"
msgstr "SSH Key Contents (konten kunci SSH)"
msgid "SSH Key File"
msgstr "SSH Key File (file kunci SSH)"
msgid "SSH Password"
msgstr "SSH Password (password SSH)"
msgid "SSH Port"
msgstr "SSH Port"
msgid "SSH Username"
msgstr "SSH Username "
msgid "SSH terminal port"
msgstr "SSH terminal port (port terminal SSH)"
msgid "Select a Driver"
msgstr "Pilih Driver"
msgid "Select a boot device"
msgstr "Pilih perangkat booting"
msgid "Select a portgroup"
msgstr "Pilih portgroup"
msgid "Set Boot Device"
msgstr "Set Boot Device (setel perangkat booting)"
msgid "Set boot device"
msgstr "Setel perangkat booting"
msgid "Soft power off"
msgstr "Soft power off"
msgid "Soft reboot"
msgstr "Soft reboot"
msgid "Submit"
msgstr "Submit (menyampaikan)"
#, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "Berhasil hapus node \"%s\""
#, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "Berhasil hapus port \"%s\""
#, python-format
msgid "Successfully deleted portgroup \"%s\""
msgid_plural "Successfully deleted portgroups \"%s\""
msgstr[0] "Successfully deleted portgroup \"%s\""
#, python-format
msgid "Successfully updated node %s"
msgstr "Berhasil perbarui node %s"
#, python-format
msgid "Successfully updated port %s"
msgstr "Port berhasil diperbarui %s"
msgid "Target Power State"
msgstr "Target Power State"
msgid "Target Provision State"
msgstr "Target Provision State (keadaab penyediaan sasaran)"
msgid ""
"This field is disabled because a port cannot have any connectivity "
"attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless "
"its associated node is in an enroll, inspecting, mangeable state; or in "
"maintenance mode."
msgstr ""
"Field ini dinonaktifkan karena port tidak dapat memiliki atribut "
"konektivitas (pxe_enabled, local_link_connection, portgroup_id) yang "
"diperbarui kecuali node yang terkait adalah dalam keadaan enroll, "
"inspecting, mangeable; atau dalam modus pemeliharaan."
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "Tidak dapat membuat node update patch."
#, python-format
msgid "Unable to create node: %s"
msgstr "Tidak dapat membuat node: %s"
msgid "Unable to create port update patch."
msgstr "Tidak dapat membuat port update patch."
#, python-format
msgid "Unable to create port: %s"
msgstr "Tidak dapat membuat port: %s"
#, python-format
msgid "Unable to create portgroup: %s"
msgstr "Tidak dapat membuat portgroup: %s"
#, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "Tidak dapat menghapus node \"%s\""
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "Tidak dapat menghapus node %s: %s"
#, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "Unable to delete port \"%s\""
#, python-format
msgid "Unable to delete port: %s"
msgstr "Tidak dapat menghapus port: %s"
#, python-format
msgid "Unable to delete portgroup \"%s\""
msgid_plural "Unable to delete portgroups \"%s\""
msgstr[0] "Unable to delete portgroup \"%s\""
#, python-format
msgid "Unable to delete portgroup: %s"
msgstr "Tidak dapat menghapus portgroup: %s"
#, python-format
msgid "Unable to get console for node %s: %s"
msgstr "Tidak dapat mendapatkan konsol untuk node %s: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "Tidak dapat daya mati pada node: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Tidak dapat mengambil Ironic driver: %s"
#, python-format
msgid "Unable to retrieve Ironic node portgroups: %s"
msgstr "Unable to retrieve Ironic node portgroups: %s"
#, python-format
msgid "Unable to retrieve Ironic nodes. %s"
msgstr "Tidak dapat mengambil node Ironis. %s"
#, python-format
msgid "Unable to retrieve boot device for Ironic node. %s"
msgstr "Tidak dapat mengambil perangkat boot untuk simpul Ironis. %s"
#, python-format
msgid "Unable to retrieve driver properties: %s"
msgstr "Tidak dapat mengambil driver property: %s"
#, python-format
msgid "Unable to retrieve portgroup ports: %s"
msgstr "Tidak dapat mengambil port portgroup: %s"
#, python-format
msgid "Unable to retrieve supported boot devices for Ironic node. %s"
msgstr ""
"Tidak dapat mengambil perangkat booting yang didukung untuk node Ironic. %s"
#, python-format
msgid "Unable to retrieve the Ironic node ports: %s"
msgstr "Tidak dapat mengambil Ironic node port: %s"
#, python-format
msgid "Unable to retrieve the Ironic node: %s"
msgstr "Tidak dapat mengambil Ironic node: %s"
#, python-format
msgid "Unable to set Ironic node %s maintenance state: %s"
msgstr "Unable to set Ironic node %s maintenance state: %s"
#, python-format
msgid "Unable to set boot device: %s"
msgstr "Tidak dapat mengatur perangkat booting: %s"
#, python-format
msgid "Unable to set console mode: %s"
msgstr "Tidak dapat mengatur mode konsol: %s"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "Tidak dapat mengatur keadaan provision node: %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "Tidak dapat memperbarui node %s: %s"
#, python-format
msgid "Unable to update port %s: %s"
msgstr "Tidak dapat memperbarui port %s: %s"
#, python-format
msgid "Unable to validate node %s: %s"
msgstr "Tidak dapat memvalidasi node %s: %s"
msgid "Unable to validate the JSON input"
msgstr "Tidak dapat memvalidasi input JSON"
msgid "Update Node"
msgstr "Update Node"
msgid "Update Port"
msgstr "Update Port"
msgid "Updated At"
msgstr "Diperbarui pada"
msgid "Valid"
msgstr "Valid (sah)"
msgid "Virtualization Software"
msgstr "Virtualization Software (software virtualisasi)"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,18 +0,0 @@
# 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 ベアメタルプロビジョニング"

View File

@ -1,419 +0,0 @@
# Akihiro Motoki <amotoki@gmail.com>, 2016. #zanata
# 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
# Akihiro Motoki <amotoki@gmail.com>, 2017. #zanata
# Shu Muto <shu-mutou@rf.jp.nec.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev70\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-18 16:08+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-06-30 03:16+0000\n"
"Last-Translator: Akihiro Motoki <amotoki@gmail.com>\n"
"Language-Team: Japanese\n"
"Language: ja\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(デフォルト\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr "ノード %s のプロビジョニング状態を変更するための要求が作成されました。"
msgid "Abort cleaning"
msgstr "クリーニングの中止"
msgid "Actions"
msgstr "アクション"
#, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] "ノード「%s」 を削除してよろしいですか?この操作は取り消せません。"
#, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] "ポート「%s」 を削除してよろしいですか?この操作は取り消せません。"
msgid "Cancel"
msgstr "取り消し"
msgid "Chassis ID"
msgstr "シャーシ ID"
msgid "Choose an Image"
msgstr "イメージの選択"
msgid "Clean Step"
msgstr "クリーニングステップ"
msgid "Configuration"
msgstr "設定"
msgid "Console Enabled"
msgstr "コンソールの有効化"
msgid "Create Port"
msgstr "ポートの作成"
msgid "Created At"
msgstr "作成時刻"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "デフォルトを ([^\"\\. ]+|\"[^\"]+\") にします"
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "ノードの削除"
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "ポートの削除"
msgid "Deploy Kernel"
msgstr "カーネルのデプロイ"
msgid "Deploy Ramdisk"
msgstr "RAM ディスクのデプロイ"
msgid "Driver"
msgstr "ドライバー"
msgid "Driver Details"
msgstr "ドライバー詳細"
msgid "Driver Info"
msgstr "ドライバー情報"
msgid "Driver Validation"
msgstr "ドライバーの検証"
msgid "Edit Node"
msgstr "ノードの編集"
msgid "Edit Port"
msgstr "ポートの編集"
msgid "Enroll Node"
msgstr "ノードの登録"
msgid "Extra"
msgstr "拡張"
msgid "Extras"
msgstr "拡張"
msgid "General"
msgstr "一般"
msgid "Image Source"
msgstr "イメージソース"
msgid "Inspect"
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 "Interface"
msgstr "インターフェース"
msgid "Kernel"
msgstr "カーネル"
msgid "Last Error"
msgstr "最後のエラー"
msgid "Local link connection"
msgstr "ローカルリンク接続"
msgid "MAC Address"
msgstr "MAC アドレス"
msgid "MAC address"
msgstr "MAC アドレス"
msgid "MAC address for this port. Required."
msgstr "このポートの MAC アドレス。必須。"
msgid "MAC address or OpenFlow datapath ID"
msgstr "MAC アドレスあるいは OpenFlow データパス ID"
msgid "Maintenance"
msgstr "メンテナンス"
msgid "Maintenance Reason"
msgstr "メンテナンスの理由"
msgid "Move to"
msgstr "移動先"
msgid "Name"
msgstr "名前"
msgid "No Instance"
msgstr "インスタンスなし"
msgid "No network ports have been defined"
msgstr "ネットワークポートが定義されていません"
msgid "Node"
msgstr "ノード"
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 "(.*) の1つを指定する必要があります。"
msgid "Overview"
msgstr "概要"
msgid "Port successfully created"
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 "Reason"
msgstr "理由"
msgid "Refresh"
msgstr "最新表示"
msgid "Refresh page to see updated power status"
msgstr "電源状態を更新するにはページをリフレッシュしてください"
msgid "Required"
msgstr "必須"
msgid "Reservation"
msgstr "予約"
msgid "Root GB"
msgstr "ルートディスク (GB)"
msgid "SSH Address"
msgstr "SSH アドレス"
msgid "SSH Key Contents"
msgstr "SSH 鍵の内容"
msgid "SSH Key File"
msgstr "SSH 鍵ファイル"
msgid "SSH Password"
msgstr "SSH パスワード"
msgid "SSH Port"
msgstr "SSH ポート"
msgid "SSH Username"
msgstr "SSH ユーザー名"
msgid "SSH terminal port"
msgstr "SSH ターミナルのポート"
msgid "Select a Driver"
msgstr "ドライバーを選択してください"
msgid "Submit"
msgstr "送信"
#, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "ノード「%s」を正常に削除しました"
#, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "ポート \"%s\" を正常に削除しました"
#, python-format
msgid "Successfully updated node %s"
msgstr "ノード %s の更新に成功しました"
#, python-format
msgid "Successfully updated port %s"
msgstr "ポート %s の更新に成功しました"
msgid "Target Power State"
msgstr "ターゲット電源状態"
msgid "Target Provision State"
msgstr "ターゲットプロビジョニング状態"
msgid ""
"This field is disabled because a port cannot have any connectivity "
"attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless "
"its associated node is in an enroll, inspecting, mangeable state; or in "
"maintenance mode."
msgstr ""
"ポートが接続のための属性 (PXE 有効、ローカルリンク接続、ポートグループ ID) の"
"いずれも持たないため、関連付けられたノードが登録、検査、管理可能な状態に更新"
"されるまで、あるいはメンテナンスモードになるまで、この項目は無効です。"
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "ノードを更新するパッチを作成できません。"
#, python-format
msgid "Unable to create node: %s"
msgstr "ノードを作成できません: %s"
msgid "Unable to create port update patch."
msgstr "ポートを更新するパッチを作成できません。"
#, python-format
msgid "Unable to create port: %s"
msgstr "ポートを作成できません: %s"
#, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "ノード 「%s」を削除できません"
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "ノード %s を削除できません: %s"
#, fuzzy, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "ポート \"%s\" を削除できません"
#, python-format
msgid "Unable to delete port: %s"
msgstr "ポートを削除できません: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "ードを電源OFFにできません: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Ironic ドライバーの一覧を取得できません: %s"
#, python-format
msgid "Unable to retrieve Ironic nodes. %s"
msgstr "Ironic ノードの一覧を取得できません: %s"
#, 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"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "ノードのプロビジョニング状態を設定できませんでした: %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "ノード %s を更新できません: %s"
#, python-format
msgid "Unable to update port %s: %s"
msgstr "ポート %s の更新に失敗しました: %s"
#, python-format
msgid "Unable to validate node %s: %s"
msgstr "ノード %s を検証できません: %s"
msgid "Update Node"
msgstr "ノードの更新"
msgid "Update Port"
msgstr "ポートの更新"
msgid "Updated At"
msgstr "最終更新"
msgid "Valid"
msgstr "有効"
msgid "Virtualization Software"
msgstr "仮想化ソフトウェア"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "(?:value )?のデフォルトは ([^\"\\. ]+|\"[^\"]+\") です"

View File

@ -1,18 +0,0 @@
# Eunseop Shin <kairos9603@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.1.1.dev9\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-10-07 16:48+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-10-20 05:14+0000\n"
"Last-Translator: Eunseop Shin <kairos9603@gmail.com>\n"
"Language-Team: Korean (South Korea)\n"
"Language: ko-KR\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid "Ironic Bare Metal Provisioning"
msgstr "Ironic 베어메탈 프로비저닝"

View File

@ -1,395 +0,0 @@
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Eunseop Shin <kairos9603@khu.ac.kr>, 2016. #zanata
# HYUNGBAI PARK <openstack.make@gmail.com>, 2016. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2016. #zanata
# Sungjin Kang <gang.sungjin@gmail.com>, 2016. #zanata
# ChungYoung Cho <openstack.cho@gmail.com>, 2017. #zanata
# Ian Y. Choi <ianyrchoi@gmail.com>, 2017. #zanata
# Sungjin Kang <gang.sungjin@gmail.com>, 2017. #zanata
# Wonil Choi <wonil0522@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev70\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-18 16:08+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-06-30 03:16+0000\n"
"Last-Translator: Sungjin Kang <gang.sungjin@gmail.com>\n"
"Language-Team: Korean (South Korea)\n"
"Language: ko-KR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr "노드 %s의 프로비저닝 상태 변경을 위한 요청이 처리되었습니다."
msgid "Abort cleaning"
msgstr "클린업 취소"
msgid "Actions"
msgstr "작업"
#, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] "노드 \"%s\" 를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다."
#, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] "포트 \"%s\" 를 삭제하시겠습니까? 이 작업은 취소할 수 없습니다."
msgid "Cancel"
msgstr "취소하기"
msgid "Chassis ID"
msgstr "Chassis ID"
msgid "Choose an Image"
msgstr "이미지 선택하기"
msgid "Clean Step"
msgstr "클린업 단계"
msgid "Configuration"
msgstr "구성"
msgid "Console Enabled"
msgstr "콘솔 활성화"
msgid "Create Port"
msgstr "포트 생성하기"
msgid "Created At"
msgstr "생성 시점"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "노드 삭제"
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "포트 삭제"
msgid "Deploy Kernel"
msgstr "커널 배치하기"
msgid "Deploy Ramdisk"
msgstr "램디스크 배치하기"
msgid "Driver"
msgstr "드라이버"
msgid "Driver Details"
msgstr "드라이버 세부사항"
msgid "Driver Info"
msgstr "드라이버 정보"
msgid "Driver Validation"
msgstr "드라이버 확인"
msgid "Edit Node"
msgstr "노드 편집"
msgid "Enroll Node"
msgstr "노드 등록하기"
msgid "Extra"
msgstr "Extra"
msgid "Extra Property Name"
msgstr "Extra 속성 명칭"
msgid "Extras"
msgstr "Extra"
msgid "General"
msgstr "일반"
msgid "Image Source"
msgstr "이미지 소스"
msgid "Inspect"
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 "Interface"
msgstr "인터페이스"
msgid "Kernel"
msgstr "커널"
msgid "Last Error"
msgstr "마지막 에러"
msgid "MAC Address"
msgstr "MAC 주소"
msgid "MAC address"
msgstr "MAC 주소"
msgid "MAC address for this port. Required."
msgstr "이 포트를 위한 MAC 주소. 필수항목."
msgid "Maintenance"
msgstr "유지보수"
msgid "Maintenance Reason"
msgstr "유지보수 사유"
msgid "Move to"
msgstr "이동"
msgid "Name"
msgstr "이름"
msgid "No Instance"
msgstr "인스턴스 없음"
msgid "No network ports have been defined"
msgstr "네트워크 포트가 정의되지 않았습니다"
msgid "Node"
msgstr "노드"
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 "One of this, (.*) must be specified\\."
msgid "Overview"
msgstr "개요"
msgid "Port successfully created"
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 "Reason"
msgstr "원인"
msgid "Refresh"
msgstr "갱신"
msgid "Refresh page to see updated power status"
msgstr "갱신된 전원 상태를 보기 위해 페이지 새로 고치기"
msgid "Required"
msgstr "필수항목"
msgid "Reservation"
msgstr "예약"
msgid "Root GB"
msgstr "루트 GB"
msgid "SSH Address"
msgstr "SSH주소"
msgid "SSH Key Contents"
msgstr "SSH 키콘텐츠"
msgid "SSH Key File"
msgstr "SSH Key File"
msgid "SSH Password"
msgstr "SSH 패스워드"
msgid "SSH Port"
msgstr "SSH 포트"
msgid "SSH Username"
msgstr "SSH Username"
msgid "SSH terminal port"
msgstr "SSH 터미널포트"
msgid "Select a Driver"
msgstr "드라이버 선택하기"
msgid "Submit"
msgstr "제출"
#, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "노드 \"%s\" 를 성공적으로 삭제했습니다"
#, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "포트 \"%s\" 를 성공적으로 삭제했습니다"
#, python-format
msgid "Successfully updated node %s"
msgstr "노드 %s가 성공적으로 업데이트 됨."
msgid "Target Power State"
msgstr "대상 전원 상태"
msgid "Target Provision State"
msgstr "대상 권한설정 상태"
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "노드 업데이트 패치를 만들수 없습니다."
#, python-format
msgid "Unable to create node: %s"
msgstr "노드를 생성할 수 없습니다: %s"
#, python-format
msgid "Unable to create port: %s"
msgstr "포트를 생성할 수 없습니다: %s"
#, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "노드 \"%s\" 를 삭제할 수 없습니다"
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "노드 %s를 삭제할 수 없습니다: %s"
#, fuzzy, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "포트를 삭제할 수 없습니다: %s"
#, python-format
msgid "Unable to delete port: %s"
msgstr "포트를 삭제할 수 없습니다: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "노드의 전원을 끌 수 없습니다: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Ironic 드라이버를 되찾을 수 없습니다: %s"
#, python-format
msgid "Unable to retrieve Ironic nodes. %s"
msgstr "Ironic 노드를 가져올 수 없습니다: %s "
#, python-format
msgid "Unable to retrieve boot device for Ironic node. %s"
msgstr "Ironic 노드를 위한 부트 장치를 가져올 수 없습니다: %s"
#, 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 "
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "노드 공급 상태를 설정 할 수 없습니다: %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "노드 %s를 업데이트 할 수 없습니다: %s"
#, python-format
msgid "Unable to validate node %s: %s"
msgstr "노드 %s를 검증할수 없습니다: %s"
msgid "Update Node"
msgstr "노드 업데이트"
msgid "Updated At"
msgstr "업데이트 시점"
msgid "Valid"
msgstr "유효"
msgid "Virtualization Software"
msgstr "가상화 소프트트웨어"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,19 +0,0 @@
# Alexander <ainikitenkov@gmail.com>, 2016. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.1.1.dev9\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2016-10-07 16:48+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2016-10-19 02:08+0000\n"
"Last-Translator: Alexander <ainikitenkov@gmail.com>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"X-Generator: Zanata 3.7.3\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
msgid "Ironic Bare Metal Provisioning"
msgstr "Разворачивание на пустой системе - Ironic"

View File

@ -1,354 +0,0 @@
# Ilya Alekseyev <ilyaalekseyev@acm.org>, 2015. #zanata
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
# Alexander <ainikitenkov@gmail.com>, 2016. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Fedor Tarasenko <feodor.tarasenko@gmail.com>, 2016. #zanata
# Alexander <ainikitenkov@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev70\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-18 16:08+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-02-03 10:49+0000\n"
"Last-Translator: Alexander <ainikitenkov@gmail.com>\n"
"Language-Team: Russian\n"
"Language: ru\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=3; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n"
"%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2)\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(По умолчанию\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr "Запрос был создал для изменения состояния подготовки узла %s"
msgid "Actions"
msgstr "Действия"
#, fuzzy, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] ""
"Вы действительно хотите удалить узел \"%s\"? Это действие не может быть "
"отменено."
msgstr[1] ""
msgstr[2] ""
#, fuzzy, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] ""
"Вы действительно хотите удалить порт \"%s\"? Это действие не может быть "
"отменено."
msgstr[1] ""
msgstr[2] ""
msgid "Cancel"
msgstr "Отмена"
msgid "Chassis ID"
msgstr "ID устройства"
msgid "Choose an Image"
msgstr "Выберите образ"
msgid "Configuration"
msgstr "Конфигурация"
msgid "Console Enabled"
msgstr "Консоль активирована"
msgid "Create Port"
msgstr "Создать порт"
msgid "Created At"
msgstr "Создано"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "По умолчанию ([^\"\\. ]+|\"[^\"]+\")"
#, fuzzy
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "Удалить узел"
msgstr[1] ""
msgstr[2] ""
#, fuzzy
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "Удалить порт"
msgstr[1] ""
msgstr[2] ""
msgid "Deploy Kernel"
msgstr "Развернуть ядро"
msgid "Deploy Ramdisk"
msgstr "Развернуть Ramdisk"
msgid "Driver"
msgstr "Драйвер"
msgid "Driver Details"
msgstr "Детали дравера"
msgid "Driver Info"
msgstr "Информация о драйвере"
msgid "Edit Node"
msgstr "Редактировать узел"
msgid "Enroll Node"
msgstr "Зарегистрировать узел"
msgid "Extra"
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 "MAC Address"
msgstr "MAC адрес"
msgid "MAC address"
msgstr "MAC адрес"
msgid "MAC address for this port. Required."
msgstr "MAC адрес для этого порта. Обязательный параметр."
msgid "Maintenance"
msgstr "Техническое обслуживание"
msgid "Maintenance Reason"
msgstr "Причина технического обслуживания"
msgid "Name"
msgstr "Имя"
msgid "No Instance"
msgstr "Нет инстанса"
msgid "No network ports have been defined"
msgstr "Сетевые порты не были заданы"
msgid "Node"
msgstr "Узел"
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 "Port successfully created"
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 "Ramdisk"
msgid "Refresh"
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 "Выберите драйвер"
msgid "Submit"
msgstr "Отправить"
#, fuzzy, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "Узел \"%s\" успешно удален"
msgstr[1] ""
msgstr[2] ""
#, fuzzy, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "Порт \"%s\" успешно удален"
msgstr[1] ""
msgstr[2] ""
#, python-format
msgid "Successfully updated node %s"
msgstr "Узел %s успешно обновлен"
msgid "Target Power State"
msgstr "Целевое состояние"
msgid "Target Provision State"
msgstr "Целевой статус развертывания"
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "Невозможно создать патч обновления узла."
#, python-format
msgid "Unable to create node: %s"
msgstr "Невозможно создать узел: %s"
#, python-format
msgid "Unable to create port: %s"
msgstr "Невозможно создать порт: %s"
#, fuzzy, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "Невозможно удалить узел \"%s\""
msgstr[1] ""
msgstr[2] ""
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "Невозможно удалить узел %s: %s"
#, fuzzy, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "Невозможно удалить порт \"%s\""
msgstr[1] ""
msgstr[2] ""
#, python-format
msgid "Unable to delete port: %s"
msgstr "Невозможно удалить порт: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "Невозможно выключить узел: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Невозможно получить драйвера Ironic: %s"
#, 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"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "Не удается установить состояние подготовки узла: %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "Невозможно обновить узел %s: %s"
msgid "Update Node"
msgstr "Обновить узел"
msgid "Updated At"
msgstr "Обновлено"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "По умолчанию (?:value )? - ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,18 +0,0 @@
# işbaran akçayır <isbaran@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev39\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-06-20 18:25+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-05-22 12:06+0000\n"
"Last-Translator: işbaran akçayır <isbaran@gmail.com>\n"
"Language-Team: Turkish (Turkey)\n"
"Language: tr-TR\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=2; plural=(n>1)\n"
msgid "Ironic Bare Metal Provisioning"
msgstr "Ironic Saf Metal Hazırlığı"

View File

@ -1,516 +0,0 @@
# Mücahit Büyükyılmaz <mucahit@deltanoc.com>, 2015. #zanata
# OpenStack Infra <zanata@openstack.org>, 2015. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2016. #zanata
# Mücahit Büyükyılmaz <mucahit@deltanoc.com>, 2016. #zanata
# Andreas Jaeger <jaegerandi@gmail.com>, 2017. #zanata
# işbaran akçayır <isbaran@gmail.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev53\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-06-30 15:16+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-06-30 03:16+0000\n"
"Last-Translator: işbaran akçayır <isbaran@gmail.com>\n"
"Language-Team: Turkish (Turkey)\n"
"Language: tr-TR\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
"X-Generator: Zanata 3.9.6\n"
"X-POOTLE-MTIME: 1495454551.000000\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr " ([^\" ]+|\"[^\"]+\") \\(Öntanımlı\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgid "Abort cleaning"
msgstr "Temizliği iptal et"
msgid "Actions"
msgstr "İşlemler"
msgid "Add Extra"
msgstr "Ek Ekle"
msgid "Add Instance Property"
msgstr "Sunucu Özelliği Ekle"
msgid "Add Property"
msgstr "Özellik Ekle"
#, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] ""
"\"%s\" düğümünü silmek istediğinize emin misiniz? Bu eylem geri alınamaz."
msgstr[1] ""
"\"%s\" düğümlerini silmek istediğinize emin misiniz? Bu eylem geri alınamaz."
#, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] ""
"\"%s\" bağlantı noktasını silmek istediğinize emin misiniz? Bu eylem geri "
"alınamaz."
msgstr[1] ""
"\"%s\" bağlantı noktalarını silmek istediğinize emin misiniz? Bu eylem geri "
"alınamaz."
msgid "Boot Device"
msgstr "Önyükleme Aygıtı"
msgid "Cancel"
msgstr "İptal"
msgid "Chassis ID"
msgstr "Şasi ID'si"
msgid "Choose an Image"
msgstr "Bir İmaj seçin"
msgid "Clean"
msgstr "Temizle"
msgid "Clean Node"
msgstr "Düğümü Temizle"
msgid "Clean Step"
msgstr "Temizlik Adımı"
msgid "Clean node"
msgstr "Düğümü sil"
msgid "Clean steps should be an non-empty array"
msgstr "Temizlik adımları boş-olmayan bir dizi olmalı"
msgid "Click link to view console"
msgstr "Konsolu görüntülemek için bağlantıya tıklayın"
msgid "Configuration"
msgstr "Yapılandırma"
msgid "Console Enabled"
msgstr "Konsol Etkin"
msgid "Console Info."
msgstr "Konsol Bilgisi."
msgid "Create Port"
msgstr "Bağlantı Noktası Oluştur"
msgid "Created At"
msgstr "Oluşturulduğu zaman"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "Öntanımlı olarak ([^\"\\. ]+|\"[^\"]+\")"
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "Düğümü Sil"
msgstr[1] "Düğümleri Sil"
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "Bağlantı Noktasını Sil"
msgstr[1] "Bağlantı Noktalarını Sil"
msgid "Deploy Kernel"
msgstr "Çekirdeği Yerleştir"
msgid "Deploy Ramdisk"
msgstr "Ramdisk'i Yerleştir"
msgid "Device"
msgstr "Aygıt"
msgid "Driver"
msgstr "Sürücü"
msgid "Driver Details"
msgstr "Sürücü Ayrıntıları"
msgid "Driver Info"
msgstr "Sürücü Bilgisi"
msgid "Driver Validation"
msgstr "Sürücü Onaylama"
msgid ""
"Each cleaning step must be an object that contains \"interface\" and \"step"
"\" properties"
msgstr ""
"Her temizlik adımı \"arayüz\" ve \"adım\" özelliklerinden birini içeren "
"nesne olmalı"
msgid "Edit Node"
msgstr "Düğümü Düzenle"
msgid "Edit Port"
msgstr "Bağlantı Noktasını Düzenle"
msgid "Enroll Node"
msgstr "Düğümü Kaydet"
msgid "Extra"
msgstr "Ek"
msgid "Extra Property Name"
msgstr "Ek Özellik İsmi"
msgid "Extras"
msgstr "Ekler"
msgid "General"
msgstr "Genel"
msgid "Image Source"
msgstr "İmaj Kaynağı"
msgid "Inspect"
msgstr "İncele"
msgid "Inspection Finished At"
msgstr "İnceleme Şu Anda Bitti"
msgid "Inspection Started At"
msgstr "İnceleme Şu Anda Başlatıldı"
msgid "Instance ID"
msgstr "Instance ID"
msgid "Instance Info"
msgstr "Sunucu Bilgisi"
msgid "Instance Name"
msgstr "Instance Adı"
msgid "Instance Property Name"
msgstr "Sunucu Özellik İsmi"
msgid "Interface"
msgstr "Arayüz"
msgid "Kernel"
msgstr "Çekirdek"
msgid "Last Error"
msgstr "Son Hata"
msgid "Local link connection"
msgstr "Yerel bağlantı"
msgid "MAC Address"
msgstr "MAC Adresi"
msgid "MAC address"
msgstr "MAC adresi"
msgid "MAC address or OpenFlow datapath ID"
msgstr "MAC adresi veya OpenFlow veriyolu ID'si"
msgid "Maintenance"
msgstr "Bakım"
msgid "Maintenance Reason"
msgstr "Bakım Sebebi"
msgid "Move to"
msgstr "Taşı"
msgid "Name"
msgstr "İsim"
msgid "Network Interface"
msgstr "Ağ Arayüzü"
msgid "No Instance"
msgstr "Sunucu Yok"
msgid "No network ports have been defined"
msgstr "Hiçbir ağ bağlantı noktası tanımlanmamış"
msgid "No reason given."
msgstr "Sebep verilmemiş."
msgid "Node"
msgstr "Düğüm"
msgid "Node Driver"
msgstr "Düğüm Sürücüsü"
msgid "Node ID"
msgstr "Düğüm Kimliği"
msgid "Node Info"
msgstr "Düğüm Bilgisi"
msgid "Node Name"
msgstr "Düğüm İsmi"
msgid "One of this, (.*) must be specified\\."
msgstr "Bunlardan biri, (.*) belirtilmeli\\."
msgid "Overview"
msgstr "Önizleme"
msgid "PXE enabled"
msgstr "PXE etkin"
msgid "Persistent"
msgstr "Kalıcı"
msgid "Port successfully created"
msgstr "Bağlantı noktası başarıyla oluşturuldu"
msgid "Ports"
msgstr "Portlar"
msgid "Power State"
msgstr "Güç Durumu"
msgid "Power off"
msgstr "Güç kapa"
msgid "Power on"
msgstr "Güç aç"
msgid "Properties"
msgstr "Özellikler"
msgid "Property Name"
msgstr "Özellik İsmi"
msgid "Provide a list of cleaning steps in JSON format"
msgstr "JSON biçiminde temizlik adımları listesi sağlayın"
msgid ""
"Provide a reason for why you are putting the selected node(s) into "
"maintenance mode (optional)"
msgstr ""
"Seçili düğüm(ler)i neden bakım kipine soktuğunuzla ilgili bir sebep verin "
"(isteğe bağlı)"
msgid "Provision State"
msgstr "Hazırlık Durumu"
msgid "Provisioning State"
msgstr "Hazırlık Durumu"
msgid "Provisioning Status"
msgstr "Hazırlık Durumu"
msgid "Put Node(s) Into Maintenance Mode"
msgstr "Düğüm(ler)i Bakım Kipine Sok"
msgid "Ramdisk"
msgstr "Ramdisk"
msgid "Reason"
msgstr "Sebep"
msgid "Reboot"
msgstr "Yeniden başlat"
msgid "Refresh"
msgstr "Tazele"
msgid "Refresh page to see updated console details"
msgstr "Güncel konsol ayrıntılarını görmek için sayfayı tazeleyin"
msgid "Refresh page to see updated power status"
msgstr "Güncel güç durumunu görmek için sayfayı tazeleyin"
msgid "Required"
msgstr "Gerekli"
msgid "Reservation"
msgstr "Yer ayırmalar"
msgid "Root GB"
msgstr "Kök GB"
msgid "SSH Address"
msgstr "SSH Adresi"
msgid "SSH Key Contents"
msgstr "SSH Anahtar İçeriği"
msgid "SSH Key File"
msgstr "SSH Anahtar Dosyası"
msgid "SSH Password"
msgstr "SSH Parolası"
msgid "SSH Port"
msgstr "SSH Bağlantı Noktası"
msgid "SSH Username"
msgstr "SSH Kullanıcı adı"
msgid "SSH terminal port"
msgstr "SSH terminal bağlantı noktası"
msgid "Select a Driver"
msgstr "Bir Sürücü Seçin"
msgid "Soft power off"
msgstr "Yumuşak güç kapa"
msgid "Soft reboot"
msgstr "Yumuşak yeniden başlatma"
msgid "Submit"
msgstr "Gönder"
#, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "\"%s\" düğümü başarıyla silindi."
msgstr[1] "\"%s\" düğümleri başarıyla silindi."
#, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "\"%s\" bağlantı noktası başarıyla silindi"
msgstr[1] "\"%s\" bağlantı noktaları başarıyla silindi"
#, python-format
msgid "Successfully updated node %s"
msgstr "Düğüm %s başarıyla güncellendi"
#, python-format
msgid "Successfully updated port %s"
msgstr "Bağlantı noktası %s başarıyla güncellendi"
msgid "Target Power State"
msgstr "Hedef Güç Durumu"
msgid "Target Provision State"
msgstr "Hedef Hazırlık Durumu"
msgid ""
"This field is disabled because a port cannot have any connectivity "
"attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless "
"its associated node is in an enroll, inspecting, mangeable state; or in "
"maintenance mode."
msgstr ""
"Bu alan kapalı çünkü bağlantı noktası bağlanırlık öznitelikleri "
"(pxe_enabled, local_link_connection, portgroup_id), ilişkili düğümü kayıt, "
"inceleme, yönetilir durum; veya bakım kipinde olmadan güncellenemez."
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "Düğüm güncelleme yaması oluşturulamadı."
#, python-format
msgid "Unable to create node: %s"
msgstr "Düğüm oluşturulamıyor: %s"
msgid "Unable to create port update patch."
msgstr "Bağlantı noktası güncelleme yaması oluşturulamadı."
#, python-format
msgid "Unable to create port: %s"
msgstr "Bağlantı noktası oluşturulamıyor: %s"
#, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "\"%s\" düğümü silinemedi"
msgstr[1] "\"%s\" düğümleri silinemedi"
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "Düğüm %s silinemiyor: %s"
#, fuzzy, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "\"%s\" bağlantı noktası silinemiyor"
msgstr[1] "\"%s\" bağlantı noktaları silinemiyor"
#, python-format
msgid "Unable to delete port: %s"
msgstr "Bağlantı noktası silinemiyor: %s"
#, python-format
msgid "Unable to get console for node %s: %s"
msgstr "Düğüm %s için konsol alınamadı: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "Düğümün gücü kesilemiyor: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "Ironic sürücüleri alınamıyor: %s"
#, python-format
msgid "Unable to retrieve Ironic nodes. %s"
msgstr "Ironic düğümler alınamadı. %s"
#, python-format
msgid "Unable to retrieve boot device for Ironic node. %s"
msgstr "Ironic düğüm için ön yükleme aygıtı alınamadı. %s"
#, python-format
msgid "Unable to retrieve driver properties: %s"
msgstr "Sürücü özellikleri alınamıyor: %s"
#, python-format
msgid "Unable to retrieve the Ironic node ports: %s"
msgstr "Ironic düğümü bağlantı noktaları alınamadı: %s"
#, python-format
msgid "Unable to retrieve the Ironic node: %s"
msgstr "Ironic düğümü alınamadı: %s"
#, python-format
msgid "Unable to set console mode: %s"
msgstr "Konsol kipi ayarlanamıyor: %s"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "Düğüm hazırlık durumu ayarlanamıyor: %s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "Düğüm %s güncellenemedi: %s"
#, python-format
msgid "Unable to update port %s: %s"
msgstr "Bağlantı noktası %s güncellenemiyor: %s"
#, python-format
msgid "Unable to validate node %s: %s"
msgstr "Düğüm %s onaylanamıyor: %s"
msgid "Unable to validate the JSON input"
msgstr "JSON girdisi onaylanamadı"
msgid "Update Node"
msgstr "Düğümü Güncelle"
msgid "Update Port"
msgstr "Bağlantı Noktasını Güncelle"
msgid "Updated At"
msgstr "Güncelleme saati"
msgid "Valid"
msgstr "Geçerli"
msgid "Virtualization Software"
msgstr "Sanallaştırma Yazılımı"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "öntanımlı (?:değer )? ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,18 +0,0 @@
# 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 配置向导"

View File

@ -1,607 +0,0 @@
# 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
# Bin <liubin@glab.cn>, 2017. #zanata
# Gaoxiao Zhu <zhu.gaoxiao@h3c.com>, 2017. #zanata
# liujunpeng <liujunpeng@inspur.com>, 2017. #zanata
msgid ""
msgstr ""
"Project-Id-Version: ironic-ui 2.3.1.dev73\n"
"Report-Msgid-Bugs-To: https://bugs.launchpad.net/openstack-i18n/\n"
"POT-Creation-Date: 2017-07-21 15:07+0000\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"PO-Revision-Date: 2017-07-22 03:50+0000\n"
"Last-Translator: Bin <liubin@glab.cn>\n"
"Language-Team: Chinese (China)\n"
"Language: zh-CN\n"
"X-Generator: Zanata 3.9.6\n"
"Plural-Forms: nplurals=1; plural=0\n"
msgid " ([^\" ]+|\"[^\"]+\") \\(Default\\)"
msgstr "([^\" ]+|\"[^\"]+\") \\(Default标签\\)"
msgid "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
msgstr "(?:[Oo]ne of )(?!this)((?:(?:\"[^\"]+\"|[^,\\. ]+)(?:, |\\.))+)"
#, python-format
msgid "A request has been made to change the provisioning state of node %s"
msgstr "更改节点 \"%s\" 服务状态的请求已提交。"
msgid "Abort cleaning"
msgstr "放弃清理"
msgid "Actions"
msgstr "动作"
msgid "Add Extra"
msgstr "增加额外信息"
msgid "Add Instance Property"
msgstr "添加实例属性"
msgid "Add Property"
msgstr "添加属性"
#, python-format
msgid ""
"Are you sure you want to delete node \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete nodes \"%s\"? This action cannot be undone."
msgstr[0] "你确认要删除节点\"%s\"嘛?此操作将不可恢复"
#, python-format
msgid ""
"Are you sure you want to delete port \"%s\"? This action cannot be undone."
msgid_plural ""
"Are you sure you want to delete ports \"%s\"? This action cannot be undone."
msgstr[0] "你确认要删除端口\"%s\"吗?本操作无法恢复。"
#, python-format
msgid ""
"Are you sure you want to delete portgroup \"%s\"? This action cannot be "
"undone."
msgid_plural ""
"Are you sure you want to delete portgroups \"%s\"? This action cannot be "
"undone."
msgstr[0] "你确定要删除端口组\"%s\"?本操作无法恢复。"
msgid "Attributes"
msgstr "属性"
msgid "Boot Device"
msgstr "引导设备"
msgid "Cancel"
msgstr "取消"
msgid "Chassis ID"
msgstr "机架ID"
msgid "Choose an Image"
msgstr "选择一个镜像"
msgid "Clean"
msgstr "清理"
msgid "Clean Node"
msgstr "清理节点"
msgid "Clean Step"
msgstr "清理步骤"
msgid "Clean node"
msgstr "清理节点"
msgid "Clean steps should be an non-empty array"
msgstr "清理步骤应该是一个非空数组"
msgid "Click link to view console"
msgstr "点击链接查看控制台"
msgid "Configuration"
msgstr "配置"
msgid "Console Enabled"
msgstr "允许控制台"
msgid "Console Info."
msgstr "控制台信息。"
msgid "Create Port"
msgstr "创建端口"
msgid "Created At"
msgstr "创建于"
msgid "Defaults to ([^\"\\. ]+|\"[^\"]+\")"
msgstr "默认为 ([^\"\\. ]+|\"[^\"]+\")"
msgid "Delete Node"
msgid_plural "Delete Nodes"
msgstr[0] "删除节点"
msgid "Delete Port"
msgid_plural "Delete Ports"
msgstr[0] "删除端口"
msgid "Delete Portgroup"
msgid_plural "Delete Portgroups"
msgstr[0] "删除端口组"
msgid "Deploy Kernel"
msgstr "部署内核"
msgid "Deploy Ramdisk"
msgstr "部署虚拟内存盘"
msgid "Device"
msgstr "设备"
msgid "Driver"
msgstr "驱动"
msgid "Driver Details"
msgstr "驱动详情"
msgid "Driver Info"
msgstr "驱动信息"
msgid "Driver Validation"
msgstr "驱动验证"
msgid ""
"Each cleaning step must be an object that contains \"interface\" and \"step"
"\" properties"
msgstr "每一步清理步骤必需是一个包含“接口”和“步骤”属性的对象"
msgid "Edit Node"
msgstr "编辑节点"
msgid "Edit Port"
msgstr "编辑端口"
msgid "Enroll Node"
msgstr "注册节点"
msgid "Extra"
msgstr "额外信息"
msgid "Extra Property Name"
msgstr "额外属性名称"
msgid "Extras"
msgstr "额外信息"
msgid "General"
msgstr "概要信息"
msgid "Image Source"
msgstr "镜像源"
msgid "Indicates whether this port should be used when PXE booting this node"
msgstr "当PXE应到该节点时表明此端口是否应该被使用"
msgid "Inspect"
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 "Instance Property Name"
msgstr "实例属性名称"
msgid "Interface"
msgstr "接口"
msgid "Kernel"
msgstr "内核"
msgid "Last Error"
msgstr "最近的一次错误"
msgid "Local Link Connection"
msgstr "本地链接连接"
msgid "Local link connection"
msgstr "本地链接连接"
msgid "MAC Address"
msgstr "MAC地址"
msgid "MAC address"
msgstr "MAC地址"
msgid "MAC address for this port. Required."
msgstr "需要该端口的MAC地址"
msgid "MAC address or OpenFlow datapath ID"
msgstr "MAC地址或OpenFlow datapath ID"
msgid "Maintenance"
msgstr "维护"
msgid "Maintenance Reason"
msgstr "维护原因"
msgid "Move to"
msgstr "移动"
msgid "Name"
msgstr "名称"
msgid "Network Interface"
msgstr "网络接口"
msgid "No Instance"
msgstr "没有实例"
msgid "No network ports have been defined"
msgstr "未定义网络端口"
msgid "No portgroups have been defined"
msgstr "未定义端口组"
msgid "No reason given."
msgstr "没有原因提供。"
msgid "Node"
msgstr "节点"
#, python-format
msgid "Node %s is already in target maintenance state."
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 "PXE Enabled"
msgstr "PXE启用"
msgid "PXE enabled"
msgstr "PXE启用"
msgid "Persistent"
msgstr "持久"
msgid "Port successfully created"
msgstr "端口创建成功"
msgid "Portgroup"
msgstr "端口组"
msgid "Portgroup successfully created"
msgstr "端口组创建成功"
msgid "Portgroup that this port belongs to"
msgstr "此端口所属的端口组"
msgid "Portgroups"
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 list of cleaning steps in JSON format"
msgstr "提供一个JSON格式的清理步骤列表"
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 "Reason"
msgstr "原因"
msgid "Reboot"
msgstr "重启"
msgid "Refresh"
msgstr "刷新"
msgid "Refresh page to see set boot device"
msgstr "刷新页面来设置引导设备"
msgid "Refresh page to see updated console details"
msgstr "刷新页面查看更新的控制台细节"
msgid "Refresh page to see updated power status"
msgstr "刷新页面来确认电源状态是否更新"
msgid "Required"
msgstr "必需的"
msgid "Reservation"
msgstr "预留"
msgid "Resource Class"
msgstr "资源类"
msgid "Root GB"
msgstr "根磁盘 GB"
msgid "SSH Address"
msgstr "SSH 地址"
msgid "SSH Key Contents"
msgstr "SSH 密钥内容"
msgid "SSH Key File"
msgstr "SSH 密钥文件"
msgid "SSH Password"
msgstr "SSH 密码"
msgid "SSH Port"
msgstr "SSH端口"
msgid "SSH Username"
msgstr "SSH用户名"
msgid "SSH terminal port"
msgstr "SSH 终端端口"
msgid "Select a Driver"
msgstr "选择一种驱动"
msgid "Select a boot device"
msgstr "选择一个引导设备"
msgid "Select a portgroup"
msgstr "选择一个端口组"
msgid "Set Boot Device"
msgstr "设置引导设备"
msgid "Set boot device"
msgstr "设置引导设备"
msgid "Soft power off"
msgstr "软关机"
msgid "Soft reboot"
msgstr "软重启"
msgid "Submit"
msgstr "提交"
#, python-format
msgid "Successfully deleted node \"%s\""
msgid_plural "Successfully deleted nodes \"%s\""
msgstr[0] "成功删除节点\"%s\""
#, python-format
msgid "Successfully deleted port \"%s\""
msgid_plural "Successfully deleted ports \"%s\""
msgstr[0] "删除端口\"%s\"成功"
#, python-format
msgid "Successfully deleted portgroup \"%s\""
msgid_plural "Successfully deleted portgroups \"%s\""
msgstr[0] "成功删除端口组\"%s\""
#, python-format
msgid "Successfully updated node %s"
msgstr "成功更新节点:“%s”。"
#, python-format
msgid "Successfully updated port %s"
msgstr "成功更新端口:“%s”。"
msgid "Target Power State"
msgstr "标记电源状态"
msgid "Target Provision State"
msgstr "标记配置状态"
msgid ""
"This field is disabled because a port cannot have any connectivity "
"attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless "
"its associated node is in an enroll, inspecting, mangeable state; or in "
"maintenance mode."
msgstr ""
"这些域被禁用了,因为端口不能有任何已更新的连接属性((pxe_enabled, "
"local_link_connection, portgroup_id),除非节点处于注册,检查,可管理状态或处"
"于维护模式。"
msgid "UUID"
msgstr "UUID"
msgid "Unable to create node update patch."
msgstr "不能创建节点更新补丁"
#, python-format
msgid "Unable to create node: %s"
msgstr "无法创建Ironic节点: %s"
msgid "Unable to create port update patch."
msgstr "不能创建端口更新补丁"
#, python-format
msgid "Unable to create port: %s"
msgstr "无法创建端口: %s"
#, python-format
msgid "Unable to create portgroup: %s"
msgstr "无法创建端口组: %s"
#, python-format
msgid "Unable to delete node \"%s\""
msgid_plural "Unable to delete nodes \"%s\""
msgstr[0] "无法删除节点\"%s\""
#, python-format
msgid "Unable to delete node %s: %s"
msgstr "无法删除Ironic节点\"%s\": %s"
#, python-format
msgid "Unable to delete port \"%s\""
msgid_plural "Unable to delete ports \"%s\""
msgstr[0] "无法删除端口\"%s\""
#, python-format
msgid "Unable to delete port: %s"
msgstr "无法删除端口: %s"
#, python-format
msgid "Unable to delete portgroup \"%s\""
msgid_plural "Unable to delete portgroups \"%s\""
msgstr[0] "无法删除端口组\"%s\""
#, python-format
msgid "Unable to delete portgroup: %s"
msgstr "无法删除端口组:%s"
#, python-format
msgid "Unable to get console for node %s: %s"
msgstr "无法获取节点的控制台 %s: %s"
#, python-format
msgid "Unable to power off the node: %s"
msgstr "无法关闭节点电源: %s"
#, python-format
msgid "Unable to retrieve Ironic drivers: %s"
msgstr "无法获取Ironic驱动: %s"
#, python-format
msgid "Unable to retrieve Ironic node portgroups: %s"
msgstr "无法获取Ironic节点端口组信息: %s"
#, python-format
msgid "Unable to retrieve Ironic nodes. %s"
msgstr "无法获取Ironic节点信息: %s"
#, python-format
msgid "Unable to retrieve boot device for Ironic node. %s"
msgstr "无法获取Ironic节点引导设备信息。%s"
#, python-format
msgid "Unable to retrieve driver properties: %s"
msgstr "无法获取驱动属性: %s"
#, python-format
msgid "Unable to retrieve portgroup ports: %s"
msgstr "无法获取端口组端口:%s"
#, python-format
msgid "Unable to retrieve supported boot devices for Ironic node. %s"
msgstr "无法获取Ironic节点支持的引导设备信息。%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"
#, python-format
msgid "Unable to set Ironic node %s maintenance state: %s"
msgstr "不能设置Ironic节点%s维护状态%s"
#, python-format
msgid "Unable to set boot device: %s"
msgstr "不能设置引导设备:%s"
#, python-format
msgid "Unable to set console mode: %s"
msgstr "不能设置控制台模式:%s"
#, python-format
msgid "Unable to set node provision state: %s"
msgstr "不能设置节点提供状态:%s"
#, python-format
msgid "Unable to update node %s: %s"
msgstr "无法更新节点%s%s"
#, python-format
msgid "Unable to update port %s: %s"
msgstr "无法更新端口%s%s"
#, python-format
msgid "Unable to validate node %s: %s"
msgstr "无法验证节点%s%s"
msgid "Unable to validate the JSON input"
msgstr "无法验证JSON输入"
msgid "Update Node"
msgstr "更新节点"
msgid "Update Port"
msgstr "更新端口"
msgid "Updated At"
msgstr "已更新于"
msgid "Valid"
msgstr "有效"
msgid "Virtualization Software"
msgstr "虚拟化软件"
msgid "default (?:value )?is ([^\"\\. ]+|\"[^\"]+\")"
msgstr "默认值 (?:value )?是 ([^\"\\. ]+|\"[^\"]+\")"

View File

@ -1,35 +0,0 @@
/*
* 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);
}
};
}
})();

View File

@ -1,275 +0,0 @@
/*
* 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 support operations on an Ironic node
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('BaseNodeController', BaseNodeController);
BaseNodeController.$inject = [
'$uibModalInstance',
'horizon.app.core.openstack-service-api.ironic',
'horizon.app.core.openstack-service-api.glance',
'horizon.dashboard.admin.ironic.base-node.service',
'horizon.dashboard.admin.ironic.validHostNamePattern',
'$log',
'ctrl'
];
function BaseNodeController($uibModalInstance,
ironic,
glance,
baseNodeService,
validHostNamePattern,
$log,
ctrl) {
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;
ctrl.modalTitle = gettext("Node");
ctrl.submitButtonTitle = gettext("Submit");
/* A property-collection is a set of properties that will be displayed
in the node view as a minimal browser ui that supports:
- adding new properties
- displaying the list of properties in the set
- changing the value of properties
Collection attributes:
id: Name of the property inside the node object that is used
to store the collection.
formId: Name of the controller variable that can be used to
access the property collection form.
prompt: Label used to prompt the user to add properties
to the collection.
placeholder: Label used to guide the user in providiing property
values.
*/
ctrl.propertyCollections = [
{id: "properties",
formId: "properties_form",
title: gettext("Properties"),
addPrompt: gettext("Add Property"),
placeholder: gettext("Property Name")
},
{id: "extra",
formId: "extra_form",
title: gettext("Extras"),
addPrompt: gettext("Add Extra"),
placeholder: gettext("Extra Property Name")
}];
// Node object suitable for Ironic api
ctrl.node = {
name: null,
driver: null,
driver_info: {},
network_interface: null,
resource_class: null
};
angular.forEach(ctrl.propertyCollections, function(collection) {
ctrl.node[collection.id] = {};
});
/**
* @description Get the list of currently active Ironic drivers
*
* @return {void}
*/
ctrl._loadDrivers = function() {
return ironic.getDrivers().then(function(drivers) {
ctrl.drivers = drivers;
});
};
/**
* @description Get the list of images from Glance
*
* @return {void}
*/
ctrl._getImages = function() {
glance.getImages().then(function(response) {
ctrl.images = response.data.items;
});
};
/**
* @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 {[]} Ordered list of groups of strongly related properties
*/
ctrl._sortDriverProperties = function() {
// Build dependency graph between driver properties
var graph = new baseNodeService.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(baseNodeService.compareDriverPropertyGroups);
$log.debug("Found the following property groups: " +
baseNodeService.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;
return ironic.getDriverProperties(driverName).then(function(properties) {
ctrl.driverProperties = {};
angular.forEach(properties, function(desc, property) {
ctrl.driverProperties[property] =
new baseNodeService.DriverProperty(property,
desc,
ctrl.driverProperties);
});
ctrl.driverPropertyGroups = ctrl._sortDriverProperties();
ctrl.loadingDriverProperties = false;
});
};
/**
* @description Cancel the current node operation
*
* @return {void}
*/
ctrl.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
/**
* @description Check whether the specified property already exists
*
* @param {string} collectionId - Collection ID
* @param {string} propertyName - Name of the property
* @return {boolean} True if the property already exists,
* otherwise false
*/
ctrl.collectionCheckPropertyUnique = function(collectionId, propertyName) {
return !(propertyName in ctrl.node[collectionId]);
};
/**
* @description Delete a node metadata property
*
* @param {string} collectionId - Collection ID
* @param {string} propertyName - Name of the property
* @return {void}
*/
ctrl.collectionDeleteProperty = function(collectionId, propertyName) {
delete ctrl.node[collectionId][propertyName];
};
/**
* @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();
};
/**
* @description Check whether the node definition form is ready for
* to be submitted.
*
* @return {boolean} True if the form is ready to be submitted,
* otherwise false.
*/
ctrl.readyToSubmit = function() {
var ready = true;
if (ctrl.driverProperties) {
for (var i = 0; i < ctrl.propertyCollections.length; i++) {
var collection = ctrl.propertyCollections[i];
if (ctrl[collection.formId].$invalid) {
ready = false;
break;
}
}
} else {
ready = false;
}
return ready;
};
}
})();

View File

@ -1,107 +0,0 @@
/*
* Copyright 2017 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.base-node', function () {
var ironicBackendMockService, uibModalInstance;
var ctrl = {};
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.framework.util'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(module(function($provide) {
uibModalInstance = {
dismiss: jasmine.createSpy()
};
$provide.value('$uibModalInstance', uibModalInstance);
}));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service',
{});
}));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(function($injector) {
ironicBackendMockService =
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
ironicBackendMockService.init();
var controller = $injector.get('$controller');
controller('BaseNodeController', {ctrl: ctrl});
}));
afterEach(function() {
ironicBackendMockService.postTest();
});
it('controller should be defined', function () {
expect(ctrl).toBeDefined();
});
it('base construction', function () {
expect(ctrl.drivers).toBeNull();
expect(ctrl.images).toBeNull();
expect(ctrl.loadingDriverProperties).toBe(false);
expect(ctrl.driverProperties).toBeNull();
expect(ctrl.driverPropertyGroups).toBeNull();
expect(ctrl.modalTitle).toBeDefined();
angular.forEach(ctrl.propertyCollections, function(collection) {
expect(Object.getOwnPropertyNames(collection).sort()).toEqual(
PROPERTY_COLLECTION_PROPERTIES.sort());
});
expect(ctrl.propertyCollections)
.toContain(jasmine.objectContaining({id: "properties"}));
expect(ctrl.propertyCollections)
.toContain(jasmine.objectContaining({id: "extra"}));
expect(ctrl.node).toEqual({
name: null,
driver: null,
driver_info: {},
properties: {},
extra: {},
network_interface: null,
resource_class: null});
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
BASE_NODE_CONTROLLER_PROPERTIES.sort());
});
it('_loadDrivers', function () {
ctrl._loadDrivers();
ironicBackendMockService.flush();
expect(ctrl.drivers).toEqual(ironicBackendMockService.getDrivers());
});
it('_getImages', function () {
ctrl._getImages();
ironicBackendMockService.flush();
expect(ctrl.images).toEqual(ironicBackendMockService.getImages());
});
it('cancel', function () {
ctrl.cancel();
expect(uibModalInstance.dismiss).toHaveBeenCalledWith('cancel');
});
});
})();

View File

@ -1,276 +0,0 @@
<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">{$ ::ctrl.modalTitle $}</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>
<!--base node form-->
<form id="baseNodeForm"
name="baseNodeForm">
<!--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': baseNodeForm.name.$invalid &&
baseNodeForm.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>
<!--resource class-->
<div class="form-group"
ng-class="{'has-error': baseNodeForm.resource_class.$invalid &&
baseNodeForm.resource_class.$dirty}">
<label for="resource_class"
class="control-label"
translate>Resource Class</label>
<div>
<input type="text maxlength=80"
class="form-control"
ng-model="ctrl.node.resource_class"
id="resource_class"
name="resource_class"
placeholder="{$ ::'A resource class name for future nova scheduling, in stand-alone usage only. Optional.' | translate $}"/>
</div>
</div>
<!--network interface-->
<div class="form-group">
<label for="network_interface"
class="control-label"
translate>
Network Interface
</label>
<span class="help-icon"
data-container="body"
title=""
data-toggle="tooltip"
data-original-title="{$ ::'Network interface used for switching between provisioning, tenant, and cleaning networks.' | translate $}">
<span class="fa fa-question-circle"></span>
</span>
<div>
<div class="btn-group">
<label class="btn btn-default"
ng-repeat="opt in ['noop', 'flat', 'neutron']"
ng-model="ctrl.node.network_interface"
uib-btn-radio="opt">{$ opt $}</label>
</div>
</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>
<!--property collections-->
<div ng-repeat="collection in ctrl.propertyCollections">
<form
id="add-{$ collection.id $}-form"
name="add-{$ collection.id $}-form">
<div class="form-group">
<label for="add-{$ collection.id $}-input"
class="control-label">
{$ collection.title $}</label>
<div class="input-group input-group-sm">
<span class="input-group-addon"
style="width:25%;text-align:right">
{$ collection.addPrompt $}:</span>
<input class="form-control"
id="add-{$ collection.id $}-input"
type="text"
ng-model="ctrl[collection.id]"
placeholder="{$ collection.placeholder $}"/>
<span class="input-group-btn">
<button class="btn btn-primary"
type="button"
ng-disabled="!ctrl[collection.id] ||
!ctrl.collectionCheckPropertyUnique(collection.id,
ctrl[collection.id]) ||
add-{$ collection.id $}-form.$invalid"
ng-click="ctrl.node[collection.id][ctrl[collection.id]] = null;
ctrl[collection.id] = null">
<span class="fa fa-plus"> </span>
</button>
</span>
</div>
</div>
</form>
<form id="ctrl.{$ collection.formId $}"
name="ctrl.{$ collection.formId $}">
<div class="form-group">
<div class="input-group input-group-sm"
ng-repeat="(propertyName, propertyValue) in ctrl.node[collection.id]">
<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[collection.id][propertyName]"
ng-required="true"/>
<div class="input-group-btn">
<a class="btn btn-default"
ng-click="ctrl.collectionDeleteProperty(collection.id, propertyName)">
<span class="fa fa-minus"> </span>
</a>
</div>
</div>
</div>
</form>
</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': baseNodeForm.{$ name $}.$invalid &&
baseNodeForm.{$ 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() $}">
<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>
{$ 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"
uib-btn-radio="opt">{$ opt $}</label>
</div>
</div>
</div>
</div>
</div>
<!--end driver details tab-->
</div>
<!--end tabbed content-->
</form>
<!--end base 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.readyToSubmit()"
ng-click="ctrl.submit()"
class="btn btn-primary">
{$ ::ctrl.submitButtonTitle $}
</button>
</div>

View File

@ -1,741 +0,0 @@
/*
* 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.base-node.service',
baseNodeService);
baseNodeService.$inject = [
'$uibModal',
'$log',
'horizon.dashboard.admin.ironic.validHostNamePattern',
'horizon.dashboard.admin.ironic.validUuidPattern'
];
function baseNodeService($uibModal,
$log,
validHostNamePattern,
validUuidPattern) {
var service = {
DriverProperty: DriverProperty,
PostfixExpr: PostfixExpr,
Graph: Graph,
driverPropertyGroupHasRequired: driverPropertyGroupHasRequired,
driverPropertyGroupsToString: driverPropertyGroupsToString,
compareDriverPropertyGroups: compareDriverPropertyGroups
};
var VALID_ADDRESS_HOSTNAME_REGEX = new RegExp(VALID_IPV4_ADDRESS + "|" +
VALID_IPV6_ADDRESS + "|" +
validHostNamePattern);
var VALID_IMAGE_REGEX = new RegExp(validUuidPattern + "|" +
"^(https?|file)://.+$");
/**
The DriverProperty class is used to represent an ironic driver
property. It is currently used by the base-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
positive 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);
}
});
};
/**
* @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 of 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;
}
return service;
}
})();

View File

@ -1,270 +0,0 @@
/**
* 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.base-node.service',
function() {
var service;
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', jasmine.createSpy());
}));
beforeEach(inject(function($injector) {
service =
$injector.get('horizon.dashboard.admin.ironic.base-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(undefined);
expect(property.inputValue).toBe(undefined);
expect(property.getInputValue()).toBe(undefined);
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');
});
});
describe('PostfixExpr', function() {
it('Base construction', function() {
var expr = new service.PostfixExpr();
var ret = expr.evaluate({});
expect(ret[0]).toBe(service.PostfixExpr.status.MALFORMED);
expect(ret[1]).toBe(service.PostfixExpr.UNDEFINED);
});
function evalBinary(val1, val2, op) {
var propertySet = {};
var prop1 = new service.DriverProperty("prop1", "", propertySet);
propertySet.prop1 = prop1;
var prop2 = new service.DriverProperty("prop2", "", propertySet);
propertySet.prop2 = prop2;
var expr = new service.PostfixExpr();
expr.addProperty("prop1");
expr.addProperty("prop2");
prop1.inputValue = val1;
prop2.inputValue = val2;
expr.addOperator(op);
return expr.evaluate(propertySet);
}
it('T and T', function() {
var ret = evalBinary(true, true, service.PostfixExpr.op.AND);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('T and F', function() {
var ret = evalBinary(true, false, service.PostfixExpr.op.AND);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('F and T', function() {
var ret = evalBinary(false, true, service.PostfixExpr.op.AND);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('F and F', function() {
var ret = evalBinary(false, false, service.PostfixExpr.op.AND);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('T or T', function() {
var ret = evalBinary(true, true, service.PostfixExpr.op.OR);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('T or F', function() {
var ret = evalBinary(true, false, service.PostfixExpr.op.OR);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('F or T', function() {
var ret = evalBinary(false, true, service.PostfixExpr.op.OR);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('F or F', function() {
var ret = evalBinary(false, false, service.PostfixExpr.op.OR);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('T eq T', function() {
var ret = evalBinary(true, true, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('T eq F', function() {
var ret = evalBinary(true, false, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('F eq T', function() {
var ret = evalBinary(false, true, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('F eq F', function() {
var ret = evalBinary(false, false, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('1 eq 1', function() {
var ret = evalBinary(1, 1, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(true);
});
it('1 eq 0', function() {
var ret = evalBinary(1, 0, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
it('"1" eq 1', function() {
var ret = evalBinary('1', 1, service.PostfixExpr.op.EQ);
expect(ret[0]).toBe(service.PostfixExpr.status.OK);
expect(ret[1]).toBe(false);
});
});
describe('DriverPropertyGroup', function() {
it('driverPropertyGroupHasRequired', function () {
var dp1 = new service.DriverProperty("dp-1", " Required.", []);
var dp2 = new service.DriverProperty("dp-2", " ", []);
expect(service.driverPropertyGroupHasRequired).toBeDefined();
expect(service.driverPropertyGroupHasRequired([])).toBe(false);
expect(service.driverPropertyGroupHasRequired([dp1])).toBe(true);
expect(service.driverPropertyGroupHasRequired([dp2])).toBe(false);
expect(service.driverPropertyGroupHasRequired([dp1, dp2])).toBe(true);
});
it('driverPropertyGroupsToString', function () {
var dp1 = new service.DriverProperty("dp-1", " Required.", []);
var dp2 = new service.DriverProperty("dp-2", " ", []);
expect(service.driverPropertyGroupsToString).toBeDefined();
expect(service.driverPropertyGroupsToString([])).toBe("[]");
expect(service.driverPropertyGroupsToString([[dp1]]))
.toBe("[[dp-1]]");
expect(service.driverPropertyGroupsToString([[dp1], [dp2]]))
.toBe("[[dp-1], [dp-2]]");
});
it('compareDriverPropertyGroups', function () {
var dp1 = new service.DriverProperty("dp-1", " Required.", []);
var dp2 = new service.DriverProperty("dp-2", " ", []);
expect(service.compareDriverPropertyGroups).toBeDefined();
expect(service.compareDriverPropertyGroups([dp1], [dp1])).toBe(0);
expect(service.compareDriverPropertyGroups([dp1], [dp2])).toBe(-1);
expect(service.compareDriverPropertyGroups([dp2], [dp1])).toBe(1);
// smaller group precedes larger group
expect(service.compareDriverPropertyGroups([dp1], [dp1, dp2]))
.toBe(-1);
// group order decided on lexographic comparison of names of first
// property
expect(service.compareDriverPropertyGroups([dp2, dp1], [dp1, dp2]))
.toBe(1);
});
});
});
})();

View File

@ -1,228 +0,0 @@
/*
* 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 support operations on an Ironic port
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('BasePortController', BasePortController);
BasePortController.$inject = [
'$uibModalInstance',
'horizon.dashboard.admin.ironic.validMacAddressPattern',
'horizon.dashboard.admin.ironic.validDatapathIdPattern',
'horizon.dashboard.admin.ironic.form-field.service',
'horizon.app.core.openstack-service-api.ironic',
'ctrl',
'node'
];
/**
* @description Utility class used to manage local-link-connection
* form fields.
*
* @param {string} formFieldService - Provider service for creating
* form fields.
* @param {string} validMacAddressPattern - Regular expression
* pattern used to test for valid mac addresses.
* @param {string} validDatapathIdPattern - Regular expression
* pattern used to test for valid datapath ids.
* @return {void}
*/
function LocalLinkConnectionMgr(formFieldService,
validMacAddressPattern,
validDatapathIdPattern) {
var mgr = this;
mgr.port_id = new formFieldService.FormField(
{id: 'port_id', title: 'port_id'});
mgr.switch_id = new formFieldService.FormField(
{id: 'switch_id',
title: 'switch_id',
desc: gettext("MAC address or OpenFlow datapath ID"),
pattern: new RegExp(validMacAddressPattern + '|' +
validDatapathIdPattern)});
mgr.switch_info = new formFieldService.FormField(
{id: 'switch_info', title: 'switch_info'});
mgr.fields = {
port_id: mgr.port_id,
switch_id: mgr.switch_id,
switch_info: mgr.switch_info
};
/**
* Update the required property of each field based on current values
*
* @return {void}
*/
mgr.update = function() {
var required = mgr.port_id.hasValue() || mgr.switch_id.hasValue();
mgr.port_id.required = required;
mgr.switch_id.required = required;
};
// Add form field value change handlers
angular.forEach(mgr.fields, function(field) {
field.change = mgr.update;
});
/**
* Generate an attribute object that conforms to the format
* required for port creation using the Ironic client
*
* @return {object|null} local_link_connection attribute object.
* A value of null is returned if the local-link-connection
* information is incomplete.
*/
mgr.toPortAttr = function() {
var attr = null;
if (mgr.port_id.hasValue() &&
mgr.switch_id.hasValue()) {
attr = {};
attr.port_id = mgr.port_id.value;
attr.switch_id = mgr.switch_id.value;
if (mgr.switch_info.hasValue()) {
attr.switch_info = mgr.switch_info.value;
}
}
return attr;
};
/**
* @description Set values of form fields;
*
* @param {object} values - Dictionary of values indexed by
* property-name
* @return {void}
*/
mgr.setValues = function(values) {
angular.forEach(mgr.fields, function(field, propertyName) {
if (angular.isDefined(values[propertyName])) {
field.value = values[propertyName];
}
});
};
/**
* @description Disable the local-link-connection form fields.
*
* @param {string} reason - Optional reason for disabling fields.
* @return {void}
*/
mgr.disable = function(reason) {
angular.forEach(mgr.fields, function(item) {
item.disable(reason);
});
};
}
function BasePortController($uibModalInstance,
validMacAddressPattern,
validDatapathIdPattern,
formFieldService,
ironic,
ctrl,
node) {
ctrl.port = {
extra: {},
node_uuid: node.uuid
};
ctrl.address = new formFieldService.FormField({
id: "macAddress",
title: gettext("MAC address"),
desc: gettext("MAC address for this port. Required."),
pattern: new RegExp(validMacAddressPattern),
value: null,
required: true,
autoFocus: true
});
ctrl.pxeEnabled = new formFieldService.FormField({
type: "radio",
id: "pxeEnabled",
title: gettext("PXE enabled"),
desc: gettext(
"Indicates whether this port should be used when PXE booting this node"),
options: ['True', 'False'],
value: 'True'});
ctrl.portgroup_uuid = new formFieldService.FormField({
type: "select",
id: "portgroup-uuid",
title: gettext("Portgroup"),
desc: gettext("Portgroup that this port belongs to"),
portgroups: [],
options: "portgroup.uuid as portgroup.name ? portgroup.name : portgroup.uuid for portgroup in field.portgroups", // eslint-disable-line max-len
value: null});
// Object used to manage local-link-connection form fields
ctrl.localLinkConnection =
new LocalLinkConnectionMgr(formFieldService,
validMacAddressPattern,
validDatapathIdPattern);
ironic.getPortgroups(node.uuid).then(function(portgroups) {
var field = ctrl.portgroup_uuid;
if (portgroups.length > 0) {
field.portgroups.push({uuid: null, name: gettext("Select a portgroup")});
}
field.portgroups = field.portgroups.concat(portgroups);
if (portgroups.length === 0) {
field.disable();
}
});
/**
* Cancel the modal
*
* @return {void}
*/
ctrl.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
/**
* 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);
};
}
})();

View File

@ -1,87 +0,0 @@
<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">{$ ::ctrl.modalTitle $}</h3>
</div>
<div class="modal-body">
<form id="CreatePortForm" name="CreatePortForm">
<form-field field="ctrl.address" form="CreatePortForm"></form-field>
<form-field field="ctrl.pxeEnabled" form="CreatePortForm"></form-field>
<form-field field="ctrl.portgroup_uuid" form="CreatePortForm"></form-field>
</form>
<form id="LocalLinkConnectionForm"
name="LocalLinkConnectionForm"
class="well well-sm">
<h4 translate>Local link connection</h4>
<div class="form-group"
ng-repeat="(propertyName, propertyField) in
ctrl.localLinkConnection.fields">
<form-field field="propertyField" form="LocalLinkConnectionForm"></form-field>
</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 ||
LocalLinkConnectionForm.$invalid ||
ExtraForm.$invalid"
ng-click="ctrl.submit()"
class="btn btn-primary">
{$ ::ctrl.submitButtonTitle $}
</button>
</div>

View File

@ -1,65 +0,0 @@
/*
* Copyright 2017 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
/**
* @ngdoc controller
* @name horizon.dashboard.admin.ironic:BootDeviceController
* @ngController
*
* @description
* Controller used to prompt the user for information associated with
* setting the boot device of a node
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('BootDeviceController', BootDeviceController);
BootDeviceController.$inject = [
'$uibModalInstance',
'horizon.app.core.openstack-service-api.ironic',
'node'
];
function BootDeviceController($uibModalInstance, ironic, node) {
var ctrl = this;
ctrl.modalTitle = gettext("Set Boot Device");
ironic.getSupportedBootDevices(node.uuid).then(
function(bootDevices) {
ctrl.supportedBootDevices = bootDevices;
});
// Initialize form fields to current values
ctrl.bootDevice = null;
ctrl.persistent = 'False';
ironic.getBootDevice(node.uuid).then(function(device) {
ctrl.bootDevice = device.boot_device;
ctrl.persistent = device.persistent ? 'True' : 'False';
});
ctrl.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
ctrl.setSelectedBootDevice = function() {
$uibModalInstance.close({device: ctrl.bootDevice,
persistent: ctrl.persistent === 'True'});
};
}
})();

View File

@ -1,97 +0,0 @@
/*
* Copyright 2017 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.BootDeviceController', function () {
var BOOT_DEVICE_CONTROLLER_PROPERTIES = [
'bootDevice',
'cancel',
'modalTitle',
'persistent',
'setSelectedBootDevice',
'supportedBootDevices'
];
var uibModalInstance, ironicBackendMockService, node;
var ctrl = {};
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.framework.util'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(module(function($provide) {
uibModalInstance = {
close: jasmine.createSpy(),
dismiss: jasmine.createSpy()
};
$provide.value('$uibModalInstance', uibModalInstance);
}));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service',
{});
}));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(function($injector) {
ironicBackendMockService =
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
ironicBackendMockService.init();
var ironicAPI =
$injector.get('horizon.app.core.openstack-service-api.ironic');
ironicAPI.createNode(
{driver: ironicBackendMockService.params.defaultDriver})
.then(function(response) {
node = response.data;
var controller = $injector.get('$controller');
ctrl = controller('BootDeviceController', {node: node});
});
ironicBackendMockService.flush();
}));
it('controller should be defined', function () {
expect(ctrl).toBeDefined();
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
BOOT_DEVICE_CONTROLLER_PROPERTIES.sort());
expect(ctrl.supportedBootDevices).toEqual(
ironicBackendMockService.getNodeSupportedBootDevices(node.uuid));
var bootDevice = ironicBackendMockService.getNodeBootDevice(node.uuid);
expect(ctrl.bootDevice).toEqual(bootDevice.boot_device);
expect(ctrl.persistent).toEqual(bootDevice.persistent ? 'True' : 'False');
});
it('cancel', function () {
ctrl.cancel();
expect(uibModalInstance.dismiss).toHaveBeenCalled();
});
it('setSelectedBootDevice', function () {
ctrl.bootDevice = 'pxe';
ctrl.persistent = 'False';
ctrl.setSelectedBootDevice();
expect(uibModalInstance.close).toHaveBeenCalledWith(
{device: ctrl.bootDevice,
persistent: ctrl.persistent === 'True'});
});
});
})();

View File

@ -1,58 +0,0 @@
<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">{$ ::ctrl.modalTitle $}</h3>
</div>
<div class="modal-body">
<form id="SetBootDeviceForm" name="SetBootDeviceForm">
<!--boot device-->
<div class="form-group required">
<label for="bootDevice"
class="control-label"
translate>Boot Device</label>
<span class="hz-icon-required fa fa-asterisk"></span>
<div>
<select id="bootDevice"
class="form-control"
ng-options="device as device for device in ctrl.supportedBootDevices"
ng-model="ctrl.bootDevice">
<option value="" disabled selected translate>Select a boot device</option>
</select>
</div>
</div>
<div class="form-group">
<div>
<label for="persistent"
class="control-label"
translate>Persistent</label>
</div>
<div class="btn-group" id="persistent">
<label class="btn btn-default"
ng-model="ctrl.persistent"
ng-repeat="opt in ['True', 'False']"
uib-btn-radio="opt">{$ opt $}</label>
</div>
</div>
</form>
</div>
<!--modal footer-->
<div class="modal-footer">
<button class="btn btn-default secondary"
type="button"
ng-click="ctrl.cancel()"
translate>
Cancel
</button>
<button class="btn btn-primary"
type="button"
ng-click="ctrl.setSelectedBootDevice()"
ng-disabled="ctrl.bootDevice === null"
translate>
Set Boot Device
</button>
</div>

View File

@ -1,68 +0,0 @@
/*
* Copyright 2017 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
/*
* @ngdoc service
* @name horizon.dashboard.admin.ironic.bootdevice.service
* @description Service for setting the boot device of a node
*/
angular
.module('horizon.dashboard.admin.ironic')
.factory('horizon.dashboard.admin.ironic.bootdevice.service',
bootDeviceService);
bootDeviceService.$inject = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath',
'horizon.app.core.openstack-service-api.ironic'
];
function bootDeviceService($uibModal, basePath, ironic) {
var service = {
setBootDevice: setBootDevice
};
return service;
/*
* @description Set the boot device of a specified node
*
* @param {object} node - node object
* @return {promise}
*/
function setBootDevice(node) {
var promise;
var options = {
controller: "BootDeviceController as ctrl",
backdrop: 'static',
resolve: {
node: function() {
return node;
}
},
templateUrl: basePath + '/bootdevice/bootdevice.html'
};
promise = $uibModal.open(options).result.then(
function(result) {
return ironic.nodeSetBootDevice(node.uuid,
result.device,
result.persistent);
});
return promise;
}
}
})();

View File

@ -1,142 +0,0 @@
/**
* Copyright 2017 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";
/**
* @description Unit tests for the Ironic-UI boot-device service
*/
describe('horizon.dashboard.admin.ironic.bootdevice.service',
function() {
var $q,
$uibModal,
bootDeviceService,
ironicAPI,
ironicBackendMockService,
defaultDriver;
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.framework.util'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {
open: function() {
return $q.when({device: 'pxe',
persistent: true});
}
});
}));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service', {
add: function() {}
});
}));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(function($injector) {
ironicBackendMockService =
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
ironicBackendMockService.init();
defaultDriver = ironicBackendMockService.params.defaultDriver;
}));
beforeEach(inject(function($injector) {
$q = $injector.get('$q');
$uibModal = $injector.get('$uibModal');
ironicAPI =
$injector.get('horizon.app.core.openstack-service-api.ironic');
bootDeviceService =
$injector.get('horizon.dashboard.admin.ironic.bootdevice.service');
}));
it('defines the bootDeviceService', function() {
expect(bootDeviceService).toBeDefined();
expect(bootDeviceService.setBootDevice).toBeDefined();
});
afterEach(function() {
ironicBackendMockService.postTest();
});
/**
* @description Utility function that creates a node and returns
* both it and its boot device
*
* @return {promise} Containing node and boot_device
*/
function createNode() {
return ironicAPI.createNode({driver: defaultDriver})
.then(function(response) {
return response.data;
})
.then(function(node) {
return ironicAPI.getBootDevice(node.uuid).then(function(device) {
return {node: node, boot_device: device};
});
});
}
it('setBootDevice', function() {
var targetBootDevice = {
device: "safe",
persistent: false
};
spyOn($uibModal, 'open').and.returnValue(
{result: $q.when(targetBootDevice)});
createNode().then(function(data) {
expect(data.node.boot_device).not.toEqual(targetBootDevice.device);
bootDeviceService.setBootDevice(data.node)
.then(function() {
ironicAPI.getBootDevice(data.node.uuid).then(function(device) {
expect(device).toEqual(
{boot_device: targetBootDevice.device,
persistent: targetBootDevice.persistent});
});
})
.catch(fail);
});
ironicBackendMockService.flush();
});
it('setBootDevice - cancel', function() {
spyOn($uibModal, 'open').and.returnValue(
{result: $q.reject('cancel')});
createNode().then(function(data) {
bootDeviceService.setBootDevice(data.node)
.then(fail)
.catch(function() {
ironicAPI.getBootDevice(data.node.uuid).then(function(device) {
expect(device).toEqual(data.boot_device);
});
});
});
ironicBackendMockService.flush();
});
});
})();

View File

@ -1,69 +0,0 @@
/*
* Copyright 2017 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';
/**
* @ngdoc controller
* @name horizon.dashboard.admin.ironic:CleanNodeController
* @ngController
*
* @description
* Controller used to prompt the user for a list of clean-steps
* in JSON format that will be applied a node
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('CleanNodeController', CleanNodeController);
CleanNodeController.$inject = [
'$uibModalInstance'
];
function CleanNodeController($uibModalInstance) {
var ctrl = this;
ctrl.errMsg = '';
ctrl.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
ctrl.clean = function(cleanSteps) {
try {
var steps = JSON.parse(cleanSteps);
if (angular.isArray(steps) && steps.length > 0) {
var valid = true;
angular.forEach(steps, function(step) {
if (angular.isUndefined(step.interface) ||
angular.isUndefined(step.step)) {
valid = false;
}
});
if (valid) {
$uibModalInstance.close(steps);
} else {
ctrl.errMsg = gettext('Each cleaning step must be an object that contains "interface" and "step" properties'); // eslint-disable-line max-len
}
} else {
ctrl.errMsg = gettext('Clean steps should be an non-empty array');
}
} catch (e) {
ctrl.errMsg = gettext('Unable to validate the JSON input');
}
};
}
})();

View File

@ -1,41 +0,0 @@
<div class="modal-header" modal-draggable>
<h3 class="modal-title" translate>Clean Node</h3>
</div>
<div class="modal-body clearfix">
<div class="content">
<div translate class="subtitle">Provide a list of cleaning steps in JSON format</div>
<div class="form-group"
ng-init="cleanSteps=''">
<div class="form-field">
<textarea type="text"
class="form-control input-sm"
ng-model="cleanSteps"
ng-change="ctrl.errMsg=''"
auto-focus
rows="8"
required
placeholder=""/>
</div>
</div>
<div uib-alert
ng-hide="ctrl.errMsg === ''"
ng-class="'alert-danger'">
{$ ctrl.errMsg $}
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default secondary"
type="button"
ng-click="ctrl.cancel()"
translate>
Cancel
</button>
<button class="btn btn-primary"
type="button"
ng-disabled="cleanSteps === '' || ctrl.errMsg !== ''"
ng-click="ctrl.clean(cleanSteps)"
translate>
Clean node
</button>
</div>

View File

@ -1,64 +0,0 @@
/*
* Copyright 2016 Cray Inc.
* Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
/*
* @ngdoc service
* @name horizon.dashboard.admin.ironic.maintenance.service
* @description Service for putting nodes in, and removing them from
* maintenance mode
*/
angular
.module('horizon.dashboard.admin.ironic')
.factory('horizon.dashboard.admin.ironic.clean-node.service',
cleanNodeService);
cleanNodeService.$inject = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath',
'horizon.app.core.openstack-service-api.ironic'
];
function cleanNodeService($uibModal, basePath, ironic) {
var service = {
clean: clean
};
return service;
/*
* @description Initiate manual cleaning of an Ironic node.
* The user is prompted for a list of steps that are then
* used to clean the node.
*
* @param {object} node - Node to be cleaned
* @return {void}
*/
function clean(node) {
var options = {
controller: 'CleanNodeController as ctrl',
backdrop: 'static',
templateUrl: basePath + '/clean-node/clean-node.html'
};
$uibModal.open(options).result.then(function(cleanSteps) {
return ironic.setNodeProvisionState(node.uuid,
'clean',
cleanSteps);
});
}
}
})();

View File

@ -1,85 +0,0 @@
/*
* 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',
'$controller',
'$uibModalInstance',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.events',
'node'
];
function CreatePortController($rootScope,
$controller,
$uibModalInstance,
ironic,
ironicEvents,
node) {
var ctrl = this;
$controller('BasePortController',
{ctrl: ctrl,
node: node,
$uibModalInstance: $uibModalInstance});
ctrl.modalTitle = gettext("Create Port");
ctrl.submitButtonTitle = ctrl.modalTitle;
/**
* Create the defined port
*
* @return {void}
*/
ctrl.createPort = function() {
var port = angular.copy(ctrl.port);
port.address = ctrl.address.value;
var attr = ctrl.localLinkConnection.toPortAttr();
if (attr) {
port.local_link_connection = attr;
}
if (ctrl.pxeEnabled.value !== 'True') {
port.pxe_enabled = ctrl.pxeEnabled.value;
}
if (ctrl.portgroup_uuid.value !== null) {
port.portgroup_uuid = ctrl.portgroup_uuid.value;
}
ironic.createPort(port).then(
function(createdPort) {
$rootScope.$emit(ironicEvents.CREATE_PORT_SUCCESS);
$uibModalInstance.close(createdPort);
});
};
ctrl.submit = function() {
ctrl.createPort();
};
}
})();

View File

@ -1,56 +0,0 @@
/*
* 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 = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath'
];
function createPortService($uibModal, basePath) {
var service = {
createPort: createPort
};
return service;
/**
* @description Launch a modal dialog that will guide the user
* in creating a new port
*
* @param {object} node - Node to which the port will be associated
* @return {promise} Object describing the created port
*/
function createPort(node) {
var options = {
controller: 'CreatePortController as ctrl',
backdrop: 'static',
resolve: {
node: function() {
return node;
}
},
templateUrl: basePath + '/base-port/base-port.html'
};
return $uibModal.open(options).result;
}
}
})();

View File

@ -1,173 +0,0 @@
/*
* Copyright 2017 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 edit an existing Ironic node
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('EditNodeController', EditNodeController);
EditNodeController.$inject = [
'$rootScope',
'$controller',
'$uibModalInstance',
'horizon.framework.widgets.toast.service',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.events',
'horizon.dashboard.admin.ironic.edit-node.service',
'horizon.dashboard.admin.ironic.update-patch.service',
'$log',
'node'
];
function EditNodeController($rootScope,
$controller,
$uibModalInstance,
toastService,
ironic,
ironicEvents,
editNodeService,
updatePatchService,
$log,
node) {
var ctrl = this;
$controller('BaseNodeController',
{ctrl: ctrl,
$uibModalInstance: $uibModalInstance});
ctrl.modalTitle = gettext("Edit Node");
ctrl.submitButtonTitle = gettext("Update Node");
ctrl.baseNode = null;
var instanceInfoId = "instance_info";
ctrl.propertyCollections.push(
{id: instanceInfoId,
formId: "instance_info_form",
title: gettext("Instance Info"),
addPrompt: gettext("Add Instance Property"),
placeholder: gettext("Instance Property Name")});
ctrl.node[instanceInfoId] = {};
ctrl.node[instanceInfoId] = {};
init(node);
function init(node) {
ctrl._loadDrivers().then(function() {
_loadNodeData(node.uuid);
});
ctrl._getImages();
}
function _loadNodeData(nodeId) {
ironic.getNode(nodeId).then(function(node) {
ctrl.baseNode = node;
ctrl.node.name = node.name;
ctrl.node.resource_class = node.resource_class;
ctrl.node.network_interface = node.network_interface;
for (var i = 0; i < ctrl.drivers.length; i++) {
if (ctrl.drivers[i].name === node.driver) {
ctrl.selectedDriver = ctrl.drivers[i];
break;
}
}
ctrl.loadDriverProperties(node.driver).then(function() {
angular.forEach(node.driver_info, function(value, property) {
if (angular.isDefined(ctrl.driverProperties[property])) {
ctrl.driverProperties[property].inputValue = value;
}
});
});
ctrl.node.properties = angular.copy(node.properties);
ctrl.node.extra = angular.copy(node.extra);
ctrl.node.instance_info = angular.copy(node.instance_info);
ctrl.node.uuid = node.uuid;
});
}
/**
* @description Construct a patch that converts source node into
* target node
*
* @param {object} sourceNode - Source node
* @param {object} targetNode - Target node
* @return {object[]} Array of patch instructions
*/
ctrl.buildPatch = function(sourceNode, targetNode) {
var patcher = new updatePatchService.UpdatePatch();
var PatchItem = function PatchItem(id, path) {
this.id = id;
this.path = path;
};
angular.forEach([new PatchItem("name", "/name"),
new PatchItem("resource_class", "/resource_class"),
new PatchItem("network_interface", "/network_interface"),
new PatchItem("driver", "/driver"),
new PatchItem("properties", "/properties"),
new PatchItem("extra", "/extra"),
new PatchItem("driver_info", "/driver_info"),
new PatchItem("instance_info", "/instance_info")],
function(item) {
patcher.buildPatch(sourceNode[item.id],
targetNode[item.id],
item.path);
});
return patcher.getPatch();
};
ctrl.submit = function() {
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;
}
});
$log.info("Updating node " + JSON.stringify(ctrl.baseNode));
$log.info("to " + JSON.stringify(ctrl.node));
var patch = ctrl.buildPatch(ctrl.baseNode, ctrl.node);
$log.info("patch = " + JSON.stringify(patch.patch));
if (patch.status === updatePatchService.UpdatePatch.status.OK) {
ironic.updateNode(ctrl.baseNode.uuid, patch.patch).then(function(node) {
$rootScope.$emit(ironicEvents.EDIT_NODE_SUCCESS);
$uibModalInstance.close(node);
});
} else {
toastService.add('error',
gettext('Unable to create node update patch.'));
}
};
}
})();

View File

@ -1,102 +0,0 @@
/*
* Copyright 2015 Hewlett Packard Enterprise Development Company LP
* 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.edit-node', function () {
var ironicBackendMockService, ctrl, editNode, updatePatchService;
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.framework.util'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(module(function($provide) {
$provide.value('$uibModalInstance', {});
}));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service',
{});
}));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(function($injector) {
ironicBackendMockService =
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
ironicBackendMockService.init();
updatePatchService =
$injector.get('horizon.dashboard.admin.ironic.update-patch.service');
var ironicAPI =
$injector.get('horizon.app.core.openstack-service-api.ironic');
ironicAPI.createNode(
{driver: ironicBackendMockService.params.defaultDriver})
.then(function(response) {
editNode = response.data;
var controller = $injector.get('$controller');
ctrl = controller('EditNodeController', {node: editNode});
});
ironicBackendMockService.flush();
}));
afterEach(function() {
ironicBackendMockService.postTest();
});
it('controller should be defined', function () {
expect(ctrl).toBeDefined();
});
it('controller base construction', function () {
expect(ctrl.baseNode).toEqual(
ironicBackendMockService.getNode(editNode.uuid));
expect(ctrl.propertyCollections)
.toContain(jasmine.objectContaining({id: "instance_info"}));
angular.forEach(ctrl.propertyCollections, function(collection) {
expect(Object.getOwnPropertyNames(collection).sort()).toEqual(
PROPERTY_COLLECTION_PROPERTIES.sort());
});
expect(ctrl.node.name).toEqual(editNode.name);
expect(ctrl.node.resource_class).toEqual(editNode.resource_class);
expect(ctrl.node.network_interface).toEqual(editNode.network_interface);
expect(ctrl.node.properties).toEqual(editNode.properties);
expect(ctrl.node.extra).toEqual(editNode.extra);
expect(ctrl.node.instance_info).toEqual(editNode.instance_info);
expect(ctrl.node.uuid).toEqual(editNode.uuid);
var properties = angular.copy(BASE_NODE_CONTROLLER_PROPERTIES);
properties.push('baseNode',
'buildPatch',
'selectedDriver',
'submit');
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
properties.sort());
});
it('buildPatch', function () {
var patch = ctrl.buildPatch(editNode, editNode);
expect(patch.patch).toEqual([]);
expect(patch.status).toEqual(updatePatchService.UpdatePatch.status.OK);
});
});
})();

View File

@ -1,50 +0,0 @@
/*
* 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.edit-node.service',
editNodeService);
editNodeService.$inject = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath'
];
function editNodeService($uibModal, basePath) {
var service = {
editNode: editNode
};
function editNode(node) {
var options = {
controller: 'EditNodeController as ctrl',
backdrop: 'static',
resolve: {
node: function() {
return node;
}
},
templateUrl: basePath + '/base-node/base-node.html'
};
return $uibModal.open(options).result;
}
return service;
}
})();

View File

@ -1,133 +0,0 @@
/*
* 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 UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG = gettext("This field is disabled because a port cannot have any connectivity attributes (pxe_enabled, local_link_connection, portgroup_id) updated unless its associated node is in an enroll, inspecting, mangeable state; or in maintenance mode."); // eslint-disable-line max-len
/**
* Controller used to edit a specified node port
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('EditPortController', EditPortController);
EditPortController.$inject = [
'$rootScope',
'$controller',
'$uibModalInstance',
'$log',
'$q',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.events',
'horizon.dashboard.admin.ironic.update-patch.service',
'port',
'node'
];
function EditPortController($rootScope,
$controller,
$uibModalInstance,
$log,
$q,
ironic,
ironicEvents,
updatePatchService,
port,
node) {
var ctrl = this;
$controller('BasePortController',
{ctrl: ctrl,
node: node,
$uibModalInstance: $uibModalInstance});
ctrl.modalTitle = gettext("Edit Port");
ctrl.submitButtonTitle = gettext("Update Port");
var cannotEditConnectivityAttr =
!(node.maintenance || (node.provision_state === "enroll" ||
node.provision_state === "inspecting" ||
node.provision_state === "manageable"));
// Initialize form fields
ctrl.address.value = port.address;
if ((node.provision_state === "active" || node.instance_uuid) &&
!node.maintenance) {
ctrl.address.disable();
}
ctrl.pxeEnabled.value = port.pxe_enabled ? 'True' : 'False';
ctrl.portgroup_uuid.value = port.portgroup_uuid;
if (cannotEditConnectivityAttr) {
angular.forEach(
[ctrl.pxeEnabled, ctrl.portgroup_uuid],
function(field) {
field.disable(UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG);
});
}
ctrl.localLinkConnection.setValues(
port.local_link_connection);
if (cannotEditConnectivityAttr) {
ctrl.localLinkConnection.disable(
UNABLE_TO_UPDATE_CONNECTIVITY_ATTR_MSG);
}
ctrl.port.extra = angular.copy(port.extra);
/**
* Apply updates to the port being edited
*
* @return {void}
*/
ctrl.updatePort = function() {
var patcher = new updatePatchService.UpdatePatch();
$log.info("Updating port " + JSON.stringify(port));
patcher.buildPatch(port.address, ctrl.address.value, "/address");
patcher.buildPatch(port.pxe_enabled ? 'True' : 'False',
ctrl.pxeEnabled.value,
"/pxe_enabled");
patcher.buildPatch(port.local_link_connection,
ctrl.localLinkConnection.toPortAttr(),
"/local_link_connection");
patcher.buildPatch(port.extra, ctrl.port.extra, "/extra");
patcher.buildPatch(port.portgroup_uuid,
ctrl.portgroup_uuid.value,
"/portgroup_uuid");
var patch = patcher.getPatch();
$log.info("patch = " + JSON.stringify(patch.patch));
if (patch.status === updatePatchService.UpdatePatch.status.OK) {
ironic.updatePort(port.uuid, patch.patch).then(function(port) {
$rootScope.$emit(ironicEvents.EDIT_PORT_SUCCESS);
$uibModalInstance.close(port);
});
} else {
toastService.add('error',
gettext('Unable to create port update patch.'));
}
};
ctrl.submit = function() {
ctrl.updatePort();
};
}
})();

View File

@ -1,60 +0,0 @@
/*
* Copyright 2017 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.edit-port.service',
editPortService);
editPortService.$inject = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath'
];
function editPortService($uibModal, basePath) {
var service = {
editPort: editPort
};
/**
* @description: Edit a specified port
*
* @param {object} port - Port to be edited
* @param {object} node - Node to which the port is attached
* @return {promise} Promise containing the updated port
*/
function editPort(port, node) {
var options = {
controller: 'EditPortController as ctrl',
backdrop: 'static',
resolve: {
port: function() {
return port;
},
node: function() {
return node;
}
},
templateUrl: basePath + '/base-port/base-port.html'
};
return $uibModal.open(options).result;
}
return service;
}
})();

View File

@ -1,37 +0,0 @@
/*
* 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;
});
}
};
}
})();

View File

@ -1,83 +0,0 @@
/*
* 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',
'$controller',
'$uibModalInstance',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.events',
'$log'
];
function EnrollNodeController($rootScope,
$controller,
$uibModalInstance,
ironic,
ironicEvents,
$log) {
var ctrl = this;
$controller('BaseNodeController',
{ctrl: ctrl,
$uibModalInstance: $uibModalInstance});
ctrl.modalTitle = gettext("Enroll Node");
ctrl.submitButtonTitle = ctrl.modalTitle;
init();
function init() {
ctrl._loadDrivers();
ctrl._getImages();
}
ctrl.submit = function() {
$log.debug(">> EnrollNodeController.submit()");
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(response) {
$log.info("create node response = " + JSON.stringify(response));
$rootScope.$emit(ironicEvents.ENROLL_NODE_SUCCESS);
$uibModalInstance.close(response.data);
});
$log.debug("<< EnrollNodeController.submit()");
};
}
})();

View File

@ -1,88 +0,0 @@
/*
* Copyright 2017 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', function () {
var ironicBackendMockService, rootScope, ironicEvents, uibModalInstance;
var ctrl = {};
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.framework.util'));
beforeEach(module(function($provide) {
$provide.value('$uibModal', {});
}));
beforeEach(module(function($provide) {
uibModalInstance = {
close: jasmine.createSpy()
};
$provide.value('$uibModalInstance', uibModalInstance);
}));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service',
{});
}));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(function($injector) {
rootScope = $injector.get('$rootScope');
ironicEvents = $injector.get('horizon.dashboard.admin.ironic.events');
}));
beforeEach(inject(function($injector) {
ironicBackendMockService =
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
ironicBackendMockService.init();
var controller = $injector.get('$controller');
ctrl = controller('EnrollNodeController');
ironicBackendMockService.flush();
}));
afterEach(function() {
ironicBackendMockService.postTest();
});
it('controller should be defined', function () {
expect(ctrl).toBeDefined();
});
it('base construction', function () {
var properties = angular.copy(BASE_NODE_CONTROLLER_PROPERTIES);
properties.push('submit');
expect(Object.getOwnPropertyNames(ctrl).sort()).toEqual(
properties.sort());
});
it('submit - success', function () {
spyOn(rootScope, '$emit');
var nodeName = "node_" + Date.now();
ctrl.node.name = nodeName;
ctrl.node.driver = ironicBackendMockService.params.defaultDriver;
ctrl.submit();
ironicBackendMockService.flush();
expect(rootScope.$emit)
.toHaveBeenCalledWith(ironicEvents.ENROLL_NODE_SUCCESS);
expect(uibModalInstance.close)
.toHaveBeenCalledWith(ironicBackendMockService.getNode(nodeName));
});
});
})();

View File

@ -1,51 +0,0 @@
/*
* 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.enroll-node.service',
enrollNodeService);
enrollNodeService.$inject = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath'
];
function enrollNodeService($uibModal, basePath) {
var service = {
enrollNode: enrollNode
};
/**
* @description Launch a modal dialog that will guide the user
* in enrolling a new node
*
* @return {promise} Object describing the enrolled node
*/
function enrollNode() {
var options = {
controller: 'EnrollNodeController as ctrl',
backdrop: 'static',
templateUrl: basePath + '/base-node/base-node.html'
};
return $uibModal.open(options).result;
}
return service;
}
})();

View File

@ -1,52 +0,0 @@
/*
* Copyright 2017 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('formField', FormField);
FormField.$inject = [
'$timeout',
'$compile',
'horizon.dashboard.admin.ironic.basePath'
];
function FormField($timeout, $compile, basePath) {
return {
restrict: 'E',
scope: {
field: '=',
form: '='
},
templateUrl: basePath + '/form-field.html',
link: function(scope, element) {
// Process the auto-focus attribute
if (scope.field.autoFocus) {
// Need to defer processing until the DOM is fully instantiated
$timeout(function() {
var inputs = element.find('input');
if (inputs[0]) {
inputs.attr('auto-focus', '');
$compile(element.contents())(scope);
}
});
}
}
};
}
})();

View File

@ -1,56 +0,0 @@
<div class="form-group"
ng-class="{'has-error': form[field.id].$invalid &&
form[field.id].$dirty}">
<label for="{$ field.id $}"
class="control-label">{$ field.title $}</label>
<span ng-if="field.desc"
class="help-icon"
data-container="body"
data-html="true"
title=""
data-toggle="tooltip"
data-original-title="{$ field.desc $}">
<span class="fa fa-question-circle"></span>
</span>
<span ng-if="field.info"
class="help-icon"
data-container="body"
data-html="true"
title=""
data-toggle="tooltip"
data-original-title="{$ field.info $}">
<span class="fa fa-info-circle"></span>
</span>
<span ng-if="field.required"
class="hz-icon-required fa fa-asterisk"></span>
<div ng-switch="field.type">
<input ng-switch-when="input"
type="text"
class="form-control"
ng-model="field.value"
id="{$ field.id $}"
name="{$ field.id $}"
ng-required="field.required"
ng-disabled="field.disabled"
ng-pattern="field.pattern"
ng-change="{$ field.change $}"
placeholder="{$ field.desc $}"/>
<div ng-switch-when="radio"
class="btn-group"
id="{$ field.id $}">
<label class="btn btn-default"
ng-model="field.value"
ng-repeat="opt in field.options"
ng-disabled="field.disabled"
uib-btn-radio="opt">{$ opt $}</label>
</div>
<div ng-switch-when="select">
<select id="field.id"
class="form-control"
ng-disabled="field.disabled"
ng-options="{$ field.options $}"
ng-model="field.value">
</select>
</div>
</div>
</div>

View File

@ -1,103 +0,0 @@
/*
* Copyright 2017 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.form-field.service',
formFieldService);
function formFieldService() {
var service = {
FormField: FormField
};
/**
* @description Utility class for managing form fields.
* Used is association with the form-field directive.
*
* @param {object} args - Base properties are:
* type [string] - Field type. One of: 'input', 'radio', 'select'
* id [string] - id/name of the DOM value element
* title [string] - Label used to identify the field to the user
* options - type == radio [array]:
* List of options for a radio field
* type == select [string]:
* String expression that is passed to ng-options
* value - Initial value of the field
* required [boolean] - Does the field require a value
* desc [string] - Field description
* pattern [RegExp] - Regular expression pattern used to match
* valid input values
* disabled [boolean] - Is the field disabled
* info [string] - Additional information about the current state of
* the field. It will be displayed in a tooltip
* associated with the field.
* autoFocus [boolean] - True if the focus should be set to this field. Only
* applies to fields of type input.
* change [string] - Expression to be evaluated when the value of this
* field changes. Only applies to fields of type input.
*
* @return {void}
*/
function FormField(args) {
var field = this;
field.type = 'input';
field.id = undefined;
field.title = undefined;
field.options = undefined;
field.value = undefined;
field.required = false;
field.desc = undefined;
field.pattern = undefined;
field.disabled = false;
field.info = undefined;
field.autoFocus = false;
field.change = undefined;
angular.forEach(args, function(value, arg) {
field[arg] = value;
});
/**
* @description Test whether the field has a non-empty value.
* Note that an empty value can be either '' or undefined in the
* case of a required field
*
* @return {boolean} Return true if the field has a value
*/
this.hasValue = function() {
return angular.isDefined(this.value) && this.value !== '';
};
/**
* @description Disable this field.
*
* @param {string} reason - Optional reason for disabling this field.
* @return {void}
*/
this.disable = function(reason) {
this.disabled = true;
if (angular.isDefined(reason)) {
this.info = reason;
}
};
}
return service;
}
})();

View File

@ -1,90 +0,0 @@
/**
* Copyright 2017 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";
/**
* @description Unit tests for the form-field service
*/
describe(
'horizon.dashboard.admin.ironic.form-field.service',
function() {
var formFieldService;
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(inject(function($injector) {
formFieldService =
$injector.get('horizon.dashboard.admin.ironic.form-field.service');
}));
it('defines the form-field service', function() {
expect(formFieldService).toBeDefined();
});
it('FormField - default construction', function() {
var field = new formFieldService.FormField({});
expect(field.type).toEqual('input');
expect(field.id).toBeUndefined();
expect(field.title).toBeUndefined();
expect(field.options).toBeUndefined();
expect(field.value).toBeUndefined();
expect(field.required).toBe(false);
expect(field.desc).toBeUndefined();
expect(field.pattern).toBeUndefined();
expect(field.disabled).toBe(false);
expect(field.info).toBeUndefined();
expect(field.autoFocus).toBe(false);
expect(field.change).toBeUndefined();
expect(field.hasValue).toBeDefined();
expect(field.disable).toBeDefined();
});
it('FormField - local parameters', function() {
var title = "title";
var field = new formFieldService.FormField({
title: title
});
expect(field.title).toBe(title);
});
it('hasValue', function() {
var field = new formFieldService.FormField({});
expect(field.hasValue()).toBe(false);
field.value = '';
expect(field.hasValue()).toBe(false);
field.value = null;
expect(field.hasValue()).toBe(true);
field.value = 'True';
expect(field.hasValue()).toBe(true);
});
it('disable', function() {
var field = new formFieldService.FormField({});
expect(field.disabled).toBe(false);
field.disable();
expect(field.disabled).toBe(true);
});
});
})();

View File

@ -1,718 +0,0 @@
/*
* © Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
* © 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';
/**
* @description Service that provides a mock for the Ironic backend.
*/
angular
.module('horizon.dashboard.admin.ironic')
.factory('horizon.dashboard.admin.ironic.backend-mock.service',
ironicBackendMockService);
ironicBackendMockService.$inject = [
'$httpBackend',
'horizon.framework.util.uuid.service',
'horizon.dashboard.admin.ironic.validMacAddressPattern'
];
function ironicBackendMockService($httpBackend,
uuidService,
validMacAddressPattern) {
// Default node object.
var defaultNode = {
chassis_uuid: null,
clean_step: {},
console_enabled: false,
driver: undefined,
driver_info: {},
driver_internal_info: {},
extra: {},
inspection_finished_at: null,
inspection_started_at: null,
instance_info: {},
instance_uuid: null,
last_error: null,
maintenance: false,
maintenance_reason: null,
name: null,
network_interface: "flat",
power_state: null,
properties: {},
provision_state: "enroll",
provision_updated_at: null,
raid_config: {},
reservation: null,
resource_class: null,
target_power_state: null,
target_provision_state: null,
target_raid_config: {},
updated_at: null,
uuid: undefined
};
// Default port object.
var defaultPort = {
address: undefined,
created_at: null,
extra: {},
internal_info: {},
local_link_connection: {},
node_uuid: undefined,
portgroup_uuid: null,
pxe_enabled: true,
updated_at: null,
uuid: undefined
};
// Default portgroup object.
var defaultPortgroup = {
address: null,
created_at: null,
extra: {},
internal_info: {},
mode: "active-backup",
name: null,
node_uuid: undefined,
ports: [],
properties: {},
standalone_ports_supported: true,
updated_at: null,
uuid: undefined
};
// Value of the next available system port
var nextAvailableSystemPort = 1024;
// Additional service parameters
var params = {
// Console info
consoleType: "shellinabox",
consoleUrl: "http://localhost:",
defaultDriver: "agent_ipmitool",
supportedBootDevices: ["pxe", "bios", "safe"]
};
// List of supported drivers
var drivers = [{name: params.defaultDriver}];
// List of images
var images = [];
var service = {
params: params,
init: init,
flush: flush,
postTest: postTest,
getNode: getNode,
getNodeBootDevice: getNodeBootDevice,
getNodeSupportedBootDevices: getNodeSupportedBootDevices,
nodeGetConsoleUrl: nodeGetConsoleUrl,
getDrivers: getDrivers,
getImages: getImages,
getPort: getPort,
getPortgroup: getPortgroup
};
var responseCode = {
SUCCESS: 200,
EMPTY_RESPONSE: 204,
BAD_QUERY: 400,
RESOURCE_NOT_FOUND: 404,
RESOURCE_CONFLICT: 409
};
// Dictionary of active nodes indexed by node-id (uuid and name)
var nodes = {};
// Dictionary of active ports indexed by port-uuid
var ports = {};
// Dictionary of active portgroups indexed by portgroup-uuid
var portgroups = {};
return service;
/**
* @description Get and reserve the next available system port.
*
* @return {int} Port number.
*/
function getNextAvailableSystemPort() {
return nextAvailableSystemPort++;
}
/**
* @description Create a backend managed node.
*
* @param {object} params - Dictionary of parameters that define
* the node to be created.
* @return {object|null} Node object, or null if the node could
* not be created.
*/
function createNode(params) {
var node = null;
if (angular.isDefined(params.driver)) {
node = angular.copy(defaultNode);
angular.forEach(params, function(value, key) {
node[key] = value;
});
if (angular.isUndefined(node.uuid)) {
node.uuid = uuidService.generate();
}
var backendNode = {
base: node,
consolePort: getNextAvailableSystemPort(),
ports: {}, // Indexed by port-uuid
portgroups: {}, // Indexed by portgroup-uuid
supportedBootDevices: service.params.supportedBootDevices,
bootDevice: {
boot_device: service.params.supportedBootDevices[0],
persistent: true
}
};
nodes[node.uuid] = backendNode;
if (node.name !== null) {
nodes[node.name] = backendNode;
}
}
return node;
}
/**
* @description Get a specified node.
*
* @param {string} nodeId - Uuid or name of the requested node.
* @return {object|null} Base node object, or null if the node
* does not exist.
*/
function getNode(nodeId) {
return angular.isDefined(nodes[nodeId]) ? nodes[nodeId].base : null;
}
/**
* @description Get the boot device of a specified node.
*
* @param {string} nodeId - Uuid or name of the requested node.
* @return {object} Boot device.
*/
function getNodeBootDevice(nodeId) {
return angular.isDefined(nodes[nodeId])
? nodes[nodeId].bootDevice : undefined;
}
/**
* @description Get the list of supported boot devices of
* a specified node.
*
* @param {string} nodeId - Uuid or name of the requested node.
* @return {string []} List of supported boot devices.
*/
function getNodeSupportedBootDevices(nodeId) {
return angular.isDefined(nodes[nodeId])
? nodes[nodeId].supportedBootDevices : undefined;
}
/*
* @description Get the console-url for a specified node.
*
* @param {string} nodeId - Uuid or name of the node.
* @return {string|null} Console url if the console is enabled,
* null otherwise.
*/
function nodeGetConsoleUrl(nodeId) {
return nodes[nodeId].base.console_enabled
? service.params.consoleUrl + nodes[nodeId].consolePort
: null;
}
/**
* @description Test whether a mac address is being used by an
* existing port.
*
* @param {string} address - Mac address.
* @return {boolean} True if the mac address is being used by
* another port, otherwise false.
*/
function macAddressInUse(address) {
for (var uuid in ports) {
if (ports.hasOwnProperty(uuid) &&
angular.isDefined(ports[uuid].address)) {
if (ports[uuid].address === address) {
return true;
}
}
}
return false;
}
/**
* @description Create a backend managed port.
*
* @param {object} params - Dictionary of parameters that define
* the port to be created.
* @return {object|null} Port object, or null if the port could
* not be created.
*/
function createPort(params) {
var port = null;
var status = responseCode.BAD_QUERY;
if (angular.isDefined(params.address) &&
angular.isDefined(params.node_uuid) &&
params.address.match(validMacAddressPattern) &&
angular.isDefined(nodes[params.node_uuid])) {
if (macAddressInUse(params.address)) {
status = responseCode.RESOURCE_CONFLICT;
} else {
port = angular.copy(defaultPort);
angular.forEach(params, function(value, key) {
port[key] = value;
});
if (angular.isUndefined(port.uuid)) {
port.uuid = uuidService.generate();
}
ports[port.uuid] = port;
nodes[port.node_uuid].ports[port.uuid] = port;
status = responseCode.SUCCESS;
}
}
return [status, port];
}
/**
* @description Create a portgroup.
* This function is not yet fully implemented.
*
* @param {object} params - Dictionary of parameters that define
* the portgroup to be created.
* @return {object|null} Portgroup object, or null if the port could
* not be created.
*/
function createPortgroup(params) {
var portgroup = null;
var status = responseCode.BAD_QUERY;
if (angular.isDefined(nodes[params.node_uuid])) {
if (angular.isDefined(params.name) &&
params.name !== null &&
angular.isDefined(portgroups[params.name])) {
status = responseCode.RESOURCE_CONFLICT;
} else {
portgroup = angular.copy(defaultPortgroup);
angular.forEach(params, function(value, key) {
portgroup[key] = value;
});
if (angular.isUndefined(portgroup.uuid)) {
portgroup.uuid = uuidService.generate();
}
portgroups[portgroup.uuid] = portgroup;
if (portgroup.name !== null) {
portgroups[portgroup.name] = portgroup;
}
nodes[portgroup.node_uuid].portgroups[portgroup.uuid] = portgroup;
}
status = responseCode.SUCCESS;
}
return [status, portgroup];
}
/**
* description Get a specified port.
*
* @param {string} portUuid - Uuid of the requested port.
* @return {object|null} Port object, or null if the port
* does not exist.
*/
function getPort(portUuid) {
return angular.isDefined(ports[portUuid]) ? ports[portUuid] : null;
}
/**
* description Get a specified portgroup.
*
* @param {string} portgroupId - Uuid or name of the requested portgroup.
* @return {object|null} Portgroup object, or null if the portgroup
* does not exist.
*/
function getPortgroup(portgroupId) {
return angular.isDefined(portgroups[portgroupId])
? portgroups[portgroupId] : null;
}
/**
* @description Initialize the Backend-Mock service.
* Create the handlers that intercept http requests.
*
* @return {void}
*/
function init() {
// Create node
$httpBackend.whenPOST(/\/api\/ironic\/nodes\/$/)
.respond(function(method, url, data) {
var node = createNode(JSON.parse(data).node);
return [node ? responseCode.SUCCESS : responseCode.BAD_QUERY, node];
});
// Delete node
$httpBackend.whenDELETE(/\/api\/ironic\/nodes\/$/)
.respond(function(method, url, data) {
var nodeId = JSON.parse(data).node;
var status = responseCode.RESOURCE_NOT_FOUND;
if (angular.isDefined(nodes[nodeId])) {
var node = nodes[nodeId].base;
if (node.name !== null) {
delete nodes[node.name];
delete nodes[node.uuid];
} else {
delete nodes[nodeId];
}
status = responseCode.EMPTY_RESPONSE;
}
return [status, ""];
});
function _addItem(node, path, value) {
var parts = path.substring(1).split("/");
var leaf = parts.pop();
var obj = node;
for (var i = 0; i < parts.length; i++) {
var part = parts[i];
if (angular.isUndefined(obj[part])) {
obj[part] = {};
}
obj = obj[part];
}
obj[leaf] = value;
}
function _removeItem(node, path) {
var parts = path.substring(1).split("/");
var leaf = parts.pop();
var obj = node;
for (var i = 0; i < parts.length; i++) {
obj = obj[parts[i]];
}
delete obj[leaf];
}
function _replaceItem(node, path, value) {
if (path === "/name" &&
node.name !== null) {
delete nodes[node.name];
if (value !== null) {
nodes[value] = node;
}
}
var parts = path.substring(1).split("/");
var leaf = parts.pop();
var obj = node;
for (var i = 0; i < parts.length; i++) {
obj = obj[parts[i]];
}
obj[leaf] = value;
}
// Update node
$httpBackend.whenPATCH(/\/api\/ironic\/nodes\/([^\/]+)$/,
undefined,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
var status = responseCode.RESOURCE_NOT_FOUND;
var node = service.getNode(params.nodeId);
if (angular.isDefined(node)) {
var patch = JSON.parse(data).patch;
angular.forEach(patch, function(operation) {
switch (operation.op) {
case "add":
_addItem(node, operation.path, operation.value);
break;
case "remove":
_removeItem(node, operation.path);
break;
case "replace":
_replaceItem(node, operation.path, operation.value);
break;
default:
}
});
status = responseCode.SUCCESS;
}
return [status, node];
});
// Get node
$httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)$/,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
if (angular.isDefined(nodes[params.nodeId])) {
return [responseCode.SUCCESS, nodes[params.nodeId].base];
} else {
return [responseCode.RESOURCE_NOT_FOUND, null];
}
});
// Get console
$httpBackend.whenGET(/\/api\/ironic\/nodes\/(.+)\/states\/console/,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
var node = nodes[params.nodeId];
var consoleEnabled = node.base.console_enabled;
var consoleInfo = consoleEnabled
? {console_type: service.params.consoleType,
url: service.params.consoleUrl + node.consolePort}
: null;
var info = {
console_enabled: consoleEnabled,
console_info: consoleInfo};
return [responseCode.SUCCESS, info];
});
// Set console
$httpBackend.whenPUT(/\/api\/ironic\/nodes\/(.+)\/states\/console/,
undefined,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
data = JSON.parse(data);
nodes[params.nodeId].base.console_enabled = data.enabled;
return [responseCode.SUCCESS, {}];
});
// Get the ports belonging to a specified node
$httpBackend.whenGET(/\/api\/ironic\/ports/)
.respond(responseCode.SUCCESS, []);
// Get boot device
$httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/boot_device$/,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
if (angular.isDefined(nodes[params.nodeId])) {
return [200, nodes[params.nodeId].bootDevice];
} else {
return [400, null];
}
});
// Get supported boot devices
$httpBackend.whenGET(
/\/api\/ironic\/nodes\/([^\/]+)\/boot_device\/supported$/,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
if (angular.isDefined(nodes[params.nodeId])) {
return [200, nodes[params.nodeId].supportedBootDevices];
} else {
return [400, null];
}
});
// Set boot device
$httpBackend.whenPUT(/\/api\/ironic\/nodes\/(.+)\/boot_device/,
undefined,
undefined,
['nodeId'])
.respond(function(method, url, data, headers, params) {
data = JSON.parse(data);
var status = 404;
if (angular.isDefined(nodes[params.nodeId])) {
var node = nodes[params.nodeId];
if (node.supportedBootDevices.indexOf(data.boot_device) !== -1) {
node.bootDevice.boot_device = data.boot_device;
if (angular.isDefined(data.persistent)) {
node.bootDevice.persistent = data.persistent;
}
status = 200;
}
}
return [status, null];
});
// Validate the interfaces associated with a specified node
$httpBackend.whenGET(/\/api\/ironic\/nodes\/([^\/]+)\/validate$/,
undefined,
['nodeId'])
.respond(responseCode.SUCCESS, []);
// Get the currently available drivers
$httpBackend.whenGET(/\/api\/ironic\/drivers\/$/)
.respond(responseCode.SUCCESS, {drivers: drivers});
// Get driver properties
$httpBackend.whenGET(/\/api\/ironic\/drivers\/([^\/]+)\/properties$/,
undefined,
['driverName'])
.respond(responseCode.SUCCESS, []);
// Get glance images
$httpBackend.whenGET(/\/api\/glance\/images/)
.respond(responseCode.SUCCESS, {items: images});
// Create port
$httpBackend.whenPOST(/\/api\/ironic\/ports\/$/)
.respond(function(method, url, data) {
return createPort(JSON.parse(data).port);
});
// Delete port
$httpBackend.whenDELETE(/\/api\/ironic\/ports\/$/)
.respond(function(method, url, data) {
var portUuid = JSON.parse(data).port_uuid;
var status = responseCode.RESOURCE_NOT_FOUND;
if (angular.isDefined(ports[portUuid])) {
delete ports[portUuid];
status = responseCode.EMPTY_RESPONSE;
}
return [status, ""];
});
// Get ports
$httpBackend.whenGET(/\/api\/ironic\/ports\/$/)
.respond(function(method, url, data) {
var nodeId = JSON.parse(data).node_id;
var status = responseCode.RESOURCE_NOT_FOUND;
var ports = [];
if (angular.isDefined(nodes[nodeId])) {
angular.forEach(nodes[nodeId].ports, function(port) {
ports.push(port);
});
status = responseCode.SUCCESS;
}
return [status, {ports: ports}];
});
// Create portgroup
$httpBackend.whenPOST(/\/api\/ironic\/portgroups\/$/)
.respond(function(method, url, data) {
return createPortgroup(JSON.parse(data));
});
// Get portgroups. This function is not fully implemented.
$httpBackend.whenGET(/\/api\/ironic\/ports\/$/)
.respond(function(method, url, data) {
var nodeId = JSON.parse(data).node_id;
var status = responseCode.RESOURCE_NOT_FOUND;
var portgroups = [];
if (angular.isDefined(nodes[nodeId])) {
angular.forEach(nodes[nodeId].portgroups, function(portgroup) {
portgroups.push(portgroup);
});
status = responseCode.SUCCESS;
}
return [status, {portgroups: portgroups}];
});
// Delete portgroup. This function is not yet implemented.
$httpBackend.whenDELETE(/\/api\/ironic\/portgroups\/$/)
.respond(function(method, url, data) {
var portgroupId = JSON.parse(data).portgroup_id;
var status = responseCode.RESOURCE_NOT_FOUND;
if (angular.isDefined(portgroups[portgroupId])) {
var portgroup = portgroups[portgroupId];
if (portgroup.name !== null) {
delete portgroups[portgroup.name];
delete portgroups[portgroup.uuid];
} else {
delete portgroups[portgroupId];
}
status = responseCode.EMPTY_RESPONSE;
}
return [status, ""];
});
// Get portgroup ports
$httpBackend.whenGET(/\/api\/ironic\/portgroups\/([^\/]+)\/ports$/,
undefined,
['portgroupId'])
.respond(function(method, url, data, headers, params) {
var ports = [];
var status = responseCode.RESOURCE_NOT_FOUND;
if (angular.isDefined(portgroups[params.portgroupId])) {
var portgroup = portgroups[params.portgroupId];
var node = nodes[portgroup.node_uuid];
angular.forEach(node.ports, function(port) {
if (port.portgroup_uuid === portgroup.uuid) {
ports.push(port);
}
});
status = responseCode.SUCCESS;
}
return [status, {ports: ports}];
});
} // init()
/**
* @description Get the list of supported drivers
*
* @return {[]} Array of driver objects
*/
function getDrivers() {
return drivers;
}
/**
* @description Get the list of images
*
* @return {[]} Array of image objects
*/
function getImages() {
return images;
}
/**
* @description Flush pending requests
*
* @return {void}
*/
function flush() {
$httpBackend.flush();
}
/**
* @description Post test verifications.
* This function should be called after completion of a unit test.
*
* @return {void}
*/
function postTest() {
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
}
} // ironicBackendMockService()
})();

View File

@ -1,60 +0,0 @@
/*
* © Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function () {
'use strict';
/**
* @ngdoc overview
* @ngname horizon.dashboard.admin.ironic
*
* @description
* Provides all of the services and widgets required
* to support and display Ironic related content.
*/
angular
.module('horizon.dashboard.admin.ironic', [])
.config(config);
config.$inject = ['$provide', '$windowProvider'];
function config($provide, $windowProvider) {
$provide.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
$provide.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
$provide.constant('horizon.dashboard.admin.ironic.validMacAddressPattern',
'^[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2}){5}$'); // eslint-disable-line max-len
$provide.constant('horizon.dashboard.admin.ironic.validDatapathIdPattern',
'^[0-9A-Fa-f]{16}$'); // eslint-disable-line max-len
var path = $windowProvider.$get().STATIC_URL + 'dashboard/admin/ironic/';
$provide.constant('horizon.dashboard.admin.ironic.basePath', path);
var events = {
ENROLL_NODE_SUCCESS:'horizon.dashboard.admin.ironic.ENROLL_NODE_SUCCESS',
DELETE_NODE_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_NODE_SUCCESS',
EDIT_NODE_SUCCESS:'horizon.dashboard.admin.ironic.EDIT_NODE_SUCCESS',
CREATE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.CREATE_PORT_SUCCESS',
DELETE_PORT_SUCCESS:'horizon.dashboard.admin.ironic.DELETE_PORT_SUCCESS',
EDIT_PORT_SUCCESS:'horizon.dashboard.admin.ironic.EDIT_PORT_SUCCESS'
};
$provide.constant('horizon.dashboard.admin.ironic.events', events);
}
})();

View File

@ -1,673 +0,0 @@
/*
* © Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
* © Copyright 2016 Cray Inc.
* Copyright 2017 Intel Corporation
*
* 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.app.core.openstack-service-api')
.factory('horizon.app.core.openstack-service-api.ironic', ironicAPI);
ironicAPI.$inject = [
'$q',
'horizon.framework.util.http.service',
'horizon.framework.widgets.toast.service',
'horizon.dashboard.admin.ironic.node-error.service'
];
/**
* @description Service that provides access to the Ironic client API
*
* @param {object} $q - Promise provider
* @param {object} apiService - HTTP service
* @param {object} toastService - User message service
* @param {object} nodeErrorService - Node error service
* @return {object} Ironic API service
*/
function ironicAPI($q, apiService, toastService, nodeErrorService) {
var service = {
createNode: createNode,
createPort: createPort,
deleteNode: deleteNode,
deletePort: deletePort,
getDrivers: getDrivers,
getDriverProperties: getDriverProperties,
getNode: getNode,
getNodes: getNodes,
getPortsWithNode: getPortsWithNode,
getBootDevice: getBootDevice,
getSupportedBootDevices: getSupportedBootDevices,
nodeGetConsole: nodeGetConsole,
nodeSetConsoleMode: nodeSetConsoleMode,
nodeSetMaintenance: nodeSetMaintenance,
nodeSetBootDevice: nodeSetBootDevice,
nodeSetPowerState: nodeSetPowerState,
setNodeProvisionState: setNodeProvisionState,
updateNode: updateNode,
updatePort: updatePort,
validateNode: validateNode,
createPortgroup: createPortgroup,
getPortgroups: getPortgroups,
deletePortgroup: deletePortgroup,
getPortgroupPorts: getPortgroupPorts
};
return service;
/**
* @description Retrieve a list of nodes
* http://developer.openstack.org/api-ref/baremetal/?
* expanded=create-node-detail#list-nodes-detailed
*
* @return {promise} Node collection in JSON
*/
function getNodes() {
return apiService.get('/api/ironic/nodes/')
.then(function(response) {
angular.forEach(response.data.nodes, function(node) {
nodeErrorService.checkNodeError(node);
});
return response.data.nodes;
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve Ironic nodes. %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Retrieve information about the given node.
*
* http://developer.openstack.org/api-ref/baremetal/?
* expanded=create-node-detail#list-nodes-detailed
*
* @param {string} uuid UUID or logical name of a node.
* @return {promise} Node
*/
function getNode(uuid) {
return apiService.get('/api/ironic/nodes/' + uuid)
.then(function(response) {
nodeErrorService.checkNodeError(response.data);
return response.data; // The node
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve the Ironic node: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Retrieve the boot device for a node
* https://developer.openstack.org/api-ref/baremetal/#get-boot-device
*
* @param {string} nodeId UUID or logical name of a node.
* @return {promise} Dictionary describing the current boot device
*/
function getBootDevice(nodeId) {
return apiService.get('/api/ironic/nodes/' + nodeId + '/boot_device')
.then(function(response) {
return response.data;
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve boot device for Ironic node. %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Retrieve the supported boot devices for a node
* https://developer.openstack.org/api-ref/baremetal/#get-supported-boot-devices
*
* @param {string} nodeId UUID or logical name of a node.
* @return {promise} List of supported boot devices
*/
function getSupportedBootDevices(nodeId) {
return apiService.get('/api/ironic/nodes/' + nodeId +
'/boot_device/supported')
.then(function(response) {
return response.data; // List of supported boot devices
})
.catch(function(response) {
var msg = interpolate(
gettext(
'Unable to retrieve supported boot devices for Ironic node. %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Retrieve a list of ports associated with a node.
*
* http://developer.openstack.org/api-ref/baremetal/#list-detailed-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)
.then(function(response) {
// Add id and name properties to support delete operations
// using the deleteModalService
angular.forEach(response.data.ports, function(port) {
port.id = port.uuid;
port.name = port.address;
});
return response.data.ports;
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve the Ironic node ports: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Set the maintenance state of a node
*
* http://developer.openstack.org/api-ref/baremetal/#set-maintenance-flag
*
* @param {string} nodeId UUID or logical name of a node.
* @param {boolean} mode - True to put the node in maintenance mode,
* false to remove it from maintenance mode.
* @param {string} reason - Reason for putting the node in maintenance.
* @return {promise} Promise
*/
function nodeSetMaintenance(nodeId, mode, reason) {
var url = '/api/ironic/nodes/' + nodeId + '/maintenance';
var promise = mode
? apiService.patch(url,
{maint_reason: reason ? reason
: gettext("No reason given.")})
: apiService.delete(url);
return promise.catch(function(response) {
var msg = interpolate(
gettext('Unable to set Ironic node %s maintenance state: %s'),
[nodeId, response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Set the boot device of a node
*
* http://developer.openstack.org/api-ref/baremetal/#set-boot-device
*
* @param {string} nodeId UUID or logical name of a node.
* @param {string} bootDevice - Selected boot device.
* @param {Boolean} persistent - True or False.
* @return {promise} Promise
*/
function nodeSetBootDevice(nodeId, bootDevice, persistent) {
return apiService.put('/api/ironic/nodes/' + nodeId + '/boot_device',
{boot_device: bootDevice,
persistent: persistent})
.then(function() {
toastService.add('success',
gettext('Refresh page to see set boot device'));
})
.catch(function(response) {
var msg = interpolate(gettext('Unable to set boot device: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Set the power state of the node.
*
* http://developer.openstack.org/api-ref/baremetal/#change-node-power-state
*
* @param {string} uuid UUID or logical name of a node.
* @param {string} state - Target power state ['on', 'off', 'reboot']
* @param {boolean} soft - Flag for graceful power 'off' or reboot
* @return {promise} Promise
*/
function nodeSetPowerState(uuid, state, soft) {
var data = {
state: state
};
if (angular.isDefined(soft)) {
data.soft = soft;
}
return apiService.patch('/api/ironic/nodes/' + uuid + '/states/power',
data)
.then(function() {
toastService.add('success',
gettext('Refresh page to see updated power status'));
})
.catch(function(response) {
var msg = interpolate(gettext('Unable to power off the node: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Set the target provision state of the node.
*
* http://developer.openstack.org/api-ref/baremetal/#change-node-provision-state
*
* @param {string} uuid UUID of a node.
* @param {string} verb Provisioning verb used to move node to desired
* target state
* @param {object []} cleanSteps - List of cleaning steps. Only used
* when the value of verb is 'clean'
* @return {promise} Promise
*/
function setNodeProvisionState(uuid, verb, cleanSteps) {
return apiService.put('/api/ironic/nodes/' + uuid + '/states/provision',
{verb: verb,
clean_steps: cleanSteps})
.then(function() {
var msg = gettext(
'A request has been made to change the provisioning state of node %s');
toastService.add('success', interpolate(msg, [uuid], false));
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to set node provision state: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Create an Ironic node
*
* http://developer.openstack.org/api-ref/baremetal/#create-node
*
* @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)
.catch(function(response) {
var msg = interpolate(gettext('Unable to create node: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Delete the specified node from inventory
*
* http://developer.openstack.org/api-ref/baremetal/#delete-node
*
* @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)
.catch(function(response) {
var msg = interpolate(gettext('Unable to delete node %s: %s'),
[nodeIdent, response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Update the definition of a specified node.
*
* http://developer.openstack.org/api-ref/baremetal/#update-node
*
* @param {string} uuid UUID of a node.
* @param {object[]} patch Sequence of update operations
* @return {promise} Promise
*/
function updateNode(uuid, patch) {
var data = {
patch: patch
};
return apiService.patch('/api/ironic/nodes/' + uuid, data)
.then(function(response) {
var msg = gettext('Successfully updated node %s');
toastService.add('success', interpolate(msg, [uuid], false));
return response.data; // The updated node
})
.catch(function(response) {
var msg = interpolate(gettext('Unable to update node %s: %s'),
[uuid, response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Validate the specified node
*
* http://developer.openstack.org/api-ref/baremetal/#validate-node
*
* @param {string} nodeId UUID or logical name of a node.
* @return {promise} Promise. success: list of interface validation
* records, error: failure response
*/
function validateNode(nodeId) {
return apiService.get('/api/ironic/nodes/' + nodeId + '/validate',
{node: nodeId})
.catch(function(response) {
var msg = interpolate(gettext('Unable to validate node %s: %s'),
[nodeId, response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Retrieve the list of Ironic drivers
*
* http://developer.openstack.org/api-ref/baremetal/#list-drivers
*
* @return {promise} Driver collection in JSON
*/
function getDrivers() {
return apiService.get('/api/ironic/drivers/')
.then(function(response) {
return response.data.drivers;
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve Ironic drivers: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Retrieve properities of a specified driver
*
* http://developer.openstack.org/api-ref/baremetal/#show-driver-properties
*
* @param {string} driverName - Driver name
* @returns {promise} Property list
*/
function getDriverProperties(driverName) {
return apiService.get(
'/api/ironic/drivers/' + driverName + '/properties')
.then(function(response) {
return response.data; // Driver properties
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve driver properties: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Create a network port
*
* http://developer.openstack.org/api-ref/baremetal/#create-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)
.then(function(response) {
toastService.add('success',
gettext('Port successfully created'));
return response.data; // The newly created port
})
.catch(function(response) {
var msg = interpolate(gettext('Unable to create port: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Delete a network port
*
* http://developer.openstack.org/api-ref/baremetal/#delete-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)
.catch(function(response) {
var msg = interpolate(gettext('Unable to delete port: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Update the definition of a specified port.
*
* http://developer.openstack.org/api-ref/baremetal/#update-a-port
*
* @param {string} portUuid UUID of a port.
* @param {object[]} patch Sequence of update operations
* @return {promise} Promise
*/
function updatePort(portUuid, patch) {
return apiService.patch('/api/ironic/ports/' + portUuid,
{patch: patch})
.then(function(response) {
var msg = gettext('Successfully updated port %s');
toastService.add('success', interpolate(msg, [portUuid], false));
return response.data; // The updated port
})
.catch(function(response) {
var msg = interpolate(gettext('Unable to update port %s: %s'),
[portUuid, response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Set the console mode of the node.
*
* http://developer.openstack.org/api-ref/baremetal/?
* expanded=start-stop-console-detail#start-stop-console
*
* @param {string} uuid UUID of a node.
* @param {boolean} enabled true to start the console, false to stop it
* @return {promise} Promise
*/
function nodeSetConsoleMode(uuid, enabled) {
return apiService.put('/api/ironic/nodes/' + uuid + '/states/console',
{enabled: enabled})
.then(function(response) {
var msg = gettext('Refresh page to see updated console details');
toastService.add('success', interpolate(msg, [uuid], false));
return response.data;
})
.catch(function(response) {
var msg = gettext('Unable to set console mode: %s');
toastService.add('error', interpolate(msg, [response.data], false));
return $q.reject(msg);
});
}
function nodeGetConsole(uuid) {
return apiService.get('/api/ironic/nodes/' + uuid + '/states/console')
.then(function(response) {
return response.data; // Object containing console information
})
.catch(function(response) {
var msg = gettext('Unable to get console for node %s: %s');
toastService.add('error',
interpolate(msg, [uuid, response.data], false));
return $q.reject(msg);
});
}
/**
* @description Retrieve a list of portgroups associated with a node.
*
* http://developer.openstack.org/api-ref/baremetal/#list-detailed-portgroups
*
* @param {string} nodeId UUID or logical name of a node.
* @return {promise} List of portgroups.
*/
function getPortgroups(nodeId) {
return apiService.get('/api/ironic/portgroups/',
{params: {node_id: nodeId}})
.then(function(response) {
// Add id property to support delete operations
// using the deleteModalService
angular.forEach(response.data.portgroups, function(portgroup) {
portgroup.id = portgroup.uuid;
});
return response.data.portgroups;
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve Ironic node portgroups: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Create a protgroup.
*
* http://developer.openstack.org/api-ref/baremetal/#create-portgroup
*
* @param {object} params Object containing parameters that define
* the portgroup to be created.
* @return {promise} Promise containing the portgroup.
*/
function createPortgroup(params) {
return apiService.post('/api/ironic/portgroups/', params)
.then(function(response) {
toastService.add('success',
gettext('Portgroup successfully created'));
return response.data; // The newly created portgroup
})
.catch(function(response) {
var msg = interpolate(gettext('Unable to create portgroup: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Delete a portgroup.
*
* http://developer.openstack.org/api-ref/baremetal/#delete-portgroup
*
* @param {string} portgroupId UUID or name of the portgroup to be deleted.
* @return {promise} Promise.
*/
function deletePortgroup(portgroupId) {
return apiService.delete('/api/ironic/portgroups/',
{portgroup_id: portgroupId})
.catch(function(response) {
var msg = interpolate(gettext('Unable to delete portgroup: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
/**
* @description Get the ports associated with a specified portgroup.
*
* http://developer.openstack.org/api-ref/baremetal/#list-ports-by-portgroup
*
* @param {string} portgroupId UUID or name of the portgroup.
* @return {promise} Promise containing a list of ports.
*/
function getPortgroupPorts(portgroupId) {
return apiService.get(
'/api/ironic/portgroups/' + portgroupId + '/ports')
.then(function(response) {
return response.data.ports; // List of ports
})
.catch(function(response) {
var msg = interpolate(
gettext('Unable to retrieve portgroup ports: %s'),
[response.data],
false);
toastService.add('error', msg);
return $q.reject(msg);
});
}
}
}());

View File

@ -1,600 +0,0 @@
/**
* 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 IRONIC_API_PROPERTIES = [
'createNode',
'createPort',
'createPortgroup',
'deleteNode',
'deletePort',
'deletePortgroup',
'getDrivers',
'getDriverProperties',
'getNode',
'getNodes',
'getPortgroupPorts',
'getPortgroups',
'getPortsWithNode',
'getBootDevice',
'getSupportedBootDevices',
'nodeGetConsole',
'nodeSetBootDevice',
'nodeSetConsoleMode',
'nodeSetPowerState',
'nodeSetMaintenance',
'setNodeProvisionState',
'updateNode',
'updatePort',
'validateNode'
];
/**
* @description Unit tests for the Ironic-UI API service
*/
describe(
'horizon.dashboard.admin.ironic.service',
function() {
// Name of default driver used to create nodes.
var ironicAPI, ironicBackendMockService, defaultDriver;
/**
* @description Create a node.
*
* @param {object} params - Dictionary of parameters that define the node.
* @return {promise} - Promise containing the newly created node.
*/
function createNode(params) {
return ironicAPI.createNode(params)
.then(function(response) {
return response.data; // node
});
}
/**
* @description Fail the current test
*
* @return {void}
*/
function failTest() {
fail();
}
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module('horizon.framework.util'));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service', {
add: function() {}
});
}));
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(function($injector) {
ironicBackendMockService =
$injector.get('horizon.dashboard.admin.ironic.backend-mock.service');
ironicBackendMockService.init();
defaultDriver = ironicBackendMockService.params.defaultDriver;
}));
beforeEach(inject(function($injector) {
ironicAPI =
$injector.get('horizon.app.core.openstack-service-api.ironic');
}));
it('defines the ironicAPI', function() {
expect(ironicAPI).toBeDefined();
});
afterEach(function() {
ironicBackendMockService.postTest();
});
describe('ironicAPI', function() {
it('service API', function() {
expect(Object.getOwnPropertyNames(ironicAPI).sort())
.toEqual(IRONIC_API_PROPERTIES.sort());
});
it('getDrivers', function() {
ironicAPI.getDrivers()
.then(function(drivers) {
expect(drivers.length).toBeGreaterThan(0);
angular.forEach(drivers, function(driver) {
expect(driver.name).toBeDefined();
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('createNode - Minimal input data', function() {
createNode({driver: defaultDriver})
.then(function(node) {
expect(node.driver).toEqual(defaultDriver);
expect(node).toEqual(ironicBackendMockService.getNode(node.uuid));
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('createNode - Missing input data', function() {
createNode({})
.then(failTest);
ironicBackendMockService.flush();
});
it('getNode', function() {
createNode({driver: defaultDriver})
.then(function(node1) {
ironicAPI.getNode(node1.uuid).then(function(node2) {
expect(node2).toEqual(node1);
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('deleteNode', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.deleteNode(node.uuid).then(function() {
return node;
});
})
.then(function(node) {
expect(
ironicBackendMockService.getNode(node.uuid)).toBeNull();
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('deleteNode - nonexistent node', function() {
ironicAPI.deleteNode(0)
.then(failTest);
ironicBackendMockService.flush();
});
it('updateNode - resource_class', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.updateNode(
node.uuid,
[{op: "replace",
path: "/resource_class",
value: "some-resource-class"}]).then(
function(node) {
return node;
});
})
.then(function(node) {
expect(node.resource_class).toEqual("some-resource-class");
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('nodeGetConsole - console enabled', function() {
createNode({driver: defaultDriver,
console_enabled: true})
.then(function(node) {
expect(node.console_enabled).toEqual(true);
return node;
})
.then(function(node) {
return ironicAPI.nodeGetConsole(node.uuid).then(
function(consoleData) {
return {node: node, consoleData: consoleData};
});
})
.then(function(data) {
expect(data.consoleData.console_enabled).toEqual(true);
expect(data.consoleData.console_info.console_type)
.toEqual(ironicBackendMockService.params.consoleType);
expect(data.consoleData.console_info.url)
.toEqual(ironicBackendMockService.nodeGetConsoleUrl(
data.node.uuid));
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('nodeGetConsole - console not enabled', function() {
createNode({driver: defaultDriver,
console_enabled: false})
.then(function(node) {
expect(node.console_enabled).toEqual(false);
return node;
})
.then(function(node) {
return ironicAPI.nodeGetConsole(node.uuid);
})
.then(function(consoleData) {
expect(consoleData).toEqual(
{console_enabled: false,
console_info: null});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('nodeSetConsoleMode - Toggle console mode', function() {
createNode({driver: defaultDriver,
console_enabled: false})
.then(function(node) {
expect(node.console_enabled).toEqual(false);
return node;
})
.then(function(node) {
ironicAPI.nodeSetConsoleMode(node.uuid, true);
return node;
})
.then(function(node) {
return ironicAPI.getNode(node.uuid);
})
.then(function(node) {
expect(node.console_enabled).toEqual(true);
return node;
})
// Toggle back
.then(function(node) {
ironicAPI.nodeSetConsoleMode(node.uuid, false);
return node;
})
.then(function(node) {
return ironicAPI.getNode(node.uuid);
})
.then(function(node) {
expect(node.console_enabled).toEqual(false);
return node;
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('nodeSetConsoleMode - Redundant console set', function() {
createNode({driver: defaultDriver,
console_enabled: false})
.then(function(node) {
expect(node.console_enabled).toEqual(false);
return node;
})
.then(function(node) {
ironicAPI.nodeSetConsoleMode(node.uuid, false);
return node;
})
.then(function(node) {
return ironicAPI.getNode(node.uuid);
})
.then(function(node) {
expect(node.console_enabled).toEqual(false);
return node;
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('getBootDevice', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.getBootDevice(node.uuid)
.then(function(bootDevice) {
return {node: node, bootDevice: bootDevice};
});
})
.then(function(data) {
expect(data.bootDevice).toEqual(
ironicBackendMockService.getNodeBootDevice(data.node.uuid));
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('getSupportedBootDevices', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.getSupportedBootDevices(node.uuid);
})
.then(function(bootDevices) {
expect(bootDevices).toEqual(
ironicBackendMockService.params.supportedBootDevices);
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('nodeSetBootDevice', function() {
var bootDevice = {
boot_device: "bios",
persistent: false
};
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.nodeSetBootDevice(node.uuid,
bootDevice.boot_device,
bootDevice.persistent)
.then(function() {
return node;
});
})
.then(function(node) {
ironicAPI.getBootDevice(node.uuid).then(function(device) {
expect(device).toEqual(bootDevice);
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('nodeSetBootDevice - bad device', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.getBootDevice(node.uuid)
.then(function(device) {
return {node: node, currentBootDevice: device};
});
})
.then(function(data) {
ironicAPI.nodeSetBootDevice(data.node.uuid,
"bad-device",
false)
.then(failTest)
.catch(function() {
// Ensure the boot device is unchanged
ironicAPI.getBootDevice(data.node.uuid)
.then(function(device) {
expect(device).toEqual(data.currentBootDevice);
});
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('createPort', function() {
var macAddr = '00:00:00:00:00:00';
var node;
createNode({driver: defaultDriver})
.then(function(createNode) {
node = createNode;
return ironicAPI.createPort({address: macAddr,
node_uuid: node.uuid});
})
.then(function(port) {
expect(port.address).toBe(macAddr);
expect(port.node_uuid).toBe(node.uuid);
expect(port)
.toEqual(ironicBackendMockService.getPort(port.uuid));
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('createPort - missing input data', function() {
ironicAPI.createPort({})
.then(failTest);
ironicBackendMockService.flush();
});
it('createPort - bad input data', function() {
ironicAPI.createPort({address: "", node_uuid: ""})
.then(failTest);
ironicBackendMockService.flush();
});
it('createPort - duplicate mac address', function() {
var macAddr = '00:00:00:00:00:00';
var node;
createNode({driver: defaultDriver})
.then(function(createNode) {
node = createNode;
return ironicAPI.createPort({address: macAddr,
node_uuid: node.uuid});
})
.then(function(port) {
expect(port.address).toBe(macAddr);
expect(port.node_uuid).toBe(node.uuid);
expect(port)
.toEqual(ironicBackendMockService.getPort(port.uuid));
return ironicAPI.createPort({address: macAddr,
node_uuid: node.uuid});
})
.then(failTest);
ironicBackendMockService.flush();
});
it('deletePort', function() {
var macAddr = '00:00:00:00:00:00';
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.createPort({address: macAddr,
node_uuid: node.uuid});
})
.then(function(port) {
expect(port).toBeDefined();
expect(port)
.toEqual(ironicBackendMockService.getPort(port.uuid));
ironicAPI.deletePort(port.uuid).then(function() {
expect(ironicBackendMockService.getPort(port.uuid))
.toBeNull();
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('deletePort - nonexistent port', function() {
ironicAPI.deletePort(0)
.then(failTest);
ironicBackendMockService.flush();
});
it('createPortgroup', function() {
var node;
createNode({driver: defaultDriver})
.then(function(createNode) {
node = createNode;
return ironicAPI.createPortgroup({node_uuid: node.uuid});
})
.then(function(portgroup) {
expect(portgroup.node_uuid).toBe(node.uuid);
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.uuid));
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('createPortgroup - specify portgroup name', function() {
var node;
var portgroupName = "test-portgroup";
createNode({driver: defaultDriver})
.then(function(createNode) {
node = createNode;
return ironicAPI.createPortgroup({node_uuid: node.uuid,
name: portgroupName});
})
.then(function(portgroup) {
expect(portgroup.node_uuid).toBe(node.uuid);
expect(portgroup.name).toBe(portgroupName);
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.uuid));
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.name));
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('createPortgroup - missing input data', function() {
ironicAPI.createPortgroup({})
.then(failTest);
ironicBackendMockService.flush();
});
it('createPort - bad input data', function() {
ironicAPI.createPort({node_uuid: ""})
.then(failTest);
ironicBackendMockService.flush();
});
it('deletePortgroup', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.createPortgroup({node_uuid: node.uuid});
})
.then(function(portgroup) {
expect(portgroup).toBeDefined();
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.uuid));
ironicAPI.deletePortgroup(portgroup.uuid).then(function() {
expect(ironicBackendMockService.getPortgroup(portgroup.uuid))
.toBeNull();
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('deletePortgroup - by name', function() {
var portgroupName = "delete-portgroup";
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.createPortgroup({node_uuid: node.uuid,
name: portgroupName});
})
.then(function(portgroup) {
expect(portgroup).toBeDefined();
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.uuid));
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.name));
ironicAPI.deletePortgroup(portgroup.name).then(function() {
expect(ironicBackendMockService.getPortgroup(portgroup.name))
.toBeNull();
expect(ironicBackendMockService.getPortgroup(portgroup.uuid))
.toBeNull();
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
it('deletePortgroup - nonexistent portgroup', function() {
ironicAPI.deletePortgroup(0)
.then(failTest);
ironicBackendMockService.flush();
});
it('getPortgroupPorts', function() {
createNode({driver: defaultDriver})
.then(function(node) {
return ironicAPI.createPortgroup({node_uuid: node.uuid});
})
.then(function(portgroup) {
expect(portgroup).toBeDefined();
expect(portgroup)
.toEqual(ironicBackendMockService.getPortgroup(portgroup.uuid));
ironicAPI.getPortgroupPorts(portgroup.uuid).then(function(ports) {
expect(ports).toEqual([]);
});
})
.catch(failTest);
ironicBackendMockService.flush();
});
});
});
})();

View File

@ -1,48 +0,0 @@
/*
* Copyright 2016 Cray Inc.
* Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
/**
* @ngdoc controller
* @name horizon.dashboard.admin.ironic:MaintenanceController
* @ngController
*
* @description
* Controller used to prompt the user for information associated with
* putting one or more nodes into maintenance mode
*/
angular
.module('horizon.dashboard.admin.ironic')
.controller('MaintenanceController', MaintenanceController);
MaintenanceController.$inject = [
'$uibModalInstance'
];
function MaintenanceController($uibModalInstance) {
var ctrl = this;
ctrl.cancel = function() {
$uibModalInstance.dismiss('cancel');
};
ctrl.putInMaintenanceMode = function(maintReason) {
$uibModalInstance.close(maintReason);
};
}
})();

View File

@ -1,31 +0,0 @@
<div class="modal-header">
<h3 class="modal-title" translate>Put Node(s) Into Maintenance Mode</h3>
</div>
<div class="modal-body clearfix">
<div class="content">
<div translate class="subtitle">Provide a reason for why you are putting the selected node(s) into maintenance mode (optional)</div>
<div class="form-group">
<div class="form-field">
<input type="text"
class="form-control input-sm"
ng-model="maintReason"
auto-focus
placeholder=""/>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="btn btn-default secondary"
type="button"
ng-click="ctrl.cancel()"
translate>
Cancel
</button>
<button class="btn btn-primary"
type="button"
ng-click="ctrl.putInMaintenanceMode(maintReason)"
translate>
Put Node(s) Into Maintenance Mode
</button>
</div>

View File

@ -1,71 +0,0 @@
/*
* Copyright 2016 Cray Inc.
* Copyright (c) 2016 Hewlett Packard Enterprise Development Company LP
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
(function() {
'use strict';
/*
* @ngdoc service
* @name horizon.dashboard.admin.ironic.maintenance.service
* @description Service for putting nodes in, and removing them from
* maintenance mode
*/
angular
.module('horizon.dashboard.admin.ironic')
.factory('horizon.dashboard.admin.ironic.maintenance.service',
maintenanceService);
maintenanceService.$inject = [
'$uibModal',
'horizon.dashboard.admin.ironic.basePath',
'horizon.dashboard.admin.ironic.actions'
];
function maintenanceService($uibModal, basePath, nodeActions) {
var service = {
setMaintenance: setMaintenance
};
return service;
/*
* @description Set the maintenance mode of a specified list of nodes
*
* If nodes are being put into maintenance mode a modal dialog is used
* to prompt the user for a reason.
*
* @param {object[]} nodes - List of node objects
* @param {boolean} mode - Desired maintenance state.
* 'true' -> Node is in maintenance mode
* 'false' -> Node is not in maintenance mode
* @return {promise}
*/
function setMaintenance(nodes, mode) {
var promise;
if (mode) {
var options = {
controller: "MaintenanceController as ctrl",
templateUrl: basePath + '/maintenance/maintenance.html'
};
promise = $uibModal.open(options).result.then(function(reason) {
return nodeActions.setMaintenance(nodes, true, reason);
});
} else {
promise = nodeActions.setMaintenance(nodes, false);
}
return promise;
}
}
})();

View File

@ -1,46 +0,0 @@
/*
* 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");
}
};
}
})();

View File

@ -1,244 +0,0 @@
/*
* © Copyright 2015,2016 Hewlett Packard Enterprise Development Company LP
* 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';
function PowerTransition(label, state, soft) {
this.label = label;
this.state = state;
this.soft = soft;
}
var POWER_ON_TRANSITIONS = [
new PowerTransition(gettext('Power on'), 'on', false)
];
var POWER_OFF_TRANSITIONS = [
new PowerTransition(gettext('Power off'), 'off', false),
new PowerTransition(gettext('Soft power off'), 'off', true),
new PowerTransition(gettext('Reboot'), 'reboot', false),
new PowerTransition(gettext('Soft reboot'), 'reboot', true)
];
var ALL_POWER_TRANSITIONS =
POWER_ON_TRANSITIONS.concat(POWER_OFF_TRANSITIONS);
angular
.module('horizon.dashboard.admin.ironic')
.factory('horizon.dashboard.admin.ironic.actions', actions);
actions.$inject = [
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.events',
'horizon.framework.widgets.modal.deleteModalService',
'horizon.dashboard.admin.ironic.clean-node.service',
'$q',
'$rootScope'
];
function actions(ironic,
ironicEvents,
deleteModalService,
cleanNodeService,
$q,
$rootScope) {
var service = {
deleteNode: deleteNode,
deletePort: deletePort,
deletePortgroups: deletePortgroups,
setPowerState: setPowerState,
setMaintenance: setMaintenance,
setProvisionState: setProvisionState,
getPowerTransitions : getPowerTransitions
};
return service;
function deleteNode(nodes) {
var context = {
labels: {
title: ngettext("Delete Node",
"Delete Nodes",
nodes.length),
message: ngettext('Are you sure you want to delete node "%s"? ' +
'This action cannot be undone.',
'Are you sure you want to delete nodes "%s"? ' +
'This action cannot be undone.',
nodes.length),
submit: ngettext("Delete Node",
"Delete Nodes",
nodes.length),
success: ngettext('Successfully deleted node "%s"',
'Successfully deleted nodes "%s"',
nodes.length),
error: ngettext('Unable to delete node "%s"',
'Unable to delete nodes "%s"',
nodes.length)
},
deleteEntity: ironic.deleteNode,
successEvent: ironicEvents.DELETE_NODE_SUCCESS
};
return deleteModalService.open($rootScope, nodes, context);
}
// power state
/**
* @description Set the power state of a list of nodes
*
* @param {object[]} nodes - List of node objects
* @param {string} state - Target power state
* @param {boolean} [soft] - Flag for graceful power 'off' or reboot
* @return {promise} promise
*/
function setPowerState(nodes, state, soft) {
var promises = [];
angular.forEach(nodes,
function(node) {
promises.push(
ironic.nodeSetPowerState(node.uuid,
state,
soft)
);
});
return $q.all(promises);
}
// maintenance
/**
* @description Set the maintenance state of a list of nodes
*
* @param {object[]} nodes - List of node objects
* @param {boolean} mode - True if the nodes are to be put in
* maintenance mode, otherwise false.
* @param {string} [reason] - Optional reason for putting nodes in
* maintenance mode.
* @return {promise} promise
*/
function setMaintenance(nodes, mode, reason) {
var promises = [];
angular.forEach(nodes, function(node) {
var promise;
if (node.maintenance === mode) {
var msg = gettext(
"Node %s is already in target maintenance state.");
promise = $q.reject(interpolate(msg, [node.uuid], false));
} else {
promise = ironic.nodeSetMaintenance(node.uuid, mode, reason).then(
function (result) {
node.maintenance = mode;
node.maintenance_reason =
mode && angular.isDefined(reason) ? reason : "";
return result;
}
);
}
promises.push(promise);
});
return $q.all(promises);
}
/*
* @name horizon.dashboard.admin.ironic.actions.setProvisionState
* @description Set the provisioning state of a specified node
*
* @param {object} args - Object with two properties named 'node'
* and 'verb'.
* node: node object.
* verb: string the value of which is the verb used to move
* the node to the desired target state for the node.
*/
function setProvisionState(args) {
if (args.verb === 'clean') {
cleanNodeService.clean(args.node);
} else {
ironic.setNodeProvisionState(args.node.uuid, args.verb);
}
}
function deletePort(ports) {
var context = {
labels: {
title: ngettext("Delete Port",
"Delete Ports",
ports.length),
message: ngettext('Are you sure you want to delete port "%s"? ' +
'This action cannot be undone.',
'Are you sure you want to delete ports "%s"? ' +
'This action cannot be undone.',
ports.length),
submit: ngettext("Delete Port",
"Delete Ports",
ports.length),
success: ngettext('Successfully deleted port "%s"',
'Successfully deleted ports "%s"',
ports.length),
error: ngettext('Unable to delete port "%s"',
'Unable to delete ports "%s"',
ports.length)
},
deleteEntity: ironic.deletePort,
successEvent: ironicEvents.DELETE_PORT_SUCCESS
};
return deleteModalService.open($rootScope, ports, context);
}
function deletePortgroups(portgroups) {
var context = {
labels: {
title: ngettext("Delete Portgroup",
"Delete Portgroups",
portgroups.length),
message: ngettext('Are you sure you want to delete portgroup "%s"? ' +
'This action cannot be undone.',
'Are you sure you want to delete portgroups "%s"? ' +
'This action cannot be undone.',
portgroups.length),
submit: ngettext("Delete Portgroup",
"Delete Portgroups",
portgroups.length),
success: ngettext('Successfully deleted portgroup "%s"',
'Successfully deleted portgroups "%s"',
portgroups.length),
error: ngettext('Unable to delete portgroup "%s"',
'Unable to delete portgroups "%s"',
portgroups.length)
},
deleteEntity: ironic.deletePortgroup
};
return deleteModalService.open($rootScope, portgroups, context);
}
/*
* @name horizon.dashboard.admin.ironic.actions.getPowerTransitions
* @description Get the list of power transitions for a specified
* node, or all power transitions if the node is not specified.
*
* @param {object} node Node object for which power transitions
* are requested. If node is undefined all possible power transitions
* are returned.
* @return {object[]} - List of PowerTransition objects
*/
function getPowerTransitions(node) {
return angular.isUndefined(node) ? ALL_POWER_TRANSITIONS
: node.power_state === 'power on'
? POWER_OFF_TRANSITIONS : POWER_ON_TRANSITIONS;
}
}
})();

View File

@ -1,322 +0,0 @@
/*
* Copyright 2015 Hewlett Packard Enterprise Development Company LP
* 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')
.controller('horizon.dashboard.admin.ironic.NodeDetailsController',
IronicNodeDetailsController);
IronicNodeDetailsController.$inject = [
'$scope',
'$location',
'horizon.framework.widgets.toast.service',
'horizon.app.core.openstack-service-api.ironic',
'horizon.dashboard.admin.ironic.actions',
'horizon.dashboard.admin.ironic.basePath',
'horizon.dashboard.admin.ironic.edit-node.service',
'horizon.dashboard.admin.ironic.create-port.service',
'horizon.dashboard.admin.ironic.edit-port.service',
'horizon.dashboard.admin.ironic.maintenance.service',
'horizon.dashboard.admin.ironic.bootdevice.service',
'horizon.dashboard.admin.ironic.node-state-transition.service',
'horizon.dashboard.admin.ironic.validUuidPattern'
];
function IronicNodeDetailsController($scope,
$location,
toastService,
ironic,
actions,
basePath,
editNodeService,
createPortService,
editPortService,
maintenanceService,
bootDeviceService,
nodeStateTransitionService,
validUuidPattern) {
var ctrl = this;
var path = basePath + '/node-details/sections/';
ctrl.noPortsText = gettext('No network ports have been defined');
ctrl.noPortgroupsText = gettext('No portgroups have been defined');
ctrl.actions = actions;
ctrl.maintenanceService = maintenanceService;
ctrl.bootDeviceService = bootDeviceService;
ctrl.sections = [
{
heading: gettext('Overview'),
templateUrl: path + 'overview.html'
},
{
heading: gettext('Configuration'),
templateUrl: path + 'configuration.html'
}
];
ctrl.portDetailsTemplateUrl = path + "port-details.html";
ctrl.portgroupDetailsTemplateUrl = path + "portgroup-details.html";
ctrl.node = null;
ctrl.nodeValidation = [];
ctrl.nodeValidationMap = {}; // Indexed by interface
ctrl.nodeStateTransitions = [];
ctrl.nodePowerTransitions = [];
ctrl.ports = [];
ctrl.portsSrc = [];
ctrl.portgroups = [];
ctrl.portgroupsSrc = [];
ctrl.basePath = basePath;
ctrl.re_uuid = new RegExp(validUuidPattern);
ctrl.isUuid = isUuid;
ctrl.getVifPortId = getVifPortId;
ctrl.editNode = editNode;
ctrl.createPort = createPort;
ctrl.deletePort = deletePort;
ctrl.editPort = editPort;
ctrl.refresh = refresh;
ctrl.toggleConsoleMode = toggleConsoleMode;
ctrl.deletePortgroups = deletePortgroups;
$scope.emptyObject = function(obj) {
return angular.isUndefined(obj) || Object.keys(obj).length === 0;
};
$scope.isDefined = angular.isDefined;
init();
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.init
* @description Initialize the controller instance based on the
* current page url.
*
* @return {void}
*/
function init() {
// Fetch the Node ID from the URL.
var pattern = /(.*\/admin\/ironic\/)(.+)\/(detail)?/;
var uuid = $location.absUrl().match(pattern)[2];
retrieveNode(uuid).then(function () {
ctrl.nodeStateTransitions =
nodeStateTransitionService.getTransitions(ctrl.node.provision_state);
ctrl.nodePowerTransitions = actions.getPowerTransitions(ctrl.node);
retrievePorts();
retrieveBootDevice();
retrievePortgroups();
validateNode();
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.retrieveNode
* @description Retrieve the node instance for a specified node id,
* and store it in the controller instance.
*
* @param {string} uuid Node name or UUID
* @return {promise} promise
*/
function retrieveNode(uuid) {
return ironic.getNode(uuid).then(function (node) {
ctrl.node = node;
ctrl.node.id = ctrl.node.uuid;
ironic.nodeGetConsole(uuid).then(function(consoleData) {
ctrl.node.console_enabled = consoleData.console_enabled;
ctrl.node.console_info = consoleData.console_info;
});
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.retrievePorts
* @description Retrieve the ports associated with the current node,
* and store them in the controller instance.
*
* @return {void}
*/
function retrievePorts() {
ironic.getPortsWithNode(ctrl.node.uuid).then(function (ports) {
ctrl.portsSrc = ports;
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.retrieveBootDevice
* @description Retrieve the boot device associated with the current node,
* and store it in the controller instance.
*
* @return {void}
*/
function retrieveBootDevice() {
ironic.getBootDevice(ctrl.node.uuid).then(function (bootDevice) {
ctrl.node.bootDevice = bootDevice;
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.retrievePortgroups
* @description Retrieve the port groups associated with the current node,
* and store them in the controller instance.
*
* @return {void}
*/
function retrievePortgroups() {
ironic.getPortgroups(ctrl.node.uuid).then(function(portgroups) {
ctrl.portgroupsSrc = portgroups;
angular.forEach(portgroups, function(portgroup) {
portgroup.ports = [];
ironic.getPortgroupPorts(portgroup.uuid).then(function(ports) {
portgroup.ports = ports;
});
});
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.validateNode
* @description Retrieve the ports associated with the current node,
* and store them in the controller instance.
*
* @return {void}
*/
function validateNode() {
ironic.validateNode(ctrl.node.uuid).then(function(response) {
var nodeValidation = [];
ctrl.nodeValidationMap = {};
angular.forEach(response.data, function(status) {
status.id = status.interface;
nodeValidation.push(status);
ctrl.nodeValidationMap[status.interface] = status;
});
ctrl.nodeValidation = nodeValidation;
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.isUuid
* @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
*/
function isUuid(str) {
return !!str.match(ctrl.re_uuid);
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.getVifPortId
* @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
*/
function getVifPortId(port) {
return angular.isDefined(port.extra) &&
angular.isDefined(port.extra.vif_port_id)
? port.extra.vif_port_id : "";
}
/**
* @description: Edit the current node
*
* @return {void}
*/
function editNode() {
editNodeService.editNode(ctrl.node).then(function() {
ctrl.refresh();
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.createPort
* @description Initiate creation of a newtwork port for the current
* node
*
* @return {void}
*/
function createPort() {
createPortService.createPort(ctrl.node).then(function() {
ctrl.refresh();
});
}
/**
* @description: Edit a specified port
*
* @param {port} port - Port to be edited
* @return {void}
*/
function editPort(port) {
editPortService.editPort(port, ctrl.node).then(function() {
ctrl.refresh();
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.deletePort
* @description Delete a list of ports
*
* @param {port []} ports ports to be deleted
* @return {void}
*/
function deletePort(ports) {
actions.deletePort(ports).then(function() {
ctrl.refresh();
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.portgroupDelete
* @description Delete a list of portgroups.
*
* @param {port []} portgroups portgroups to be deleted.
* @return {void}
*/
function deletePortgroups(portgroups) {
actions.deletePortgroups(portgroups).then(function() {
ctrl.refresh();
});
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.refresh
* @description Update node information
*
* @return {void}
*/
function refresh() {
init();
}
/**
* @name horizon.dashboard.admin.ironic.NodeDetailsController.toggleConsoleMode
* @description Toggle the state of the console for the current node
*
* @return {void}
*/
function toggleConsoleMode() {
ironic.nodeSetConsoleMode(ctrl.node.uuid, !ctrl.node.console_enabled);
}
}
})();

View File

@ -1,208 +0,0 @@
/*
* Copyright 2015 Hewlett Packard Enterprise Development Company LP
* 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.node-details', function () {
var ctrl, $q, nodeStateTransitionService;
var nodeUuid = "0123abcd-0123-4567-abcd-0123456789ab";
var nodeName = "herp";
var numPorts = 2;
var bootDevice = {boot_device: 'pxe', persistent: true};
var consoleEnabled = true;
var consoleInfo = "console-info";
function portUuid(nodeUuid, index) {
return '' + index + index + nodeUuid.substring(2);
}
function portMacAddr(index) {
var mac = '' + index + index;
for (var i = 0; i < 5; i++) {
mac += ':' + index + index;
}
return mac;
}
function createPort(nodeUuid, index, extra) {
var port = {uuid: portUuid(nodeUuid, index),
address: portMacAddr(index)};
port.id = port.uuid;
port.name = port.address;
if (angular.isDefined(extra)) {
port.extra = extra;
}
return port;
}
function createNode(name, uuid) {
return {name: name,
uuid: uuid,
provision_state: 'enroll'};
}
var ironicAPI = {
getNode: function (uuid) {
var node = createNode(nodeName, uuid);
return $q.when(node);
},
getPortsWithNode: function (uuid) {
var ports = [];
for (var i = 0; i < numPorts; i++) {
ports.push(createPort(uuid, i));
}
return $q.when(ports);
},
getPortgroups: function() {
return $q.when([]);
},
getBootDevice: function () {
return $q.when(bootDevice);
},
validateNode: function() {
return $q.when({});
},
nodeGetConsole: function() {
return $q.when({console_enabled: consoleEnabled,
console_info: consoleInfo});
}
};
var nodeActions = {
getPowerTransitions: function() {
return [];
}
};
beforeEach(module(function($provide) {
$provide.value('$uibModal', jasmine.createSpy());
}));
beforeEach(module('horizon.dashboard.admin.ironic'));
beforeEach(module(function($provide) {
$provide.value('horizon.app.core.openstack-service-api.ironic',
ironicAPI);
}));
beforeEach(module(function($provide) {
$provide.value('horizon.framework.widgets.toast.service',
{});
}));
beforeEach(module(function($provide) {
$provide.value('horizon.dashboard.admin.ironic.edit-node.service',
{});
}));
beforeEach(module(function($provide) {
$provide.value('horizon.dashboard.admin.ironic.maintenance.service',
{});
}));
beforeEach(inject(function ($injector, _$rootScope_, _$location_) {
var scope = _$rootScope_.$new();
$q = $injector.get('$q');
var controller = $injector.get('$controller');
var $location = _$location_;
$location.path('/admin/ironic/' + nodeUuid + '/');
nodeStateTransitionService = $injector.get(
'horizon.dashboard.admin.ironic.node-state-transition.service');
ctrl = controller(
'horizon.dashboard.admin.ironic.NodeDetailsController',
{$scope: scope,
$location: $location,
'horizon.dashboard.admin.ironic.edit-port.service': {},
'horizon.dashboard.admin.ironic.actions': nodeActions});
scope.$apply();
}));
it('controller should be defined', function () {
expect(ctrl).toBeDefined();
});
it('should have a basePath', function () {
expect(ctrl.basePath).toBeDefined();
});
it('should have a node', function () {
expect(ctrl.node).toBeDefined();
var node = createNode(nodeName, nodeUuid);
node.id = node.uuid;
node.bootDevice = bootDevice;
node.console_enabled = consoleEnabled;
node.console_info = consoleInfo;
expect(ctrl.node).toEqual(node);
});
it('should have ports', function () {
expect(ctrl.portsSrc).toBeDefined();
expect(ctrl.portsSrc.length).toEqual(numPorts);
var ports = [];
for (var i = 0; i < numPorts; i++) {
var port = createPort(ctrl.node.uuid, i);
port.id = port.uuid;
port.name = port.address;
ports.push(port);
}
expect(ctrl.portsSrc).toEqual(ports);
});
it('should have a uuid regular expression pattern', function () {
expect(ctrl.re_uuid).toBeDefined();
});
it('should have an isUuid function', function () {
expect(ctrl.isUuid).toBeDefined();
expect(ctrl.isUuid(ctrl.node.uuid)).toEqual(true);
expect(ctrl.isUuid("not a uuid")).toEqual(false);
});
it('should have a getVifPortId function', function () {
expect(ctrl.getVifPortId).toBeDefined();
expect(ctrl.getVifPortId(createPort(ctrl.node.uuid, 1))).toEqual("");
var extra = {vif_port_id: "port_uuid"};
expect(ctrl.getVifPortId(createPort(ctrl.node.uuid, 1, extra))).
toEqual("port_uuid");
});
it('should have node-state-transitions', function () {
expect(ctrl.nodeStateTransitions).toBeDefined();
expect(ctrl.nodeStateTransitions).toEqual(
nodeStateTransitionService.getTransitions(ctrl.node.provision_state));
});
it('should have node-validation', function () {
expect(ctrl.nodeValidation).toBeDefined();
expect(ctrl.nodeValidation).toEqual([]);
});
it('should have a boot device', function () {
expect(ctrl.node.bootDevice).toBeDefined();
expect(ctrl.node.bootDevice).toEqual(bootDevice);
});
});
})();

View File

@ -1,77 +0,0 @@
<div class="detail-page"
ng-controller="horizon.dashboard.admin.ironic.NodeDetailsController as ctrl">
<div class="pull-right">
<button class="btn btn-default btn-sm"
style="margin-right:10px;"
ng-click="ctrl.refresh()">
<span translate>Refresh</span>
</button>
<action-list uib-dropdown>
<action button-type="split-button"
action-classes="'btn btn-default btn-sm'"
callback="ctrl.editNode">
{$ ::'Edit' | translate $}
</action>
<menu>
<li role="presentation"
ng-repeat="transition in ctrl.nodePowerTransitions">
<a role="menuitem"
ng-click="ctrl.actions.setPowerState(
[ctrl.node],
transition.state,
transition.soft);
$event.stopPropagation();
$event.preventDefault()">
<span>{$ transition.label $}</span>
</a>
</li>
<li role="presentation">
<a role="menuitem"
ng-click="ctrl.maintenanceService.setMaintenance(
[ctrl.node],
!ctrl.node.maintenance);
$event.stopPropagation();
$event.preventDefault()">
<span>{$ ctrl.node.maintenance ?
"Maintenance off" : "Maintenance on" | translate $}</span>
</a>
</li>
<li role="presentation">
<a role="menuitem"
ng-click="ctrl.bootDeviceService.setBootDevice(ctrl.node);
$event.stopPropagation();
$event.preventDefault()">
<span>{$ "Set boot device" | translate $}</span>
</a>
</li>
<li role="presentation"
ng-repeat="transition in ctrl.nodeStateTransitions">
<a role="menuitem"
ng-click="ctrl.actions.setProvisionState({
node: ctrl.node,
verb: transition.verb});
$event.stopPropagation();
$event.preventDefault()">
<span>{$ transition.label $}</span>
</a>
</li>
<action button-type="menu-item"
disabled="isDefined(ctrl.nodeValidationMap.console)
&& ctrl.nodeValidationMap.console.result===false"
callback="ctrl.toggleConsoleMode">
{$ ctrl.node.console_enabled ? 'Disable console' : 'Enable console' | translate $}
</action>
</menu>
</action-list>
</div>
<div class="clearfix"></div>
<uib-tabset>
<uib-tab ng-repeat="section in ctrl.sections"
heading="{$ section.heading $}">
<ng-include src="section.templateUrl"></ng-include>
</uib-tab>
</uib-tabset>
</div>

View File

@ -1,418 +0,0 @@
<div class="row">
<!-- General -->
<div class="col-md-12 status detail">
<h4 translate>General</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Node ID</dt>
<dd>{$ ctrl.node.uuid | noValue $}</dd>
<dt translate>Chassis ID</dt>
<dd>{$ ctrl.node.chassis_uuid | noValue $}</dd>
<dt translate>Resource Class</dt>
<dd>{$ ctrl.node.resource_class | noValue $}</dd>
<dt translate>Created At</dt>
<dd>{$ ctrl.node.created_at | date:'medium' | noValue $}</dd>
</dl>
</div>
</div>
<div class="row">
<!-- Ports -->
<div class="col-md-6 status detail">
<h4 translate>Ports</h4>
<hr class="header_rule">
<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="100" class="action-col">
<action-list uib-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.deletePort"
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>&nbsp;</th>
<th translate class="rsp-p1" style="white-space:nowrap">
MAC Address
</th>
<th translate class="rsp-p2" style="white-space:nowrap;">
PXE Enabled
</th>
<th translate class="rsp-p2" style="white-space:nowrap;">
Portgroup
</th>
<th translate class="actions_column">
Actions
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="port in ctrl.ports">
<td class="multi_select_column">
<input type="checkbox"
hz-select="port"
ng-model="tCtrl.selections[port.id].checked"/>
<td>
<i class="fa fa-chevron-right"
hz-expand-detail="fa-chevron-right fa-chevron-down"
duration="200"
item="port"></i>
</td>
<td class="rsp-p1">
{$ port.address $}
</td>
<td>
{$ port.pxe_enabled $}
<td>
{$ port.portgroup_uuid | noValue $}
</td>
<td class="actions_column">
<action-list uib-dropdown class="pull-right">
<action button-type="split-button"
action-classes="'btn btn-default btn-sm'"
callback="ctrl.editPort"
item="port">
{$ ::'Edit port' | translate $}
</action>
<menu>
<li role="presentation">
<a role="menuitem"
ng-click="ctrl.deletePort([port]);
$event.stopPropagation();
$event.preventDefault()">
<span class="fa fa-trash"></span>
<span>{$ :: 'Delete port' | translate $}</span>
</a>
</li>
</menu>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<td class="detail" colspan="100">
<hz-detail-row
template-url="ctrl.portDetailsTemplateUrl">
</hz-detail-row>
</td>
</tr>
<tr hz-no-items
items="ctrl.ports"
message="ctrl.noPortsText">
</tr>
</tbody>
</table>
</div>
<!-- Portgroups -->
<div class="col-md-6 status detail">
<h4 translate>Portgroups</h4>
<hr class="header_rule">
<table hz-table ng-cloak
st-table="ctrl.portgroups"
st-safe-src="ctrl.portgroupsSrc"
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th colspan="100" class="action-col">
<action-list uib-dropdown class="pull-right">
<action button-type="split-button"
action-classes="'btn btn-default btn-sm'"
callback="">
{$ ::'Create portgroup' | translate $}
</action>
<menu>
<action button-type="menu-item"
callback="ctrl.deletePortgroups"
item="tCtrl.selected"
disabled="tCtrl.selected.length === 0">
<span class="fa fa-trash"></span>
{$ ::'Delete portgroups' | translate $}
</action>
</menu>
</action-list>
</th>
</tr>
<tr>
<th class="multi_select_column">
<input type="checkbox"
hz-select-all="ctrl.portgroups"/>
</th>
<th>&nbsp;</th>
<th translate class="rsp-p1" style="width:100%;">
UUID
</th>
<th translate class="rsp-p2" style="white-space:nowrap;">
MAC Address
</th>
<th translate class="rsp-p2" style="width:white-space:nowrap;">
Name
</th>
<th translate class="rsp-p2" style="width:white-space:nowrap;">
Ports
</th>
<th translate class="actions_column">
Actions
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat-start="portgroup in ctrl.portgroups">
<td class="multi_select_column">
<input type="checkbox"
hz-select="portgroup"
ng-model="tCtrl.selections[portgroup.id].checked"/>
<td>
<i class="fa fa-chevron-right"
hz-expand-detail="fa-chevron-right fa-chevron-down"
duration="200"
item="portgroup"></i>
</td>
<td class="rsp-p1">
{$ portgroup.uuid $}
</td>
<td class="rsp-p1">
{$ portgroup.address | noValue $}
</td>
<td class="rsp-p1">
{$ portgroup.name | noValue $}
</td>
<td class="rsp-p1">
{$ portgroup.ports.length | noValue $}
</td>
<td class="actions_column">
<action-list uib-dropdown class="pull-right">
<action button-type="split-button"
action-classes="'btn btn-default btn-sm'"
callback=""
item="port">
{$ ::'Edit portgroup' | translate $}
</action>
<menu>
<li role="presentation"
ng-class="{disabled: portgroup.ports.length > 0}">
<a role="menuitem"
ng-click="portgroup.ports.length > 0 ||
ctrl.deletePortgroups([portgroup]);
$event.stopPropagation();
$event.preventDefault()">
<span class="fa fa-trash"></span>
<span>{$ :: 'Delete portgroup' | translate $}</span>
</a>
</li>
</menu>
</action-list>
</td>
</tr>
<tr ng-repeat-end class="detail-row">
<td class="detail" colspan="100">
<hz-detail-row
template-url="ctrl.portgroupDetailsTemplateUrl">
</hz-detail-row>
</td>
</tr>
<tr hz-no-items
items="ctrl.portgroups"
message="ctrl.noPortgroupsText">
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<!-- Driver Info -->
<div class="col-md-6 status detail">
<h4 translate>Driver Info</h4>
<hr class="header_rule">
<div ng-switch="ctrl.node.driver">
<dl ng-switch-when="pxe_ssh" class="dl-horizontal">
<dt translate>Driver</dt>
<dd>{$ ctrl.node.driver | noValue $}</dd>
<dt translate>SSH Address</dt>
<dd>{$ ctrl.node.driver_info.ssh_address | noValue $}</dd>
<dt translate>SSH Port</dt>
<dd>{$ ctrl.node.driver_info.ssh_port | noValue $}</dd>
<dt translate>SSH Username</dt>
<dd>{$ ctrl.node.driver_info.ssh_username | noValue $}</dd>
<dt ng-if="ssh_key_filename = ctrl.node.driver_info.ssh_key_filename"
translate>SSH Key File</dt>
<dd ng-if="ssh_key_filename">
{$ ssh_key_filename | noValue $}
</dd>
<dt ng-if="ssh_password = ctrl.node.driver_info.ssh_password"
translate>SSH Password</dt>
<dd ng-if="ssh_password">
{$ ssh_password | noValue $}
</dd>
<dt ng-if="ssh_key_contents = ctrl.node.driver_info.ssh_key_contents"
translate>SSH Key Contents</dt>
<dd ng-if="ssh_key_contents">
{$ ssh_key_contents | noValue $}
</dd>
<dt translate>SSH terminal port</dt>
<dd>{$ ctrl.node.driver_info.ssh_terminal_port | noValue $}</dd>
<dt translate>Virtualization Software</dt>
<dd>{$ ctrl.node.driver_info.ssh_virt_type | noValue $}</dd>
<dt translate>Deploy Kernel</dt>
<dd>
<a ng-if="deploy_kernel_is_uuid = angular.isDefined(ctrl.node.driver_info.deploy_kernel) ? ctrl.isUuid(ctrl.node.driver_info.deploy_kernel) : false"
href="/dashboard/admin/images/{$ ctrl.node.driver_info.deploy_kernel $}/detail">
{$ ctrl.node.driver_info.deploy_kernel | noValue $}
</a>
<span ng-if="!deploy_kernel_is_uuid">
{$ ctrl.node.driver_info.deploy_kernel | noValue $}
</span>
</dd>
<dt translate>Deploy Ramdisk</dt>
<dd>
<a ng-if="deploy_ramdisk_is_uuid = angular.isDefined(ctrl.node.driver_info.deploy_ramdisk) ? ctrl.isUuid(ctrl.node.driver_info.deploy_ramdisk) : false"
href="/dashboard/admin/images/{$ ctrl.node.driver_info.deploy_ramdisk $}/detail">
{$ ctrl.node.driver_info.deploy_ramdisk | noValue $}
</a>
<span ng-if="!deploy_ramdisk_is_uuid">
{$ ctrl.node.driver_info.deploy_ramdisk | noValue $}
</span>
</dd>
</dl>
<dl ng-switch-default class="dl-horizontal">
<dt ng-repeat-start="(id, value) in ctrl.node.driver_info">{$ id $}</dt>
<dd ng-repeat-end>{$ value $}</dd>
</dl>
</div>
</div>
<!-- Validation -->
<div class="col-md-6 status detail">
<h4 translate>Driver Validation</h4>
<hr class="header_rule">
<table hz-table ng-cloak
st-table="nodeValidation"
st-safe-src="ctrl.nodeValidation"
class="table table-striped table-rsp table-detail">
<thead>
<tr>
<th translate class="rsp-p1" style="white-space:nowrap">
Interface
</th>
<th translate class="rsp-p1">
Valid
</th>
<th translate class="rsp-p2" style="width:100%;">
Reason
</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="item in nodeValidation">
<td class="rsp-p1">
{$ item.id $}
</td>
<td class="rsp-p1" ng-switch="item.result">
<span ng-switch-when="true" class="fa fa-check text-success"></span>
<span ng-switch-when="false" class="fa fa-close text-danger"></span>
<span ng-switch-default class="fa fa-minus"></span>
</td>
<td class="rsp-p2">
{$ item.reason $}
</td>
</tr>
</tbody>
</table>
</div>
</div>
<div class="row">
<!-- Properties -->
<div class="col-md-6 status detail">
<h4 translate>Properties</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt ng-repeat-start="(propertyName, propertyValue) in ctrl.node.properties">
{$ propertyName $}</dt>
<dd ng-repeat-end>{$ propertyValue | noValue $}</dd>
</dl>
</div>
<!-- Instance Info -->
<div class="col-md-6 status detail">
<h4 translate>Instance Info</h4>
<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 | noValue $}</dd>
<dt translate>Ramdisk</dt>
<dd>
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.ramdisk $}/detail">
{$ ctrl.node.instance_info.ramdisk | noValue $}
</a>
</dd>
<dt translate>Kernel</dt>
<dd>
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.kernel $}/detail">
{$ ctrl.node.instance_info.kernel | noValue $}
</a>
</dd>
<dt translate>Image Source</dt>
<dd>
<a href="/dashboard/admin/images/{$ ctrl.node.instance_info.image_source $}/detail">
{$ ctrl.node.instance_info.image_source | noValue $}
</a>
</dd>
<dt translate>Root GB</dt>
<dd>{$ ctrl.node.instance_info.root_gb | noValue $}</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 | noValue $}</dd>
</dl>
</div>
</div>
</div>
<div class="row">
<!-- Extra -->
<div class="col-md-6 status detail">
<h4 translate>Extra</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt ng-repeat-start="(propertyName, propertyValue) in ctrl.node.extra">
{$ propertyName $}</dt>
<dd ng-repeat-end>{$ propertyValue | noValue $}</dd>
</dl>
</div>
<!-- Boot Device -->
<div class="col-md-6 status detail">
<h4 translate>Boot Device</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Device</dt>
<dd>{$ ctrl.node.bootDevice.boot_device | noValue $}</dd>
<dt translate>Persistent</dt>
<dd>{$ ctrl.node.bootDevice.persistent | noValue $}</dd>
</dl>
</div>
</div>

View File

@ -1,64 +0,0 @@
<div class="row">
<!-- General -->
<div class="col-md-6 status detail">
<h4 translate>General</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Name</dt>
<dd>{$ ctrl.node.name | noValue $}</dd>
<dt translate>Network Interface</dt>
<dd>{$ ctrl.node.network_interface $}</dd>
<dt translate>Maintenance</dt>
<dd>{$ ctrl.node.maintenance | yesno $}</dd>
<dt translate>Maintenance Reason</dt>
<dd>{$ ctrl.node.maintenance_reason | noValue $}</dd>
<dt translate>Inspection Started At</dt>
<dd>{$ ctrl.node.inspection_started_at | date: 'medium' | noValue $}</dd>
<dt translate>Inspection Finished At</dt>
<dd>{$ ctrl.node.inspection_finished_at | date: 'medium' | noValue $}</dd>
<dt translate>Reservation</dt>
<dd>{$ ctrl.node.reservation | noValue $}</dd>
<dt translate>Console Enabled</dt>
<dd>{$ ctrl.node.console_enabled | yesno $}</dd>
<dt translate>Console Info.</dt>
<dd ng-if="ctrl.node.console_info.type==='shellinabox'">
<a href="{$ ctrl.node.console_info.url $}"
title="{$ 'Click link to view console' | translate $}">
shellinabox
</a>
</dd>
<dd ng-if="ctrl.node.console_info.type!=='shellinabox'">
{$ ctrl.node.console_info | noValue $}
</dd>
</dl>
</div>
<!-- Provisioning Status -->
<div class="col-md-6 status detail">
<h4 translate>Provisioning Status</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt translate>Instance ID</dt>
<dd>
<a href="/admin/instances/{$ ctrl.node.instance_uuid $}/detail">
{$ ctrl.node.instance_uuid | noValue $}
</a>
</dd>
<dt translate>Power State</dt>
<dd ng-class="{'running': ctrl.node.target_power_state}">{$ ctrl.node.power_state | noValue $}</dd>
<dt translate>Target Power State</dt>
<dd>{$ ctrl.node.target_power_state | noValue $}</dd>
<dt translate>Provision State</dt>
<dd>{$ ctrl.node.provision_state | noValue $}</dd>
<dt translate>Target Provision State</dt>
<dd>{$ ctrl.node.target_provision_state | noValue $}</dd>
<dt translate>Clean Step</dt>
<dd>{$ ctrl.node.clean_step $}</dd>
<dt translate>Last Error</dt>
<dd>{$ ctrl.node.last_error | noValue $}</dd>
<dt translate>Updated At</dt>
<dd>{$ ctrl.node.updated_at | date: 'medium' | noValue $}</dd>
</dl>
</div>
</div>

Some files were not shown because too many files have changed in this diff Show More