[WIP] Horizon dashboard for tap-as-a-service
This is an implementation of a dashboard discussed on ML[1][2]. - Network topology view is based on Kilo[3]. - Tap service panel is added to Horizon dashboard. - Command options provided on CLI are supported. [1] http://lists.openstack.org/pipermail/openstack-dev/2016-March/088735.html [2] http://lists.openstack.org/pipermail/openstack-dev/2016-March/089330.html [3] https://www.openstack.org/software/kilo/ Co-Authored-By: Soichi Shigeta <shigeta.soichi@jp.fujitsu.com> Change-Id: Iceec7f873584978533907420d50988f286bf6d7a
This commit is contained in:
parent
1d58d20ede
commit
e75d42025a
|
@ -0,0 +1,31 @@
|
||||||
|
*.mo
|
||||||
|
*.pyc
|
||||||
|
*.sw?
|
||||||
|
*.sqlite3
|
||||||
|
*.lock
|
||||||
|
.environment_version
|
||||||
|
.selenium_log
|
||||||
|
.coverage*
|
||||||
|
.noseids
|
||||||
|
.DS_STORE
|
||||||
|
coverage.xml
|
||||||
|
nosetests.xml
|
||||||
|
pep8.txt
|
||||||
|
pylint.txt
|
||||||
|
reports
|
||||||
|
horizon.egg-info
|
||||||
|
openstack_dashboard/local/local_settings.py
|
||||||
|
openstack_dashboard/local/local_settings.diff
|
||||||
|
openstack_dashboard/local/.secret_key_store
|
||||||
|
openstack_dashboard/test/.secret_key_store
|
||||||
|
openstack_dashboard/wsgi/horizon.wsgi
|
||||||
|
doc/build/
|
||||||
|
doc/source/sourcecode
|
||||||
|
/static/
|
||||||
|
.venv
|
||||||
|
.tox
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
AUTHORS
|
||||||
|
ChangeLog
|
||||||
|
tags
|
|
@ -0,0 +1,5 @@
|
||||||
|
[gerrit]
|
||||||
|
host=review.openstack.org
|
||||||
|
port=29418
|
||||||
|
project=openstack/horizon.git
|
||||||
|
defaultbranch=stable/kilo
|
|
@ -0,0 +1,36 @@
|
||||||
|
{
|
||||||
|
"browser": true,
|
||||||
|
"trailing": true,
|
||||||
|
"evil": true,
|
||||||
|
|
||||||
|
"globals": {
|
||||||
|
"horizon": false,
|
||||||
|
"jQuery": false,
|
||||||
|
"$": false,
|
||||||
|
|
||||||
|
"angular": false,
|
||||||
|
"module": false,
|
||||||
|
"inject": false,
|
||||||
|
|
||||||
|
"describe": false,
|
||||||
|
"beforeEach": false,
|
||||||
|
"afterEach": false,
|
||||||
|
"beforeAll": false,
|
||||||
|
"afterAll": false,
|
||||||
|
"it": false,
|
||||||
|
"expect": false,
|
||||||
|
"spyOn": false,
|
||||||
|
"jasmine": false,
|
||||||
|
|
||||||
|
"d3": false,
|
||||||
|
|
||||||
|
"pluralidx": false,
|
||||||
|
"gettext": false,
|
||||||
|
"ngettext": false,
|
||||||
|
"gettext_noop": false,
|
||||||
|
"pgettext": false,
|
||||||
|
"npgettext": false,
|
||||||
|
"interpolate": false,
|
||||||
|
"get_format": false
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
# Format is:
|
||||||
|
# <preferred e-mail> <other e-mail 1>
|
||||||
|
# <preferred e-mail> <other e-mail 2>
|
||||||
|
<ghe@debian.org> <ghe.rivero@stackops.com>
|
||||||
|
<jake@ansolabs.com> <admin@jakedahn.com>
|
||||||
|
<launchpad@markgius.com> <mgius7096@gmail.com>
|
||||||
|
<yorik.sar@gmail.com> <yorik@ytaraday>
|
||||||
|
<jeblair@hp.com> <james.blair@rackspace.com>
|
||||||
|
<ke.wu@ibeca.me> <ke.wu@nebula.com>
|
||||||
|
Zhongyue Luo <zhongyue.nah@intel.com> <lzyeval@gmail.com>
|
||||||
|
Joe Gordon <joe.gordon0@gmail.com> <jogo@cloudscaling.com>
|
||||||
|
Kun Huang <gareth@unitedstack.com> <academicgareth@gmail.com>
|
||||||
|
Zhenguo Niu <zhenguo@unitedstack.com> <Niu.ZGlinux@gmail.com>
|
|
@ -0,0 +1,42 @@
|
||||||
|
# The format of this file isn't really documented; just use --generate-rcfile
|
||||||
|
[MASTER]
|
||||||
|
# Add <file or directory> to the black list. It should be a base name, not a
|
||||||
|
# path. You may set this option multiple times.
|
||||||
|
ignore=test
|
||||||
|
|
||||||
|
[Messages Control]
|
||||||
|
# NOTE(justinsb): We might want to have a 2nd strict pylintrc in future
|
||||||
|
# C0111: Don't require docstrings on every method
|
||||||
|
# W0511: TODOs in code comments are fine.
|
||||||
|
# W0142: *args and **kwargs are fine.
|
||||||
|
# W0622: Redefining id is fine.
|
||||||
|
disable=C0111,W0511,W0142,W0622
|
||||||
|
|
||||||
|
[Basic]
|
||||||
|
# Variable names can be 1 to 31 characters long, with lowercase and underscores
|
||||||
|
variable-rgx=[a-z_][a-z0-9_]{0,30}$
|
||||||
|
|
||||||
|
# Argument names can be 2 to 31 characters long, with lowercase and underscores
|
||||||
|
argument-rgx=[a-z_][a-z0-9_]{1,30}$
|
||||||
|
|
||||||
|
# Method names should be at least 3 characters long
|
||||||
|
# and be lowecased with underscores
|
||||||
|
method-rgx=([a-z_][a-z0-9_]{2,50}|setUp|tearDown)$
|
||||||
|
|
||||||
|
# Module names matching keystone-* are ok (files in bin/)
|
||||||
|
module-rgx=(([a-z_][a-z0-9_]*)|([A-Z][a-zA-Z0-9]+)|(keystone-[a-z0-9_-]+))$
|
||||||
|
|
||||||
|
# Don't require docstrings on tests.
|
||||||
|
no-docstring-rgx=((__.*__)|([tT]est.*)|setUp|tearDown)$
|
||||||
|
|
||||||
|
[Design]
|
||||||
|
max-public-methods=100
|
||||||
|
min-public-methods=0
|
||||||
|
max-args=6
|
||||||
|
|
||||||
|
[Variables]
|
||||||
|
|
||||||
|
# List of additional names supposed to be defined in builtins. Remember that
|
||||||
|
# you should avoid to define new builtins when possible.
|
||||||
|
# _ is used by our localization
|
||||||
|
additional-builtins=_
|
|
@ -0,0 +1,21 @@
|
||||||
|
[main]
|
||||||
|
host = https://www.transifex.com
|
||||||
|
|
||||||
|
[horizon.horizon-translations-kilo]
|
||||||
|
file_filter = horizon/locale/<lang>/LC_MESSAGES/django.po
|
||||||
|
source_file = horizon/locale/en/LC_MESSAGES/django.po
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|
||||||
|
[horizon.openstack-dashboard-translations-kilo]
|
||||||
|
file_filter = openstack_dashboard/locale/<lang>/LC_MESSAGES/django.po
|
||||||
|
source_file = openstack_dashboard/locale/en/LC_MESSAGES/django.po
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|
||||||
|
[horizon.horizon-js-translations-kilo]
|
||||||
|
file_filter = horizon/locale/<lang>/LC_MESSAGES/djangojs.po
|
||||||
|
source_file = horizon/locale/en/LC_MESSAGES/djangojs.po
|
||||||
|
source_lang = en
|
||||||
|
type = PO
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
If you would like to contribute to the development of OpenStack,
|
||||||
|
you must follow the steps documented at:
|
||||||
|
|
||||||
|
http://docs.openstack.org/developer/horizon/contributing.html
|
||||||
|
|
||||||
|
or http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||||
|
|
||||||
|
Once those steps have been completed, changes to OpenStack
|
||||||
|
should be submitted for review via the Gerrit tool, following
|
||||||
|
the workflow documented at:
|
||||||
|
|
||||||
|
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/horizon
|
|
@ -0,0 +1,14 @@
|
||||||
|
Horizon Style Commandments
|
||||||
|
==========================
|
||||||
|
|
||||||
|
- Step 1: Read the OpenStack Style Commandments
|
||||||
|
http://docs.openstack.org/developer/hacking/
|
||||||
|
- Step 2: Read [hacking] section in tox.ini to find the list of names which
|
||||||
|
can be imported directly without triggering the "H302: import only modules"
|
||||||
|
flake8 warning
|
||||||
|
- Step 3: Read on
|
||||||
|
|
||||||
|
Horizon Specific Commandments
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
- Read the Horizon contributing documentation at http://docs.openstack.org/developer/horizon/contributing.html
|
|
@ -0,0 +1,176 @@
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
recursive-include doc *.py *.rst *.css *.js *.html *.conf *.jpg *.gif *.png *.css_t
|
||||||
|
recursive-include horizon *.html *.css *.js *.csv *.template *.tmpl *.mo *.po
|
||||||
|
recursive-include openstack_dashboard *.html *.js *.less *.mo *.po *.example *.eot *.svg *.ttf *.woff *.png *.ico *.wsgi *.gif *.csv *.template
|
||||||
|
recursive-include tools *.py *.sh
|
||||||
|
|
||||||
|
include AUTHORS
|
||||||
|
include ChangeLog
|
||||||
|
include LICENSE
|
||||||
|
include Makefile
|
||||||
|
include manage.py
|
||||||
|
include README.rst
|
||||||
|
include run_tests.sh
|
||||||
|
include tox.ini
|
||||||
|
include doc/Makefile
|
||||||
|
include doc/source/_templates/.placeholder
|
||||||
|
include requirements.txt
|
||||||
|
include test-requirements.txt
|
||||||
|
|
||||||
|
exclude openstack_dashboard/local/local_settings.py
|
|
@ -0,0 +1,24 @@
|
||||||
|
PYTHON=`which python`
|
||||||
|
DESTDIR=/
|
||||||
|
PROJECT=horizon
|
||||||
|
|
||||||
|
all:
|
||||||
|
@echo "make test - Run tests"
|
||||||
|
@echo "make source - Create source package"
|
||||||
|
@echo "make install - Install on local system"
|
||||||
|
@echo "make buildrpm - Generate a rpm package"
|
||||||
|
@echo "make clean - Get rid of scratch and byte files"
|
||||||
|
|
||||||
|
source:
|
||||||
|
$(PYTHON) setup.py sdist $(COMPILE)
|
||||||
|
|
||||||
|
install:
|
||||||
|
$(PYTHON) setup.py install --root $(DESTDIR) $(COMPILE)
|
||||||
|
|
||||||
|
buildrpm:
|
||||||
|
$(PYTHON) setup.py bdist_rpm --post-install=rpm/postinstall --pre-uninstall=rpm/preuninstall
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(PYTHON) setup.py clean
|
||||||
|
rm -rf build/ MANIFEST
|
||||||
|
find . -name '*.pyc' -delete
|
|
@ -0,0 +1,51 @@
|
||||||
|
=============================
|
||||||
|
Horizon (OpenStack Dashboard)
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Horizon is a Django-based project aimed at providing a complete OpenStack
|
||||||
|
Dashboard along with an extensible framework for building new dashboards
|
||||||
|
from reusable components. The ``openstack_dashboard`` module is a reference
|
||||||
|
implementation of a Django site that uses the ``horizon`` app to provide
|
||||||
|
web-based interactions with the various OpenStack projects.
|
||||||
|
|
||||||
|
* Release management: https://launchpad.net/horizon
|
||||||
|
* Blueprints and feature specifications: https://blueprints.launchpad.net/horizon
|
||||||
|
* Issue tracking: https://bugs.launchpad.net/horizon
|
||||||
|
|
||||||
|
|
||||||
|
Using Horizon
|
||||||
|
=============
|
||||||
|
|
||||||
|
See ``doc/source/topics/install.rst`` about how to install Horizon
|
||||||
|
in your OpenStack setup. It describes the example steps and
|
||||||
|
has pointers for more detailed settings and configurations.
|
||||||
|
|
||||||
|
It is also available at http://docs.openstack.org/developer/horizon/topics/install.html.
|
||||||
|
|
||||||
|
Getting Started for Developers
|
||||||
|
==============================
|
||||||
|
|
||||||
|
``doc/source/quickstart.rst`` or
|
||||||
|
http://docs.openstack.org/developer/horizon/quickstart.html
|
||||||
|
describes how to setup Horizon development environment and start development.
|
||||||
|
|
||||||
|
Building Contributor Documentation
|
||||||
|
==================================
|
||||||
|
|
||||||
|
This documentation is written by contributors, for contributors.
|
||||||
|
|
||||||
|
The source is maintained in the ``doc/source`` directory using
|
||||||
|
`reStructuredText`_ and built by `Sphinx`_
|
||||||
|
|
||||||
|
.. _reStructuredText: http://docutils.sourceforge.net/rst.html
|
||||||
|
.. _Sphinx: http://sphinx-doc.org/
|
||||||
|
|
||||||
|
* Building Automatically::
|
||||||
|
|
||||||
|
$ ./run_tests.sh --docs
|
||||||
|
|
||||||
|
* Building Manually::
|
||||||
|
|
||||||
|
$ tools/with_venv.sh sphinx-build doc/source doc/build/html
|
||||||
|
|
||||||
|
Results are in the ``doc/build/html`` directory
|
|
@ -0,0 +1,153 @@
|
||||||
|
# Makefile for Sphinx documentation
|
||||||
|
#
|
||||||
|
|
||||||
|
# You can set these variables from the command line.
|
||||||
|
SPHINXOPTS =
|
||||||
|
SPHINXBUILD = sphinx-build
|
||||||
|
PAPER =
|
||||||
|
BUILDDIR = build
|
||||||
|
|
||||||
|
# Internal variables.
|
||||||
|
PAPEROPT_a4 = -D latex_paper_size=a4
|
||||||
|
PAPEROPT_letter = -D latex_paper_size=letter
|
||||||
|
ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||||
|
# the i18n builder cannot share the environment and doctrees with the others
|
||||||
|
I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) source
|
||||||
|
|
||||||
|
.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
|
||||||
|
|
||||||
|
help:
|
||||||
|
@echo "Please use \`make <target>' where <target> is one of"
|
||||||
|
@echo " html to make standalone HTML files"
|
||||||
|
@echo " dirhtml to make HTML files named index.html in directories"
|
||||||
|
@echo " singlehtml to make a single large HTML file"
|
||||||
|
@echo " pickle to make pickle files"
|
||||||
|
@echo " json to make JSON files"
|
||||||
|
@echo " htmlhelp to make HTML files and a HTML help project"
|
||||||
|
@echo " qthelp to make HTML files and a qthelp project"
|
||||||
|
@echo " devhelp to make HTML files and a Devhelp project"
|
||||||
|
@echo " epub to make an epub"
|
||||||
|
@echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
|
||||||
|
@echo " latexpdf to make LaTeX files and run them through pdflatex"
|
||||||
|
@echo " text to make text files"
|
||||||
|
@echo " man to make manual pages"
|
||||||
|
@echo " texinfo to make Texinfo files"
|
||||||
|
@echo " info to make Texinfo files and run them through makeinfo"
|
||||||
|
@echo " gettext to make PO message catalogs"
|
||||||
|
@echo " changes to make an overview of all changed/added/deprecated items"
|
||||||
|
@echo " linkcheck to check all external links for integrity"
|
||||||
|
@echo " doctest to run all doctests embedded in the documentation (if enabled)"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
-rm -rf $(BUILDDIR)/*
|
||||||
|
|
||||||
|
html:
|
||||||
|
$(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
|
||||||
|
|
||||||
|
dirhtml:
|
||||||
|
$(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
|
||||||
|
|
||||||
|
singlehtml:
|
||||||
|
$(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
|
||||||
|
|
||||||
|
pickle:
|
||||||
|
$(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the pickle files."
|
||||||
|
|
||||||
|
json:
|
||||||
|
$(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can process the JSON files."
|
||||||
|
|
||||||
|
htmlhelp:
|
||||||
|
$(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run HTML Help Workshop with the" \
|
||||||
|
".hhp project file in $(BUILDDIR)/htmlhelp."
|
||||||
|
|
||||||
|
qthelp:
|
||||||
|
$(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; now you can run "qcollectiongenerator" with the" \
|
||||||
|
".qhcp project file in $(BUILDDIR)/qthelp, like this:"
|
||||||
|
@echo "# qcollectiongenerator $(BUILDDIR)/qthelp/Horizon.qhcp"
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# assistant -collectionFile $(BUILDDIR)/qthelp/Horizon.qhc"
|
||||||
|
|
||||||
|
devhelp:
|
||||||
|
$(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
|
||||||
|
@echo
|
||||||
|
@echo "Build finished."
|
||||||
|
@echo "To view the help file:"
|
||||||
|
@echo "# mkdir -p $$HOME/.local/share/devhelp/Horizon"
|
||||||
|
@echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/Horizon"
|
||||||
|
@echo "# devhelp"
|
||||||
|
|
||||||
|
epub:
|
||||||
|
$(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The epub file is in $(BUILDDIR)/epub."
|
||||||
|
|
||||||
|
latex:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo
|
||||||
|
@echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
|
||||||
|
@echo "Run \`make' in that directory to run these through (pdf)latex" \
|
||||||
|
"(use \`make latexpdf' here to do that automatically)."
|
||||||
|
|
||||||
|
latexpdf:
|
||||||
|
$(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
|
||||||
|
@echo "Running LaTeX files through pdflatex..."
|
||||||
|
$(MAKE) -C $(BUILDDIR)/latex all-pdf
|
||||||
|
@echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
|
||||||
|
|
||||||
|
text:
|
||||||
|
$(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The text files are in $(BUILDDIR)/text."
|
||||||
|
|
||||||
|
man:
|
||||||
|
$(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The manual pages are in $(BUILDDIR)/man."
|
||||||
|
|
||||||
|
texinfo:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
|
||||||
|
@echo "Run \`make' in that directory to run these through makeinfo" \
|
||||||
|
"(use \`make info' here to do that automatically)."
|
||||||
|
|
||||||
|
info:
|
||||||
|
$(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
|
||||||
|
@echo "Running Texinfo files through makeinfo..."
|
||||||
|
make -C $(BUILDDIR)/texinfo info
|
||||||
|
@echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
|
||||||
|
|
||||||
|
gettext:
|
||||||
|
$(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
|
||||||
|
@echo
|
||||||
|
@echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
|
||||||
|
|
||||||
|
changes:
|
||||||
|
$(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
|
||||||
|
@echo
|
||||||
|
@echo "The overview file is in $(BUILDDIR)/changes."
|
||||||
|
|
||||||
|
linkcheck:
|
||||||
|
$(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
|
||||||
|
@echo
|
||||||
|
@echo "Link check complete; look for any errors in the above output " \
|
||||||
|
"or in $(BUILDDIR)/linkcheck/output.txt."
|
||||||
|
|
||||||
|
doctest:
|
||||||
|
$(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
|
||||||
|
@echo "Testing of doctests in the sources finished, look at the " \
|
||||||
|
"results in $(BUILDDIR)/doctest/output.txt."
|
|
@ -0,0 +1,440 @@
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
#
|
||||||
|
# Horizon documentation build configuration file, created by
|
||||||
|
# sphinx-quickstart on Thu Oct 27 11:38:59 2011.
|
||||||
|
#
|
||||||
|
# This file is execfile()d with the current directory set to its
|
||||||
|
# containing dir.
|
||||||
|
#
|
||||||
|
# Note that not all possible configuration values are present in this
|
||||||
|
# autogenerated file.
|
||||||
|
#
|
||||||
|
# All configuration values have a default; values that are commented out
|
||||||
|
# serve to show the default.
|
||||||
|
|
||||||
|
from __future__ import print_function
|
||||||
|
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
ROOT = os.path.abspath(os.path.join(BASE_DIR, "..", ".."))
|
||||||
|
|
||||||
|
sys.path.insert(0, ROOT)
|
||||||
|
|
||||||
|
# This is required for ReadTheDocs.org, but isn't a bad idea anyway.
|
||||||
|
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'openstack_dashboard.settings')
|
||||||
|
|
||||||
|
import horizon.version
|
||||||
|
|
||||||
|
|
||||||
|
def write_autodoc_index():
|
||||||
|
|
||||||
|
def find_autodoc_modules(module_name, sourcedir):
|
||||||
|
"""returns a list of modules in the SOURCE directory."""
|
||||||
|
modlist = []
|
||||||
|
os.chdir(os.path.join(sourcedir, module_name))
|
||||||
|
print("SEARCHING %s" % sourcedir)
|
||||||
|
for root, dirs, files in os.walk("."):
|
||||||
|
for filename in files:
|
||||||
|
if filename == 'tests.py':
|
||||||
|
continue
|
||||||
|
if filename.endswith(".py"):
|
||||||
|
# remove the pieces of the root
|
||||||
|
elements = root.split(os.path.sep)
|
||||||
|
# replace the leading "." with the module name
|
||||||
|
elements[0] = module_name
|
||||||
|
# and get the base module name
|
||||||
|
base, extension = os.path.splitext(filename)
|
||||||
|
if not (base == "__init__"):
|
||||||
|
elements.append(base)
|
||||||
|
result = ".".join(elements)
|
||||||
|
# print result
|
||||||
|
modlist.append(result)
|
||||||
|
return modlist
|
||||||
|
|
||||||
|
RSTDIR = os.path.abspath(os.path.join(BASE_DIR, "sourcecode"))
|
||||||
|
SRCS = [('horizon', ROOT),
|
||||||
|
('openstack_dashboard', ROOT)]
|
||||||
|
|
||||||
|
EXCLUDED_MODULES = ('horizon.test',
|
||||||
|
'openstack_dashboard.enabled',
|
||||||
|
'openstack_dashboard.test',
|
||||||
|
'openstack_dashboard.openstack.common',
|
||||||
|
)
|
||||||
|
CURRENT_SOURCES = {}
|
||||||
|
|
||||||
|
if not(os.path.exists(RSTDIR)):
|
||||||
|
os.mkdir(RSTDIR)
|
||||||
|
CURRENT_SOURCES[RSTDIR] = ['autoindex.rst']
|
||||||
|
|
||||||
|
INDEXOUT = open(os.path.join(RSTDIR, "autoindex.rst"), "w")
|
||||||
|
INDEXOUT.write("""
|
||||||
|
=================
|
||||||
|
Source Code Index
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. contents::
|
||||||
|
:depth: 1
|
||||||
|
:local:
|
||||||
|
|
||||||
|
""")
|
||||||
|
|
||||||
|
for modulename, path in SRCS:
|
||||||
|
sys.stdout.write("Generating source documentation for %s\n" %
|
||||||
|
modulename)
|
||||||
|
INDEXOUT.write("\n%s\n" % modulename.capitalize())
|
||||||
|
INDEXOUT.write("%s\n" % ("=" * len(modulename),))
|
||||||
|
INDEXOUT.write(".. toctree::\n")
|
||||||
|
INDEXOUT.write(" :maxdepth: 1\n")
|
||||||
|
INDEXOUT.write("\n")
|
||||||
|
|
||||||
|
MOD_DIR = os.path.join(RSTDIR, modulename)
|
||||||
|
CURRENT_SOURCES[MOD_DIR] = []
|
||||||
|
if not(os.path.exists(MOD_DIR)):
|
||||||
|
os.mkdir(MOD_DIR)
|
||||||
|
for module in find_autodoc_modules(modulename, path):
|
||||||
|
if any([module.startswith(exclude) for exclude
|
||||||
|
in EXCLUDED_MODULES]):
|
||||||
|
print("Excluded module %s." % module)
|
||||||
|
continue
|
||||||
|
mod_path = os.path.join(path, *module.split("."))
|
||||||
|
generated_file = os.path.join(MOD_DIR, "%s.rst" % module)
|
||||||
|
|
||||||
|
INDEXOUT.write(" %s/%s\n" % (modulename, module))
|
||||||
|
|
||||||
|
# Find the __init__.py module if this is a directory
|
||||||
|
if os.path.isdir(mod_path):
|
||||||
|
source_file = ".".join((os.path.join(mod_path, "__init__"),
|
||||||
|
"py",))
|
||||||
|
else:
|
||||||
|
source_file = ".".join((os.path.join(mod_path), "py"))
|
||||||
|
|
||||||
|
CURRENT_SOURCES[MOD_DIR].append("%s.rst" % module)
|
||||||
|
# Only generate a new file if the source has changed or we don't
|
||||||
|
# have a doc file to begin with.
|
||||||
|
if not os.access(generated_file, os.F_OK) or (
|
||||||
|
os.stat(generated_file).st_mtime <
|
||||||
|
os.stat(source_file).st_mtime):
|
||||||
|
print("Module %s updated, generating new documentation."
|
||||||
|
% module)
|
||||||
|
FILEOUT = open(generated_file, "w")
|
||||||
|
header = "The :mod:`%s` Module" % module
|
||||||
|
FILEOUT.write("%s\n" % ("=" * len(header),))
|
||||||
|
FILEOUT.write("%s\n" % header)
|
||||||
|
FILEOUT.write("%s\n" % ("=" * len(header),))
|
||||||
|
FILEOUT.write(".. automodule:: %s\n" % module)
|
||||||
|
FILEOUT.write(" :members:\n")
|
||||||
|
FILEOUT.write(" :undoc-members:\n")
|
||||||
|
FILEOUT.write(" :show-inheritance:\n")
|
||||||
|
FILEOUT.write(" :noindex:\n")
|
||||||
|
FILEOUT.close()
|
||||||
|
|
||||||
|
INDEXOUT.close()
|
||||||
|
|
||||||
|
# Delete auto-generated .rst files for sources which no longer exist
|
||||||
|
for directory, subdirs, files in list(os.walk(RSTDIR)):
|
||||||
|
for old_file in files:
|
||||||
|
if old_file not in CURRENT_SOURCES.get(directory, []):
|
||||||
|
print("Removing outdated file for %s" % old_file)
|
||||||
|
os.remove(os.path.join(directory, old_file))
|
||||||
|
|
||||||
|
|
||||||
|
write_autodoc_index()
|
||||||
|
|
||||||
|
# If extensions (or modules to document with autodoc) are in another directory,
|
||||||
|
# add these directories to sys.path here. If the directory is relative to the
|
||||||
|
# documentation root, use os.path.abspath to make it absolute, like shown here.
|
||||||
|
# sys.path.insert(0, os.path.abspath('.'))
|
||||||
|
|
||||||
|
# -- General configuration ----------------------------------------------------
|
||||||
|
|
||||||
|
# If your documentation needs a minimal Sphinx version, state it here.
|
||||||
|
# needs_sphinx = '1.0'
|
||||||
|
|
||||||
|
# Add any Sphinx extension module names here, as strings.
|
||||||
|
# They can be extensions coming with Sphinx (named 'sphinx.ext.*')
|
||||||
|
# or your custom ones.
|
||||||
|
extensions = ['sphinx.ext.autodoc',
|
||||||
|
'sphinx.ext.todo',
|
||||||
|
'sphinx.ext.coverage',
|
||||||
|
'sphinx.ext.pngmath',
|
||||||
|
'sphinx.ext.viewcode',
|
||||||
|
'oslosphinx',
|
||||||
|
]
|
||||||
|
|
||||||
|
# Add any paths that contain templates here, relative to this directory.
|
||||||
|
templates_path = ['_templates']
|
||||||
|
|
||||||
|
# The suffix of source filenames.
|
||||||
|
source_suffix = '.rst'
|
||||||
|
|
||||||
|
# The encoding of source files.
|
||||||
|
# source_encoding = 'utf-8-sig'
|
||||||
|
|
||||||
|
# The master toctree document.
|
||||||
|
master_doc = 'index'
|
||||||
|
|
||||||
|
# General information about the project.
|
||||||
|
project = u'Horizon'
|
||||||
|
copyright = u'2012, OpenStack Foundation'
|
||||||
|
|
||||||
|
# The version info for the project you're documenting, acts as replacement for
|
||||||
|
# |version| and |release|, also used in various other places throughout the
|
||||||
|
# built documents.
|
||||||
|
#
|
||||||
|
# The short X.Y version.
|
||||||
|
version = horizon.version.version_info.version_string()
|
||||||
|
# The full version, including alpha/beta/rc tags.
|
||||||
|
release = horizon.version.version_info.release_string()
|
||||||
|
|
||||||
|
# The language for content autogenerated by Sphinx. Refer to documentation
|
||||||
|
# for a list of supported languages.
|
||||||
|
# language = None
|
||||||
|
|
||||||
|
# There are two options for replacing |today|: either, you set today to some
|
||||||
|
# non-false value, then it is used:
|
||||||
|
# today = ''
|
||||||
|
# Else, today_fmt is used as the format for a strftime call.
|
||||||
|
# today_fmt = '%B %d, %Y'
|
||||||
|
|
||||||
|
# List of patterns, relative to source directory, that match files and
|
||||||
|
# directories to ignore when looking for source files.
|
||||||
|
exclude_patterns = ['**/#*', '**~', '**/#*#']
|
||||||
|
|
||||||
|
# The reST default role (used for this markup: `text`)
|
||||||
|
# to use for all documents.
|
||||||
|
# default_role = None
|
||||||
|
|
||||||
|
# If true, '()' will be appended to :func: etc. cross-reference text.
|
||||||
|
# add_function_parentheses = True
|
||||||
|
|
||||||
|
# If true, the current module name will be prepended to all description
|
||||||
|
# unit titles (such as .. function::).
|
||||||
|
# add_module_names = True
|
||||||
|
|
||||||
|
# If true, sectionauthor and moduleauthor directives will be shown in the
|
||||||
|
# output. They are ignored by default.
|
||||||
|
show_authors = False
|
||||||
|
|
||||||
|
# The name of the Pygments (syntax highlighting) style to use.
|
||||||
|
pygments_style = 'sphinx'
|
||||||
|
|
||||||
|
# A list of ignored prefixes for module index sorting.
|
||||||
|
# modindex_common_prefix = []
|
||||||
|
|
||||||
|
primary_domain = 'py'
|
||||||
|
nitpicky = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for HTML output --------------------------------------------------
|
||||||
|
|
||||||
|
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||||
|
# a list of builtin themes.
|
||||||
|
# html_theme_path = ['.']
|
||||||
|
# html_theme = '_theme'
|
||||||
|
|
||||||
|
# Theme options are theme-specific and customize the look and feel of a theme
|
||||||
|
# further. For a list of options available for each theme, see the
|
||||||
|
# documentation.
|
||||||
|
html_theme_options = {
|
||||||
|
"nosidebar": "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add any paths that contain custom themes here, relative to this directory.
|
||||||
|
# html_theme_path = []
|
||||||
|
|
||||||
|
# The name for this set of Sphinx documents. If None, it defaults to
|
||||||
|
# "<project> v<release> documentation".
|
||||||
|
# html_title = None
|
||||||
|
|
||||||
|
# A shorter title for the navigation bar. Default is the same as html_title.
|
||||||
|
# html_short_title = None
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top
|
||||||
|
# of the sidebar.
|
||||||
|
# html_logo = None
|
||||||
|
|
||||||
|
# The name of an image file (within the static path) to use as favicon of the
|
||||||
|
# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
|
||||||
|
# pixels large.
|
||||||
|
# html_favicon = None
|
||||||
|
|
||||||
|
# Add any paths that contain custom static files (such as style sheets) here,
|
||||||
|
# relative to this directory. They are copied after the builtin static files,
|
||||||
|
# so a file named "default.css" will overwrite the builtin "default.css".
|
||||||
|
html_static_path = ['_static']
|
||||||
|
|
||||||
|
# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
|
||||||
|
# using the given strftime format.
|
||||||
|
# html_last_updated_fmt = '%b %d, %Y'
|
||||||
|
git_cmd = "git log --pretty=format:'%ad, commit %h' --date=local -n1"
|
||||||
|
html_last_updated_fmt = os.popen(git_cmd).read()
|
||||||
|
|
||||||
|
# If true, SmartyPants will be used to convert quotes and dashes to
|
||||||
|
# typographically correct entities.
|
||||||
|
# html_use_smartypants = True
|
||||||
|
|
||||||
|
# Custom sidebar templates, maps document names to template names.
|
||||||
|
# html_sidebars = {}
|
||||||
|
|
||||||
|
# Additional templates that should be rendered to pages, maps page names to
|
||||||
|
# template names.
|
||||||
|
# html_additional_pages = {}
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
# html_domain_indices = True
|
||||||
|
|
||||||
|
# If false, no index is generated.
|
||||||
|
# html_use_index = True
|
||||||
|
|
||||||
|
# If true, the index is split into individual pages for each letter.
|
||||||
|
# html_split_index = False
|
||||||
|
|
||||||
|
# If true, links to the reST sources are added to the pages.
|
||||||
|
# html_show_sourcelink = True
|
||||||
|
|
||||||
|
# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
|
||||||
|
# html_show_sphinx = True
|
||||||
|
|
||||||
|
# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
|
||||||
|
# html_show_copyright = True
|
||||||
|
|
||||||
|
# If true, an OpenSearch description file will be output, and all pages will
|
||||||
|
# contain a <link> tag referring to it. The value of this option must be the
|
||||||
|
# base URL from which the finished HTML is served.
|
||||||
|
# html_use_opensearch = ''
|
||||||
|
|
||||||
|
# This is the file name suffix for HTML files (e.g. ".xhtml").
|
||||||
|
# html_file_suffix = None
|
||||||
|
|
||||||
|
# Output file base name for HTML help builder.
|
||||||
|
htmlhelp_basename = 'Horizondoc'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for LaTeX output -------------------------------------------------
|
||||||
|
|
||||||
|
latex_elements = {
|
||||||
|
# The paper size ('letterpaper' or 'a4paper').
|
||||||
|
# 'papersize': 'letterpaper',
|
||||||
|
|
||||||
|
# The font size ('10pt', '11pt' or '12pt').
|
||||||
|
# 'pointsize': '10pt',
|
||||||
|
|
||||||
|
# Additional stuff for the LaTeX preamble.
|
||||||
|
# 'preamble': '',
|
||||||
|
}
|
||||||
|
|
||||||
|
# Grouping the document tree into LaTeX files. List of tuples
|
||||||
|
# (source start file, target name, title, author, documentclass
|
||||||
|
# [howto/manual]).
|
||||||
|
latex_documents = [
|
||||||
|
('index', 'Horizon.tex', u'Horizon Documentation',
|
||||||
|
u'OpenStack Foundation', 'manual'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# The name of an image file (relative to this directory) to place at the top of
|
||||||
|
# the title page.
|
||||||
|
# latex_logo = None
|
||||||
|
|
||||||
|
# For "manual" documents, if this is true, then toplevel headings are parts,
|
||||||
|
# not chapters.
|
||||||
|
# latex_use_parts = False
|
||||||
|
|
||||||
|
# If true, show page references after internal links.
|
||||||
|
# latex_show_pagerefs = False
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
# latex_show_urls = False
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
# latex_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
# latex_domain_indices = True
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for manual page output -------------------------------------------
|
||||||
|
|
||||||
|
# One entry per manual page. List of tuples
|
||||||
|
# (source start file, name, description, authors, manual section).
|
||||||
|
man_pages = [
|
||||||
|
('index', 'horizon', u'Horizon Documentation',
|
||||||
|
[u'OpenStack'], 1)
|
||||||
|
]
|
||||||
|
|
||||||
|
# If true, show URL addresses after external links.
|
||||||
|
# man_show_urls = False
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Texinfo output -----------------------------------------------
|
||||||
|
|
||||||
|
# Grouping the document tree into Texinfo files. List of tuples
|
||||||
|
# (source start file, target name, title, author,
|
||||||
|
# dir menu entry, description, category)
|
||||||
|
texinfo_documents = [
|
||||||
|
('index', 'Horizon', u'Horizon Documentation', u'OpenStack',
|
||||||
|
'Horizon', 'One line description of project.', 'Miscellaneous'),
|
||||||
|
]
|
||||||
|
|
||||||
|
# Documents to append as an appendix to all manuals.
|
||||||
|
# texinfo_appendices = []
|
||||||
|
|
||||||
|
# If false, no module index is generated.
|
||||||
|
# texinfo_domain_indices = True
|
||||||
|
|
||||||
|
# How to display URL addresses: 'footnote', 'no', or 'inline'.
|
||||||
|
# texinfo_show_urls = 'footnote'
|
||||||
|
|
||||||
|
|
||||||
|
# -- Options for Epub output --------------------------------------------------
|
||||||
|
|
||||||
|
# Bibliographic Dublin Core info.
|
||||||
|
epub_title = u'Horizon'
|
||||||
|
epub_author = u'OpenStack'
|
||||||
|
epub_publisher = u'OpenStack'
|
||||||
|
epub_copyright = u'2012, OpenStack'
|
||||||
|
|
||||||
|
# The language of the text. It defaults to the language option
|
||||||
|
# or en if the language is not set.
|
||||||
|
# epub_language = ''
|
||||||
|
|
||||||
|
# The scheme of the identifier. Typical schemes are ISBN or URL.
|
||||||
|
# epub_scheme = ''
|
||||||
|
|
||||||
|
# The unique identifier of the text. This can be an ISBN number
|
||||||
|
# or the project homepage.
|
||||||
|
# epub_identifier = ''
|
||||||
|
|
||||||
|
# A unique identification for the text.
|
||||||
|
# epub_uid = ''
|
||||||
|
|
||||||
|
# A tuple containing the cover image and cover page html template filenames.
|
||||||
|
# epub_cover = ()
|
||||||
|
|
||||||
|
# HTML files that should be inserted before the pages created by sphinx.
|
||||||
|
# The format is a list of tuples containing the path and title.
|
||||||
|
# epub_pre_files = []
|
||||||
|
|
||||||
|
# HTML files shat should be inserted after the pages created by sphinx.
|
||||||
|
# The format is a list of tuples containing the path and title.
|
||||||
|
# epub_post_files = []
|
||||||
|
|
||||||
|
# A list of files that should not be packed into the epub file.
|
||||||
|
# epub_exclude_files = []
|
||||||
|
|
||||||
|
# The depth of the table of contents in toc.ncx.
|
||||||
|
# epub_tocdepth = 3
|
||||||
|
|
||||||
|
# Allow duplicate toc entries.
|
||||||
|
# epub_tocdup = True
|
|
@ -0,0 +1,559 @@
|
||||||
|
==================
|
||||||
|
Contributing Guide
|
||||||
|
==================
|
||||||
|
|
||||||
|
First and foremost, thank you for wanting to contribute! It's the only way
|
||||||
|
open source works!
|
||||||
|
|
||||||
|
Before you dive into writing patches, here are some of the basics:
|
||||||
|
|
||||||
|
* Project page: http://launchpad.net/horizon
|
||||||
|
* Bug tracker: https://bugs.launchpad.net/horizon
|
||||||
|
* Source code: https://github.com/openstack/horizon
|
||||||
|
* Code review: https://review.openstack.org/#q,status:open+project:openstack/horizon,n,z
|
||||||
|
* Continuous integration:
|
||||||
|
|
||||||
|
* Jenkins: https://jenkins.openstack.org
|
||||||
|
* Zuul: http://status.openstack.org/zuul
|
||||||
|
* IRC Channel: #openstack-horizon on Freenode.
|
||||||
|
|
||||||
|
Making Contributions
|
||||||
|
====================
|
||||||
|
|
||||||
|
Getting Started
|
||||||
|
---------------
|
||||||
|
|
||||||
|
We'll start by assuming you've got a working checkout of the repository (if
|
||||||
|
not then please see the :doc:`quickstart`).
|
||||||
|
|
||||||
|
Second, you'll need to take care of a couple administrative tasks:
|
||||||
|
|
||||||
|
#. Create an account on Launchpad.
|
||||||
|
#. Sign the `OpenStack Contributor License Agreement`_ and follow the associated
|
||||||
|
instructions to verify your signature.
|
||||||
|
#. Join the `Horizon Developers`_ team on Launchpad.
|
||||||
|
#. Follow the `instructions for setting up git-review`_ in your
|
||||||
|
development environment.
|
||||||
|
|
||||||
|
Whew! Got all that? Okay! You're good to go.
|
||||||
|
|
||||||
|
Ways To Contribute
|
||||||
|
------------------
|
||||||
|
|
||||||
|
The easiest way to get started with Horizon's code is to pick a bug on
|
||||||
|
Launchpad that interests you, and start working on that. Alternatively, if
|
||||||
|
there's an OpenStack API feature you would like to see implemented in Horizon
|
||||||
|
feel free to try building it.
|
||||||
|
|
||||||
|
If those are too big, there are lots of great ways to get involved without
|
||||||
|
plunging in head-first:
|
||||||
|
|
||||||
|
* Report bugs, triage new tickets, and review old tickets on
|
||||||
|
the `bug tracker`_.
|
||||||
|
* Propose ideas for improvements via `Launchpad Blueprints`_, via the
|
||||||
|
mailing list on the project page, or on IRC.
|
||||||
|
* Write documentation!
|
||||||
|
* Write unit tests for untested code!
|
||||||
|
* Help improve the `User Experience Design`_ or contribute to the `Persona Working Group`_.
|
||||||
|
|
||||||
|
.. _`bug tracker`: https://bugs.launchpad.net/horizon
|
||||||
|
.. _`Launchpad Blueprints`: https://blueprints.launchpad.net/horizon
|
||||||
|
.. _`User Experience Design`: https://wiki.openstack.org/wiki/UX#Getting_Started
|
||||||
|
.. _`Persona Working Group`: https://wiki.openstack.org/wiki/Personas
|
||||||
|
|
||||||
|
|
||||||
|
Choosing Issues To Work On
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
In general, if you want to write code, there are three cases for issues
|
||||||
|
you might want to work on:
|
||||||
|
|
||||||
|
#. Confirmed bugs
|
||||||
|
#. Approved blueprints (features)
|
||||||
|
#. New bugs you've discovered
|
||||||
|
|
||||||
|
If you have an idea for a new feature that isn't in a blueprint yet, it's
|
||||||
|
a good idea to write the blueprint first so you don't end up writing a bunch
|
||||||
|
of code that may not go in the direction the community wants.
|
||||||
|
|
||||||
|
For bugs, open the bug first, but if you can reproduce the bug reliably and
|
||||||
|
identify its cause then it's usually safe to start working on it. However,
|
||||||
|
getting independent confirmation (and verifying that it's not a duplicate)
|
||||||
|
is always a good idea if you can be patient.
|
||||||
|
|
||||||
|
After You Write Your Patch
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Once you've made your changes, there are a few things to do:
|
||||||
|
|
||||||
|
* Make sure the unit tests pass: ``./run_tests.sh``
|
||||||
|
* Make sure PEP8 is clean: ``./run_tests.sh --pep8``
|
||||||
|
* Make sure your code is ready for translation: ``./run_tests.sh --pseudo de`` See the Translatability section below for details.
|
||||||
|
* Make sure your code is up-to-date with the latest master: ``git pull --rebase``
|
||||||
|
* Finally, run ``git review`` to upload your changes to Gerrit for review.
|
||||||
|
|
||||||
|
The Horizon core developers will be notified of the new review and will examine
|
||||||
|
it in a timely fashion, either offering feedback or approving it to be merged.
|
||||||
|
If the review is approved, it is sent to Jenkins to verify the unit tests pass
|
||||||
|
and it can be merged cleanly. Once Jenkins approves it, the change will be
|
||||||
|
merged to the master repository and it's time to celebrate!
|
||||||
|
|
||||||
|
.. _`OpenStack Contributor License Agreement`: http://wiki.openstack.org/CLA
|
||||||
|
.. _`OpenStack Contributors`: https://launchpad.net/~openstack-cla
|
||||||
|
.. _`Horizon Developers`: https://launchpad.net/~horizon
|
||||||
|
.. _`instructions for setting up git-review`: http://docs.openstack.org/infra/manual/developers.html#development-workflow
|
||||||
|
|
||||||
|
Etiquette
|
||||||
|
=========
|
||||||
|
|
||||||
|
The community's guidelines for etiquette are fairly simple:
|
||||||
|
|
||||||
|
* Treat everyone respectfully and professionally.
|
||||||
|
* If a bug is "in progress" in the bug tracker, don't start working on it
|
||||||
|
without contacting the author. Try on IRC, or via the launchpad email
|
||||||
|
contact link. If you don't get a response after a reasonable time, then go
|
||||||
|
ahead. Checking first avoids duplicate work and makes sure nobody's toes
|
||||||
|
get stepped on.
|
||||||
|
* If a blueprint is assigned, even if it hasn't been started, be sure you
|
||||||
|
contact the assignee before taking it on. These larger issues often have a
|
||||||
|
history of discussion or specific implementation details that the assignee
|
||||||
|
may be aware of that you are not.
|
||||||
|
* Please don't re-open tickets closed by a core developer. If you disagree with
|
||||||
|
the decision on the ticket, the appropriate solution is to take it up on
|
||||||
|
IRC or the mailing list.
|
||||||
|
* Give credit where credit is due; if someone helps you substantially with
|
||||||
|
a piece of code, it's polite (though not required) to thank them in your
|
||||||
|
commit message.
|
||||||
|
|
||||||
|
Translatability
|
||||||
|
===============
|
||||||
|
Horizon gets translated into multiple languages. The pseudo translation tool
|
||||||
|
can be used to verify that code is ready to be translated. The pseudo tool
|
||||||
|
replaces a language's translation with a complete, fake translation. Then
|
||||||
|
you can verify that your code properly displays fake translations to validate
|
||||||
|
that your code is ready for translation.
|
||||||
|
|
||||||
|
Running the pseudo translation tool
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
#. Make sure your English po file is up to date: ``./run_tests.sh --makemessages``
|
||||||
|
#. Run the pseudo tool to create pseudo translations. For example, to replace the German translation with a pseudo translation: ``./run_tests.sh --pseudo de``
|
||||||
|
#. Compile the catalog: ``./run_tests.sh --compilemessages``
|
||||||
|
#. Run your development server.
|
||||||
|
#. Log in and change to the language you pseudo translated.
|
||||||
|
|
||||||
|
It should look weird. More specifically, the translatable segments are going
|
||||||
|
to start and end with a bracket and they are going to have some added
|
||||||
|
characters. For example, "Log In" will become "[~Log In~您好яшçあ]"
|
||||||
|
This is useful because you can inspect for the following, and consider if your
|
||||||
|
code is working like it should:
|
||||||
|
|
||||||
|
* If you see a string in English it's not translatable. Should it be?
|
||||||
|
* If you see brackets next to each other that might be concatenation. Concatenation
|
||||||
|
can make quality translations difficult or impossible. See
|
||||||
|
https://wiki.openstack.org/wiki/I18n/TranslatableStrings#Use_string_formating_variables.2C_never_perform_string_concatenation
|
||||||
|
for additional information.
|
||||||
|
* If there is unexpected wrapping/truncation there might not be enough
|
||||||
|
space for translations.
|
||||||
|
* If you see a string in the proper translated language, it comes from an
|
||||||
|
external source. (That's not bad, just sometimes useful to know)
|
||||||
|
* If you get new crashes, there is probably a bug.
|
||||||
|
|
||||||
|
Don't forget to cleanup any pseudo translated po files. Those don't get merged!
|
||||||
|
|
||||||
|
Code Style
|
||||||
|
==========
|
||||||
|
|
||||||
|
As a project, Horizon adheres to code quality standards.
|
||||||
|
|
||||||
|
Python
|
||||||
|
------
|
||||||
|
|
||||||
|
We follow PEP8_ for all our Python code, and use ``pep8.py`` (available
|
||||||
|
via the shortcut ``./run_tests.sh --pep8``) to validate that our code
|
||||||
|
meets proper Python style guidelines.
|
||||||
|
|
||||||
|
.. _PEP8: http://www.python.org/dev/peps/pep-0008/
|
||||||
|
|
||||||
|
Django
|
||||||
|
------
|
||||||
|
|
||||||
|
Additionally, we follow `Django's style guide`_ for templates, views, and
|
||||||
|
other miscellany.
|
||||||
|
|
||||||
|
.. _Django's style guide: https://docs.djangoproject.com/en/dev/internals/contributing/writing-code/coding-style/
|
||||||
|
|
||||||
|
JavaScript
|
||||||
|
----------
|
||||||
|
|
||||||
|
The following standards are divided into required and recommended sections.
|
||||||
|
Our main goal in establishing these best practices is to have code that is
|
||||||
|
reliable, readable, and maintainable.
|
||||||
|
|
||||||
|
Required
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
**Reliable**
|
||||||
|
|
||||||
|
* The code has to work on the stable and latest versions of Firefox, Chrome,
|
||||||
|
Safari, and Opera web browsers, and on Microsoft Internet Explorer 9 and
|
||||||
|
later.
|
||||||
|
|
||||||
|
* If you turned compression off during development via ``COMPRESS_ENABLED =
|
||||||
|
False`` in local_settings.py, re-enable compression and test your code
|
||||||
|
before submitting.
|
||||||
|
|
||||||
|
* Use ``===`` as opposed to ``==`` for equality checks. The ``==`` will do a
|
||||||
|
type cast before comparing, which can lead to unwanted results.
|
||||||
|
|
||||||
|
.. Note ::
|
||||||
|
If typecasting is desired, explicit casting is preferred to keep the
|
||||||
|
meaning of your code clear.
|
||||||
|
|
||||||
|
* Keep document reflows to a minimum. DOM manipulation is expensive, and can
|
||||||
|
become a performance issue. If you are accessing the DOM, make sure that you
|
||||||
|
are doing it in the most optimized way. One example is to build up a document
|
||||||
|
fragment and then append the fragment to the DOM in one pass instead of doing
|
||||||
|
multiple smaller DOM updates.
|
||||||
|
* Use “strict”, enclosing each JavaScript file inside a self-executing
|
||||||
|
function. The self-executing function keeps the strict scoped to the file,
|
||||||
|
so its variables and methods are not exposed to other JavaScript files in
|
||||||
|
the product.
|
||||||
|
|
||||||
|
.. Note ::
|
||||||
|
Using strict will throw exceptions for common coding errors, like
|
||||||
|
accessing global vars, that normally are not flagged.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
(function(){
|
||||||
|
'use strict';
|
||||||
|
// code...
|
||||||
|
})();
|
||||||
|
|
||||||
|
* Use ``forEach`` | ``each`` when looping whenever possible. AngularJS, and
|
||||||
|
jQuery both provide for each loops that provide both iteration and scope.
|
||||||
|
|
||||||
|
AngularJS:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
angular.forEach(objectToIterateOver, function(value, key) {
|
||||||
|
// loop logic
|
||||||
|
});
|
||||||
|
|
||||||
|
jQuery:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
$.each(objectToIterateOver, function( key, value ) {
|
||||||
|
// loop logic
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
* Do not put variables or functions in the global namespace. There are several
|
||||||
|
reasons why globals are bad, one being that all JavaScript included in an
|
||||||
|
application runs in the same scope. The issue with that is if another script
|
||||||
|
has the same method or variable names they overwrite each other.
|
||||||
|
* Always put ``var`` in front of your variables. Not putting ``var`` in front
|
||||||
|
of a variable puts that variable into the global space, see above.
|
||||||
|
* Do not use ``eval( )``. The eval (expression) evaluates the expression
|
||||||
|
passed to it. This can open up your code to security vulnerabilities and
|
||||||
|
other issues.
|
||||||
|
* Do not use '``with`` object {code}'. The ``with`` statement is used to access
|
||||||
|
properties of an object. The issue with ``with`` is that its execution is not
|
||||||
|
consistent, so by reading the statement in the code it is not always clear
|
||||||
|
how it is being used.
|
||||||
|
|
||||||
|
|
||||||
|
**Readable & Maintainable**
|
||||||
|
|
||||||
|
* Give meaningful names to methods and variables.
|
||||||
|
* Avoid excessive nesting.
|
||||||
|
* Avoid HTML and CSS in JS code. HTML and CSS belong in templates and
|
||||||
|
stylesheets respectively. For example:
|
||||||
|
|
||||||
|
* In our HTML files, we should focus on layout.
|
||||||
|
|
||||||
|
1. Reduce the small/random ``<script>`` and ``<style>`` elements in HTML.
|
||||||
|
|
||||||
|
2. Avoid in-lining styles into element in HTML. Use attributes and
|
||||||
|
classes instead.
|
||||||
|
* In our JS files, we should focus on logic rather than attempting to
|
||||||
|
manipulate/style elements.
|
||||||
|
|
||||||
|
1. Avoid statements such as ``element.css({property1,property2...})`` they
|
||||||
|
belong in a CSS class.
|
||||||
|
|
||||||
|
2. Avoid statements such as ``$("<div><span>abc</span></div>")`` they
|
||||||
|
belong in a HTML template file. Use ``show`` | ``hide`` | ``clone``
|
||||||
|
elements if dynamic content is required.
|
||||||
|
|
||||||
|
3. Avoid using classes for detection purposes only, instead, defer to
|
||||||
|
attributes. For example to find a div:
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
<div class="something"></div>
|
||||||
|
$(".something").html("Don't find me this way!");
|
||||||
|
|
||||||
|
Is better found like:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
<div data-something></div>
|
||||||
|
$("div[data-something]").html("You found me correctly!");
|
||||||
|
|
||||||
|
* Avoid commented-out code.
|
||||||
|
* Avoid dead code.
|
||||||
|
|
||||||
|
**Performance**
|
||||||
|
|
||||||
|
* Avoid creating instances of the same object repeatedly within the same scope.
|
||||||
|
Instead, assign the object to a variable and re-use the existing object. For
|
||||||
|
example:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
$(document).on('click', function() { /* do something. */ });
|
||||||
|
$(document).on('mouseover', function() { /* do something. */ });
|
||||||
|
|
||||||
|
A better approach:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
var $document = $(document);
|
||||||
|
$document.on('click', function() { /* do something. */ });
|
||||||
|
$document.on('mouseover', function() { /* do something. */ });
|
||||||
|
|
||||||
|
In the first approach a jQuery object for ``document`` is created each time.
|
||||||
|
The second approach creates only one jQuery object and reuses it. Each object
|
||||||
|
needs to be created, uses memory, and needs to be garbage collected.
|
||||||
|
|
||||||
|
Recommended
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
||||||
|
**Readable & Maintainable**
|
||||||
|
|
||||||
|
* Put a comment at the top of every file explaining what the purpose of this
|
||||||
|
file is when the naming is not obvious. This guideline also applies to
|
||||||
|
methods and variables.
|
||||||
|
* Source-code formatting – (or “beautification”) is recommended but should be
|
||||||
|
used with caution. Keep in mind that if you reformat an entire file that was
|
||||||
|
not previously formatted the same way, it will mess up the diff during the
|
||||||
|
code review. It is best to use a formatter when you are working on a new file
|
||||||
|
by yourself, or with others who are using the same formatter. You can also
|
||||||
|
choose to format a selected portion of a file only. Instructions for setting
|
||||||
|
up JSHint for Eclipse, Sublime Text, Notepad++ and WebStorm/PyCharm are
|
||||||
|
provided_.
|
||||||
|
* Use 2 spaces for code indentation.
|
||||||
|
* Use ``{ }`` for ``if``, ``for``, ``while`` statements, and don't combine them
|
||||||
|
on one line.
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
// Do this //Not this // Not this
|
||||||
|
if(x) { if(x) if(x) y =x;
|
||||||
|
y=x; y=x;
|
||||||
|
}
|
||||||
|
* Use JSHint in your development environment.
|
||||||
|
|
||||||
|
|
||||||
|
AngularJS
|
||||||
|
---------
|
||||||
|
The following standards are divided into required and recommended sections.
|
||||||
|
|
||||||
|
Required
|
||||||
|
~~~~~~~~
|
||||||
|
|
||||||
|
* Organization: Define your Angular app under the root Angular folder (such
|
||||||
|
as ``horizon/static/horizon/js/angular/hz.table.js``). If your application is
|
||||||
|
small enough you can choose to lump your Controllers, Directives, Filters,
|
||||||
|
etc.. all in the one file. But if you find your file is growing too large and
|
||||||
|
readability is becoming an issue, consider moving functionality into their
|
||||||
|
own files under sub folders as described in the Recommended section.
|
||||||
|
* Separate presentation and business logic. Controllers are for business logic,
|
||||||
|
and directives for presentation.
|
||||||
|
|
||||||
|
* Controllers and Services should not contain DOM references. Directives
|
||||||
|
should.
|
||||||
|
* Services are singletons and contain logic independent of view.
|
||||||
|
* Scope is not the model (model is your JavaScript Objects). The scope
|
||||||
|
references the model.
|
||||||
|
|
||||||
|
* Read-only in templates.
|
||||||
|
* Write-only in controllers.
|
||||||
|
* Since Django already uses ``{{ }}``, use ``{$ $}`` or ``{% verbatim %}``
|
||||||
|
instead.
|
||||||
|
* For localization in JavaScript files use either ``gettext`` or ``ngettext``.
|
||||||
|
Only those two methods are recognized by our tools and will be included in
|
||||||
|
the .po file after running ``./run_tests --makemessages``.
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
// recognized
|
||||||
|
gettext("translatable text");
|
||||||
|
ngettext("translatable text");
|
||||||
|
|
||||||
|
// not recognized
|
||||||
|
var _ = gettext;
|
||||||
|
_('translatable text');
|
||||||
|
|
||||||
|
$window.gettext('translatable text');
|
||||||
|
|
||||||
|
* For localization of AngularJS templates in Horizon, there are a couple of
|
||||||
|
ways to do it.
|
||||||
|
|
||||||
|
* Using ``gettext`` or ``ngettext`` function that is passed from server to
|
||||||
|
client. If you're only translating a few things, this methodology is ok
|
||||||
|
to use.
|
||||||
|
|
||||||
|
* Use an Angular directive that will fetch a django template instead of a
|
||||||
|
static HTML file. The advantage here is that you can now use
|
||||||
|
``{% trans %}`` and anything else Django has to offer. You can also cache
|
||||||
|
the page according to the locale if you know that the content is static.
|
||||||
|
|
||||||
|
Recommended
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
* Use these directories: filters, directives, controllers, and templates.
|
||||||
|
|
||||||
|
.. Note ::
|
||||||
|
|
||||||
|
When you use the directory name, the file name does not have to include
|
||||||
|
words like "directive" or "filter".
|
||||||
|
|
||||||
|
* Put "Ctrl" on the end of a controller file name.
|
||||||
|
* Don't use variables like "app" that are at the highest level in the file,
|
||||||
|
when Angular gives an alternative. For example use function chaining:
|
||||||
|
|
||||||
|
.. code ::
|
||||||
|
|
||||||
|
angular.module('my_module')
|
||||||
|
.controller('my_controller', ['$scope', function($scope) {
|
||||||
|
// controller code
|
||||||
|
}]).service('my_service', ['$scope', function($scope) {
|
||||||
|
// service code
|
||||||
|
}]);
|
||||||
|
|
||||||
|
|
||||||
|
JSHint
|
||||||
|
------
|
||||||
|
JSHint is a great tool to be used during your code editing to improve
|
||||||
|
JavaScript quality by checking your code against a configurable list of checks.
|
||||||
|
Therefore, JavaScript developers should configure their editors to use JSHint
|
||||||
|
to warn them of any such errors so they can be addressed. Since JSHint has a
|
||||||
|
ton of configuration options to choose from, links are provided below to the
|
||||||
|
options Horizon wants enforced along with the instructions for setting up
|
||||||
|
JSHint for Eclipse, Sublime Text, Notepad++ and WebStorm/PyCharm.
|
||||||
|
|
||||||
|
JSHint configuration file: `.jshintrc`_
|
||||||
|
|
||||||
|
Instructions for setting up JSHint: `JSHint setup instructions`_
|
||||||
|
|
||||||
|
.. Note ::
|
||||||
|
JSHint is part of the automated unit tests performed by Jenkins. The
|
||||||
|
automated test use the default configurations, which are less strict than
|
||||||
|
the configurations we recommended to run in your local development
|
||||||
|
environment.
|
||||||
|
|
||||||
|
.. _.jshintrc: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig/Settings#.jshintrc
|
||||||
|
.. _JSHint setup instructions: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig
|
||||||
|
.. _provided: https://wiki.openstack.org/wiki/Horizon/Javascript/EditorConfig
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
CSS
|
||||||
|
---
|
||||||
|
|
||||||
|
Style guidelines for CSS are currently quite minimal. Do your best to make the
|
||||||
|
code readable and well-organized. Two spaces are preferred for indentation
|
||||||
|
so as to match both the JavaScript and HTML files.
|
||||||
|
|
||||||
|
|
||||||
|
JavaScript and CSS libraries
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
We do not bundle the third-party code within Horizon's source tree anymore, any
|
||||||
|
code that is still there is just left over and will be cleaned up and packaged
|
||||||
|
properly eventually. What we do instead, is packaging the required files as
|
||||||
|
XStatic Python packages and adding them as dependencies to Horizon. In
|
||||||
|
particular, when you need to add a new third-party JavaScript or CSS library to
|
||||||
|
Horizon, follow those steps:
|
||||||
|
|
||||||
|
1. Check if the library is already packaged as Xstatic on PyPi, by searching
|
||||||
|
for the library name. If it already is, go to step 5. If it is, but not in
|
||||||
|
the right version, contact the original packager.
|
||||||
|
2. Package the library as an Xstatic package by following the instructions in
|
||||||
|
Xstatic documentation_.
|
||||||
|
3. `Create a new repository on StackForge`_. Use "xstatic-core" and
|
||||||
|
"xstatic-ptl" groups for the ACLs. Make sure to include the
|
||||||
|
``publish-to-pypi`` job.
|
||||||
|
4. `Setup PyPi`_ to allow OpenStack to publish your package.
|
||||||
|
5. `Tag your release`_. That will cause it to be automatically packaged and
|
||||||
|
released to PyPi.
|
||||||
|
6. Add the package to global-requirements_. Make sure to mention the license.
|
||||||
|
7. Add the package to Horizon's ``requirements.txt`` file, to its
|
||||||
|
``settings.py``, and to the ``_scripts.html`` or ``_stylesheets.html``
|
||||||
|
templates. Make sure to keep the order alphabetic.
|
||||||
|
|
||||||
|
.. _documentation: http://xstatic.rtfd.org/en/latest/packaging.html
|
||||||
|
.. _`Create a new repository on StackForge`: http://docs.openstack.org/infra/manual/creators.html
|
||||||
|
.. _global-requirements: https://github.com/openstack/requirements/blob/master/global-requirements.txt
|
||||||
|
.. _`Tag your release`: http://docs.openstack.org/infra/manual/drivers.html#tagging-a-release
|
||||||
|
.. _`Setup PyPi`: http://docs.openstack.org/infra/manual/creators.html#give-openstack-permission-to-publish-releases
|
||||||
|
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Note that once a package is released, you can not "unrealease" it. You
|
||||||
|
should never attempt to modify, delete or rename a released package without
|
||||||
|
a lot of careful planning and feedback from all projects that use it.
|
||||||
|
|
||||||
|
For the purpose of fixing packaging mistakes, XStatic has the build number
|
||||||
|
mechanism. Simply fix the error, increment the build number and release the
|
||||||
|
newer package.
|
||||||
|
|
||||||
|
|
||||||
|
HTML
|
||||||
|
----
|
||||||
|
|
||||||
|
Again, readability is paramount; however be conscientious of how the browser
|
||||||
|
will handle whitespace when rendering the output. Two spaces is the preferred
|
||||||
|
indentation style to match all front-end code.
|
||||||
|
|
||||||
|
Documentation
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Horizon's documentation is written in reStructuredText and uses Sphinx for
|
||||||
|
additional parsing and functionality, and should follow
|
||||||
|
standard practices for writing reST. This includes:
|
||||||
|
|
||||||
|
* Flow paragraphs such that lines wrap at 80 characters or less.
|
||||||
|
* Use proper grammar, spelling, capitalization and punctuation at all times.
|
||||||
|
* Make use of Sphinx's autodoc feature to document modules, classes
|
||||||
|
and functions. This keeps the docs close to the source.
|
||||||
|
* Where possible, use Sphinx's cross-reference syntax (e.g.
|
||||||
|
``:class:`~horizon.foo.Bar```) when referring to other Horizon components.
|
||||||
|
The better-linked our docs are, the easier they are to use.
|
||||||
|
|
||||||
|
Be sure to generate the documentation before submitting a patch for review.
|
||||||
|
Unexpected warnings often appear when building the documentation, and slight
|
||||||
|
reST syntax errors frequently cause links or cross-references not to work
|
||||||
|
correctly.
|
||||||
|
|
||||||
|
Conventions
|
||||||
|
-----------
|
||||||
|
|
||||||
|
Simply by convention, we have a few rules about naming:
|
||||||
|
|
||||||
|
* The term "project" is used in place of Keystone's "tenant" terminology
|
||||||
|
in all user-facing text. The term "tenant" is still used in API code to
|
||||||
|
make things more obvious for developers.
|
||||||
|
|
||||||
|
* The term "dashboard" refers to a top-level dashboard class, and "panel" to
|
||||||
|
the sub-items within a dashboard. Referring to a panel as a dashboard is
|
||||||
|
both confusing and incorrect.
|
|
@ -0,0 +1,45 @@
|
||||||
|
==========================
|
||||||
|
Frequently Asked Questions
|
||||||
|
==========================
|
||||||
|
|
||||||
|
What is the relationship between ``Dashboards``, ``Panels``, and navigation?
|
||||||
|
|
||||||
|
The navigational structure is strongly encouraged to flow from
|
||||||
|
``Dashboard`` objects as top-level navigation items to ``Panel`` objects as
|
||||||
|
sub-navigation items as in the current implementation. Template tags
|
||||||
|
are provided to automatically generate this structure.
|
||||||
|
|
||||||
|
That said, you are not required to use the provided tools and can write
|
||||||
|
templates and URLconfs by hand to create any desired structure.
|
||||||
|
|
||||||
|
Does a panel have to be an app in ``INSTALLED_APPS``?
|
||||||
|
|
||||||
|
A panel can live in any Python module. It can be a standalone which ties
|
||||||
|
into an existing dashboard, or it can be contained alongside others within
|
||||||
|
a larger dashboard "app". There is no strict enforcement here. Python
|
||||||
|
is "a language for consenting adults." A module containing a Panel does
|
||||||
|
not need to be added to ``INSTALLED_APPS``, but this is a common and
|
||||||
|
convenient way to load a standalone panel.
|
||||||
|
|
||||||
|
Could I hook an external service into a panel using, for example, an iFrame?
|
||||||
|
|
||||||
|
Panels are just entry-points to hook views into the larger dashboard
|
||||||
|
navigational structure and enforce common attributes like RBAC. The
|
||||||
|
view and corresponding templates can contain anything you would like,
|
||||||
|
including iFrames.
|
||||||
|
|
||||||
|
What does this mean for visual design?
|
||||||
|
|
||||||
|
The ability to add an arbitrary number of top-level navigational items
|
||||||
|
(``Dashboard`` objects) poses a new design challenge. Horizon's lead
|
||||||
|
designer has taken on the challenge of providing a reference design
|
||||||
|
for Horizon which supports this possibility.
|
||||||
|
|
||||||
|
What browsers are supported?
|
||||||
|
|
||||||
|
Horizon is primarily tested and supported on the latest version of Firefox,
|
||||||
|
the latest version of Chrome, and IE9+. Issues related to Safari and Opera
|
||||||
|
will also be considered. The list of supported browsers and versions is
|
||||||
|
informally documented on the `Browser Support wiki page
|
||||||
|
<https://wiki.openstack.org/wiki/Horizon/BrowserSupport>`_.
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
========
|
||||||
|
Glossary
|
||||||
|
========
|
||||||
|
|
||||||
|
Horizon
|
||||||
|
|
||||||
|
The OpenStack dashboard project. Also the name of the top-level
|
||||||
|
Python object which handles registration for the app.
|
||||||
|
|
||||||
|
Dashboard
|
||||||
|
|
||||||
|
A Python class representing a top-level navigation item (e.g. "project")
|
||||||
|
which provides a consistent API for Horizon-compatible applications.
|
||||||
|
|
||||||
|
Panel
|
||||||
|
|
||||||
|
A Python class representing a sub-navigation item (e.g. "instances")
|
||||||
|
which contains all the necessary logic (views, forms, tests, etc.) for
|
||||||
|
that interface.
|
||||||
|
|
||||||
|
Project
|
||||||
|
|
||||||
|
Used in user-facing text in place of the term "Tenant" which is Keystone's
|
||||||
|
word.
|
|
@ -0,0 +1,132 @@
|
||||||
|
..
|
||||||
|
Copyright 2012 OpenStack Foundation
|
||||||
|
All Rights Reserved.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
not use this file except in compliance with the License. You may obtain
|
||||||
|
a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
License for the specific language governing permissions and limitations
|
||||||
|
under the License.
|
||||||
|
|
||||||
|
========================================
|
||||||
|
Horizon: The OpenStack Dashboard Project
|
||||||
|
========================================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
Horizon is the canonical implementation of `OpenStack's Dashboard
|
||||||
|
<https://github.com/openstack/horizon>`_, which provides a web based user
|
||||||
|
interface to OpenStack services including Nova, Swift, Keystone, etc.
|
||||||
|
|
||||||
|
For a more in-depth look at Horizon and its architecture, see the
|
||||||
|
:doc:`Introduction to Horizon <intro>`.
|
||||||
|
|
||||||
|
To learn what you need to know to get going, see the :doc:`quickstart`.
|
||||||
|
|
||||||
|
Using Horizon
|
||||||
|
=============
|
||||||
|
|
||||||
|
How to use Horizon in your own projects.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
topics/install
|
||||||
|
topics/deployment
|
||||||
|
topics/settings
|
||||||
|
topics/customizing
|
||||||
|
|
||||||
|
Developer Docs
|
||||||
|
==============
|
||||||
|
|
||||||
|
For those wishing to develop Horizon itself, or go in-depth with building
|
||||||
|
your own :class:`~horizon.Dashboard` or :class:`~horizon.Panel` classes,
|
||||||
|
the following documentation is provided.
|
||||||
|
|
||||||
|
General information
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
Brief guides to areas of interest and importance when developing Horizon.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
intro
|
||||||
|
quickstart
|
||||||
|
topics/tutorial
|
||||||
|
contributing
|
||||||
|
testing
|
||||||
|
|
||||||
|
Topic Guides
|
||||||
|
------------
|
||||||
|
|
||||||
|
Information on how to work with specific areas of Horizon can be found in
|
||||||
|
the following topic guides.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
topics/workflows
|
||||||
|
topics/tables
|
||||||
|
topics/policy
|
||||||
|
topics/testing
|
||||||
|
topics/table_actions
|
||||||
|
|
||||||
|
API Reference
|
||||||
|
-------------
|
||||||
|
|
||||||
|
In-depth documentation for Horizon and its APIs.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
ref/run_tests
|
||||||
|
ref/horizon
|
||||||
|
ref/workflows
|
||||||
|
ref/tables
|
||||||
|
ref/tabs
|
||||||
|
ref/forms
|
||||||
|
ref/middleware
|
||||||
|
ref/context_processors
|
||||||
|
ref/decorators
|
||||||
|
ref/exceptions
|
||||||
|
ref/test
|
||||||
|
ref/local_conf
|
||||||
|
|
||||||
|
Source Code Reference
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Auto-generated reference for the complete source code.
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
sourcecode/autoindex
|
||||||
|
|
||||||
|
Release Notes
|
||||||
|
=============
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:glob:
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
releases/*
|
||||||
|
|
||||||
|
Information
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. toctree::
|
||||||
|
:maxdepth: 1
|
||||||
|
|
||||||
|
faq
|
||||||
|
glossary
|
||||||
|
|
||||||
|
* :ref:`genindex`
|
||||||
|
* :ref:`modindex`
|
|
@ -0,0 +1,121 @@
|
||||||
|
==============
|
||||||
|
Horizon Basics
|
||||||
|
==============
|
||||||
|
|
||||||
|
Values
|
||||||
|
======
|
||||||
|
|
||||||
|
"Think simple" as my old master used to say - meaning reduce
|
||||||
|
the whole of its parts into the simplest terms, getting back
|
||||||
|
to first principles.
|
||||||
|
|
||||||
|
-- Frank Lloyd Wright
|
||||||
|
|
||||||
|
Horizon holds several key values at the core of its design and architecture:
|
||||||
|
|
||||||
|
* Core Support: Out-of-the-box support for all core OpenStack projects.
|
||||||
|
* Extensible: Anyone can add a new component as a "first-class citizen".
|
||||||
|
* Manageable: The core codebase should be simple and easy-to-navigate.
|
||||||
|
* Consistent: Visual and interaction paradigms are maintained throughout.
|
||||||
|
* Stable: A reliable API with an emphasis on backwards-compatibility.
|
||||||
|
* Usable: Providing an *awesome* interface that people *want* to use.
|
||||||
|
|
||||||
|
The only way to attain and uphold those ideals is to make it *easy* for
|
||||||
|
developers to implement those values.
|
||||||
|
|
||||||
|
History
|
||||||
|
=======
|
||||||
|
|
||||||
|
Horizon started life as a single app to manage OpenStack's compute project.
|
||||||
|
As such, all it needed was a set of views, templates, and API calls.
|
||||||
|
|
||||||
|
From there it grew to support multiple OpenStack projects and APIs gradually,
|
||||||
|
arranged rigidly into "dash" and "syspanel" groupings.
|
||||||
|
|
||||||
|
During the "Diablo" release cycle an initial plugin system was added using
|
||||||
|
signals to hook in additional URL patterns and add links into the "dash"
|
||||||
|
and "syspanel" navigation.
|
||||||
|
|
||||||
|
This incremental growth served the goal of "Core Support" phenomenally, but
|
||||||
|
left "Extensible" and "Manageable" behind. And while the other key values took
|
||||||
|
shape of their own accord, it was time to re-architect for an extensible,
|
||||||
|
modular future.
|
||||||
|
|
||||||
|
|
||||||
|
The Current Architecture & How It Meets Our Values
|
||||||
|
==================================================
|
||||||
|
|
||||||
|
At its core, **Horizon should be a registration pattern for
|
||||||
|
applications to hook into**. Here's what that means and how it is
|
||||||
|
implemented in terms of our values:
|
||||||
|
|
||||||
|
Core Support
|
||||||
|
------------
|
||||||
|
|
||||||
|
Horizon ships with three central dashboards, a "User Dashboard", a
|
||||||
|
"System Dashboard", and a "Settings" dashboard. Between these three they
|
||||||
|
cover the core OpenStack applications and deliver on Core Support.
|
||||||
|
|
||||||
|
The Horizon application also ships with a set of API abstractions
|
||||||
|
for the core OpenStack projects in order to provide a consistent, stable set
|
||||||
|
of reusable methods for developers. Using these abstractions, developers
|
||||||
|
working on Horizon don't need to be intimately familiar with the APIs of
|
||||||
|
each OpenStack project.
|
||||||
|
|
||||||
|
Extensible
|
||||||
|
----------
|
||||||
|
|
||||||
|
A Horizon dashboard application is based around the :class:`~horizon.Dashboard`
|
||||||
|
class that provides a consistent API and set of capabilities for both
|
||||||
|
core OpenStack dashboard apps shipped with Horizon and equally for third-party
|
||||||
|
apps. The :class:`~horizon.Dashboard` class is treated as a top-level
|
||||||
|
navigation item.
|
||||||
|
|
||||||
|
Should a developer wish to provide functionality within an existing dashboard
|
||||||
|
(e.g. adding a monitoring panel to the user dashboard) the simple registration
|
||||||
|
pattern makes it possible to write an app which hooks into other dashboards
|
||||||
|
just as easily as creating a new dashboard. All you have to do is import the
|
||||||
|
dashboard you wish to modify.
|
||||||
|
|
||||||
|
Manageable
|
||||||
|
----------
|
||||||
|
|
||||||
|
Within the application, there is a simple method for registering a
|
||||||
|
:class:`~horizon.Panel` (sub-navigation items). Each panel contains the
|
||||||
|
necessary logic (views, forms, tests, etc.) for that interface. This granular
|
||||||
|
breakdown prevents files (such as ``api.py``) from becoming thousands of
|
||||||
|
lines long and makes code easy to find by correlating it directly to the
|
||||||
|
navigation.
|
||||||
|
|
||||||
|
Consistent
|
||||||
|
----------
|
||||||
|
|
||||||
|
By providing the necessary core classes to build from, as well as a
|
||||||
|
solid set of reusable templates and additional tools (base form classes,
|
||||||
|
base widget classes, template tags, and perhaps even class-based views)
|
||||||
|
we can maintain consistency across applications.
|
||||||
|
|
||||||
|
Stable
|
||||||
|
------
|
||||||
|
|
||||||
|
By architecting around these core classes and reusable components we
|
||||||
|
create an implicit contract that changes to these components will be
|
||||||
|
made in the most backwards-compatible ways whenever possible.
|
||||||
|
|
||||||
|
Usable
|
||||||
|
------
|
||||||
|
|
||||||
|
Ultimately that's up to each and every developer that touches the code,
|
||||||
|
but if we get all the other goals out of the way then we are free to focus
|
||||||
|
on the best possible experience.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:doc:`Quickstart <quickstart>`
|
||||||
|
A short guide to getting started with using Horizon.
|
||||||
|
|
||||||
|
:doc:`Frequently Asked Questions <faq>`
|
||||||
|
Common questions and answers.
|
||||||
|
|
||||||
|
:doc:`Glossary <glossary>`
|
||||||
|
Common terms and their definitions.
|
|
@ -0,0 +1,314 @@
|
||||||
|
==========
|
||||||
|
Quickstart
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. Note ::
|
||||||
|
|
||||||
|
This section has been tested for Horizon on Ubuntu (12.04-64) and Fedora-based (RHEL 6.4) distributions. Feel free to add notes and any changes according to your experiences or operating system.
|
||||||
|
|
||||||
|
Linux Systems
|
||||||
|
=============
|
||||||
|
|
||||||
|
Install the prerequisite packages.
|
||||||
|
|
||||||
|
On Ubuntu::
|
||||||
|
|
||||||
|
> sudo apt-get install git python-dev python-virtualenv libssl-dev libffi-dev
|
||||||
|
|
||||||
|
On Fedora-based distributions (e.g., Fedora/RHEL/CentOS/Scientific Linux)::
|
||||||
|
|
||||||
|
> sudo yum install gcc git-core python-devel python-virtualenv openssl-devel libffi-devel which
|
||||||
|
|
||||||
|
Setup
|
||||||
|
=====
|
||||||
|
|
||||||
|
To setup a Horizon development environment simply clone the Horizon git
|
||||||
|
repository from http://github.com/openstack/horizon and execute the
|
||||||
|
``run_tests.sh`` script from the root folder (see :doc:`ref/run_tests`)::
|
||||||
|
|
||||||
|
> git clone https://github.com/openstack/horizon.git
|
||||||
|
> cd horizon
|
||||||
|
> ./run_tests.sh
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Running ``run_tests.sh`` will build a virtualenv, ``.venv``, where all the
|
||||||
|
python dependencies for Horizon are installed and referenced. After the
|
||||||
|
dependencies are installed, the unit test suites in the Horizon repo will be
|
||||||
|
executed. There should be no errors from the tests.
|
||||||
|
|
||||||
|
Next you will need to setup your Django application config by copying ``openstack_dashboard/local/local_settings.py.example`` to ``openstack_dashboard/local/local_settings.py``. To do this quickly you can use the following command::
|
||||||
|
|
||||||
|
> cp openstack_dashboard/local/local_settings.py.example openstack_dashboard/local/local_settings.py
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
To add new settings or customize existing settings, modify the ``local_settings.py`` file.
|
||||||
|
|
||||||
|
Horizon assumes a single end-point for OpenStack services which defaults to
|
||||||
|
the local host (127.0.0.1), as is the default in DevStack. If this is not the
|
||||||
|
case change the ``OPENSTACK_HOST`` setting in the
|
||||||
|
``openstack_dashboard/local/local_settings.py`` file, to the actual IP address
|
||||||
|
of the OpenStack end-point Horizon should use.
|
||||||
|
|
||||||
|
You can save changes you made to
|
||||||
|
``openstack_dashboard/local/local_settings.py`` with the following command::
|
||||||
|
|
||||||
|
> python manage.py migrate_settings --gendiff
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This creates a ``local_settings.diff`` file which is a diff between
|
||||||
|
``local_settings.py`` and ``local_settings.py.example``
|
||||||
|
|
||||||
|
If you upgrade Horizon, you might need to update your
|
||||||
|
``openstack_dashboard/local/local_settings.py`` file with new parameters from
|
||||||
|
``openstack_dashboard/local/local_settings.py.example`` to do so, first update
|
||||||
|
Horizon::
|
||||||
|
|
||||||
|
> git remote update && git pull --ff-only origin master
|
||||||
|
|
||||||
|
Then update your ``openstack_dashboard/local/local_settings.py`` file::
|
||||||
|
|
||||||
|
> mv openstack_dashboard/local/local_settings.py openstack_dashboard/local/local_settings.py.old
|
||||||
|
> python manage.py migrate_settings
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This applies ``openstack_dashboard/local/local_settings.diff`` on
|
||||||
|
``openstack_dashboard/local/local_settings.py.example`` to regenerate an
|
||||||
|
``openstack_dashboard/local/local_settings.py`` file.
|
||||||
|
The migration can sometimes have difficulties to migrate some settings, if
|
||||||
|
this happens you will be warned with a conflict message pointing to an
|
||||||
|
``openstack_dashboard/local/local_settings.py_Some_DateTime.rej`` file.
|
||||||
|
In this file, you will see the lines which could not be automatically
|
||||||
|
changed and you will have to redo only these few changes manually instead
|
||||||
|
of modifying the full
|
||||||
|
``openstack_dashboard/local/local_settings.py.example`` file.
|
||||||
|
|
||||||
|
When all settings have been migrated, it is safe to regenerate a clean diff in
|
||||||
|
order to prevent Conflicts for future migrations::
|
||||||
|
|
||||||
|
> mv openstack_dashboard/local/local_settings.diff openstack_dashboard/local/local_settings.diff.old
|
||||||
|
> python manage.py migrate_settings --gendiff
|
||||||
|
|
||||||
|
To start the Horizon development server use ``run_tests.sh``::
|
||||||
|
|
||||||
|
> ./run_tests.sh --runserver localhost:9000
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The default port for runserver is 8000 which is already consumed by
|
||||||
|
heat-api-cfn in DevStack. If not running in DevStack
|
||||||
|
`./run_tests.sh --runserver` will start the test server at
|
||||||
|
`http://localhost:8000`.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``run_tests.sh`` script provides wrappers around ``manage.py``.
|
||||||
|
For more information on manage.py which is a django, see
|
||||||
|
`https://docs.djangoproject.com/en/dev/ref/django-admin/`
|
||||||
|
|
||||||
|
|
||||||
|
Once the Horizon server is running, point a web browser to http://localhost:9000
|
||||||
|
or to the IP and port the server is listening for.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The ``DevStack`` project (http://devstack.org/) can be used to install
|
||||||
|
an OpenStack development environment from scratch. For a local.conf that
|
||||||
|
enables most services that Horizon supports managing see
|
||||||
|
:doc:`local.conf <ref/local_conf>`
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The minimum required set of OpenStack services running includes the
|
||||||
|
following:
|
||||||
|
|
||||||
|
* Nova (compute, api, scheduler, and network)
|
||||||
|
* Glance
|
||||||
|
* Keystone
|
||||||
|
* Neutron (unless nova-network is used)
|
||||||
|
|
||||||
|
Horizon provides optional support for other services.
|
||||||
|
See :ref:`system-requirements-label` for the supported services.
|
||||||
|
If Keystone endpoint for a service is configured, Horizon detects it
|
||||||
|
and enables its support automatically.
|
||||||
|
|
||||||
|
|
||||||
|
Editing Horizon's Source
|
||||||
|
========================
|
||||||
|
|
||||||
|
Although DevStack installs and configures an instance of Horizon when running
|
||||||
|
stack.sh, the preferred development setup follows the instructions above on the
|
||||||
|
server/VM running DevStack. The are several advantages to maintaining a
|
||||||
|
separate copy of the Horizon repo, rather than editing the devstack installed
|
||||||
|
copy.
|
||||||
|
|
||||||
|
* Source code changes aren't as easily lost when running unstack.sh/stack.sh
|
||||||
|
* The development server picks up source code changes (other than JavaScript
|
||||||
|
and CSS due to compression and compilation) while still running.
|
||||||
|
* Log messages and print statements go directly to the console.
|
||||||
|
* Debugging with pdb becomes much simpler to interact with.
|
||||||
|
|
||||||
|
.. Note::
|
||||||
|
JavaScript and CSS changes require a development server restart. Also,
|
||||||
|
forcing a refresh of the page (e.g. using Shift-F5) in the browser is
|
||||||
|
required to pull down non-cached versions of the CSS and JavaScript. The
|
||||||
|
default setting in Horizon is to do compilation and compression of these
|
||||||
|
files at server startup. If you have configured your local copy to do
|
||||||
|
offline compression, more steps are required.
|
||||||
|
|
||||||
|
|
||||||
|
Horizon's Structure
|
||||||
|
===================
|
||||||
|
|
||||||
|
This project is a bit different from other OpenStack projects in that it has
|
||||||
|
two very distinct components underneath it: ``horizon``, and
|
||||||
|
``openstack_dashboard``.
|
||||||
|
|
||||||
|
The ``horizon`` directory holds the generic libraries and components that can
|
||||||
|
be used in any Django project.
|
||||||
|
|
||||||
|
The ``openstack_dashboard`` directory contains a reference Django project that
|
||||||
|
uses ``horizon``.
|
||||||
|
|
||||||
|
For development, both pieces share an environment which (by default) is
|
||||||
|
built with the ``tools/install_venv.py`` script. That script creates a
|
||||||
|
virtualenv and installs all the necessary packages.
|
||||||
|
|
||||||
|
If dependencies are added to either ``horizon`` or ``openstack_dashboard``,
|
||||||
|
they should be added to ``requirements.txt``.
|
||||||
|
|
||||||
|
.. important::
|
||||||
|
|
||||||
|
If you do anything which changes the environment (adding new dependencies
|
||||||
|
or renaming directories are both great examples) be sure to increment the
|
||||||
|
``environment_version`` counter in :doc:`run_tests.sh <ref/run_tests>`.
|
||||||
|
|
||||||
|
Project
|
||||||
|
=======
|
||||||
|
|
||||||
|
Dashboard configuration
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
To add a new dashboard to your project, you need to add a configuration file to
|
||||||
|
``openstack_dashboard/local/enabled`` directory. For more information on this,
|
||||||
|
see :ref:`pluggable-settings-label`.
|
||||||
|
|
||||||
|
There is also an alternative way to add a new dashboard, by adding it to
|
||||||
|
Django's ``INSTALLED_APPS`` setting. For more information about this, see
|
||||||
|
:ref:`dashboards`. However, please note that the recommended way is to take
|
||||||
|
advantage of the pluggable settings feature.
|
||||||
|
|
||||||
|
URLs
|
||||||
|
----
|
||||||
|
|
||||||
|
Then you add a single line to your project's ``urls.py``::
|
||||||
|
|
||||||
|
url(r'', include(horizon.urls)),
|
||||||
|
|
||||||
|
Those urls are automatically constructed based on the registered Horizon apps.
|
||||||
|
If a different URL structure is desired it can be constructed by hand.
|
||||||
|
|
||||||
|
Templates
|
||||||
|
---------
|
||||||
|
|
||||||
|
Pre-built template tags generate navigation. In your ``nav.html``
|
||||||
|
template you might have the following::
|
||||||
|
|
||||||
|
{% load horizon %}
|
||||||
|
|
||||||
|
<div class='nav'>
|
||||||
|
{% horizon_main_nav %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
And in your ``sidebar.html`` you might have::
|
||||||
|
|
||||||
|
{% load horizon %}
|
||||||
|
|
||||||
|
<div class='sidebar'>
|
||||||
|
{% horizon_dashboard_nav %}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
These template tags are aware of the current "active" dashboard and panel
|
||||||
|
via template context variables and will render accordingly.
|
||||||
|
|
||||||
|
Application
|
||||||
|
===========
|
||||||
|
|
||||||
|
Structure
|
||||||
|
---------
|
||||||
|
|
||||||
|
An application would have the following structure (we'll use project as
|
||||||
|
an example)::
|
||||||
|
|
||||||
|
project/
|
||||||
|
|---__init__.py
|
||||||
|
|---dashboard.py <-----Registers the app with Horizon and sets dashboard properties
|
||||||
|
|---overview/
|
||||||
|
|---images/
|
||||||
|
|-- images
|
||||||
|
|-- __init__.py
|
||||||
|
|---panel.py <-----Registers the panel in the app and defines panel properties
|
||||||
|
|-- snapshots/
|
||||||
|
|-- templates/
|
||||||
|
|-- tests.py
|
||||||
|
|-- urls.py
|
||||||
|
|-- views.py
|
||||||
|
...
|
||||||
|
...
|
||||||
|
|
||||||
|
Dashboard Classes
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Inside of ``dashboard.py`` you would have a class definition and the registration
|
||||||
|
process::
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
....
|
||||||
|
# ObjectStorePanels is an example for a PanelGroup
|
||||||
|
# for panel classes in general, see below
|
||||||
|
class ObjectStorePanels(horizon.PanelGroup):
|
||||||
|
slug = "object_store"
|
||||||
|
name = _("Object Store")
|
||||||
|
panels = ('containers',)
|
||||||
|
|
||||||
|
|
||||||
|
class Project(horizon.Dashboard):
|
||||||
|
name = _("Project") # Appears in navigation
|
||||||
|
slug = "project" # Appears in URL
|
||||||
|
# panels may be strings or refer to classes, such as
|
||||||
|
# ObjectStorePanels
|
||||||
|
panels = (BasePanels, NetworkPanels, ObjectStorePanels)
|
||||||
|
default_panel = 'overview'
|
||||||
|
...
|
||||||
|
|
||||||
|
horizon.register(Project)
|
||||||
|
|
||||||
|
Panel Classes
|
||||||
|
-------------
|
||||||
|
|
||||||
|
To connect a :class:`~horizon.Panel` with a :class:`~horizon.Dashboard` class
|
||||||
|
you register it in a ``panel.py`` file like so::
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project import dashboard
|
||||||
|
|
||||||
|
|
||||||
|
class Images(horizon.Panel):
|
||||||
|
name = "Images"
|
||||||
|
slug = 'images'
|
||||||
|
permissions = ('openstack.roles.admin', 'my.other.permission',)
|
||||||
|
|
||||||
|
|
||||||
|
# You could also register your panel with another application's dashboard
|
||||||
|
dashboard.Project.register(Images)
|
||||||
|
|
||||||
|
By default a :class:`~horizon.Panel` class looks for a ``urls.py`` file in the
|
||||||
|
same directory as ``panel.py`` to include in the rollup of url patterns from
|
||||||
|
panels to dashboards to Horizon, resulting in a wholly extensible, configurable
|
||||||
|
URL structure.
|
|
@ -0,0 +1,6 @@
|
||||||
|
==========================
|
||||||
|
Horizon Context Processors
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. automodule:: horizon.context_processors
|
||||||
|
:members:
|
|
@ -0,0 +1,6 @@
|
||||||
|
==================
|
||||||
|
Horizon Decorators
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: horizon.decorators
|
||||||
|
:members:
|
|
@ -0,0 +1,6 @@
|
||||||
|
==================
|
||||||
|
Horizon Exceptions
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: horizon.exceptions
|
||||||
|
:members:
|
|
@ -0,0 +1,98 @@
|
||||||
|
=============
|
||||||
|
Horizon Forms
|
||||||
|
=============
|
||||||
|
|
||||||
|
Horizon ships with some very useful base form classes, form fields,
|
||||||
|
class-based views, and javascript helpers which streamline most of the common
|
||||||
|
tasks related to form handling.
|
||||||
|
|
||||||
|
Form Classes
|
||||||
|
============
|
||||||
|
|
||||||
|
.. automodule:: horizon.forms.base
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Form Fields
|
||||||
|
===========
|
||||||
|
|
||||||
|
.. automodule:: horizon.forms.fields
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Form Views
|
||||||
|
==========
|
||||||
|
|
||||||
|
.. automodule:: horizon.forms.views
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Forms Javascript
|
||||||
|
================
|
||||||
|
|
||||||
|
Switchable Fields
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
By marking fields with the ``"switchable"`` and ``"switched"`` classes along
|
||||||
|
with defining a few data attributes you can programmatically hide, show,
|
||||||
|
and rename fields in a form.
|
||||||
|
|
||||||
|
The triggers are fields using a ``select`` input widget, marked with the
|
||||||
|
"switchable" class, and defining a "data-slug" attribute. When they are changed,
|
||||||
|
any input with the ``"switched"`` class and defining a ``"data-switch-on"``
|
||||||
|
attribute which matches the ``select`` input's ``"data-slug"`` attribute will be
|
||||||
|
evaluated for necessary changes. In simpler terms, if the ``"switched"`` target
|
||||||
|
input's ``"switch-on"`` matches the ``"slug"`` of the ``"switchable"`` trigger
|
||||||
|
input, it gets switched. Simple, right?
|
||||||
|
|
||||||
|
The ``"switched"`` inputs also need to define states. For each state in which
|
||||||
|
the input should be shown, it should define a data attribute like the
|
||||||
|
following: ``data-<slug>-<value>="<desired label>"``. When the switch event
|
||||||
|
happens the value of the ``"switchable"`` field will be compared to the
|
||||||
|
data attributes and the correct label will be applied to the field. If
|
||||||
|
a corresponding label for that value is *not* found, the field will
|
||||||
|
be hidden instead.
|
||||||
|
|
||||||
|
A simplified example is as follows::
|
||||||
|
|
||||||
|
source = forms.ChoiceField(
|
||||||
|
label=_('Source'),
|
||||||
|
choices=[
|
||||||
|
('cidr', _('CIDR')),
|
||||||
|
('sg', _('Security Group'))
|
||||||
|
],
|
||||||
|
widget=forms.Select(attrs={
|
||||||
|
'class': 'switchable',
|
||||||
|
'data-slug': 'source'
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
cidr = fields.IPField(
|
||||||
|
label=_("CIDR"),
|
||||||
|
required=False,
|
||||||
|
widget=forms.TextInput(attrs={
|
||||||
|
'class': 'switched',
|
||||||
|
'data-switch-on': 'source',
|
||||||
|
'data-source-cidr': _('CIDR')
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
security_group = forms.ChoiceField(
|
||||||
|
label=_('Security Group'),
|
||||||
|
required=False,
|
||||||
|
widget=forms.Select(attrs={
|
||||||
|
'class': 'switched',
|
||||||
|
'data-switch-on': 'source',
|
||||||
|
'data-source-sg': _('Security Group')
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
That code would create the ``"switchable"`` control field ``source``, and the
|
||||||
|
two ``"switched"`` fields ``cidr`` and ``security group`` which are hidden or
|
||||||
|
shown depending on the value of ``source``.
|
||||||
|
|
||||||
|
|
||||||
|
NOTE: A field can only safely define one slug in its ``"switch-on"`` attribute.
|
||||||
|
While switching on multiple fields is possible, the behavior is very hard to
|
||||||
|
predict due to the events being fired from the various switchable fields in
|
||||||
|
order. You generally end up just having it hidden most of the time by accident,
|
||||||
|
so it's not recommended. Instead just add a second field to the form and control
|
||||||
|
the two independently, then merge their results in the form's clean or handle
|
||||||
|
methods at the end.
|
|
@ -0,0 +1,45 @@
|
||||||
|
======================
|
||||||
|
The ``horizon`` Module
|
||||||
|
======================
|
||||||
|
|
||||||
|
.. module:: horizon
|
||||||
|
|
||||||
|
Horizon ships with a single point of contact for hooking into your project if
|
||||||
|
you aren't developing your own :class:`~horizon.Dashboard` or
|
||||||
|
:class:`~horizon.Panel`::
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
From there you can access all the key methods you need.
|
||||||
|
|
||||||
|
Horizon
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. attribute:: urls
|
||||||
|
|
||||||
|
The auto-generated URLconf for Horizon. Usage::
|
||||||
|
|
||||||
|
url(r'', include(horizon.urls)),
|
||||||
|
|
||||||
|
.. autofunction:: register
|
||||||
|
.. autofunction:: unregister
|
||||||
|
.. autofunction:: get_absolute_url
|
||||||
|
.. autofunction:: get_user_home
|
||||||
|
.. autofunction:: get_dashboard
|
||||||
|
.. autofunction:: get_default_dashboard
|
||||||
|
.. autofunction:: get_dashboards
|
||||||
|
|
||||||
|
Dashboard
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. autoclass:: Dashboard
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Panel
|
||||||
|
=====
|
||||||
|
|
||||||
|
.. autoclass:: Panel
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: PanelGroup
|
||||||
|
:members:
|
|
@ -0,0 +1,70 @@
|
||||||
|
==========
|
||||||
|
local.conf
|
||||||
|
==========
|
||||||
|
|
||||||
|
Configuring DevStack for Horizon
|
||||||
|
================================
|
||||||
|
|
||||||
|
Place the following content into `devstack/local.conf` to start the services
|
||||||
|
that Horizon supports in DevStack when `stack.sh` is run.
|
||||||
|
::
|
||||||
|
|
||||||
|
[[local|localrc]]
|
||||||
|
|
||||||
|
ADMIN_PASSWORD=pass
|
||||||
|
MYSQL_PASSWORD=pass
|
||||||
|
RABBIT_PASSWORD=pass
|
||||||
|
SERVICE_PASSWORD=pass
|
||||||
|
SERVICE_TOKEN=a682f596-76f3-11e3-b3b2-e716f9080d50
|
||||||
|
|
||||||
|
# Recloning will insure that your stack is up to date. The downside
|
||||||
|
# is overhead on restarts and potentially losing a stable environment.
|
||||||
|
# If set to yes, will reclone all repos every time stack.sh is run.
|
||||||
|
# The default is no.
|
||||||
|
#RECLONE=yes
|
||||||
|
|
||||||
|
# Note: there are several network setting changes that may be
|
||||||
|
# required to get networking properly configured in your environment.
|
||||||
|
# This file is just using the defaults set up by devstack.
|
||||||
|
# For a more detailed treatment of devstack network configuration
|
||||||
|
# options, please see: http://devstack.org/guides/single-machine.html
|
||||||
|
|
||||||
|
# Enable Swift (object-store) Service without replication
|
||||||
|
enable_service s-proxy s-object s-container s-account
|
||||||
|
SWIFT_HASH=66a3d6b56c1f479c8b4e70ab5c2000f5
|
||||||
|
SWIFT_REPLICAS=1
|
||||||
|
SWIFT_DATA_DIR=$DEST/data/swift
|
||||||
|
|
||||||
|
# enabling Neutron (network) Service
|
||||||
|
# to use nova net rather than neutron, comment out the following group
|
||||||
|
disable_service n-net
|
||||||
|
enable_service q-svc
|
||||||
|
enable_service q-agt
|
||||||
|
enable_service q-dhcp
|
||||||
|
enable_service q-l3
|
||||||
|
enable_service q-meta
|
||||||
|
enable_service q-metering
|
||||||
|
enable_service neutron
|
||||||
|
enable_service q-lbaas
|
||||||
|
enable_service q-fwaas
|
||||||
|
enable_service q-vpn
|
||||||
|
# end group
|
||||||
|
|
||||||
|
# enable Sahara (data-processing) Service
|
||||||
|
enable_service sahara
|
||||||
|
|
||||||
|
# enable Trove (database) Service
|
||||||
|
enable_service trove tr-api tr-tmgr tr-cond
|
||||||
|
|
||||||
|
# enable Ceilometer (metering) Service
|
||||||
|
enable_service ceilometer-acompute ceilometer-acentral ceilometer-anotification ceilometer-collector ceilometer-api
|
||||||
|
|
||||||
|
|
||||||
|
# Set ``OFFLINE`` to ``True`` to configure ``stack.sh`` to run cleanly without
|
||||||
|
# Internet access. ``stack.sh`` must have been previously run with Internet
|
||||||
|
# access to install prerequisites and fetch repositories.
|
||||||
|
# OFFLINE=True
|
||||||
|
|
||||||
|
[[post-config|$GLANCE_API_CONF]]
|
||||||
|
[DEFAULT]
|
||||||
|
default_store=file
|
|
@ -0,0 +1,6 @@
|
||||||
|
==================
|
||||||
|
Horizon Middleware
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. automodule:: horizon.middleware
|
||||||
|
:members:
|
|
@ -0,0 +1,263 @@
|
||||||
|
===========================
|
||||||
|
The ``run_tests.sh`` Script
|
||||||
|
===========================
|
||||||
|
|
||||||
|
.. contents:: Contents:
|
||||||
|
:local:
|
||||||
|
|
||||||
|
Horizon ships with a script called ``run_tests.sh`` at the root of the
|
||||||
|
repository. This script provides many crucial functions for the project,
|
||||||
|
and also makes several otherwise complex tasks trivial for you as a
|
||||||
|
developer.
|
||||||
|
|
||||||
|
First Run
|
||||||
|
=========
|
||||||
|
|
||||||
|
If you start with a clean copy of the Horizon repository, the first thing
|
||||||
|
you should do is to run ``./run_tests.sh`` from the root of the repository.
|
||||||
|
This will do two things for you:
|
||||||
|
|
||||||
|
#. Set up a virtual environment for both the ``horizon`` module and
|
||||||
|
the ``openstack_dashboard`` project using ``./tools/install_venv.py``.
|
||||||
|
#. Run the tests for both ``horizon`` and ``openstack_dashboard`` using
|
||||||
|
their respective environments and verify that everything is working.
|
||||||
|
|
||||||
|
Setting up the environment the first time can take several minutes, but only
|
||||||
|
needs to be done once. If dependencies are added in the future, updating the
|
||||||
|
environments will be necessary but not as time consuming.
|
||||||
|
|
||||||
|
I just want to run the tests!
|
||||||
|
=============================
|
||||||
|
|
||||||
|
Running the full set of unit tests quickly and easily is the main goal of this
|
||||||
|
script. All you need to do is::
|
||||||
|
|
||||||
|
./run_tests.sh
|
||||||
|
|
||||||
|
Yep, that's it. However, for a more thorough test run you can include the
|
||||||
|
Selenium tests by using the ``--with-selenium`` flag::
|
||||||
|
|
||||||
|
./run_tests.sh --with-selenium
|
||||||
|
|
||||||
|
If you run horizon in a minimal installation VM, you will probably need
|
||||||
|
the following (steps for Fedora 18 minimal installation):
|
||||||
|
|
||||||
|
#. Install these packages in the VM:
|
||||||
|
``yum install xorg-x11-xauth xorg-x11-fonts-Type1.noarch``.
|
||||||
|
#. Install firefox in the VM:
|
||||||
|
``yum install firefox``.
|
||||||
|
#. Connect to the VM by ``ssh -X``
|
||||||
|
(if you run ``set|grep DISP``, you should see that the DISPLAY is set).
|
||||||
|
#. Run
|
||||||
|
``./run_tests.sh --with-selenium``.
|
||||||
|
|
||||||
|
Running a subset of tests
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Instead of running all tests, you can specify an individual directory, file,
|
||||||
|
class, or method that contains test code.
|
||||||
|
|
||||||
|
To run the tests in the ``horizon/test/tests/tables.py`` file::
|
||||||
|
|
||||||
|
./run_tests.sh horizon.test.tests.tables
|
||||||
|
|
||||||
|
To run the tests in the `WorkflowsTests` class in
|
||||||
|
``horizon/test/tests/workflows``::
|
||||||
|
|
||||||
|
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests
|
||||||
|
|
||||||
|
To run just the `WorkflowsTests.test_workflow_view` test method::
|
||||||
|
|
||||||
|
./run_tests.sh horizon.test.tests.workflows:WorkflowsTests.test_workflow_view
|
||||||
|
|
||||||
|
Running the integration tests
|
||||||
|
-----------------------------
|
||||||
|
|
||||||
|
The Horizon integration tests treat Horizon as a black box, and similar
|
||||||
|
to Tempest must be run against an existing OpenStack system. These
|
||||||
|
tests are not run by default.
|
||||||
|
|
||||||
|
#. Update the configuration file
|
||||||
|
`openstack_dashboard/test/integration_tests/horizon.conf` as
|
||||||
|
required (the format is similar to the Tempest configuration file).
|
||||||
|
|
||||||
|
#. Run the tests with the following command: ::
|
||||||
|
|
||||||
|
$ ./run_tests.sh --integration
|
||||||
|
|
||||||
|
Like for the unit tests, you can choose to only run a subset. ::
|
||||||
|
|
||||||
|
$ ./run_tests.sh --integration openstack_dashboard.test.integration_tests.tests.test_login
|
||||||
|
|
||||||
|
|
||||||
|
Using Dashboard and Panel Templates
|
||||||
|
===================================
|
||||||
|
|
||||||
|
Horizon has a set of convenient management commands for creating new
|
||||||
|
dashboards and panels based on basic templates.
|
||||||
|
|
||||||
|
Dashboards
|
||||||
|
----------
|
||||||
|
|
||||||
|
To create a new dashboard, run the following::
|
||||||
|
|
||||||
|
./run_tests.sh -m startdash <dash_name>
|
||||||
|
|
||||||
|
This will create a directory with the given dashboard name, a ``dashboard.py``
|
||||||
|
module with the basic dashboard code filled in, and various other common
|
||||||
|
"boilerplate" code.
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
* ``--target``: the directory in which the dashboard files should be created.
|
||||||
|
Default: A new directory within the current directory.
|
||||||
|
|
||||||
|
Panels
|
||||||
|
------
|
||||||
|
|
||||||
|
To create a new panel, run the following::
|
||||||
|
|
||||||
|
./run_tests -m startpanel <panel_name>
|
||||||
|
|
||||||
|
This will create a directory with the given panel name, and ``panel.py``
|
||||||
|
module with the basic panel code filled in, and various other common
|
||||||
|
"boilerplate" code.
|
||||||
|
|
||||||
|
Available options:
|
||||||
|
|
||||||
|
* ``-d``, ``--dashboard``: The dotted python path to your dashboard app (the
|
||||||
|
module which containers the ``dashboard.py`` file.). If not specified, the
|
||||||
|
target dashboard should be specified in a pluggable settings file for the
|
||||||
|
panel.
|
||||||
|
* ``--target``: the directory in which the panel files should be created.
|
||||||
|
If the value is ``auto`` the panel will be created as a new directory inside
|
||||||
|
the dashboard module's directory structure. Default: A new directory within
|
||||||
|
the current directory.
|
||||||
|
|
||||||
|
Give me metrics!
|
||||||
|
================
|
||||||
|
|
||||||
|
You can generate various reports and metrics using command line arguments
|
||||||
|
to ``run_tests.sh``.
|
||||||
|
|
||||||
|
Coverage
|
||||||
|
--------
|
||||||
|
|
||||||
|
To run coverage reports::
|
||||||
|
|
||||||
|
./run_tests.sh --coverage
|
||||||
|
|
||||||
|
The reports are saved to ``./reports/`` and ``./coverage.xml``.
|
||||||
|
|
||||||
|
PEP8
|
||||||
|
----
|
||||||
|
|
||||||
|
You can check for PEP8 violations as well::
|
||||||
|
|
||||||
|
./run_tests.sh --pep8
|
||||||
|
|
||||||
|
The results are saved to ``./pep8.txt``.
|
||||||
|
|
||||||
|
PyLint
|
||||||
|
------
|
||||||
|
|
||||||
|
For more detailed code analysis you can run::
|
||||||
|
|
||||||
|
./run_tests.sh --pylint
|
||||||
|
|
||||||
|
The output will be saved in ``./pylint.txt``.
|
||||||
|
|
||||||
|
JsHint
|
||||||
|
------
|
||||||
|
|
||||||
|
For code analysis of JavaScript files::
|
||||||
|
|
||||||
|
./run_tests.sh --jshint
|
||||||
|
|
||||||
|
You need to have jshint installed before running the command.
|
||||||
|
|
||||||
|
Tab Characters
|
||||||
|
--------------
|
||||||
|
|
||||||
|
For those who dislike having a mix of tab characters and spaces for indentation
|
||||||
|
there's a command to check for that in Python, CSS, JavaScript and HTML files::
|
||||||
|
|
||||||
|
./run_tests.sh --tabs
|
||||||
|
|
||||||
|
This will output a total "tab count" and a list of the offending files.
|
||||||
|
|
||||||
|
Running the development server
|
||||||
|
==============================
|
||||||
|
|
||||||
|
As an added bonus, you can run Django's development server directly from
|
||||||
|
the root of the repository with ``run_tests.sh`` like so::
|
||||||
|
|
||||||
|
./run_tests.sh --runserver
|
||||||
|
|
||||||
|
This is effectively just an alias for::
|
||||||
|
|
||||||
|
./tools/with_venv.sh ./manage.py runserver
|
||||||
|
|
||||||
|
Generating the documentation
|
||||||
|
============================
|
||||||
|
|
||||||
|
You can build Horizon's documentation automatically by running::
|
||||||
|
|
||||||
|
./run_tests.sh --docs
|
||||||
|
|
||||||
|
The output is stored in ``./doc/build/html/``.
|
||||||
|
|
||||||
|
Updating the translation files
|
||||||
|
==============================
|
||||||
|
|
||||||
|
You can update all of the translation files for both the ``horizon`` app and
|
||||||
|
``openstack_dashboard`` project with a single command::
|
||||||
|
|
||||||
|
./run_tests.sh --makemessages
|
||||||
|
|
||||||
|
or, more compactly::
|
||||||
|
|
||||||
|
./run_tests.sh --m
|
||||||
|
|
||||||
|
Starting clean
|
||||||
|
==============
|
||||||
|
|
||||||
|
If you ever want to start clean with a new environment for Horizon, you can
|
||||||
|
run::
|
||||||
|
|
||||||
|
./run_tests.sh --force
|
||||||
|
|
||||||
|
That will blow away the existing environments and create new ones for you.
|
||||||
|
|
||||||
|
Non-interactive Mode
|
||||||
|
====================
|
||||||
|
|
||||||
|
There is an optional flag which will run the script in a non-interactive
|
||||||
|
(and eventually less verbose) mode::
|
||||||
|
|
||||||
|
./run_tests.sh --quiet
|
||||||
|
|
||||||
|
This will automatically take the default action for actions which would
|
||||||
|
normally prompt for user input such as installing/updating the environment.
|
||||||
|
|
||||||
|
Environment Backups
|
||||||
|
===================
|
||||||
|
|
||||||
|
To speed up the process of doing clean checkouts, running continuous
|
||||||
|
integration tests, etc. there are options for backing up the current
|
||||||
|
environment and restoring from a backup::
|
||||||
|
|
||||||
|
./run_tests.sh --restore-environment
|
||||||
|
./run_tests.sh --backup-environment
|
||||||
|
|
||||||
|
The environment backup is stored in ``/tmp/.horizon_environment/``.
|
||||||
|
|
||||||
|
Environment Versioning
|
||||||
|
======================
|
||||||
|
|
||||||
|
Horizon keeps track of changes to the environment by comparing the
|
||||||
|
current requirements files (``requirements.txt`` and
|
||||||
|
``test-requirements.txt``) and the files last time the virtual
|
||||||
|
environment was created or updated. If there is any difference,
|
||||||
|
the virtual environment will be update automatically when you run
|
||||||
|
``run_tests.sh``.
|
|
@ -0,0 +1,104 @@
|
||||||
|
==================
|
||||||
|
Horizon DataTables
|
||||||
|
==================
|
||||||
|
|
||||||
|
.. module:: horizon.tables
|
||||||
|
|
||||||
|
Horizon includes a componentized API for programmatically creating tables
|
||||||
|
in the UI. Why would you want this? It means that every table renders
|
||||||
|
correctly and consistently, table- and row-level actions all have a consistent
|
||||||
|
API and appearance, and generally you don't have to reinvent the wheel or
|
||||||
|
copy-and-paste every time you need a new table!
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
For usage information, tips & tricks and more examples check out the :doc:`DataTables Topic
|
||||||
|
Guide </topics/tables>`.
|
||||||
|
|
||||||
|
DataTable
|
||||||
|
=========
|
||||||
|
|
||||||
|
The core class which defines the high-level structure of the table being
|
||||||
|
represented. Example::
|
||||||
|
|
||||||
|
class MyTable(DataTable):
|
||||||
|
name = Column('name')
|
||||||
|
email = Column('email')
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
name = "my_table"
|
||||||
|
table_actions = (MyAction, MyOtherAction)
|
||||||
|
row_actions - (MyAction)
|
||||||
|
|
||||||
|
A full reference is included below:
|
||||||
|
|
||||||
|
.. autoclass:: DataTable
|
||||||
|
:members:
|
||||||
|
|
||||||
|
DataTable Options
|
||||||
|
=================
|
||||||
|
|
||||||
|
The following options can be defined in a ``Meta`` class inside a
|
||||||
|
:class:`.DataTable` class. Example::
|
||||||
|
|
||||||
|
class MyTable(DataTable):
|
||||||
|
class Meta:
|
||||||
|
name = "my_table"
|
||||||
|
verbose_name = "My Table"
|
||||||
|
|
||||||
|
.. autoclass:: horizon.tables.base.DataTableOptions
|
||||||
|
:members:
|
||||||
|
|
||||||
|
FormsetDataTable
|
||||||
|
================
|
||||||
|
|
||||||
|
You can integrate the :class:`.DataTable` with a Django Formset using one of following classes:
|
||||||
|
|
||||||
|
.. autoclass:: horizon.tables.formset.FormsetDataTableMixin
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: horizon.tables.formset.FormsetDataTable
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Table Components
|
||||||
|
================
|
||||||
|
|
||||||
|
.. autoclass:: Column
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: Row
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Actions
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. autoclass:: Action
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: LinkAction
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: FilterAction
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: FixedFilterAction
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: BatchAction
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: DeleteAction
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: UpdateAction
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Class-Based Views
|
||||||
|
=================
|
||||||
|
|
||||||
|
Several class-based views are provided to make working with DataTables
|
||||||
|
easier in your UI.
|
||||||
|
|
||||||
|
.. autoclass:: DataTableView
|
||||||
|
|
||||||
|
.. autoclass:: MultiTableView
|
|
@ -0,0 +1,45 @@
|
||||||
|
==========================
|
||||||
|
Horizon Tabs and TabGroups
|
||||||
|
==========================
|
||||||
|
|
||||||
|
.. module:: horizon.tabs
|
||||||
|
|
||||||
|
Horizon includes a set of reusable components for programmatically
|
||||||
|
building tabbed interfaces with fancy features like dynamic AJAX loading
|
||||||
|
and nearly effortless templating and styling.
|
||||||
|
|
||||||
|
Tab Groups
|
||||||
|
==========
|
||||||
|
|
||||||
|
For any tabbed interface, your fundamental element is the tab group which
|
||||||
|
contains all your tabs. This class provides a dead-simple API for building
|
||||||
|
tab groups and encapsulates all the necessary logic behind the scenes.
|
||||||
|
|
||||||
|
.. autoclass:: TabGroup
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Tabs
|
||||||
|
====
|
||||||
|
|
||||||
|
The tab itself is the discrete unit for a tab group, representing one
|
||||||
|
view of data.
|
||||||
|
|
||||||
|
.. autoclass:: Tab
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: TableTab
|
||||||
|
:members:
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
TabView
|
||||||
|
=======
|
||||||
|
|
||||||
|
There is also a useful and simple generic class-based view for handling
|
||||||
|
the display of a :class:`~horizon.tabs.TabGroup` class.
|
||||||
|
|
||||||
|
.. autoclass:: TabView
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: TabbedTableView
|
||||||
|
:members:
|
|
@ -0,0 +1,25 @@
|
||||||
|
========================
|
||||||
|
Horizon TestCase Classes
|
||||||
|
========================
|
||||||
|
|
||||||
|
.. module:: horizon.test.helpers
|
||||||
|
|
||||||
|
Horizon provides a base test case class which provides several useful
|
||||||
|
pre-prepared attributes for testing Horizon components.
|
||||||
|
|
||||||
|
.. autoclass:: TestCase
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. module :: openstack_dashboard.test.helpers
|
||||||
|
|
||||||
|
The OpenStack Dashboard also provides test case classes for greater
|
||||||
|
ease-of-use when testing APIs and OpenStack-specific auth scenarios.
|
||||||
|
|
||||||
|
.. autoclass:: TestCase
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: APITestCase
|
||||||
|
:members:
|
||||||
|
|
||||||
|
.. autoclass:: BaseAdminViewTests
|
||||||
|
:members:
|
|
@ -0,0 +1,38 @@
|
||||||
|
=================
|
||||||
|
Horizon Workflows
|
||||||
|
=================
|
||||||
|
|
||||||
|
.. module:: horizon.workflows
|
||||||
|
|
||||||
|
One of the most challenging aspects of building a compelling user experience
|
||||||
|
is crafting complex multi-part workflows. Horizon's ``workflows`` module
|
||||||
|
aims to bring that capability within everyday reach.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
For usage information, tips & tricks and more examples check out the
|
||||||
|
:doc:`Workflows Topic Guide </topics/workflows>`.
|
||||||
|
|
||||||
|
Workflows
|
||||||
|
=========
|
||||||
|
|
||||||
|
.. autoclass:: Workflow
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Steps
|
||||||
|
=====
|
||||||
|
|
||||||
|
.. autoclass:: Step
|
||||||
|
:members:
|
||||||
|
|
||||||
|
Actions
|
||||||
|
=======
|
||||||
|
|
||||||
|
.. autoclass:: Action
|
||||||
|
:members:
|
||||||
|
|
||||||
|
WorkflowView
|
||||||
|
============
|
||||||
|
|
||||||
|
.. autoclass:: WorkflowView
|
||||||
|
:members:
|
|
@ -0,0 +1,148 @@
|
||||||
|
======================
|
||||||
|
Horizon 2012.1 "Essex"
|
||||||
|
======================
|
||||||
|
|
||||||
|
Release Overview
|
||||||
|
================
|
||||||
|
|
||||||
|
During the Essex release cycle, Horizon underwent a significant set of internal
|
||||||
|
changes to allow extensibility and customization while also adding a significant
|
||||||
|
number of new features and bringing much greater stability to every interaction
|
||||||
|
with the underlying components.
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
==========
|
||||||
|
|
||||||
|
Extensibility
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Making Horizon extensible for third-party developers was one of the core
|
||||||
|
goals for the Essex release cycle. Massive strides have been made to allow
|
||||||
|
for the addition of new "plug-in" components and customization of OpenStack
|
||||||
|
Dashboard deployments.
|
||||||
|
|
||||||
|
To support this extensibility, all the components used to build on Horizon's
|
||||||
|
interface are now modular and reusable. Horizon's own dashboards use these
|
||||||
|
components, and they have all been built with third-party developers in mind.
|
||||||
|
Some of the main components are listed below.
|
||||||
|
|
||||||
|
Dashboards and Panels
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon's structure has been divided into logical groupings called dashboards
|
||||||
|
and panels. Horizon's classes representing these concepts handle all the
|
||||||
|
structural concerns associated with building a complete user interface
|
||||||
|
(navigation, access control, url structure, etc.).
|
||||||
|
|
||||||
|
Data Tables
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
One of the most common activities in a dashboard user interface is simply
|
||||||
|
displaying a list of resources or data and allowing the user to take actions on
|
||||||
|
that data. To this end, Horizon abstracted the commonalities of this task into a
|
||||||
|
reusable set of classes which allow developers to programmatically create
|
||||||
|
displays and interactions for their data with minimal effort and zero
|
||||||
|
boilerplate.
|
||||||
|
|
||||||
|
Tabs and TabGroups
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Another extremely common user-interface element is the use of "tabs" to break
|
||||||
|
down discrete groups of data into manageable chunks. Since these tabs often
|
||||||
|
encompass vastly different data, may have completely different access
|
||||||
|
restrictions, and may sometimes be better-off being loaded dynamically rather
|
||||||
|
than with the initial page load, Horizon includes tab and tab group classes for
|
||||||
|
constructing these interfaces elegantly and with no knowledge of the HTML, CSS
|
||||||
|
or JavaScript involved.
|
||||||
|
|
||||||
|
Nova Features
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Support for Nova's features has been greatly improved in Essex:
|
||||||
|
|
||||||
|
* Support for Nova volumes, including:
|
||||||
|
* Volumes creation and management.
|
||||||
|
* Volume snapshots.
|
||||||
|
* Realtime AJAX updating for volumes in transition states.
|
||||||
|
* Improved Nova instance display and interactions, including:
|
||||||
|
* Launching instances from volumes.
|
||||||
|
* Pausing/suspending instances.
|
||||||
|
* Displaying instance power states.
|
||||||
|
* Realtime AJAX updating for instances in transition states.
|
||||||
|
* Support for managing Floating IP address pools.
|
||||||
|
* New instance and volume detail views.
|
||||||
|
|
||||||
|
Settings
|
||||||
|
--------
|
||||||
|
|
||||||
|
A new "Settings" area was added that offers several useful functions:
|
||||||
|
|
||||||
|
* EC2 credentials download.
|
||||||
|
* OpenStack RC file download.
|
||||||
|
* User language preference customization.
|
||||||
|
|
||||||
|
User Experience Improvements
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
* Support for batch actions on multiple resources (e.g. terminating multiple
|
||||||
|
instances at once).
|
||||||
|
* Modal interactions throughout the entire UI.
|
||||||
|
* AJAX form submission for in-place validation.
|
||||||
|
* Improved in-context help for forms (tooltips and validation messages).
|
||||||
|
|
||||||
|
|
||||||
|
Community
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Creation and publication of a set of Human Interface Guidelines (HIG).
|
||||||
|
* Copious amounts of documentation for developers.
|
||||||
|
|
||||||
|
Under The Hood
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* Internationalization fully enabled, with all strings marked for translation.
|
||||||
|
* Client library changes:
|
||||||
|
* Full migration to python-novaclient from the deprecated openstackx library.
|
||||||
|
* Migration to python-keystoneclient from the deprecated keystone portion
|
||||||
|
of the python-novaclient library.
|
||||||
|
* Client-side templating capabilities for more easily creating dynamic
|
||||||
|
interactions.
|
||||||
|
* Frontend overhaul to use the Bootstrap CSS/JS framework.
|
||||||
|
* Centralized error handling for vastly improved stability/reliability
|
||||||
|
across APIs/clients.
|
||||||
|
* Completely revamped test suite with comprehensive test data.
|
||||||
|
* Forward-compatibility with Django 1.4 and the option of cookie-based sessions.
|
||||||
|
|
||||||
|
Known Issues and Limitations
|
||||||
|
============================
|
||||||
|
|
||||||
|
Quantum
|
||||||
|
-------
|
||||||
|
|
||||||
|
Quantum support has been removed from Horizon for the Essex release. It will be
|
||||||
|
restored in Folsom in conjunction with Quantum's first release as a core
|
||||||
|
OpenStack project.
|
||||||
|
|
||||||
|
Keystone
|
||||||
|
--------
|
||||||
|
|
||||||
|
Due to the mechanisms by which Keystone determines "admin"-ness for a user, an
|
||||||
|
admin user interacting with the "Project" dashboard may see some inconsistent
|
||||||
|
behavior such as all resources being listed instead of only those belonging to
|
||||||
|
that project, or only being able to return to the "Admin" dashboard while
|
||||||
|
accessing certain projects.
|
||||||
|
|
||||||
|
Exceptions during customization
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
Exceptions raised while overriding built-in Horizon behavior via the
|
||||||
|
"customization_module" setting may trigger a bug in the error handling
|
||||||
|
which will mask the original exception.
|
||||||
|
|
||||||
|
Backwards Compatibility
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The Essex Horizon release is only partially backwards-compatible with Diablo
|
||||||
|
OpenStack components. While it is largely possible to log in and interact, many
|
||||||
|
functions in Nova, Glance and Keystone changed too substantially in Essex to
|
||||||
|
maintain full compatibility.
|
|
@ -0,0 +1,159 @@
|
||||||
|
=======================
|
||||||
|
Horizon 2012.2 "Folsom"
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Release Overview
|
||||||
|
================
|
||||||
|
|
||||||
|
The Folsom release cycle brought several major advances to Horizon's user
|
||||||
|
experience while also reintroducing Quantum networking as a core piece
|
||||||
|
of the OpenStack Dashboard.
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
==========
|
||||||
|
|
||||||
|
Networking (Quantum)
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
With Quantum being a core project for the Folsom release, we worked closely
|
||||||
|
with the Quantum team to bring networking support back into Horizon. This
|
||||||
|
appears in two primary places: the Networks panel in both the Project and
|
||||||
|
Admin dashboards, and the Network tab in the Launch Instance workflow. Expect
|
||||||
|
further improvements in these areas as Quantum continues to mature and more
|
||||||
|
users adopt this model of virtual network management.
|
||||||
|
|
||||||
|
User Experience
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Workflows
|
||||||
|
~~~~~~~~~
|
||||||
|
|
||||||
|
By far the biggest UI/UX change in the Folsom release is the introduction of
|
||||||
|
programmatic workflows. These components allow developers to create concise
|
||||||
|
interactions that combine discrete tasks spanning multiple services and
|
||||||
|
resources in a user-friendly way and with minimal boilerplate code. Within
|
||||||
|
a workflow, related objects can also be dynamically created so users don't lose
|
||||||
|
their place when they realize the item they wanted isn't currently available.
|
||||||
|
Look for examples of these workflows in Launch Instance, Associate Floating IP,
|
||||||
|
and Create/Edit Project.
|
||||||
|
|
||||||
|
Resource Browser
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Another cool new component is an interface designed for "browsing" resources
|
||||||
|
which are nested under a parent resource. The object store (Swift) is a prime
|
||||||
|
example of this. Now there is a consistent top-level navigation for containers
|
||||||
|
on the left-hand pane of the "browser" while the right-hand pane lets you
|
||||||
|
explore within those containers and sub-folders.
|
||||||
|
|
||||||
|
User Experience Improvements
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
* Timezone support is now enabled. You can select your preferred timezone
|
||||||
|
in the User Settings panel.
|
||||||
|
|
||||||
|
Community
|
||||||
|
---------
|
||||||
|
|
||||||
|
* Third-party developers who wish to build on Horizon can get started much
|
||||||
|
faster using the new dashboard and panel templates. See the docs on
|
||||||
|
`creating a dashboard`_ and `creating a panel`_ for more information.
|
||||||
|
|
||||||
|
* A `thorough set of documentation`_ for developers on how to go about
|
||||||
|
internationalizing, localizing and translating OpenStack projects
|
||||||
|
is now available.
|
||||||
|
|
||||||
|
.. _creating a dashboard: http://docs.openstack.org/developer/horizon/topics/tutorial.html#creating-a-dashboard
|
||||||
|
.. _creating a panel: http://docs.openstack.org/developer/horizon/topics/tutorial.html#creating-a-panel
|
||||||
|
.. _thorough set of documentation: http://wiki.openstack.org/Translations
|
||||||
|
|
||||||
|
Under The Hood
|
||||||
|
--------------
|
||||||
|
|
||||||
|
* The python-swiftclient library and python-cinderclient libraries are now
|
||||||
|
used under the hood instead of cloudfiles and python-novaclient respectively.
|
||||||
|
|
||||||
|
* Internationalization of client-side JavaScript is now possible in addition
|
||||||
|
to server-side Python code.
|
||||||
|
|
||||||
|
* Keystone authentication is now handled by a proper pluggable Django
|
||||||
|
authentication backend, offering significantly better and more reliable
|
||||||
|
security for Horizon.
|
||||||
|
|
||||||
|
Other Improvements and Fixes
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Some of the general areas of improvement include:
|
||||||
|
|
||||||
|
* Images can now be added to Glance by providing a URL for Glance to download
|
||||||
|
the image data from.
|
||||||
|
|
||||||
|
* Quotas are now displayed dynamically throughout the Project dashboard.
|
||||||
|
|
||||||
|
* API endpoints are now displayed on the OpenStack RC File panel so they
|
||||||
|
can be organically discovered by an end-user.
|
||||||
|
|
||||||
|
* DataTables now support a summation row at the bottom of the table.
|
||||||
|
|
||||||
|
* Better cross-browser support (Safari and IE particularly).
|
||||||
|
|
||||||
|
* Fewer API calls to OpenStack endpoints (improves performance).
|
||||||
|
|
||||||
|
* Better validation of what actions are permitted when.
|
||||||
|
|
||||||
|
* Improved error handling and error messages.
|
||||||
|
|
||||||
|
Known Issues and Limitations
|
||||||
|
============================
|
||||||
|
|
||||||
|
Floating IPs and Quantum
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Due to the very late addition of floating IP support in Quantum, Nova's
|
||||||
|
integration there is lacking, so floating IP-related API calls to Nova will
|
||||||
|
fail when your OpenStack deployment uses Quantum for networking. This means
|
||||||
|
that Horizon actions such as "allocate" and "associate" floating IPs will
|
||||||
|
not work either since they rely on the underlying APIs.
|
||||||
|
|
||||||
|
Pagination
|
||||||
|
----------
|
||||||
|
|
||||||
|
A number of the "index" pages don't fully work with API pagination yet,
|
||||||
|
causing them to only display the first chunk of results returned by the API.
|
||||||
|
This number is often 1000 (as in the case of novaclient results), but does vary
|
||||||
|
somewhat.
|
||||||
|
|
||||||
|
Deleting large numbers of resources simultaneously
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Using the "select all" checkbox to delete large numbers of resources via the
|
||||||
|
API can cause network timeouts (depending on configuration). This is
|
||||||
|
due to the APIs not supporting bulk-deletion natively, and consequently Horizon
|
||||||
|
has to send requests to delete each resource individually behind the scenes.
|
||||||
|
|
||||||
|
Backwards Compatibility
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The Folsom Horizon release should be fully-compatible with both Folsom and
|
||||||
|
Essex versions of the rest of the OpenStack core projects (Nova, Swift, etc.).
|
||||||
|
While some features work significantly better with an all-Folsom stack due
|
||||||
|
to bugfixes, etc. in underlying services, there should not be any limitations
|
||||||
|
on what will or will not function. (Note: Quantum was not a core OpenStack
|
||||||
|
project in Essex, and thus this statement does not apply to network management.)
|
||||||
|
|
||||||
|
In terms of APIs provided for extending Horizon, there are a handful of
|
||||||
|
backwards-incompatible changes that were made:
|
||||||
|
|
||||||
|
* The ``can_haz`` and ``can_haz_list`` template filters have been renamed
|
||||||
|
to ``has_permissions`` and ``has_permissions_on_list`` respectively.
|
||||||
|
|
||||||
|
* The dashboard-specific ``base.html`` templates (e.g. ``nova/base.html``,
|
||||||
|
``syspanel/base.html``, etc.) have been removed in favor of a single
|
||||||
|
``base.html`` template.
|
||||||
|
|
||||||
|
* In conjunction with the previous item, the dashboard-specific template blocks
|
||||||
|
(e.g. ``nova_main``, ``syspanel_main``, etc.) have been removed in favor of
|
||||||
|
a single ``main`` template block.
|
||||||
|
|
||||||
|
Overall, though, great effort has been made to maintain compatibility for
|
||||||
|
third-party developers who may have built on Horizon so far.
|
|
@ -0,0 +1,274 @@
|
||||||
|
========================
|
||||||
|
Horizon 2013.1 "Grizzly"
|
||||||
|
========================
|
||||||
|
|
||||||
|
Release Overview
|
||||||
|
================
|
||||||
|
|
||||||
|
The Grizzly release cycle saw sweeping improvements to overall user experience,
|
||||||
|
huge stability improvements, lots of new networking, instance management and
|
||||||
|
image management features, a long-needed architectural clarification, and big
|
||||||
|
increases in community engagement! Read on to get the specifics.
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
==========
|
||||||
|
|
||||||
|
New Features
|
||||||
|
------------
|
||||||
|
|
||||||
|
Networking
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Quantum added a huge number of new features in Grizzly, including L3 support
|
||||||
|
(routers), load balancers, network topology infographics, better compatibility
|
||||||
|
with Nova networking APIs (VNIC ordering when launching an instance; security
|
||||||
|
groups and floating IP integration) and vastly improved informational displays.
|
||||||
|
|
||||||
|
Direct Image Upload To Glance
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
It is now possible (though there are numerous deployment/security implications)
|
||||||
|
to upload an image file directly from a user's hard disk to Glance through
|
||||||
|
Horizon. For multi-GB images it is still strongly recommended that the upload
|
||||||
|
be done using the Glance CLI. Further improvements to this feature will come in
|
||||||
|
future releases.
|
||||||
|
|
||||||
|
Flavor Extra Specs Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In Folsom, Nova added support for "extra specs" on flavors--additional metadata
|
||||||
|
which custom schedulers could use for appropriately scheduling instances. As of
|
||||||
|
the Grizzly release, Horizon now supports reading and writing that data on any
|
||||||
|
flavor.
|
||||||
|
|
||||||
|
Migrate Instance
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Administrators now have the ability to migrate an instance off of its current
|
||||||
|
host via the Admin dashboard's Instances panel.
|
||||||
|
|
||||||
|
|
||||||
|
User Experience Improvements
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
"Not Authorized" & Being Logged Out
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A shocking number of the problems first-time deployers of OpenStack have can be
|
||||||
|
summarized as "I thought I set everything up, then I tried to log into the
|
||||||
|
dashboard and I was immediately logged back out." The root cause of this was
|
||||||
|
that in an effort to be as secure as possible any 401 or 403 response from
|
||||||
|
any service API was being treated the same as if it was an attempt to access
|
||||||
|
an unauthorized portion of Horizon, and the user was summarily logged out with
|
||||||
|
little to no information as to why.
|
||||||
|
|
||||||
|
In Grizzly we have instead chosen to improve this by treating service API
|
||||||
|
401 and 403 errors as slightly less severe than unauthorized access attempts
|
||||||
|
to restricted areas of Horizon. The reason for this is threefold:
|
||||||
|
|
||||||
|
#. For a non-malicious user these errors are almost 100% the result of
|
||||||
|
misconfiguration and this makes debugging possible.
|
||||||
|
#. A malicious user can make the exact same "unauthorized" requests via the
|
||||||
|
CLI as they can via the dashboard; no special privileges are granted.
|
||||||
|
#. API errors are generated by external systems not under the purview of our
|
||||||
|
project and while we should attempt to respect and take appropriate action
|
||||||
|
on those errors, we should not do anything drastic or even potentially
|
||||||
|
destructive because of them.
|
||||||
|
|
||||||
|
Going forward the user will not be logged out, but no information will be
|
||||||
|
populated on the page and they will be presented with error messages informing
|
||||||
|
them that they are unauthorized for the data they attempted to access.
|
||||||
|
|
||||||
|
Reorganizations
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A couple of long-standing user confusions were fixed in Grizzly.
|
||||||
|
|
||||||
|
First off, the API Access panel (containing a user's API endpoints, rc files,
|
||||||
|
and EC2 credentials) was moved from Settings to the Access & Security section
|
||||||
|
of the Project dashboard.
|
||||||
|
|
||||||
|
Second, the Default Quotas and Services panels (which were both strictly
|
||||||
|
informational) were combined into tabs in a single System Info panel to make
|
||||||
|
it clear that these panels are thematically related, and to create a home for
|
||||||
|
informational-only displays like these.
|
||||||
|
|
||||||
|
One-click Floating IP Management
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A common complaint from users was that associating a floating IP to an
|
||||||
|
instance involved numerous clicks and form selections for something that
|
||||||
|
the majority of users had no knowledge of and didn't care about. As such, a
|
||||||
|
one-click "simple" floating IP association option has been created. For
|
||||||
|
deployments which only have a single floating IP pool, this allows users to
|
||||||
|
ignore explicit floating IP management and just click a button to associate
|
||||||
|
or disassociate a floating IP with an instance.
|
||||||
|
|
||||||
|
Organized Images
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The Images table now has a new feature: predefined filters for seeing your own
|
||||||
|
images, images that have been shared with you, or public images. This makes
|
||||||
|
finding the image you're looking for a great deal easier and more pleasant.
|
||||||
|
|
||||||
|
Security Group Rule Editing Improvements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The security group rule editing experience has always been inherently very
|
||||||
|
complicated simply given the number of options and the very technical terms
|
||||||
|
involved. Moreover, the combined table-plus-form approach the OpenStack
|
||||||
|
Dashboard had taken only made the UX more frustrating for an already difficult
|
||||||
|
area.
|
||||||
|
|
||||||
|
In Grizzly this has all been reworked to be significantly simpler, and to
|
||||||
|
provide as much contextual help and streamlining as possible.
|
||||||
|
|
||||||
|
Icons!
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
In an effort to make the dashboard more at-a-glance usable, we've added icons
|
||||||
|
to most of the common action buttons throughout the dashboard.
|
||||||
|
|
||||||
|
"More Actions", More Better
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Lots of feedback came in that the "more actions" dropdown menu (for tables with
|
||||||
|
numerous actions available on each row) was confusing to new users and/or
|
||||||
|
difficult to click.
|
||||||
|
|
||||||
|
We've now improved it so that the button to open the menu is clearly labeled
|
||||||
|
and the hitbox for clicking it is significantly larger.
|
||||||
|
|
||||||
|
|
||||||
|
Community
|
||||||
|
---------
|
||||||
|
|
||||||
|
Docs, docs, and more docs!
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Large amounts of new documentation was added during the Grizzly cycle, most
|
||||||
|
notably are sections documenting: all of the available settings for Horizon and
|
||||||
|
the OpenStack Dashboard; security and deployment considerations; and deeper
|
||||||
|
guides on customizing the OpenStack Dashboard.
|
||||||
|
|
||||||
|
IRC Meeting
|
||||||
|
~~~~~~~~~~~
|
||||||
|
|
||||||
|
During the Grizzly cycle we started holding a weekly project meeting on IRC.
|
||||||
|
This has been extremely beneficial for the growth and progress of the project.
|
||||||
|
Check out the `OpenStack Meetings wiki page`_ for specifics.
|
||||||
|
|
||||||
|
.. _OpenStack Meetings wiki page: https://wiki.openstack.org/wiki/Meetings#Horizon_team_meeting
|
||||||
|
|
||||||
|
|
||||||
|
Under The Hood
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Legacy Dashboard Names & Code Separation
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Very early in the Grizzly cycle we took the opportunity to do some longstanding
|
||||||
|
cleanup and refactoring work. The "nova" dashboard was renamed to "project" and
|
||||||
|
the "syspanel" dashboard was renamed to "admin" to better reflect their
|
||||||
|
respective purposes.
|
||||||
|
|
||||||
|
Moreover, a better separation was created between code related to the core
|
||||||
|
Horizon framework code (which is not related to OpenStack specifically) and
|
||||||
|
the OpenStack Dashboard code. At this point *all* code related to OpenStack
|
||||||
|
lives in the OpenStack Dashboard directory, while the Horizon framework is
|
||||||
|
completely agnostic and is a reusable Django app.
|
||||||
|
|
||||||
|
Object Storage Delimiters and Pseudo-folder Objects
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
When Horizon's object storage interface was first added, Swift's documentation
|
||||||
|
recommended adding 0-byte objects with a special content type to denote
|
||||||
|
pseudo-folders within a container. They have since decided that this is not the
|
||||||
|
recommended practice, and that pseudo-folders should only be demarcated by
|
||||||
|
a delimiting character (usually "/") in the object name.
|
||||||
|
|
||||||
|
Horizon has been updated under the hood to use this method, which should bring
|
||||||
|
it better into line with how most deployments are using their object storage.
|
||||||
|
|
||||||
|
|
||||||
|
Other Improvements and Fixes
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
* Support for Keystone's PKI tokens.
|
||||||
|
|
||||||
|
* Flavor editing was made significantly more stable.
|
||||||
|
|
||||||
|
* Security groups can be added to a running instance.
|
||||||
|
|
||||||
|
* Volume quotas are handled by the appropriate service depending on whether
|
||||||
|
or not Cinder is enabled.
|
||||||
|
|
||||||
|
* Password confirmation boxes are now validated for matching passwords on
|
||||||
|
the client side for more immediate feedback.
|
||||||
|
|
||||||
|
* Numerous fixes to display more and better information for instances and
|
||||||
|
volumes in their overview pages.
|
||||||
|
|
||||||
|
* Improved unicode support for the Object Storage panels.
|
||||||
|
|
||||||
|
* Logout now attempts to delete the token(s) associated with the current
|
||||||
|
session to avoid replay attacks, etc.
|
||||||
|
|
||||||
|
* Various fixes for browser compatibility and rendering.
|
||||||
|
|
||||||
|
* Many, many other bugfixes and improvements. Check out Launchpad for the full
|
||||||
|
list of what went on in Grizzly.
|
||||||
|
|
||||||
|
|
||||||
|
Known Issues and Limitations
|
||||||
|
============================
|
||||||
|
|
||||||
|
Editing a Flavor Which Results In An API Error Will Delete The Flavor
|
||||||
|
---------------------------------------------------------------------
|
||||||
|
|
||||||
|
Due to the way that Nova handles flavor editing/replacement it is necessary
|
||||||
|
to delete the old flavor before creating the replacement flavor. As such,
|
||||||
|
if an API error occurs while creating the replacement it is possible to
|
||||||
|
lose the old flavor without the new one being created.
|
||||||
|
|
||||||
|
Creating Rich Network Topologies
|
||||||
|
--------------------------------
|
||||||
|
|
||||||
|
Due to several Quantum features landing very late in the Grizzly cycle, it
|
||||||
|
is not possible to create particularly complex networking configurations
|
||||||
|
through the OpenStack Dashboard. These features will continue to grow
|
||||||
|
throughout future releases.
|
||||||
|
|
||||||
|
Loadbalancer Feature
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
The Loadbalancer feature landed in the 11th hour for both Quantum and Horizon
|
||||||
|
and, though we did our best to test it, may still contain undiscovered bugs. It
|
||||||
|
is best considered a "beta" or "experimental" feature for the Grizzly release.
|
||||||
|
|
||||||
|
Quantum Brocade Plugin Not Compatible
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
The Brocade plugin for Quantum does not support key features of the floating
|
||||||
|
IP addresses API which are considered central to Horizon's functionality. As
|
||||||
|
such, it is not compatible with the Grizzly release's Quantum integration.
|
||||||
|
|
||||||
|
Deleting large numbers of resources simultaneously
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Using the "select all" checkbox to delete large numbers of resources via the
|
||||||
|
API can cause network timeouts (depending on configuration). This is
|
||||||
|
due to the APIs not supporting bulk-deletion natively, and consequently Horizon
|
||||||
|
has to send requests to delete each resource individually behind the scenes.
|
||||||
|
|
||||||
|
Backwards Compatibility
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The Grizzly Horizon release should be fully compatible with both Grizzly and
|
||||||
|
Folsom versions of the rest of the OpenStack core projects (Nova, Swift, etc.).
|
||||||
|
While some features work significantly better with an all-Grizzly stack due
|
||||||
|
to bugfixes, etc. in underlying services, there should not be limitations
|
||||||
|
on what will or will not function.
|
||||||
|
|
||||||
|
Overall, great effort has been made to maintain compatibility for
|
||||||
|
third-party developers who may have built on Horizon so far.
|
|
@ -0,0 +1,254 @@
|
||||||
|
=======================
|
||||||
|
Horizon 2013.2 "Havana"
|
||||||
|
=======================
|
||||||
|
|
||||||
|
Release Overview
|
||||||
|
================
|
||||||
|
|
||||||
|
The Havana release cycle brings support for *three* new projects, plus
|
||||||
|
significant new features for several existing projects. On top of that, many
|
||||||
|
aspects of user experience have been improved for both end users and
|
||||||
|
administrators. The community continues to grow and expand. The Havana release
|
||||||
|
is solidly the best release of the OpenStack Dashboard project yet!
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
==========
|
||||||
|
|
||||||
|
New Features
|
||||||
|
------------
|
||||||
|
|
||||||
|
Heat
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
The OpenStack Orchestration project (Heat) debuted in Havana, and Horizon
|
||||||
|
delivers full support for managing your Heat stacks. Highlights include
|
||||||
|
support for dynamic form generation from supported Heat template formats,
|
||||||
|
stack topology visualizations, and full stack resource inspection.
|
||||||
|
|
||||||
|
Ceilometer
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Also debuting in Havana is the OpenStack Metering project (Ceilometer). Initial
|
||||||
|
support for Ceilometer is included in Horizon so that it is possible for an
|
||||||
|
administrator to query the usage of the cloud through the OpenStack Dashboard
|
||||||
|
and better understand how the system is functioning and being utilized.
|
||||||
|
|
||||||
|
Domains, Groups, and More: Keystone v3 API Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
With the OpenStack Identity Service (Keystone) v3 API fully fledged in the
|
||||||
|
Havana release, Horizon has added full support for all the new features such
|
||||||
|
as Domains and Groups, Role management and assignment to Domains and Groups,
|
||||||
|
Domain-based authentication, and Domain context switching.
|
||||||
|
|
||||||
|
Trove Databases
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The OpenStack Database as a Service project (Trove) graduated from incubation
|
||||||
|
in the Havana cycle, and thanks to their industriousness they delivered a
|
||||||
|
set of panels for the OpenStack dashboard to allow for provisioning and managing
|
||||||
|
your Trove databases and backups. Disclaimer: Given that Trove's first official
|
||||||
|
release as an integrated project will not be until Icehouse this feature should
|
||||||
|
still be considered experimental and may be subject to change.
|
||||||
|
|
||||||
|
Nova Features
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The number of OpenStack Compute (Nova) features that are supported in Horizon
|
||||||
|
continues to grow. New features in the Havana release include:
|
||||||
|
|
||||||
|
* Editable default quotas.
|
||||||
|
* The ability for an administrator to reset the password of a server/instance.
|
||||||
|
* Availability zone support.
|
||||||
|
* Improved region support.
|
||||||
|
* Instance resizing.
|
||||||
|
* Improved boot-from-volume support.
|
||||||
|
* Per-project flavor support.
|
||||||
|
|
||||||
|
All of these provide a richer set of options for controlling where, when and how
|
||||||
|
instances are launched, and improving how they're managed once they're up and
|
||||||
|
running.
|
||||||
|
|
||||||
|
Neutron Features
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A number of important new OpenStack Networking (Neutron) features are showcased
|
||||||
|
in the Havana release, most notably:
|
||||||
|
|
||||||
|
* VPN as a Service.
|
||||||
|
* Firewall as a Service.
|
||||||
|
* Editable and interactive network topology visualizations.
|
||||||
|
* Full security group and quota parity between Neutron and Nova network.
|
||||||
|
|
||||||
|
These features allow for tremendous flexibility when constructing
|
||||||
|
software-defined networks for your cloud using Neutron.
|
||||||
|
|
||||||
|
|
||||||
|
User Experience Improvements
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Self-Service Password Change
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Empowered by changes to the Keystone API, users can now change their own
|
||||||
|
passwords without the need to involve an administrator. This is more secure and
|
||||||
|
takes the hassle out of things for everyone.
|
||||||
|
|
||||||
|
Better Admin Information Architecture
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Several sections of the Admin dashboard have been rearranged to more logically
|
||||||
|
group information together. Additionally, new sources of information have been
|
||||||
|
added to allow Admins to better understand the state of the hosts in the cloud
|
||||||
|
and their relationship to host aggregates, availability zones, etc.
|
||||||
|
|
||||||
|
Improved Messaging To Users On Logout
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Several new indicators have been added to inform users why they've been logged
|
||||||
|
out when they land on the login screen unexpectedly. These indicators make it
|
||||||
|
clear whether the user's session has expired, they timed out due to inactivity,
|
||||||
|
or they are not authorized for the section of the dashboard they attempted to
|
||||||
|
access.
|
||||||
|
|
||||||
|
Security Group Rule Templates
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Since there are many very common security group rules which users tediously
|
||||||
|
re-add each time (rules for SSH and ping, for example) the Horizon team has
|
||||||
|
added pre-configured templates for common rules which a user can select and
|
||||||
|
add to their security group with two clicks. These rules are configurable
|
||||||
|
via the ``SECURITY_GROUP_RULES`` setting.
|
||||||
|
|
||||||
|
|
||||||
|
Community
|
||||||
|
---------
|
||||||
|
|
||||||
|
Translation Team
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The OpenStack Translations team came fully into its own during the Havana cycle
|
||||||
|
and the quality of the translations in Horizon are the best yet by far.
|
||||||
|
Congratulations to that team for their success in building the community that
|
||||||
|
started primarily within the OpenStack Dashboard project.
|
||||||
|
|
||||||
|
User Experience Group
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
A fledgling OpenStack User Experience Group formed during the Havana cycle with
|
||||||
|
the mission of improving UX throughout OpenStack. They have quickly made
|
||||||
|
themselves indispensable to the process of designing and improving features in
|
||||||
|
the OpenStack Dashboard. Expect significant future improvement in User
|
||||||
|
Experience now that there are dedicated people actively collaborating in the
|
||||||
|
open to raise the bar.
|
||||||
|
|
||||||
|
|
||||||
|
Under The Hood
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Less Complicated LESS Compilation: No More NodeJS
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Due to outcry from various parties, and made possible by improvements in the
|
||||||
|
Python community's support for LESS, Horizon has removed all traces of NodeJS
|
||||||
|
from the project. We now use the ``lesscpy`` module to compile our LESS into
|
||||||
|
the final stylesheets. This should not affect most users in any way, but it
|
||||||
|
should make life easier for downstream distributions and the like.
|
||||||
|
|
||||||
|
Role-Based Access Controls
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon has begun the transition to using the other OpenStack projects'
|
||||||
|
``policy.json`` files to enforce access controls in the dashboard if the files
|
||||||
|
are provided. This means access controls are more configurable and can be kept
|
||||||
|
in sync between the originating project and Horizon. Currently this is only
|
||||||
|
supported for Keystone and parts of Nova's policy files. Full support will
|
||||||
|
come in the next release. You will need to set the ``POLICY_FILES_PATH`` and
|
||||||
|
``POLICY_FILES`` settings in order to enable this feature.
|
||||||
|
|
||||||
|
|
||||||
|
Other Improvements and Fixes
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
* Swift container and object metadata are now supported.
|
||||||
|
* New visualizations for utilization and quotas.
|
||||||
|
* The Cisco N1K Router plugin's additional features are available through a
|
||||||
|
special additional dashboard when enabled and supported in Neutron.
|
||||||
|
* Support for self-signed or other specified SSL certificate checking.
|
||||||
|
* Glance image types are now configurable.
|
||||||
|
* Sorting has been improved in many places through the dashboard.
|
||||||
|
* API call efficiency optimizations.
|
||||||
|
* Required fields in forms are now better indicated.
|
||||||
|
* Session timeout can now be enabled to log out the user after a period of
|
||||||
|
inactivity as a security feature.
|
||||||
|
* Significant PEP8 and code quality compliance improvements.
|
||||||
|
* Hundreds of bugfixes and minor user experience improvements.
|
||||||
|
|
||||||
|
|
||||||
|
Upgrade Information
|
||||||
|
===================
|
||||||
|
|
||||||
|
Allowed Hosts
|
||||||
|
-------------
|
||||||
|
|
||||||
|
For production deployments of Horizon you must add the ``ALLOWED_HOSTS``
|
||||||
|
setting to your ``local_settings.py`` file. This setting
|
||||||
|
was added in Django 1.5 and is an important security feature. For more
|
||||||
|
information on it please consult the ``local_settings.py.example`` file
|
||||||
|
or Django's documentation.
|
||||||
|
|
||||||
|
Enabling Keystone and Neutron Features
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
If you have existing configurations for the ``OPENSTACK_KEYSTONE_BACKEND``
|
||||||
|
or ``OPENSTACK_NEUTRON_NETWORK`` settings, you will want to consult the
|
||||||
|
``local_settings.example.py`` file for information on the new options that
|
||||||
|
have been added. Existing configurations will continue to work, but may not
|
||||||
|
have the correct keys to enable some of the new features in Havana.
|
||||||
|
|
||||||
|
|
||||||
|
Known Issues and Limitations
|
||||||
|
============================
|
||||||
|
|
||||||
|
Session Creation and Health Checks
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
If you use a health monitoring service that pings the home page combined with
|
||||||
|
a database-backed session backend you may experience excessive session creation.
|
||||||
|
This issue is slated to be fixed soon, but in the interim the recommended
|
||||||
|
solution is to write a periodic job that deletes expired sessions from your
|
||||||
|
session store on a regular basis.
|
||||||
|
|
||||||
|
Deleting large numbers of resources simultaneously
|
||||||
|
--------------------------------------------------
|
||||||
|
|
||||||
|
Using the "select all" checkbox to delete large numbers of resources at once
|
||||||
|
can cause network timeouts (depending on configuration). This is due to the
|
||||||
|
underlying APIs not supporting bulk-deletion natively, and consequently Horizon
|
||||||
|
has to send requests to delete each resource individually behind the scenes.
|
||||||
|
|
||||||
|
Conflicting Security Group Names With Neutron
|
||||||
|
---------------------------------------------
|
||||||
|
|
||||||
|
Whereas Nova Network uses only the name of a security group when specifying
|
||||||
|
security groups at instance launch time, Neutron can accept either a name or
|
||||||
|
a UUID. In order to support both, Horizon passes in the name of the selected
|
||||||
|
security groups. However, due to some data-isolation issues in Neutron there is
|
||||||
|
an issue that can arise if an admin user tries to specify a security group with
|
||||||
|
the same name as another security group in a different project which they also
|
||||||
|
have access to. Neutron will find multiple matches for the security group
|
||||||
|
name and will fail to launch the instance. The current workaround is to treat
|
||||||
|
security group names as unique for admin users.
|
||||||
|
|
||||||
|
|
||||||
|
Backwards Compatibility
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The Havana Horizon release should be fully compatible with both Havana and
|
||||||
|
Grizzly versions of the rest of the OpenStack integrated projects (Nova, Swift,
|
||||||
|
etc.). New features in other OpenStack projects which did not exist in Grizzly
|
||||||
|
will obviously only work in Horizon if the rest of the stack supports them as
|
||||||
|
well.
|
||||||
|
|
||||||
|
Overall, great effort has been made to maintain compatibility for
|
||||||
|
third-party developers who have built on Horizon so far.
|
|
@ -0,0 +1,190 @@
|
||||||
|
=========================
|
||||||
|
Horizon 2014.1 "Icehouse"
|
||||||
|
=========================
|
||||||
|
|
||||||
|
Release Overview
|
||||||
|
================
|
||||||
|
|
||||||
|
The Icehouse release cycle brings several improvements to Horizon's
|
||||||
|
user experience, improved extensibility, and support for many
|
||||||
|
additional features in existing projects. The community continues to
|
||||||
|
grow. Read more for the specifics.
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
==========
|
||||||
|
|
||||||
|
New Features
|
||||||
|
------------
|
||||||
|
|
||||||
|
Nova
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
The number of OpenStack Compute (Nova) features that are supported in Icehouse
|
||||||
|
grew. New features in the Icehouse release include:
|
||||||
|
|
||||||
|
* Live Migration Support
|
||||||
|
* HyperV Console Support
|
||||||
|
* Disk config extension support
|
||||||
|
* Improved support for managing host aggregates and availability zones
|
||||||
|
* Support for easily setting flavor extra specs
|
||||||
|
|
||||||
|
Cinder
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
In an ongoing effort to implement Role Based Access Support throughout Horizon,
|
||||||
|
access controls were added in the OpenStack Volume (Cinder) related panels.
|
||||||
|
Utilization of the Cinder v2 API is now a supported option in the Icehouse
|
||||||
|
release. The ability to extend volumes is now available as well.
|
||||||
|
|
||||||
|
Neutron
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Display of Router Rules for routers where they are defined is now supported in
|
||||||
|
Horizon.
|
||||||
|
|
||||||
|
Swift
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
With Icehouse, the ability for users to create containers and mark them as
|
||||||
|
public is now available. Links are added to download these public containers.
|
||||||
|
Users can now explicitly create pseudo directories rather than being required to
|
||||||
|
create them as part of the container creation process.
|
||||||
|
|
||||||
|
Heat
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
In Icehouse, Horizon delivers support for updating existing Heat stacks.
|
||||||
|
Now stacks that have already been deployed can be adjusted and redeployed. The
|
||||||
|
updated template is also validated when updated. Additionally, support for
|
||||||
|
adding environment files was included.
|
||||||
|
|
||||||
|
Ceilometer
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon has added support for administrators to query Ceilometer and
|
||||||
|
view a daily usage report per project across services through the
|
||||||
|
OpenStack Dashboard to better understand how system resources are being
|
||||||
|
consumed by individual projects.
|
||||||
|
|
||||||
|
Trove Databases
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The OpenStack Database as a Service project (Trove) is part of the
|
||||||
|
integrated release in the Icehouse cycle. Improvements to the client
|
||||||
|
connections and overall stability were added in the Icehouse cycle.
|
||||||
|
|
||||||
|
|
||||||
|
User Experience Improvements
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Extensible Enhancements
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The primary dashboard and panel navigation has been updated from the tab
|
||||||
|
navigation to an accordion implementation. Dashboards and Panel Groups are now
|
||||||
|
expandable and collapsible in the page navigation. This change allows for the
|
||||||
|
addition of more dashboards as well as accommodates the increasing number of
|
||||||
|
panels in dashboards.
|
||||||
|
|
||||||
|
Wizard
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
Horizon now provides a Wizard control to complete multi-step interdependent
|
||||||
|
tasks. This is now utilized in the create network action.
|
||||||
|
|
||||||
|
Inline Table Editing
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tables can now be written to support editing fields in the table to reduce the
|
||||||
|
need for opening separate forms. The first sample of this is in the Admin
|
||||||
|
dashboard, Projects panel.
|
||||||
|
|
||||||
|
Self-Service Password Change
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Leveraging enhancements to Identity API v3 (Keystone), users can now change
|
||||||
|
their own passwords without the need to involve an administrator. This
|
||||||
|
functionality was previously only available with Identity API v2.0.
|
||||||
|
|
||||||
|
Server Side Table Filtering
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Tables can now easily be wired to filter results from underlying API calls
|
||||||
|
based on criteria selected by the user rather than just perform an on page
|
||||||
|
search. The first example of this is in the Admin dashboard, Instances panel.
|
||||||
|
|
||||||
|
Under The Hood
|
||||||
|
--------------
|
||||||
|
|
||||||
|
JavaScript
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
In a move to provide a better user experience, Horizon has adopted AngularJS as
|
||||||
|
the primary JavaScript framework. JavaScript is now a browser requirement to
|
||||||
|
run the Horizon interface. More to come in Juno.
|
||||||
|
|
||||||
|
Plugin Architecture
|
||||||
|
~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon now boasts dynamic loading/disabling of dashboards, panel groups and
|
||||||
|
panels. By merely adding a file in the ``enabled`` directory, the selection of
|
||||||
|
items loaded into Horizon can be altered. Editing the Django settings file is
|
||||||
|
no longer required.
|
||||||
|
|
||||||
|
For more information see :ref:`pluggable-settings-label`
|
||||||
|
|
||||||
|
Integration Test Framework
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon now supports running integration tests against a working devstack system. There is a limited test suite, but this a great step forward and allows full integration testing.
|
||||||
|
|
||||||
|
Django 1.6 Support
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Django versions 1.4 - 1.6 are now supported by Horizon.
|
||||||
|
|
||||||
|
|
||||||
|
Upgrade Information
|
||||||
|
===================
|
||||||
|
|
||||||
|
Beginning with the Icehouse cycle, there is now a requirement for JavaScript
|
||||||
|
support in browsers used with OpenStack Dashboard.
|
||||||
|
|
||||||
|
Page Layout Changes
|
||||||
|
-------------------
|
||||||
|
|
||||||
|
The overall structure of the page layout in Horizon has been altered. Existing
|
||||||
|
templates by 3rd parties to override page templates may require some rework.
|
||||||
|
|
||||||
|
Default Hypervisor Settings Changes
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
The default for ``can_set_password`` is now ``False``. This means that unless
|
||||||
|
the setting is explicitly set to ``True``, the option to set an
|
||||||
|
'Admin password' for an instance will not be shown in the Launch Instance
|
||||||
|
workflow. Not all hypervisors support this feature which created confusion with
|
||||||
|
users.
|
||||||
|
|
||||||
|
The default for ``can_set_mountpoint`` is now ``False``, and should be set to
|
||||||
|
``True`` in the settings in order to add the option to set the mount point for
|
||||||
|
volumes in the dashboard. At this point only the Xen hypervisor supports this
|
||||||
|
feature.
|
||||||
|
|
||||||
|
To change the behavior around hypervisor management in Horizon you must add the
|
||||||
|
``OPENSTACK_HYPERVISOR_FEATURES`` setting to your ``settings.py`` or
|
||||||
|
``local_settings.py`` file.
|
||||||
|
|
||||||
|
For more information see :ref:`hypervisor-settings-label`
|
||||||
|
|
||||||
|
Known Issues and Limitations
|
||||||
|
============================
|
||||||
|
|
||||||
|
Multi-Domain Cross Service Support
|
||||||
|
----------------------------------
|
||||||
|
|
||||||
|
While Horizon supports managing Identity v3 entities and authenticating in a
|
||||||
|
multi-domain Keystone configuration, there is a v3, v2.0 token compatibility
|
||||||
|
issue when trying to manage resources for users outside the ``default``
|
||||||
|
domain. For this reason, v2.0 has been restored as the default API version
|
||||||
|
for OpenStack Identity (Keystone). For a single domain environment, Keystone
|
||||||
|
v3 API can still be used via the ``OPENSTACK_API_VERSION`` setting.
|
|
@ -0,0 +1,180 @@
|
||||||
|
=====================
|
||||||
|
Horizon 2014.2 "Juno"
|
||||||
|
=====================
|
||||||
|
|
||||||
|
Release Overview
|
||||||
|
================
|
||||||
|
|
||||||
|
The Juno release cycle brings a significant update to the user experience;
|
||||||
|
numerous stability improvements; support for Sahara; and significant
|
||||||
|
enhancements in feature support for networking, volumes, databases and images.
|
||||||
|
The community continues to grow and gain speed. Read on for more details.
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
==========
|
||||||
|
|
||||||
|
New Features
|
||||||
|
------------
|
||||||
|
|
||||||
|
Sahara
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
The OpenStack Data Processing project (Sahara) was formally included into the
|
||||||
|
integrated release in Juno and Horizon includes broad support for managing your
|
||||||
|
data processing. You can specify and build clusters to utilize several data
|
||||||
|
types with user specified jobs while tracking the progress of those jobs.
|
||||||
|
|
||||||
|
Neutron
|
||||||
|
~~~~~~~
|
||||||
|
|
||||||
|
Neutron added several new features in Juno, including:
|
||||||
|
|
||||||
|
* DVR (Distributed Virtual Routing)
|
||||||
|
* L3 HA support
|
||||||
|
* IPv6 subnet modes
|
||||||
|
|
||||||
|
Horizon provides support for these new features with the Juno release. These
|
||||||
|
features provide much greater flexibility in specifying software defined
|
||||||
|
networks.
|
||||||
|
|
||||||
|
An existing feature in Neutron that Horizon now supports is the MAC learning
|
||||||
|
extension.
|
||||||
|
|
||||||
|
Glance
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
In Juno, Glance introduced the ability to manage a catalog of metadata
|
||||||
|
definitions where users can register the metadata definitions to be used on
|
||||||
|
various resource types including images, volumes, aggregates, and flavors.
|
||||||
|
Support for viewing and editing the assignment of these metadata tags is
|
||||||
|
included in Horizon.
|
||||||
|
|
||||||
|
Cinder
|
||||||
|
~~~~~~
|
||||||
|
|
||||||
|
In a continued effort to provide more complete API support, several
|
||||||
|
additional features of the Cinder API are now supported in Horizon in the
|
||||||
|
Juno release.
|
||||||
|
|
||||||
|
Some of these features include:
|
||||||
|
* Creating and restoring volume backups
|
||||||
|
* Enabling resetting the state of a snapshot
|
||||||
|
* Enabling resetting the state of a volume
|
||||||
|
* Supporting upload-to-image
|
||||||
|
* Volume retype
|
||||||
|
* QoS (quality of service) support.
|
||||||
|
|
||||||
|
Trove
|
||||||
|
~~~~~
|
||||||
|
|
||||||
|
Trove supports using multiple types of datastores, e.g., mysql, redis, mongodb.
|
||||||
|
Users can now select from the list of datastores supported by the cloud operator
|
||||||
|
when creating their database instances.
|
||||||
|
|
||||||
|
Another addition is support for utilizing and restoring from incremental
|
||||||
|
database backups.
|
||||||
|
|
||||||
|
To improve support for Neutron based clouds, when creating a database instance,
|
||||||
|
the user can now specify the NIC for the database instance on creation allowing
|
||||||
|
direct access to the instance by the user.
|
||||||
|
|
||||||
|
Nova
|
||||||
|
~~~~
|
||||||
|
|
||||||
|
The new Nova instance actions view provides a list of all actions taken on
|
||||||
|
all instances in the current project allowing users to view resulting errors or
|
||||||
|
actions taken by other users on those instances.
|
||||||
|
|
||||||
|
Administrators now have the ability to evacuate hosts off hypervisors which can
|
||||||
|
aid in system maintenance by providing a mechanism to migrate all instances to
|
||||||
|
other hosts.
|
||||||
|
|
||||||
|
Improved Plugin Support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The plugin system in Horizon continued to improve in the Juno release.
|
||||||
|
Some of those improvements:
|
||||||
|
|
||||||
|
* Support for adding plugin specific AngularJS modules
|
||||||
|
* Support for adding static files, e.g., CSS, JS, images
|
||||||
|
* Ability to add exceptions
|
||||||
|
* Fixing ordering issues
|
||||||
|
* Numerous other bug fixes
|
||||||
|
|
||||||
|
Enhanced RBAC support
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In an ongoing effort to support richer role based access control (RBAC) in
|
||||||
|
Horizon, the views for several more services were enhanced with RBAC checks to
|
||||||
|
determine user access to actions. The newly supported services are compute,
|
||||||
|
network and orchestration. These changes allow operators to implement finer
|
||||||
|
grained access control than just "member" and "admin".
|
||||||
|
|
||||||
|
The identity panels (domains, projects, users, roles, groups) have also been
|
||||||
|
converted to support RBAC at the view level. The identity panels have been
|
||||||
|
moved from the admin dashboard into their own 'Identity' dashboard and
|
||||||
|
accessibility is determined by policies alone. This is the first step toward
|
||||||
|
consolidating the near duplicate content of the project and admin dashboards
|
||||||
|
into single views supporting a wide range of roles.
|
||||||
|
|
||||||
|
UX Changes
|
||||||
|
~~~~~~~~~~
|
||||||
|
|
||||||
|
In Juno, Horizon transitioned to utilizing Bootstrap v3. Horizon had been
|
||||||
|
pinned to an older version of Bootstrap for several releases. This change now
|
||||||
|
allows Horizon to pick up numerous bug fixes and overall improvements in the
|
||||||
|
Bootstrap framework. The look and feel remains mainly consistent with the
|
||||||
|
Icehouse release.
|
||||||
|
|
||||||
|
Under the Hood
|
||||||
|
--------------
|
||||||
|
|
||||||
|
Improved Translatability
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In an effort to improve the translations for Horizon, updates to remove
|
||||||
|
concatenations and better handle tense were made.
|
||||||
|
|
||||||
|
JavaScript Libraries Extracted
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
As part of the Horizon team's ongoing efforts to split the repository into more
|
||||||
|
logical pieces, all the 3rd party JavaScript libraries that Horizon depends on
|
||||||
|
have been removed from the Horizon code base and python xstatic packages have
|
||||||
|
been utilized instead. The xstatic format allows for easy consumption by the
|
||||||
|
Django framework Horizon is built on. Now JavaScript libraries are utilized
|
||||||
|
like any other python dependency in Horizon.
|
||||||
|
|
||||||
|
Conversion from LESS to SCSS
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The supported stylesheets in Horizon have been converted to utilize SCSS rather
|
||||||
|
than LESS. The change was necessary due to a prevalent lack of support for LESS
|
||||||
|
compilers in python. This change also allowed us to upgrade to Bootstrap 3, as
|
||||||
|
parts of the Bootstrap 3 LESS stylesheets were not supported by existing python
|
||||||
|
based LESS compilers.
|
||||||
|
|
||||||
|
Known Issues and Limitations
|
||||||
|
============================
|
||||||
|
|
||||||
|
Rendering issues in extensions
|
||||||
|
------------------------------
|
||||||
|
The conversion to utilizing Bootstrap v3 can cause content extensions written
|
||||||
|
on top of Horizon to have rendering issues. Most of these are fixed by a simple
|
||||||
|
CSS class name substitutions. These issues are primarily seen with buttons and
|
||||||
|
panel content widths.
|
||||||
|
|
||||||
|
Online Compression
|
||||||
|
------------------
|
||||||
|
With the move to SCSS, there may be issues with utilizing online compression in
|
||||||
|
non-DEBUG mode in Horizon. Offline compression continues to work as in previous
|
||||||
|
releases.
|
||||||
|
|
||||||
|
https://bugs.launchpad.net/horizon/+bug/1379761
|
||||||
|
|
||||||
|
Neutron L3 HA
|
||||||
|
-------------
|
||||||
|
The HA property is updateable in the UI, however, Neutron API does not allow the
|
||||||
|
update operation because toggling HA support does not work.
|
||||||
|
|
||||||
|
https://bugs.launchpad.net/horizon/+bug/1379761
|
|
@ -0,0 +1,59 @@
|
||||||
|
=======================
|
||||||
|
Horizon's tests and you
|
||||||
|
=======================
|
||||||
|
|
||||||
|
How to run the tests
|
||||||
|
====================
|
||||||
|
|
||||||
|
Because Horizon is composed of both the ``horizon`` app and the
|
||||||
|
``openstack_dashboard`` reference project, there are in fact two sets of unit
|
||||||
|
tests. While they can be run individually without problem, there is an easier
|
||||||
|
way:
|
||||||
|
|
||||||
|
Included at the root of the repository is the ``run_tests.sh`` script
|
||||||
|
which invokes both sets of tests, and optionally generates analyses on both
|
||||||
|
components in the process. This script is what Jenkins uses to verify the
|
||||||
|
stability of the project, so you should make sure you run it and it passes
|
||||||
|
before you submit any pull requests/patches.
|
||||||
|
|
||||||
|
To run the tests::
|
||||||
|
|
||||||
|
$ ./run_tests.sh
|
||||||
|
|
||||||
|
It's also possible to :doc:`run a subset of unit tests<ref/run_tests>`.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
:doc:`ref/run_tests`
|
||||||
|
Full reference for the ``run_tests.sh`` script.
|
||||||
|
|
||||||
|
|
||||||
|
By default running the Selenium tests will open your Firefox browser (you have
|
||||||
|
to install it first, else an error is raised), and you will be able to see the
|
||||||
|
tests actions.
|
||||||
|
If you want to run the suite headless, without being able to see them (as they
|
||||||
|
are ran on Jenkins), you can run the tests:
|
||||||
|
|
||||||
|
$ ./run_tests.sh --with-selenium --selenium-headless
|
||||||
|
|
||||||
|
Selenium will use a virtual display in this case, instead of your own. In order
|
||||||
|
to run the tests this way you have to install the dependency `xvfb`, like this:
|
||||||
|
|
||||||
|
$ sudo apt-get install xvfb
|
||||||
|
|
||||||
|
for a Debian OS flavour, or for Fedora/Red Hat flavours:
|
||||||
|
|
||||||
|
$ sudo yum install xorg-x11-server-Xvfb
|
||||||
|
|
||||||
|
Writing tests
|
||||||
|
=============
|
||||||
|
|
||||||
|
Horizon uses Django's unit test machinery (which extends Python's ``unittest2``
|
||||||
|
library) as the core of its test suite. As such, all tests for the Python code
|
||||||
|
should be written as unit tests. No doctests please.
|
||||||
|
|
||||||
|
In general new code without unit tests will not be accepted, and every bugfix
|
||||||
|
*must* include a regression test.
|
||||||
|
|
||||||
|
For a much more in-depth discussion of testing, see the :doc:`testing topic
|
||||||
|
guide </topics/testing>`.
|
|
@ -0,0 +1,283 @@
|
||||||
|
===================
|
||||||
|
Customizing Horizon
|
||||||
|
===================
|
||||||
|
|
||||||
|
Themes
|
||||||
|
======
|
||||||
|
|
||||||
|
As of the Kilo release, styling for the OpenStack Dashboard can be altered
|
||||||
|
through the use of a theme. A theme is a directory containing a
|
||||||
|
``_variables.scss`` file to override the color codes used throughout the SCSS
|
||||||
|
and a ``_styles.scss`` file with additional styles to load after dashboard
|
||||||
|
styles have loaded.
|
||||||
|
|
||||||
|
To use a custom theme, set ``CUSTOM_THEME_PATH`` in ``local_settings.py`` to
|
||||||
|
the directory location for the theme (e.g., ``"static/themes/blue"``). The
|
||||||
|
path can either be relative to the ``openstack_dashboard`` directory or an
|
||||||
|
absolute path to an accessible location on the file system. The default
|
||||||
|
``CUSTOM_THEME_PATH`` is ``static/themes/default``.
|
||||||
|
|
||||||
|
Both the Dashboard custom variables and Bootstrap variables can be overridden.
|
||||||
|
For a full list of the Dashboard SCSS variables that can be changed, see the
|
||||||
|
variables file at ``openstack_dashboard/static/dashboard/scss/_variables.scss``.
|
||||||
|
|
||||||
|
Changing the Logo
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
There are currently two places where the OpenStack logo is pulled in
|
||||||
|
through the stylesheets. The first is shown at the login screen and the other
|
||||||
|
on top of the menu bar. To override the logo place your logo in your themes
|
||||||
|
directory and set the image to use in ``_styles.scss``. For example::
|
||||||
|
|
||||||
|
#splash .login {
|
||||||
|
background-image: url(/static/themes/THEME/logo-splash.png);
|
||||||
|
}
|
||||||
|
|
||||||
|
.topbar {
|
||||||
|
h1.brand a {
|
||||||
|
background-image: url(/static/themes/THEME/logo.png);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
``THEME`` should be replaced by the name of your theme directory.
|
||||||
|
The dimensions should be ``width: 216px, height: 35px`` for a drop in
|
||||||
|
replacement.
|
||||||
|
|
||||||
|
Prior to the Kilo release the images files inside of Horizon needed to be
|
||||||
|
replaced by your images files or the Horizon stylesheets needed to be altered
|
||||||
|
to point to the location of your image.
|
||||||
|
|
||||||
|
Changing the Site Title
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The OpenStack Dashboard Site Title branding (i.e. "**OpenStack** Dashboard")
|
||||||
|
can be overwritten by adding the attribute ``SITE_BRANDING``
|
||||||
|
to ``local_settings.py`` with the value being the desired name.
|
||||||
|
|
||||||
|
The file ``local_settings.py`` can be found at the Horizon directory path of
|
||||||
|
``openstack_dashboard/local/local_settings.py``.
|
||||||
|
|
||||||
|
Changing the Brand Link
|
||||||
|
=======================
|
||||||
|
|
||||||
|
The logo also acts as a hyperlink. The default behavior is to redirect to
|
||||||
|
``horizon:user_home``. By adding the attribute ``SITE_BRANDING_LINK`` with
|
||||||
|
the desired url target e.g., ``http://sample-company.com`` in
|
||||||
|
``local_settings.py``, the target of the hyperlink can be changed.
|
||||||
|
|
||||||
|
Modifying Existing Dashboards and Panels
|
||||||
|
========================================
|
||||||
|
|
||||||
|
If you wish to alter dashboards or panels which are not part of your codebase,
|
||||||
|
you can specify a custom python module which will be loaded after the entire
|
||||||
|
Horizon site has been initialized, but prior to the URLconf construction.
|
||||||
|
This allows for common site-customization requirements such as:
|
||||||
|
|
||||||
|
* Registering or unregistering panels from an existing dashboard.
|
||||||
|
* Changing the names of dashboards and panels.
|
||||||
|
* Re-ordering panels within a dashboard or panel group.
|
||||||
|
|
||||||
|
To specify the python module containing your modifications, add the key
|
||||||
|
``customization_module`` to your ``HORIZON_CONFIG`` dictionary in
|
||||||
|
``local_settings.py``. The value should be a string containing the path to your
|
||||||
|
module in dotted python path notation. Example::
|
||||||
|
|
||||||
|
HORIZON_CONFIG = {
|
||||||
|
"customization_module": "my_project.overrides"
|
||||||
|
}
|
||||||
|
|
||||||
|
You can do essentially anything you like in the customization module. For
|
||||||
|
example, you could change the name of a panel::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
# Rename "User Settings" to "User Options"
|
||||||
|
settings = horizon.get_dashboard("settings")
|
||||||
|
user_panel = settings.get_panel("user")
|
||||||
|
user_panel.name = _("User Options")
|
||||||
|
|
||||||
|
Or get the instances panel::
|
||||||
|
|
||||||
|
projects_dashboard = horizon.get_dashboard("project")
|
||||||
|
instances_panel = projects_dashboard.get_panel("instances")
|
||||||
|
|
||||||
|
And limit access to users with the Keystone Admin role::
|
||||||
|
|
||||||
|
permissions = list(getattr(instances_panel, 'permissions', []))
|
||||||
|
permissions.append('openstack.roles.admin')
|
||||||
|
instances_panel.permissions = tuple(permissions)
|
||||||
|
|
||||||
|
Or just remove it entirely::
|
||||||
|
|
||||||
|
projects_dashboard.unregister(instances_panel.__class__)
|
||||||
|
|
||||||
|
You can also override existing methods with your own versions::
|
||||||
|
|
||||||
|
# Disable Floating IPs
|
||||||
|
from openstack_dashboard.dashboards.project.access_and_security import tabs
|
||||||
|
from openstack_dashboard.dashboards.project.instances import tables
|
||||||
|
|
||||||
|
NO = lambda *x: False
|
||||||
|
|
||||||
|
tabs.FloatingIPsTab.allowed = NO
|
||||||
|
tables.AssociateIP.allowed = NO
|
||||||
|
tables.SimpleAssociateIP.allowed = NO
|
||||||
|
tables.SimpleDisassociateIP.allowed = NO
|
||||||
|
|
||||||
|
You could also customize what columns are displayed in an existing
|
||||||
|
table, by redefining the ``columns`` attribute of its ``Meta``
|
||||||
|
class. This can be achieved in 3 steps:
|
||||||
|
|
||||||
|
#. Extend the table that you wish to modify
|
||||||
|
#. Redefine the ``columns`` attribute under the ``Meta`` class for this
|
||||||
|
new table
|
||||||
|
#. Modify the ``table_class`` attribute for the related view so that it
|
||||||
|
points to the new table
|
||||||
|
|
||||||
|
|
||||||
|
For example, if you wished to remove the Admin State column from the
|
||||||
|
:class:`~openstack_dashboard.dashboards.admin.networks.tables.NetworksTable`,
|
||||||
|
you could do the following::
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.project.networks import tables
|
||||||
|
from openstack_dashboard.dashboards.project.networks import views
|
||||||
|
|
||||||
|
class MyNetworksTable(tables.NetworksTable):
|
||||||
|
|
||||||
|
class Meta(tables.NetworksTable.Meta):
|
||||||
|
columns = ('name', 'subnets', 'shared', 'status')
|
||||||
|
|
||||||
|
views.IndexView.table_class = MyNetworksTable
|
||||||
|
|
||||||
|
If you want to add a column you can override the parent table in a
|
||||||
|
similar way, add the new column definition and then use the ``Meta``
|
||||||
|
``columns`` attribute to control the column order as needed.
|
||||||
|
|
||||||
|
.. NOTE::
|
||||||
|
|
||||||
|
``my_project.overrides`` needs to be importable by the python process running
|
||||||
|
Horizon.
|
||||||
|
If your module is not installed as a system-wide python package,
|
||||||
|
you can either make it installable (e.g., with a setup.py)
|
||||||
|
or you can adjust the python path used by your WSGI server to include its location.
|
||||||
|
|
||||||
|
Probably the easiest way is to add a ``python-path`` argument to
|
||||||
|
the ``WSGIDaemonProcess`` line in Apache's Horizon config.
|
||||||
|
|
||||||
|
Assuming your ``my_project`` module lives in ``/opt/python/my_project``,
|
||||||
|
you'd make it look like the following::
|
||||||
|
|
||||||
|
WSGIDaemonProcess [... existing options ...] python-path=/opt/python
|
||||||
|
|
||||||
|
|
||||||
|
Button Icons
|
||||||
|
============
|
||||||
|
|
||||||
|
Horizon uses font icons (glyphicons) from Twitter Bootstrap to add icons to buttons.
|
||||||
|
Please see http://bootstrapdocs.com/v3.1.1/docs/components/#glyphicons for instructions
|
||||||
|
how to use icons in the code.
|
||||||
|
|
||||||
|
To add icon to Table Action, use icon property. Example:
|
||||||
|
|
||||||
|
class CreateSnapshot(tables.LinkAction):
|
||||||
|
name = "snapshot"
|
||||||
|
verbose_name = _("Create Snapshot")
|
||||||
|
icon = "camera"
|
||||||
|
|
||||||
|
Additionally, the site-wide default button classes can be configured by
|
||||||
|
setting ``ACTION_CSS_CLASSES`` to a tuple of the classes you wish to appear
|
||||||
|
on all action buttons in your ``local_settings.py`` file.
|
||||||
|
|
||||||
|
|
||||||
|
Custom Stylesheets
|
||||||
|
==================
|
||||||
|
|
||||||
|
It is possible to define custom stylesheets for your dashboards. Horizon's base
|
||||||
|
template ``horizon/templates/base.html`` defines multiple blocks that
|
||||||
|
can be overridden.
|
||||||
|
|
||||||
|
To define custom css files that apply only to a specific dashboard, create
|
||||||
|
a base template in your dashboard's templates folder, which extends Horizon's
|
||||||
|
base template e.g. ``openstack_dashboard/dashboards/my_custom_dashboard/
|
||||||
|
templates/my_custom_dashboard/base.html``.
|
||||||
|
|
||||||
|
In this template, redefine ``block css``. (Don't forget to include
|
||||||
|
``_stylesheets.html`` which includes all Horizon's default stylesheets.)::
|
||||||
|
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
{% include "_stylesheets.html" %}
|
||||||
|
|
||||||
|
{% load compress %}
|
||||||
|
{% compress css %}
|
||||||
|
<link href='{{ STATIC_URL }}my_custom_dashboard/scss/my_custom_dashboard.scss' type='text/scss' media='screen' rel='stylesheet' />
|
||||||
|
{% endcompress %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
The custom stylesheets then reside in the dashboard's own ``static`` folder
|
||||||
|
``openstack_dashboard/dashboards/my_custom_dashboard/static/
|
||||||
|
my_custom_dashboard/scss/my_custom_dashboard.scss``.
|
||||||
|
|
||||||
|
All dashboard's templates have to inherit from dashboard's base.html::
|
||||||
|
|
||||||
|
{% extends 'my_custom_dashboard/base.html' %}
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
|
Custom Javascript
|
||||||
|
=================
|
||||||
|
|
||||||
|
Similarly to adding custom styling (see above), it is possible to include
|
||||||
|
custom javascript files.
|
||||||
|
|
||||||
|
All Horizon's javascript files are listed in the ``horizon/_scripts.html``
|
||||||
|
partial template, which is included in Horizon's base template in ``block js``.
|
||||||
|
|
||||||
|
To add custom javascript files, create an ``_scripts.html`` partial template in
|
||||||
|
your dashboard ``openstack_dashboard/dashboards/my_custom_dashboard/
|
||||||
|
templates/my_custom_dashboard/_scripts.html`` which extends
|
||||||
|
``horizon/_scripts.html``. In this template override the
|
||||||
|
``block custom_js_files`` including your custom javascript files::
|
||||||
|
|
||||||
|
{% extends 'horizon/_scripts.html' %}
|
||||||
|
|
||||||
|
{% block custom_js_files %}
|
||||||
|
<script src='{{ STATIC_URL }}my_custom_dashboard/js/my_custom_js.js' type='text/javascript' charset='utf-8'></script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
In your dashboard's own base template ``openstack_dashboard/dashboards/
|
||||||
|
my_custom_dashboard/templates/my_custom_dashboard/base.html`` override
|
||||||
|
``block js`` with inclusion of dashboard's own ``_scripts.html``::
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
{% include "my_custom_dashboard/_scripts.html" %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
The result is a single compressed js file consisting both Horizon and
|
||||||
|
dashboard's custom scripts.
|
||||||
|
|
||||||
|
Additionally, some marketing and analytics scripts require you to place them
|
||||||
|
within the page's <head> tag. To do this, place them within the
|
||||||
|
``horizon/_custom_head_js.html`` file. Similar to the ``_scripts.html`` file
|
||||||
|
mentioned above, you may link to an existing file::
|
||||||
|
|
||||||
|
<script src='{{ STATIC_URL }}/my_custom_dashboard/js/my_marketing_js.js' type='text/javascript' charset='utf-8'></script>
|
||||||
|
|
||||||
|
or you can paste your script directly in the file, being sure to use
|
||||||
|
appropriate tags::
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
//some javascript
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
Customizing Meta Attributes
|
||||||
|
===========================
|
||||||
|
|
||||||
|
To add custom metadata attributes to your project's base template, include
|
||||||
|
them in the ``horizon/_custom_meta.html`` file. The contents of this file will be
|
||||||
|
inserted into the page's <head> just after the default Horizon meta tags.
|
|
@ -0,0 +1,227 @@
|
||||||
|
=================
|
||||||
|
Deploying Horizon
|
||||||
|
=================
|
||||||
|
|
||||||
|
This guide aims to cover some common questions, concerns and pitfalls you
|
||||||
|
may encounter when deploying Horizon in a production environment.
|
||||||
|
|
||||||
|
.. seealso:: :doc:`settings`
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The Service Catalog returned by the Identity Service after a user
|
||||||
|
has successfully authenticated determines the dashboards and panels
|
||||||
|
that will be available within the OpenStack Dashboard. If you are not
|
||||||
|
seeing a particular service you expected (e.g. Object Storage/Swift or
|
||||||
|
Networking/Neutron) make sure your Service Catalog is configured correctly.
|
||||||
|
|
||||||
|
Prior to the Essex release of Horizon these features were controlled by
|
||||||
|
individual settings in the ``local_settings.py`` file. This code has been
|
||||||
|
long-since removed and those pre-Essex settings have no impact now.
|
||||||
|
|
||||||
|
Configure Your Identity Service Host
|
||||||
|
====================================
|
||||||
|
|
||||||
|
The one thing you *must* do in order to run Horizon is to specify the
|
||||||
|
host for your OpenStack Identity Service endpoint. To do this, set the value
|
||||||
|
of the ``OPENSTACK_HOST`` settings in your ``local_settings.py`` file.
|
||||||
|
|
||||||
|
Logging
|
||||||
|
=======
|
||||||
|
|
||||||
|
Logging is an important concern for production deployments, and the intricacies
|
||||||
|
of good logging configuration go far beyond what can be covered here. However
|
||||||
|
there are a few points worth noting about the logging included with Horizon,
|
||||||
|
how to customize it, and where other components may take over:
|
||||||
|
|
||||||
|
* Horizon's logging uses Django's logging configuration mechanism, which
|
||||||
|
can be customized in your ``local_settings.py`` file through the
|
||||||
|
``LOGGING`` dictionary.
|
||||||
|
* Horizon's default logging example sets the log level to ``"INFO"``, which is
|
||||||
|
a reasonable choice for production deployments. For development, however,
|
||||||
|
you may want to change the log level to ``"DEBUG"``.
|
||||||
|
* Horizon also uses a number of 3rd-party clients which log separately. The
|
||||||
|
log level for these can still be controlled through Horizon's ``LOGGING``
|
||||||
|
config, however behaviors may vary beyond Horizon's control.
|
||||||
|
* For more information regarding configuring logging in Horizon, please
|
||||||
|
read the `Django logging directive`_ and the `Python logging directive`_
|
||||||
|
documentation. Horizon is built on Python and Django.
|
||||||
|
|
||||||
|
.. _Django logging directive: https://docs.djangoproject.com/en/1.5/topics/logging
|
||||||
|
.. _Python logging directive: http://docs.python.org/2/library/logging.html
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
At this time there is `a known bug in python-keystoneclient`_ where it will
|
||||||
|
log the complete request body of any request sent to Keystone through it
|
||||||
|
(including logging passwords in plain text) when the log level is set to
|
||||||
|
``"DEBUG"``. If this behavior is not desired, make sure your log level is
|
||||||
|
``"INFO"`` or higher.
|
||||||
|
|
||||||
|
.. _a known bug in python-keystoneclient: https://bugs.launchpad.net/keystone/+bug/1004114
|
||||||
|
|
||||||
|
File Uploads
|
||||||
|
============
|
||||||
|
|
||||||
|
Horizon allows users to upload files via their web browser to other OpenStack
|
||||||
|
services such as Glance and Swift. Files uploaded through this mechanism are
|
||||||
|
first stored on the Horizon server before being forwarded on - files are not
|
||||||
|
uploaded directly or streamed as Horizon receives them. As Horizon itself does
|
||||||
|
not impose any restrictions on the size of file uploads, production deployments
|
||||||
|
will want to consider configuring their server hosting the Horizon application
|
||||||
|
to enforce such a limit to prevent large uploads exhausting system resources
|
||||||
|
and disrupting services. Deployments using Apache2 can use the
|
||||||
|
`LimitRequestBody directive`_ to achieve this.
|
||||||
|
|
||||||
|
Uploads to the Glance image store service tend to be particularly large - in
|
||||||
|
the order of hundreds of megabytes to multiple gigabytes. Deployments are able
|
||||||
|
to disable local image uploads through Horizon by setting
|
||||||
|
``HORIZON_IMAGES_ALLOW_UPLOAD`` to ``False`` in your ``local_settings.py``
|
||||||
|
file.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
This will not disable image creation altogether, as this setting does not
|
||||||
|
affect images created by specifying an image location (URL) as the image source.
|
||||||
|
|
||||||
|
|
||||||
|
.. _LimitRequestBody directive: http://httpd.apache.org/docs/2.2/mod/core.html#limitrequestbody
|
||||||
|
|
||||||
|
Session Storage
|
||||||
|
===============
|
||||||
|
|
||||||
|
Horizon uses `Django's sessions framework`_ for handling user session data;
|
||||||
|
however that's not the end of the story. There are numerous session backends
|
||||||
|
available, which are controlled through the ``SESSION_ENGINE`` setting in
|
||||||
|
your ``local_settings.py`` file. What follows is a quick discussion of the
|
||||||
|
pros and cons of each of the common options as they pertain to deploying
|
||||||
|
Horizon specifically.
|
||||||
|
|
||||||
|
.. _Django's sessions framework: https://docs.djangoproject.com/en/dev/topics/http/sessions/
|
||||||
|
|
||||||
|
Local Memory Cache
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Enabled by::
|
||||||
|
|
||||||
|
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||||
|
CACHES = {
|
||||||
|
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache'
|
||||||
|
}
|
||||||
|
|
||||||
|
Local memory storage is the quickest and easiest session backend to set up,
|
||||||
|
as it has no external dependencies whatsoever. However, it has two significant
|
||||||
|
drawbacks:
|
||||||
|
|
||||||
|
* No shared storage across processes or workers.
|
||||||
|
* No persistence after a process terminates.
|
||||||
|
|
||||||
|
The local memory backend is enabled as the default for Horizon solely because
|
||||||
|
it has no dependencies. It is not recommended for production use, or even for
|
||||||
|
serious development work. For better options, read on.
|
||||||
|
|
||||||
|
Memcached
|
||||||
|
---------
|
||||||
|
|
||||||
|
Enabled by::
|
||||||
|
|
||||||
|
SESSION_ENGINE = 'django.contrib.sessions.backends.cache'
|
||||||
|
CACHES = {
|
||||||
|
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache'
|
||||||
|
'LOCATION': 'my_memcached_host:11211',
|
||||||
|
}
|
||||||
|
|
||||||
|
External caching using an application such as memcached offers persistence
|
||||||
|
and shared storage, and can be very useful for small-scale deployment and/or
|
||||||
|
development. However, for distributed and high-availability scenarios
|
||||||
|
memcached has inherent problems which are beyond the scope of this
|
||||||
|
documentation.
|
||||||
|
|
||||||
|
Memcached is an extremely fast and efficient cache backend for cases where it
|
||||||
|
fits the deployment need. But it's not appropriate for all scenarios.
|
||||||
|
|
||||||
|
Requirements:
|
||||||
|
|
||||||
|
* Memcached service running and accessible.
|
||||||
|
* Python memcached module installed.
|
||||||
|
|
||||||
|
Database
|
||||||
|
--------
|
||||||
|
|
||||||
|
Enabled by::
|
||||||
|
|
||||||
|
SESSION_ENGINE = 'django.core.cache.backends.db.DatabaseCache'
|
||||||
|
DATABASES = {
|
||||||
|
'default': {
|
||||||
|
# Database configuration here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Database-backed sessions are scalable (using an appropriate database strategy),
|
||||||
|
persistent, and can be made high-concurrency and highly-available.
|
||||||
|
|
||||||
|
The downside to this approach is that database-backed sessions are one of the
|
||||||
|
slower session storages, and incur a high overhead under heavy usage. Proper
|
||||||
|
configuration of your database deployment can also be a substantial
|
||||||
|
undertaking and is far beyond the scope of this documentation.
|
||||||
|
|
||||||
|
Cached Database
|
||||||
|
---------------
|
||||||
|
|
||||||
|
To mitigate the performance issues of database queries, you can also consider
|
||||||
|
using Django's ``cached_db`` session backend which utilizes both your database
|
||||||
|
and caching infrastructure to perform write-through caching and efficient
|
||||||
|
retrieval. You can enable this hybrid setting by configuring both your database
|
||||||
|
and cache as discussed above and then using::
|
||||||
|
|
||||||
|
SESSION_ENGINE = "django.contrib.sessions.backends.cached_db"
|
||||||
|
|
||||||
|
Cookies
|
||||||
|
-------
|
||||||
|
|
||||||
|
If you're using Django 1.4 or later, a new session backend is available to you
|
||||||
|
which avoids server load and scaling problems: the ``signed_cookies`` backend!
|
||||||
|
|
||||||
|
This backend stores session data in a cookie which is stored by the
|
||||||
|
user's browser. The backend uses a cryptographic signing technique to ensure
|
||||||
|
session data is not tampered with during transport (**this is not the same
|
||||||
|
as encryption, session data is still readable by an attacker**).
|
||||||
|
|
||||||
|
The pros of this session engine are that it doesn't require any additional
|
||||||
|
dependencies or infrastructure overhead, and it scales indefinitely as long
|
||||||
|
as the quantity of session data being stored fits into a normal cookie.
|
||||||
|
|
||||||
|
The biggest downside is that it places session data into storage on the user's
|
||||||
|
machine and transports it over the wire. It also limits the quantity of
|
||||||
|
session data which can be stored.
|
||||||
|
|
||||||
|
For a thorough discussion of the security implications of this session backend,
|
||||||
|
please read the `Django documentation on cookie-based sessions`_.
|
||||||
|
|
||||||
|
.. _Django documentation on cookie-based sessions: https://docs.djangoproject.com/en/dev/topics/http/sessions/#using-cookie-based-sessions
|
||||||
|
|
||||||
|
Secure Site Recommendations
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
When implementing Horizon for public usage, with the website served through
|
||||||
|
HTTPS, it is recommended that the following settings are applied.
|
||||||
|
|
||||||
|
To help protect the session cookies from `cross-site scripting`_, add the
|
||||||
|
following to ``local_settings.py``::
|
||||||
|
|
||||||
|
CSRF_COOKIE_SECURE = True
|
||||||
|
SESSION_COOKIE_SECURE = True
|
||||||
|
|
||||||
|
Note that the CSRF_COOKIE_SECURE option is only available from Django 1.4. It
|
||||||
|
does no harm to have the setting in earlier versions, but it does not take effect.
|
||||||
|
|
||||||
|
You can also disable `browser autocompletion`_ for the authentication form by
|
||||||
|
modifying the ``HORIZON_CONFIG`` dictionary in ``local_settings.py`` by adding
|
||||||
|
the key ``password_autocomplete`` with the value ``off`` as shown here::
|
||||||
|
|
||||||
|
HORIZON_CONFIG = {
|
||||||
|
...
|
||||||
|
'password_autocomplete': 'off',
|
||||||
|
}
|
||||||
|
|
||||||
|
.. _cross-site scripting: https://www.owasp.org/index.php/HttpOnly
|
||||||
|
.. _browser autocompletion: https://wiki.mozilla.org/The_autocomplete_attribute_and_web_documents_using_XHTML
|
|
@ -0,0 +1,100 @@
|
||||||
|
==================
|
||||||
|
Installing Horizon
|
||||||
|
==================
|
||||||
|
|
||||||
|
This page covers the basic installation of Horizon.
|
||||||
|
|
||||||
|
.. _system-requirements-label:
|
||||||
|
|
||||||
|
System Requirements
|
||||||
|
===================
|
||||||
|
|
||||||
|
* Python 2.7
|
||||||
|
* Django 1.6 (1.4 and 1.5 are supported too)
|
||||||
|
* Minimum required set of running OpenStack services are:
|
||||||
|
|
||||||
|
* Nova
|
||||||
|
* Keystone
|
||||||
|
* Glance
|
||||||
|
* Neutron (unless nova-network is used)
|
||||||
|
|
||||||
|
* All other services are optional.
|
||||||
|
Horizon supports the following services in Juno release.
|
||||||
|
If Keystone endpoint for a service is configured,
|
||||||
|
Horizon detects it and enables its support automatically.
|
||||||
|
|
||||||
|
* Swift
|
||||||
|
* Cinder
|
||||||
|
* Heat
|
||||||
|
* Ceilometer
|
||||||
|
* Trove
|
||||||
|
* Sahara
|
||||||
|
|
||||||
|
Installation
|
||||||
|
============
|
||||||
|
|
||||||
|
1. Compile translation message catalogs for internationalization.
|
||||||
|
This step is not required if you do not need to support languages
|
||||||
|
other than English. GNU ``gettext`` tool is required to compile
|
||||||
|
message catalogs::
|
||||||
|
|
||||||
|
$ sudo apt-get install gettext
|
||||||
|
$ ./run_tests.sh --compilemessages
|
||||||
|
|
||||||
|
This command compiles translation message catalogs within Python
|
||||||
|
virtualenv named ``.venv``. After this step, you can remove
|
||||||
|
``.venv`` directory safely.
|
||||||
|
|
||||||
|
2. Install Horizon python module into your system. Run the following
|
||||||
|
in the top directory::
|
||||||
|
|
||||||
|
$ sudo pip install .
|
||||||
|
|
||||||
|
3. Create ``openstack_dashboard/local/local_settings.py``.
|
||||||
|
It is usually a good idea to copy
|
||||||
|
``openstack_dashboard/local/local_settings.py.example`` and edit it.
|
||||||
|
At least we need to customize the following variables in this file.
|
||||||
|
|
||||||
|
* ``ALLOWED_HOSTS`` (unless ``DEBUG`` is ``True``)
|
||||||
|
* ``OPENSTACK_KEYSTONE_URL``
|
||||||
|
|
||||||
|
For more details, please refer to :doc:`deployment` and :doc:`settings`.
|
||||||
|
|
||||||
|
4. Optional: Django has a Compressor feature that performs many enhancements
|
||||||
|
for the delivery of static files, including standardization and
|
||||||
|
minification/uglification. This processing can be run either online or
|
||||||
|
offline (pre-processed). Letting the compression process occur at runtime
|
||||||
|
will incur processing and memory use when the resources are first requested;
|
||||||
|
doing it ahead of time removes those runtime penalties.
|
||||||
|
|
||||||
|
If you want the static files to be processed before server runtime, you'll
|
||||||
|
need to configure your local_settings.py to specify
|
||||||
|
``COMPRESS_OFFLINE = True``, then run the following commands::
|
||||||
|
|
||||||
|
$ ./manage.py collectstatic
|
||||||
|
$ ./manage.py compress
|
||||||
|
|
||||||
|
5. Set up a web server with WSGI support.
|
||||||
|
It is optional but recommended in production deployments.
|
||||||
|
For example, install Apache web server on Ubuntu::
|
||||||
|
|
||||||
|
$ sudo apt-get install apache2 libapache2-mod-wsgi
|
||||||
|
|
||||||
|
Then configure the web server to host OpenStack Dashboard via WSGI.
|
||||||
|
For apache2 web server, you may need to create
|
||||||
|
``/etc/apache2/sites-available/horizon.conf``.
|
||||||
|
The template in devstack is a good example of the file.
|
||||||
|
http://git.openstack.org/cgit/openstack-dev/devstack/tree/files/apache-horizon.template
|
||||||
|
|
||||||
|
6. Finally, enable the above configuration and restart the web server::
|
||||||
|
|
||||||
|
$ sudo a2ensite horizon
|
||||||
|
$ sudo service apache2 restart
|
||||||
|
|
||||||
|
Next Steps
|
||||||
|
==========
|
||||||
|
|
||||||
|
* :doc:`deployment` covers some common questions, concerns and pitfalls you
|
||||||
|
may encounter when deploying Horizon in a production environment.
|
||||||
|
* :doc:`settings` lists the available settings for Horizon.
|
||||||
|
* :doc:`customizing` describes how to customizing Horizon as you want.
|
|
@ -0,0 +1,148 @@
|
||||||
|
============================================================
|
||||||
|
Horizon Policy Enforcement (RBAC: Role Based Access Control)
|
||||||
|
============================================================
|
||||||
|
|
||||||
|
Introduction
|
||||||
|
============
|
||||||
|
|
||||||
|
Horizon's policy enforcement builds on the oslo-incubator policy engine.
|
||||||
|
The basis of which is ``openstack_dashboard/openstack/common/policy.py``.
|
||||||
|
Services in OpenStack use the oslo policy engine to define policy rules
|
||||||
|
to limit access to APIs based primarily on role grants and resource
|
||||||
|
ownership.
|
||||||
|
|
||||||
|
The Keystone v3 API provides an interface for creating/reading/updating
|
||||||
|
policy files in the keystone database. However, at this time services
|
||||||
|
do not load the policy files into Keystone. Thus, the implementation in
|
||||||
|
Horizon is based on copies of policy.json files found in the service's
|
||||||
|
source code. The long-term goal is to read/utilize/update these policy
|
||||||
|
files in Horizon.
|
||||||
|
|
||||||
|
The service rules files are loaded into the policy engine to determine
|
||||||
|
access rights to actions and service APIs.
|
||||||
|
|
||||||
|
Horizon Settings
|
||||||
|
================
|
||||||
|
|
||||||
|
There are a few settings that must be in place for the Horizon policy
|
||||||
|
engine to work.
|
||||||
|
|
||||||
|
``POLICY_FILES_PATH``
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Default: ``os.path.join(ROOT_PATH, "conf")``
|
||||||
|
|
||||||
|
Specifies where service based policy files are located. These are used to
|
||||||
|
define the policy rules actions are verified against. This value must contain
|
||||||
|
the files listed in ``POLICY_FILES`` or all policy checks will pass.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The path to deployment specific policy files can be specified in
|
||||||
|
``local_settings.py`` to override the default location.
|
||||||
|
|
||||||
|
|
||||||
|
``POLICY_FILES``
|
||||||
|
----------------
|
||||||
|
|
||||||
|
Default: ``{'identity': 'keystone_policy.json', 'compute': 'nova_policy.json'}``
|
||||||
|
|
||||||
|
This should essentially be the mapping of the contents of ``POLICY_FILES_PATH``
|
||||||
|
to service types. When policy.json files are added to the directory
|
||||||
|
``POLICY_FILES_PATH``, they should be included here too. Without this mapping,
|
||||||
|
there is no way to map service types with policy rules, thus two policy.json
|
||||||
|
files containing a "default" rule would be ambiguous.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Deployment specific policy files can be specified in ``local_settings.py``
|
||||||
|
to override the default policy files. It is imperative that these policy
|
||||||
|
files match those deployed in the target OpenStack installation. Otherwise,
|
||||||
|
the displayed actions and the allowed action will not match.
|
||||||
|
|
||||||
|
``POLICY_CHECK_FUNCTION``
|
||||||
|
-------------------------
|
||||||
|
|
||||||
|
Default: ``policy.check``
|
||||||
|
|
||||||
|
This value should not be changed, although removing it would be a means to
|
||||||
|
bypass all policy checks.
|
||||||
|
|
||||||
|
|
||||||
|
How user's roles are determined
|
||||||
|
===============================
|
||||||
|
|
||||||
|
Each policy check uses information about the user stored on the request to
|
||||||
|
determine the user's roles. This information was extracted from the scoped
|
||||||
|
token received from Keystone when authenticating.
|
||||||
|
|
||||||
|
Entity ownership is also a valid role. To verify access to specific entities
|
||||||
|
like a project, the target must be specified. See the section
|
||||||
|
:ref:`rule targets <rule_targets>` later in this document.
|
||||||
|
|
||||||
|
How to Utilize RBAC
|
||||||
|
===================
|
||||||
|
|
||||||
|
The primary way to add role based access control checks to panels is in the
|
||||||
|
definition of table actions. When implementing a derived action class,
|
||||||
|
setting the :attr:`~horizon.tables.Action.policy_rules` attribute to valid
|
||||||
|
policy rules will force a policy check before the
|
||||||
|
:meth:`horizon.tables.Action.allowed` method is called on the action. These
|
||||||
|
rules are defined in the policy files pointed to by ``POLICY_PATH`` and
|
||||||
|
``POLICY_FILES``. The rules are role based, where entity owner is also a
|
||||||
|
role. The format for the ``policy_rules`` is a list of two item tuples. The
|
||||||
|
first component of the tuple is the scope of the policy rule, this is the
|
||||||
|
service type. This informs the policy engine which policy file to reference.
|
||||||
|
The second component is the rule to enforce from the policy file specified by
|
||||||
|
the scope. An example tuple is::
|
||||||
|
|
||||||
|
("identity", "identity:get_user")
|
||||||
|
|
||||||
|
x tuples can be added to enforce x rules.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
If a rule specified is not found in the policy file, the policy check
|
||||||
|
will return False and the action will not be allowed.
|
||||||
|
|
||||||
|
The secondary way to add a role based check is to directly use the
|
||||||
|
:meth:`~openstack_dashboard.policy.check` method. The method takes a list
|
||||||
|
of actions, same format as the :attr:`~horizon.tables.Action.policy_rules`
|
||||||
|
attribute detailed above; the current request object; and a dictionary of
|
||||||
|
action targets. This is the method that :class:`horizon.tables.Action` class
|
||||||
|
utilizes. Examples look like::
|
||||||
|
|
||||||
|
from openstack_dashboard import policy
|
||||||
|
|
||||||
|
allowed = policy.check((("identity", "identity:get_user"),
|
||||||
|
("identity", "identity:get_project"),), request)
|
||||||
|
|
||||||
|
can_see = policy.check((("identity", "identity:get_user"),), request,
|
||||||
|
target={"domain_id": domainId})
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
Any time multiple rules are specified in a single `policy.check` method
|
||||||
|
call, the result is the logical `and` of each rule check. So, if any
|
||||||
|
rule fails verification, the result is `False`.
|
||||||
|
|
||||||
|
.. _rule_targets:
|
||||||
|
|
||||||
|
Rule Targets
|
||||||
|
============
|
||||||
|
|
||||||
|
Some rules allow access if the user owns the entity. Policy check targets
|
||||||
|
specify particular entities to check for user ownership. The target parameter
|
||||||
|
to the :meth:`~openstack_dashboard.policy.check` method is a simple dictionary.
|
||||||
|
For instance, the target for checking access a project looks like::
|
||||||
|
|
||||||
|
{"project_id": "0905760626534a74979afd3f4a9d67f1"}
|
||||||
|
|
||||||
|
If the value matches the ``project_id`` to which the user's token is scoped,
|
||||||
|
then access is allowed.
|
||||||
|
|
||||||
|
When deriving the :class:`horizon.tables.Action` class for use in a table, if
|
||||||
|
a policy check is desired for a particular target, the implementer should
|
||||||
|
override the :meth:`horizon.tables.Action.get_policy_target` method. This
|
||||||
|
allows a programmatic way to specify the target based on the current datum. The
|
||||||
|
value returned should be the target dictionary.
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,300 @@
|
||||||
|
============================================
|
||||||
|
Tutorial: Adding a complex action to a table
|
||||||
|
============================================
|
||||||
|
|
||||||
|
This tutorial covers how to add a more complex action to a table, one that requires
|
||||||
|
an action and form definitions, as well as changes to the view, urls, and table.
|
||||||
|
|
||||||
|
This tutorial assumes you have already completed :doc:`Building a Dashboard using
|
||||||
|
Horizon </topics/tutorial>`. If not, please do so now as we will be modifying the
|
||||||
|
files created there.
|
||||||
|
|
||||||
|
This action will create a snapshot of the instance. When the action is taken,
|
||||||
|
it will display a form that will allow the user to enter a snapshot name,
|
||||||
|
and will create that snapshot when the form is closed using the ``Create snapshot``
|
||||||
|
button.
|
||||||
|
|
||||||
|
Defining the view
|
||||||
|
=================
|
||||||
|
|
||||||
|
To define the view, we must create a view class, along with the template (``HTML``)
|
||||||
|
file and the form class for that view.
|
||||||
|
|
||||||
|
The template file
|
||||||
|
-----------------
|
||||||
|
The template file contains the HTML that will be used to show the view.
|
||||||
|
|
||||||
|
Create a ``create_snapshot.html`` file under the ``mypanel/templates/mypanel``
|
||||||
|
directory and add the following code::
|
||||||
|
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Create Snapshot" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Create a Snapshot") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% include 'mydashboard/mypanel/_create_snapshot.html' %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
As you can see, the main body will be defined in ``_create_snapshot.html``,
|
||||||
|
so we must also create that file under the ``mypanel/templates/mypanel``
|
||||||
|
directory. It should contain the following code::
|
||||||
|
|
||||||
|
{% extends "horizon/common/_modal_form.html" %}
|
||||||
|
{% load i18n %}
|
||||||
|
|
||||||
|
{% block modal-body-right %}
|
||||||
|
<h3>{% trans "Description:" %}</h3>
|
||||||
|
<p>{% trans "Snapshots preserve the disk state of a running instance." %}</p>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
The form
|
||||||
|
--------
|
||||||
|
|
||||||
|
Horizon provides a :class:`~horizon.forms.base.SelfHandlingForm` class which simplifies
|
||||||
|
some of the details involved in creating a form. Our form will derive from this
|
||||||
|
class, adding a character field to allow the user to specify a name for the
|
||||||
|
snapshot, and handling the successful closure of the form by calling the nova
|
||||||
|
api to create the snapshot.
|
||||||
|
|
||||||
|
Create the ``forms.py`` file under the ``mypanel`` directory and add the following::
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSnapshot(forms.SelfHandlingForm):
|
||||||
|
instance_id = forms.CharField(label=_("Instance ID"),
|
||||||
|
widget=forms.HiddenInput(),
|
||||||
|
required=False)
|
||||||
|
name = forms.CharField(max_length=255, label=_("Snapshot Name"))
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
try:
|
||||||
|
snapshot = api.nova.snapshot_create(request,
|
||||||
|
data['instance_id'],
|
||||||
|
data['name'])
|
||||||
|
return snapshot
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(request,
|
||||||
|
_('Unable to create snapshot.'))
|
||||||
|
|
||||||
|
|
||||||
|
The view
|
||||||
|
--------
|
||||||
|
|
||||||
|
Now, the view will tie together the template and the form. Horizon provides a
|
||||||
|
:class:`~horizon.forms.views.ModalFormView` class which simplifies the creation of a
|
||||||
|
view that will contain a modal form.
|
||||||
|
|
||||||
|
Open the ``views.py`` file under the ``mypanel`` directory and add the code
|
||||||
|
for the CreateSnapshotView and the necessary imports. The complete
|
||||||
|
file should now look something like this::
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.core.urlresolvers import reverse_lazy
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tabs
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import forms
|
||||||
|
|
||||||
|
from horizon.utils import memoized
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel \
|
||||||
|
import forms as project_forms
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel \
|
||||||
|
import tabs as mydashboard_tabs
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(tabs.TabbedTableView):
|
||||||
|
tab_group_class = mydashboard_tabs.MypanelTabs
|
||||||
|
# A very simple class-based view...
|
||||||
|
template_name = 'mydashboard/mypanel/index.html'
|
||||||
|
|
||||||
|
def get_data(self, request, context, *args, **kwargs):
|
||||||
|
# Add data to the context here...
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSnapshotView(forms.ModalFormView):
|
||||||
|
form_class = project_forms.CreateSnapshot
|
||||||
|
template_name = 'mydashboard/mypanel/create_snapshot.html'
|
||||||
|
success_url = reverse_lazy("horizon:project:images:index")
|
||||||
|
modal_id = "create_snapshot_modal"
|
||||||
|
modal_header = _("Create Snapshot")
|
||||||
|
submit_label = _("Create Snapshot")
|
||||||
|
submit_url = "horizon:mydashboard:mypanel:create_snapshot"
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_object(self):
|
||||||
|
try:
|
||||||
|
return api.nova.server_get(self.request,
|
||||||
|
self.kwargs["instance_id"])
|
||||||
|
except Exception:
|
||||||
|
exceptions.handle(self.request,
|
||||||
|
_("Unable to retrieve instance."))
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
return {"instance_id": self.kwargs["instance_id"]}
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(CreateSnapshotView, self).get_context_data(**kwargs)
|
||||||
|
instance_id = self.kwargs['instance_id']
|
||||||
|
context['instance_id'] = instance_id
|
||||||
|
context['instance'] = self.get_object()
|
||||||
|
context['submit_url'] = reverse(self.submit_url, args=[instance_id])
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
Adding the url
|
||||||
|
==============
|
||||||
|
|
||||||
|
We must add the url for our new view. Open the ``urls.py`` file under
|
||||||
|
the ``mypanel`` directory and add the following as a new url pattern::
|
||||||
|
|
||||||
|
url(r'^(?P<instance_id>[^/]+)/create_snapshot/$',
|
||||||
|
views.CreateSnapshotView.as_view(),
|
||||||
|
name='create_snapshot'),
|
||||||
|
|
||||||
|
The complete ``urls.py`` file should look like this::
|
||||||
|
|
||||||
|
from django.conf.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^\?tab=mypanel_tabs_tab$',
|
||||||
|
views.IndexView.as_view(), name='mypanel_tabs'),
|
||||||
|
url(r'^(?P<instance_id>[^/]+)/create_snapshot/$',
|
||||||
|
views.CreateSnapshotView.as_view(),
|
||||||
|
name='create_snapshot'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Define the action
|
||||||
|
=================
|
||||||
|
|
||||||
|
Horizon provides a :class:`~horizon.tables.LinkAction` class which simplifies
|
||||||
|
adding an action which can be used to display another view.
|
||||||
|
|
||||||
|
We will add a link action to the table that will be accessible from each row
|
||||||
|
in the table. The action will use the view defined above to create a snapshot
|
||||||
|
of the instance represented by the row in the table.
|
||||||
|
|
||||||
|
To do this, we must edit the ``tables.py`` file under the ``mypanel`` directory
|
||||||
|
and add the following::
|
||||||
|
|
||||||
|
def is_deleting(instance):
|
||||||
|
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
|
||||||
|
if not task_state:
|
||||||
|
return False
|
||||||
|
return task_state.lower() == "deleting"
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSnapshotAction(tables.LinkAction):
|
||||||
|
name = "snapshot"
|
||||||
|
verbose_name = _("Create Snapshot")
|
||||||
|
url = "horizon:mydashboard:mypanel:create_snapshot"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "camera"
|
||||||
|
|
||||||
|
# This action should be disabled if the instance
|
||||||
|
# is not active, or the instance is being deleted
|
||||||
|
def allowed(self, request, instance=None):
|
||||||
|
return instance.status in ("ACTIVE") \
|
||||||
|
and not is_deleting(instance)
|
||||||
|
|
||||||
|
|
||||||
|
We must also add our new action as a row action for the table::
|
||||||
|
|
||||||
|
row_actions = (CreateSnapshotAction,)
|
||||||
|
|
||||||
|
|
||||||
|
The complete ``tables.py`` file should look like this::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
|
||||||
|
def is_deleting(instance):
|
||||||
|
task_state = getattr(instance, "OS-EXT-STS:task_state", None)
|
||||||
|
if not task_state:
|
||||||
|
return False
|
||||||
|
return task_state.lower() == "deleting"
|
||||||
|
|
||||||
|
|
||||||
|
class CreateSnapshotAction(tables.LinkAction):
|
||||||
|
name = "snapshot"
|
||||||
|
verbose_name = _("Create Snapshot")
|
||||||
|
url = "horizon:mydashboard:mypanel:create_snapshot"
|
||||||
|
classes = ("ajax-modal",)
|
||||||
|
icon = "camera"
|
||||||
|
|
||||||
|
def allowed(self, request, instance=None):
|
||||||
|
return instance.status in ("ACTIVE") \
|
||||||
|
and not is_deleting(instance)
|
||||||
|
|
||||||
|
|
||||||
|
class MyFilterAction(tables.FilterAction):
|
||||||
|
name = "myfilter"
|
||||||
|
|
||||||
|
|
||||||
|
class InstancesTable(tables.DataTable):
|
||||||
|
name = tables.Column("name", verbose_name=_("Name"))
|
||||||
|
status = tables.Column("status", verbose_name=_("Status"))
|
||||||
|
zone = tables.Column('availability_zone', verbose_name=_("Availability Zone"))
|
||||||
|
image_name = tables.Column('image_name', verbose_name=_("Image Name"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
name = "instances"
|
||||||
|
verbose_name = _("Instances")
|
||||||
|
table_actions = (MyFilterAction,)
|
||||||
|
row_actions = (CreateSnapshotAction,)
|
||||||
|
|
||||||
|
|
||||||
|
Run and check the dashboard
|
||||||
|
===========================
|
||||||
|
|
||||||
|
We must once again run horizon to verify our dashboard is working::
|
||||||
|
|
||||||
|
./run_tests.sh --runserver 0.0.0.0:8877
|
||||||
|
|
||||||
|
|
||||||
|
Go to ``http://<your server>:8877`` using a browser. After login as an admin,
|
||||||
|
display ``My Panel`` to see the ``Instances`` table. For every ``ACTIVE``
|
||||||
|
instance in the table, there will be a ``Create Snapshot`` action on the row.
|
||||||
|
Click on ``Create Snapshot``, enter a snapshot name in the form that is shown,
|
||||||
|
then click to close the form. The ``Project Images`` view should be shown with
|
||||||
|
the new snapshot added to the table.
|
||||||
|
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
==========
|
||||||
|
|
||||||
|
What you've learned here is the fundamentals of how to add a table action that
|
||||||
|
requires a form for data entry. This can easily be expanded from creating a
|
||||||
|
snapshot to other API calls that require more complex forms to gather the
|
||||||
|
necessary information.
|
||||||
|
|
||||||
|
If you have feedback on how this tutorial could be improved, please feel free
|
||||||
|
to submit a bug against ``Horizon`` in `launchpad`_.
|
||||||
|
|
||||||
|
.. _launchpad: https://bugs.launchpad.net/horizon
|
|
@ -0,0 +1,387 @@
|
||||||
|
======================
|
||||||
|
DataTables Topic Guide
|
||||||
|
======================
|
||||||
|
|
||||||
|
Horizon provides the :mod:`horizon.tables` module to provide
|
||||||
|
a convenient, reusable API for building data-driven displays and interfaces.
|
||||||
|
The core components of this API fall into three categories: ``DataTables``,
|
||||||
|
``Actions``, and ``Class-based Views``.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
For a detailed API information check out the :doc:`DataTables Reference
|
||||||
|
Guide </ref/tables>`.
|
||||||
|
|
||||||
|
Tables
|
||||||
|
======
|
||||||
|
|
||||||
|
The majority of interface in a dashboard-style interface ends up being
|
||||||
|
tabular displays of the various resources the dashboard interacts with.
|
||||||
|
The :class:`~horizon.tables.DataTable` class exists so you don't have to
|
||||||
|
reinvent the wheel each time.
|
||||||
|
|
||||||
|
Creating your own tables
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
Creating a table is fairly simple:
|
||||||
|
|
||||||
|
#. Create a subclass of :class:`~horizon.tables.DataTable`.
|
||||||
|
#. Define columns on it using :class:`~horizon.tables.Column`.
|
||||||
|
#. Create an inner ``Meta`` class to contain the special options for
|
||||||
|
this table.
|
||||||
|
#. Define any actions for the table, and add them to
|
||||||
|
:attr:`~horizon.tables.DataTableOptions.table_actions` or
|
||||||
|
:attr:`~horizon.tables.DataTableOptions.row_actions`.
|
||||||
|
|
||||||
|
Examples of this can be found in any of the ``tables.py`` modules included
|
||||||
|
in the reference modules under ``horizon.dashboards``.
|
||||||
|
|
||||||
|
Connecting a table to a view
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Once you've got your table set up the way you like it, the next step is to
|
||||||
|
wire it up to a view. To make this as easy as possible Horizon provides the
|
||||||
|
:class:`~horizon.tables.DataTableView` class-based view which can be subclassed
|
||||||
|
to display your table with just a couple lines of code. At its simplest, it
|
||||||
|
looks like this::
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
from .tables import MyTable
|
||||||
|
|
||||||
|
|
||||||
|
class MyTableView(tables.DataTableView):
|
||||||
|
table_class = MyTable
|
||||||
|
template_name = "my_app/my_table_view.html"
|
||||||
|
|
||||||
|
def get_data(self):
|
||||||
|
return my_api.objects.list()
|
||||||
|
|
||||||
|
In the template you would just need to include the following to render the
|
||||||
|
table::
|
||||||
|
|
||||||
|
{{ table.render }}
|
||||||
|
|
||||||
|
That's it! Easy, right?
|
||||||
|
|
||||||
|
Actions
|
||||||
|
=======
|
||||||
|
|
||||||
|
Actions comprise any manipulations that might happen on the data in the table
|
||||||
|
or the table itself. For example, this may be the standard object CRUD, linking
|
||||||
|
to related views based on the object's id, filtering the data in the table,
|
||||||
|
or fetching updated data when appropriate.
|
||||||
|
|
||||||
|
When actions get run
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
There are two points in the request-response cycle in which actions can
|
||||||
|
take place; prior to data being loaded into the table, and after the data
|
||||||
|
is loaded. When you're using one of the pre-built class-based views for
|
||||||
|
working with your tables the pseudo-workflow looks like this:
|
||||||
|
|
||||||
|
#. The request enters view.
|
||||||
|
#. The table class is instantiated without data.
|
||||||
|
#. Any "preemptive" actions are checked to see if they should run.
|
||||||
|
#. Data is fetched and loaded into the table.
|
||||||
|
#. All other actions are checked to see if they should run.
|
||||||
|
#. If none of the actions have caused an early exit from the view,
|
||||||
|
the standard response from the view is returned (usually the
|
||||||
|
rendered table).
|
||||||
|
|
||||||
|
The benefit of the multi-step table instantiation is that you can use
|
||||||
|
preemptive actions which don't need access to the entire collection of data
|
||||||
|
to save yourself on processing overhead, API calls, etc.
|
||||||
|
|
||||||
|
Basic actions
|
||||||
|
-------------
|
||||||
|
|
||||||
|
At their simplest, there are three types of actions: actions which act on the
|
||||||
|
data in the table, actions which link to related resources, and actions that
|
||||||
|
alter which data is displayed. These correspond to
|
||||||
|
:class:`~horizon.tables.Action`, :class:`~horizon.tables.LinkAction`, and
|
||||||
|
:class:`~horizon.tables.FilterAction`.
|
||||||
|
|
||||||
|
Writing your own actions generally starts with subclassing one of those
|
||||||
|
action classes and customizing the designated attributes and methods.
|
||||||
|
|
||||||
|
Shortcut actions
|
||||||
|
----------------
|
||||||
|
|
||||||
|
There are several common tasks for which Horizon provides pre-built shortcut
|
||||||
|
classes. These include :class:`~horizon.tables.BatchAction`, and
|
||||||
|
:class:`~horizon.tables.DeleteAction`. Each of these abstracts away nearly
|
||||||
|
all of the boilerplate associated with writing these types of actions and
|
||||||
|
provides consistent error handling, logging, and user-facing interaction.
|
||||||
|
|
||||||
|
It is worth noting that ``BatchAction`` and ``DeleteAction`` are extensions
|
||||||
|
of the standard ``Action`` class. Some ``BatchAction`` or ``DeleteAction``
|
||||||
|
classes may cause some unrecoverable results, like deleted images or
|
||||||
|
unrecoverable instances. It may be helpful to specify specific help_text to
|
||||||
|
explain the concern to the user, such as "Deleted images are not recoverable".
|
||||||
|
|
||||||
|
Preemptive actions
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Action classes which have their :attr:`~horizon.tables.Action.preempt`
|
||||||
|
attribute set to ``True`` will be evaluated before any data is loaded into
|
||||||
|
the table. As such, you must be careful not to rely on any table methods that
|
||||||
|
require data, such as :meth:`~horizon.tables.DataTable.get_object_display` or
|
||||||
|
:meth:`~horizon.tables.DataTable.get_object_by_id`. The advantage of preemptive
|
||||||
|
actions is that you can avoid having to do all the processing, API calls, etc.
|
||||||
|
associated with loading data into the table for actions which don't require
|
||||||
|
access to that information.
|
||||||
|
|
||||||
|
Policy checks on actions
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The :attr:`~horizon.tables.Action.policy_rules` attribute, when set, will
|
||||||
|
validate access to the action using the policy rules specified. The attribute
|
||||||
|
is a list of scope/rule pairs. Where the scope is the service type defining
|
||||||
|
the rule and the rule is a rule from the corresponding service policy.json
|
||||||
|
file. The format of :attr:`horizon.tables.Action.policy_rules` looks like::
|
||||||
|
|
||||||
|
(("identity", "identity:get_user"),)
|
||||||
|
|
||||||
|
Multiple checks can be made for the same action by merely adding more tuples
|
||||||
|
to the list. The policy check will use information stored in the session
|
||||||
|
about the user and the result of
|
||||||
|
:meth:`~horizon.tables.Action.get_policy_target` (which can be overridden in
|
||||||
|
the derived action class) to determine if the user
|
||||||
|
can execute the action. If the user does not have access to the action, the
|
||||||
|
action is not added to the table.
|
||||||
|
|
||||||
|
If :attr:`~horizon.tables.Action.policy_rules` is not set, no policy checks
|
||||||
|
will be made to determine if the action should be visible and will be
|
||||||
|
displayed solely based on the result of
|
||||||
|
:meth:`~horizon.tables.Action.allowed`.
|
||||||
|
|
||||||
|
For more information on policy based Role Based Access Control see:
|
||||||
|
:doc:`Horizon Policy Enforcement (RBAC: Role Based Access Control) </topics/policy>`.
|
||||||
|
|
||||||
|
Table Cell filters (decorators)
|
||||||
|
===============================
|
||||||
|
|
||||||
|
DataTable displays lists of objects in rows and object attributes in cell.
|
||||||
|
How should we proceed, if we want to decorate some column, e.g. if we have
|
||||||
|
column ``memory`` which returns a number e.g. 1024, and we want to show
|
||||||
|
something like 1024.00 GB inside table?
|
||||||
|
|
||||||
|
Decorator pattern
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The clear anti-pattern is defining the new attributes on object like
|
||||||
|
``ram_float_format_2_gb`` or to tweak a DataTable in any way for displaying
|
||||||
|
purposes.
|
||||||
|
|
||||||
|
The cleanest way is to use ``filters``. Filters are decorators, following GOF
|
||||||
|
``Decorator pattern``. This way ``DataTable logic`` and ``displayed object
|
||||||
|
logic`` are correctly separated from ``presentation logic`` of the object
|
||||||
|
inside of the various tables. And therefore the filters are reusable in all
|
||||||
|
tables.
|
||||||
|
|
||||||
|
Filter function
|
||||||
|
---------------
|
||||||
|
|
||||||
|
Horizon DatablesTable takes a tuple of pointers to filter functions
|
||||||
|
or anonymous lambda functions. When displaying a ``Cell``, ``DataTable``
|
||||||
|
takes ``Column`` filter functions from left to right, using the returned value
|
||||||
|
of the previous function as a parameter of the following function. Then
|
||||||
|
displaying the returned value of the last filter function.
|
||||||
|
|
||||||
|
A valid filter function takes one parameter and returns the decorated value.
|
||||||
|
So e.g. these are valid filter functions ::
|
||||||
|
|
||||||
|
# Filter function.
|
||||||
|
def add_unit(v):
|
||||||
|
return str(v) + " GB"
|
||||||
|
|
||||||
|
# Or filter lambda function.
|
||||||
|
lambda v: str(v) + " GB"
|
||||||
|
|
||||||
|
# This is also a valid definition of course, although for the change of the
|
||||||
|
# unit parameter, function has to be wrapped by lambda
|
||||||
|
# (e.g. floatformat function example below).
|
||||||
|
def add_unit(v, unit="GB"):
|
||||||
|
return str(v) + " " + unit
|
||||||
|
|
||||||
|
Using filters in DataTable column
|
||||||
|
---------------------------------
|
||||||
|
|
||||||
|
DataTable takes tuple of filter functions, so e.g. this is valid decorating
|
||||||
|
of a value with float format and with unit ::
|
||||||
|
|
||||||
|
ram = tables.Column(
|
||||||
|
"ram",
|
||||||
|
verbose_name=_('Memory'),
|
||||||
|
filters=(lambda v: floatformat(v, 2),
|
||||||
|
add_unit))
|
||||||
|
|
||||||
|
It always takes tuple, so using only one filter would look like this ::
|
||||||
|
|
||||||
|
filters=(lambda v: floatformat(v, 2),)
|
||||||
|
|
||||||
|
The decorated parameter doesn't have to be only a string or number, it can
|
||||||
|
be anything e.g. list or an object. So decorating of object, that has
|
||||||
|
attributes value and unit would look like this ::
|
||||||
|
|
||||||
|
ram = tables.Column(
|
||||||
|
"ram",
|
||||||
|
verbose_name=_('Memory'),
|
||||||
|
filters=(lambda x: getattr(x, 'value', '') +
|
||||||
|
" " + getattr(x, 'unit', ''),))
|
||||||
|
|
||||||
|
Available filters
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
There are a load of filters, that can be used, defined in django already:
|
||||||
|
https://github.com/django/django/blob/master/django/template/defaultfilters.py
|
||||||
|
|
||||||
|
So it's enough to just import and use them, e.g. ::
|
||||||
|
|
||||||
|
from django.template import defaultfilters as filters
|
||||||
|
|
||||||
|
# code omitted
|
||||||
|
filters=(filters.yesno, filters.capfirst)
|
||||||
|
|
||||||
|
|
||||||
|
from django.template.defaultfilters import timesince
|
||||||
|
from django.template.defaultfilters import title
|
||||||
|
|
||||||
|
# code omitted
|
||||||
|
filters=(parse_isotime, timesince)
|
||||||
|
|
||||||
|
|
||||||
|
Inline editing
|
||||||
|
==============
|
||||||
|
|
||||||
|
Table cells can be easily upgraded with in-line editing. With use of
|
||||||
|
django.form.Field, we are able to run validations of the field and correctly
|
||||||
|
parse the data. The updating process is fully encapsulated into table
|
||||||
|
functionality, communication with the server goes through AJAX in JSON format.
|
||||||
|
The javacript wrapper for inline editing allows each table cell that has
|
||||||
|
in-line editing available to:
|
||||||
|
|
||||||
|
#. Refresh itself with new data from the server.
|
||||||
|
#. Display in edit mod.
|
||||||
|
#. Send changed data to server.
|
||||||
|
#. Display validation errors.
|
||||||
|
|
||||||
|
There are basically 3 things that need to be defined in the table in order
|
||||||
|
to enable in-line editing.
|
||||||
|
|
||||||
|
Fetching the row data
|
||||||
|
---------------------
|
||||||
|
|
||||||
|
Defining an ``get_data`` method in a class inherited from ``tables.Row``.
|
||||||
|
This method takes care of fetching the row data. This class has to be then
|
||||||
|
defined in the table Meta class as ``row_class = UpdateRow``.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
class UpdateRow(tables.Row):
|
||||||
|
# this method is also used for automatic update of the row
|
||||||
|
ajax = True
|
||||||
|
|
||||||
|
def get_data(self, request, project_id):
|
||||||
|
# getting all data of all row cells
|
||||||
|
project_info = api.keystone.tenant_get(request, project_id,
|
||||||
|
admin=True)
|
||||||
|
return project_info
|
||||||
|
|
||||||
|
Updating changed cell data
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
Define an ``update_cell`` method in the class inherited from
|
||||||
|
``tables.UpdateAction``. This method takes care of saving the data of the
|
||||||
|
table cell. There can be one class for every cell thanks to the
|
||||||
|
``cell_name`` parameter. This class is then defined in tables column as
|
||||||
|
``update_action=UpdateCell``, so each column can have its own updating
|
||||||
|
method.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
class UpdateCell(tables.UpdateAction):
|
||||||
|
def allowed(self, request, project, cell):
|
||||||
|
# Determines whether given cell or row will be inline editable
|
||||||
|
# for signed in user.
|
||||||
|
return api.keystone.keystone_can_edit_project()
|
||||||
|
|
||||||
|
def update_cell(self, request, project_id, cell_name, new_cell_value):
|
||||||
|
# in-line update project info
|
||||||
|
try:
|
||||||
|
project_obj = datum
|
||||||
|
# updating changed value by new value
|
||||||
|
setattr(project_obj, cell_name, new_cell_value)
|
||||||
|
|
||||||
|
# sending new attributes back to API
|
||||||
|
api.keystone.tenant_update(
|
||||||
|
request,
|
||||||
|
project_id,
|
||||||
|
name=project_obj.name,
|
||||||
|
description=project_obj.description,
|
||||||
|
enabled=project_obj.enabled)
|
||||||
|
|
||||||
|
except Conflict:
|
||||||
|
# Validation error for naming conflict, raised when user
|
||||||
|
# choose the existing name. We will raise a
|
||||||
|
# ValidationError, that will be sent back to the client
|
||||||
|
# browser and shown inside of the table cell.
|
||||||
|
message = _("This name is already taken.")
|
||||||
|
raise ValidationError(message)
|
||||||
|
except:
|
||||||
|
# Other exception of the API just goes through standard
|
||||||
|
# channel
|
||||||
|
exceptions.handle(request, ignore=True)
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
Defining a form_field for each Column that we want to be in-line edited.
|
||||||
|
------------------------------------------------------------------------
|
||||||
|
|
||||||
|
Form field should be ``django.form.Field`` instance, so we can use django
|
||||||
|
validations and parsing of the values sent by POST (in example validation
|
||||||
|
``required=True`` and correct parsing of the checkbox value from the POST
|
||||||
|
data).
|
||||||
|
|
||||||
|
Form field can be also ``django.form.Widget`` class, if we need to just
|
||||||
|
display the form widget in the table and we don't need Field functionality.
|
||||||
|
|
||||||
|
Then connecting ``UpdateRow`` and ``UpdateCell`` classes to the table.
|
||||||
|
|
||||||
|
Example::
|
||||||
|
|
||||||
|
class TenantsTable(tables.DataTable):
|
||||||
|
# Adding html text input for inline editing, with required validation.
|
||||||
|
# HTML form input will have a class attribute tenant-name-input, we
|
||||||
|
# can define here any HTML attribute we need.
|
||||||
|
name = tables.Column('name', verbose_name=_('Name'),
|
||||||
|
form_field=forms.CharField(required=True),
|
||||||
|
form_field_attributes={'class':'tenant-name-input'},
|
||||||
|
update_action=UpdateCell)
|
||||||
|
|
||||||
|
# Adding html textarea without required validation.
|
||||||
|
description = tables.Column(lambda obj: getattr(obj, 'description', None),
|
||||||
|
verbose_name=_('Description'),
|
||||||
|
form_field=forms.CharField(
|
||||||
|
widget=forms.Textarea(),
|
||||||
|
required=False),
|
||||||
|
update_action=UpdateCell)
|
||||||
|
|
||||||
|
# Id will not be inline edited.
|
||||||
|
id = tables.Column('id', verbose_name=_('Project ID'))
|
||||||
|
|
||||||
|
# Adding html checkbox, that will be shown inside of the table cell with
|
||||||
|
# label
|
||||||
|
enabled = tables.Column('enabled', verbose_name=_('Enabled'), status=True,
|
||||||
|
form_field=forms.BooleanField(
|
||||||
|
label=_('Enabled'),
|
||||||
|
required=False),
|
||||||
|
update_action=UpdateCell)
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
name = "tenants"
|
||||||
|
verbose_name = _("Projects")
|
||||||
|
# Connection to UpdateRow, so table can fetch row data based on
|
||||||
|
# their primary key.
|
||||||
|
row_class = UpdateRow
|
||||||
|
|
|
@ -0,0 +1,276 @@
|
||||||
|
===================
|
||||||
|
Testing Topic Guide
|
||||||
|
===================
|
||||||
|
|
||||||
|
Having good tests in place is absolutely critical for ensuring a stable,
|
||||||
|
maintainable codebase. Hopefully that doesn't need any more explanation.
|
||||||
|
|
||||||
|
However, what defines a "good" test is not always obvious, and there are
|
||||||
|
a lot of common pitfalls that can easily shoot your test suite in the
|
||||||
|
foot.
|
||||||
|
|
||||||
|
If you already know everything about testing but are fed up with trying to
|
||||||
|
debug why a specific test failed, you can skip the intro and jump
|
||||||
|
straight to :ref:`debugging_unit_tests`.
|
||||||
|
|
||||||
|
An overview of testing
|
||||||
|
======================
|
||||||
|
|
||||||
|
There are three main types of tests, each with their associated pros and cons:
|
||||||
|
|
||||||
|
Unit tests
|
||||||
|
----------
|
||||||
|
|
||||||
|
These are isolated, stand-alone tests with no external dependencies. They are
|
||||||
|
written from the perspective of "knowing the code", and test the assumptions
|
||||||
|
of the codebase and the developer.
|
||||||
|
|
||||||
|
Pros:
|
||||||
|
|
||||||
|
* Generally lightweight and fast.
|
||||||
|
* Can be run anywhere, anytime since they have no external dependencies.
|
||||||
|
|
||||||
|
Cons:
|
||||||
|
|
||||||
|
* Easy to be lax in writing them, or lazy in constructing them.
|
||||||
|
* Can't test interactions with live external services.
|
||||||
|
|
||||||
|
Functional tests
|
||||||
|
----------------
|
||||||
|
|
||||||
|
These are generally also isolated tests, though sometimes they may interact
|
||||||
|
with other services running locally. The key difference between functional
|
||||||
|
tests and unit tests, however, is that functional tests are written from the
|
||||||
|
perspective of the user (who knows nothing about the code) and only knows
|
||||||
|
what they put in and what they get back. Essentially this is a higher-level
|
||||||
|
testing of "does the result match the spec?".
|
||||||
|
|
||||||
|
Pros:
|
||||||
|
|
||||||
|
* Ensures that your code *always* meets the stated functional requirements.
|
||||||
|
* Verifies things from an "end user" perspective, which helps to ensure
|
||||||
|
a high-quality experience.
|
||||||
|
* Designing your code with a functional testing perspective in mind helps
|
||||||
|
keep a higher-level viewpoint in mind.
|
||||||
|
|
||||||
|
Cons:
|
||||||
|
|
||||||
|
* Requires an additional layer of thinking to define functional requirements
|
||||||
|
in terms of inputs and outputs.
|
||||||
|
* Often requires writing a separate set of tests and/or using a different
|
||||||
|
testing framework from your unit tests.
|
||||||
|
* Doesn't offer any insight into the quality or status of the underlying code,
|
||||||
|
only verifies that it works or it doesn't.
|
||||||
|
|
||||||
|
Integration Tests
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
This layer of testing involves testing all of the components that your
|
||||||
|
codebase interacts with or relies on in conjunction. This is equivalent to
|
||||||
|
"live" testing, but in a repeatable manner.
|
||||||
|
|
||||||
|
Pros:
|
||||||
|
|
||||||
|
* Catches *many* bugs that unit and functional tests will not.
|
||||||
|
* Doesn't rely on assumptions about the inputs and outputs.
|
||||||
|
* Will warn you when changes in external components break your code.
|
||||||
|
|
||||||
|
Cons:
|
||||||
|
|
||||||
|
* Difficult and time-consuming to create a repeatable test environment.
|
||||||
|
* Did I mention that setting it up is a pain?
|
||||||
|
|
||||||
|
So what should I write?
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
A few simple guidelines:
|
||||||
|
|
||||||
|
#. Every bug fix should have a regression test. Period.
|
||||||
|
|
||||||
|
#. When writing a new feature, think about writing unit tests to verify
|
||||||
|
the behavior step-by-step as you write the feature. Every time you'd
|
||||||
|
go to run your code by hand and verify it manually, think "could I
|
||||||
|
write a test to do this instead?". That way when the feature is done
|
||||||
|
and you're ready to commit it you've already got a whole set of tests
|
||||||
|
that are more thorough than anything you'd write after the fact.
|
||||||
|
|
||||||
|
#. Write tests that hit every view in your application. Even if they
|
||||||
|
don't assert a single thing about the code, it tells you that your
|
||||||
|
users aren't getting fatal errors just by interacting with your code.
|
||||||
|
|
||||||
|
What makes a good unit test?
|
||||||
|
============================
|
||||||
|
|
||||||
|
Limiting our focus just to unit tests, there are a number of things you can
|
||||||
|
do to make your unit tests as useful, maintainable, and unburdensome as
|
||||||
|
possible.
|
||||||
|
|
||||||
|
Test data
|
||||||
|
---------
|
||||||
|
|
||||||
|
Use a single, consistent set of test data. Grow it over time, but do everything
|
||||||
|
you can not to fragment it. It quickly becomes unmaintainable and perniciously
|
||||||
|
out-of-sync with reality.
|
||||||
|
|
||||||
|
Make your test data as accurate to reality as possible. Supply *all* the
|
||||||
|
attributes of an object, provide objects in all the various states you may want
|
||||||
|
to test.
|
||||||
|
|
||||||
|
If you do the first suggestion above *first* it makes the second one far less
|
||||||
|
painful. Write once, use everywhere.
|
||||||
|
|
||||||
|
To make your life even easier, if your codebase doesn't have a built-in
|
||||||
|
ORM-like function to manage your test data you can consider building (or
|
||||||
|
borrowing) one yourself. Being able to do simple retrieval queries on your
|
||||||
|
test data is incredibly valuable.
|
||||||
|
|
||||||
|
Mocking
|
||||||
|
-------
|
||||||
|
|
||||||
|
Mocking is the practice of providing stand-ins for objects or pieces of code
|
||||||
|
you don't need to test. While convenient, they should be used with *extreme*
|
||||||
|
caution.
|
||||||
|
|
||||||
|
Why? Because overuse of mocks can rapidly land you in a situation where you're
|
||||||
|
not testing any real code. All you've done is verified that your mocking
|
||||||
|
framework returns what you tell it to. This problem can be very tricky to
|
||||||
|
recognize, since you may be mocking things in ``setUp`` methods, other modules,
|
||||||
|
etc.
|
||||||
|
|
||||||
|
A good rule of thumb is to mock as close to the source as possible. If you have
|
||||||
|
a function call that calls an external API in a view , mock out the external
|
||||||
|
API, not the whole function. If you mock the whole function you've suddenly
|
||||||
|
lost test coverage for an entire chunk of code *inside* your codebase. Cut the
|
||||||
|
ties cleanly right where your system ends and the external world begins.
|
||||||
|
|
||||||
|
Similarly, don't mock return values when you could construct a real return
|
||||||
|
value of the correct type with the correct attributes. You're just adding
|
||||||
|
another point of potential failure by exercising your mocking framework instead
|
||||||
|
of real code. Following the suggestions for testing above will make this a lot
|
||||||
|
less burdensome.
|
||||||
|
|
||||||
|
Assertions and verification
|
||||||
|
---------------------------
|
||||||
|
|
||||||
|
Think long and hard about what you really want to verify in your unit test. In
|
||||||
|
particular, think about what custom logic your code executes.
|
||||||
|
|
||||||
|
A common pitfall is to take a known test object, pass it through your code,
|
||||||
|
and then verify the properties of that object on the output. This is all well
|
||||||
|
and good, except if you're verifying properties that were untouched by your
|
||||||
|
code. What you want to check are the pieces that were *changed*, *added*, or
|
||||||
|
*removed*. Don't check the object's id attribute unless you have reason to
|
||||||
|
suspect it's not the object you started with. But if you added a new attribute
|
||||||
|
to it, be damn sure you verify that came out right.
|
||||||
|
|
||||||
|
It's also very common to avoid testing things you really care about because
|
||||||
|
it's more difficult. Verifying that the proper messages were displayed to the
|
||||||
|
user after an action, testing for form errors, making sure exception handling
|
||||||
|
is tested... these types of things aren't always easy, but they're extremely
|
||||||
|
necessary.
|
||||||
|
|
||||||
|
To that end, Horizon includes several custom assertions to make these tasks
|
||||||
|
easier. :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`,
|
||||||
|
:meth:`~horizon.test.helpers.TestCase.assertMessageCount`, and
|
||||||
|
:meth:`~horizon.test.helpers.TestCase.assertNoMessages` all exist for exactly
|
||||||
|
these purposes. Moreover, they provide useful output when things go wrong so
|
||||||
|
you're not left scratching your head wondering why your view test didn't
|
||||||
|
redirect as expected when you posted a form.
|
||||||
|
|
||||||
|
.. _debugging_unit_tests:
|
||||||
|
|
||||||
|
Debugging Unit Tests
|
||||||
|
====================
|
||||||
|
|
||||||
|
Tips and tricks
|
||||||
|
---------------
|
||||||
|
|
||||||
|
#. Use :meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors`
|
||||||
|
immediately after your ``client.post`` call for tests that handle form views.
|
||||||
|
This will immediately fail if your form POST failed due to a validation error
|
||||||
|
and tell you what the error was.
|
||||||
|
|
||||||
|
#. Use :meth:`~horizon.test.helpers.TestCase.assertMessageCount` and
|
||||||
|
:meth:`~horizon.test.helpers.TestCase.assertNoMessages` when a piece of code
|
||||||
|
is failing inexplicably. Since the core error handlers attach user-facing
|
||||||
|
error messages (and since the core logging is silenced during test runs)
|
||||||
|
these methods give you the dual benefit of verifying the output you expect
|
||||||
|
while clearly showing you the problematic error message if they fail.
|
||||||
|
|
||||||
|
#. Use Python's ``pdb`` module liberally. Many people don't realize it works
|
||||||
|
just as well in a test case as it does in a live view. Simply inserting
|
||||||
|
``import pdb; pdb.set_trace()`` anywhere in your codebase will drop the
|
||||||
|
interpreter into an interactive shell so you can explore your test
|
||||||
|
environment and see which of your assumptions about the code isn't,
|
||||||
|
in fact, flawlessly correct.
|
||||||
|
|
||||||
|
Common pitfalls
|
||||||
|
---------------
|
||||||
|
|
||||||
|
There are a number of typical (and non-obvious) ways to break the unit tests.
|
||||||
|
Some common things to look for:
|
||||||
|
|
||||||
|
#. Make sure you stub out the method exactly as it's called in the code
|
||||||
|
being tested. For example, if your real code calls
|
||||||
|
``api.keystone.tenant_get``, stubbing out ``api.tenant_get`` (available
|
||||||
|
for legacy reasons) will fail.
|
||||||
|
|
||||||
|
#. When defining the expected input to a stubbed call, make sure the
|
||||||
|
arguments are *identical*, this includes ``str`` vs. ``int`` differences.
|
||||||
|
|
||||||
|
#. Make sure your test data are completely in line with the expected inputs.
|
||||||
|
Again, ``str`` vs. ``int`` or missing properties on test objects will
|
||||||
|
kill your tests.
|
||||||
|
|
||||||
|
#. Make sure there's nothing amiss in your templates (particularly the
|
||||||
|
``{% url %}`` tag and its arguments). This often comes up when refactoring
|
||||||
|
views or renaming context variables. It can easily result in errors that
|
||||||
|
you might not stumble across while clicking around the development server.
|
||||||
|
|
||||||
|
#. Make sure you're not redirecting to views that no longer exist, e.g.
|
||||||
|
the ``index`` view for a panel that got combined (such as instances &
|
||||||
|
volumes).
|
||||||
|
|
||||||
|
#. Make sure your mock calls are in order before calling ``mox.ReplayAll``.
|
||||||
|
The order matters.
|
||||||
|
|
||||||
|
#. Make sure you repeat any stubbed out method calls that happen more than
|
||||||
|
once. They don't automatically repeat, you have to explicitly define them.
|
||||||
|
While this is a nuisance, it makes you acutely aware of how many API
|
||||||
|
calls are involved in a particular function.
|
||||||
|
|
||||||
|
Understanding the output from ``mox``
|
||||||
|
-------------------------------------
|
||||||
|
|
||||||
|
Horizon uses ``mox`` as its mocking framework of choice, and while it
|
||||||
|
offers many nice features, its output when a test fails can be quite
|
||||||
|
mysterious.
|
||||||
|
|
||||||
|
Unexpected Method Call
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This occurs when you stubbed out a piece of code, and it was subsequently
|
||||||
|
called in a way that you didn't specify it would be. There are two reasons
|
||||||
|
this tends to come up:
|
||||||
|
|
||||||
|
#. You defined the expected call, but a subtle difference crept in. This
|
||||||
|
may be a string versus integer difference, a string versus unicode
|
||||||
|
difference, a slightly off date/time, or passing a name instead of an id.
|
||||||
|
|
||||||
|
#. The method is actually being called *multiple times*. Since mox uses
|
||||||
|
a call stack internally, it simply pops off the expected method calls to
|
||||||
|
verify them. That means once a call is used once, it's gone. An easy way
|
||||||
|
to see if this is the case is simply to copy and paste your method call a
|
||||||
|
second time to see if the error changes. If it does, that means your method
|
||||||
|
is being called more times than you think it is.
|
||||||
|
|
||||||
|
Expected Method Never Called
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This one is the opposite of the unexpected method call. This one means you
|
||||||
|
told mox to expect a call and it didn't happen. This is almost always the
|
||||||
|
result of an error in the conditions of the test. Using the
|
||||||
|
:meth:`~openstack_dashboard.test.helpers.TestCase.assertNoFormErrors` and
|
||||||
|
:meth:`~horizon.test.helpers.TestCase.assertMessageCount` will make it readily
|
||||||
|
apparent what the problem is in the majority of cases. If not, then use ``pdb``
|
||||||
|
and start interrupting the code flow to see where things are getting off track.
|
|
@ -0,0 +1,629 @@
|
||||||
|
============================================
|
||||||
|
Tutorial: Building a Dashboard using Horizon
|
||||||
|
============================================
|
||||||
|
|
||||||
|
This tutorial covers how to use the various components in Horizon to build
|
||||||
|
an example dashboard and a panel with a tab which has a table containing data
|
||||||
|
from the back end.
|
||||||
|
|
||||||
|
As an example, we'll create a new ``My Dashboard`` dashboard with a ``My Panel``
|
||||||
|
panel that has an ``Instances Tab`` tab. The tab has a table which contains the
|
||||||
|
data pulled by the Nova instances API.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This tutorial assumes you have either a ``devstack`` or ``openstack``
|
||||||
|
environment up and running.
|
||||||
|
There are a variety of other resources which may be helpful to read first.
|
||||||
|
For example, you may want to start
|
||||||
|
with the :doc:`Horizon quickstart guide </quickstart>` or the
|
||||||
|
`Django tutorial`_.
|
||||||
|
|
||||||
|
.. _Django tutorial: https://docs.djangoproject.com/en/1.6/intro/tutorial01/
|
||||||
|
|
||||||
|
|
||||||
|
Creating a dashboard
|
||||||
|
====================
|
||||||
|
|
||||||
|
The quick version
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
Horizon provides a custom management command to create a typical base
|
||||||
|
dashboard structure for you. Run the following commands at the same location
|
||||||
|
where the ``run_tests.sh`` file resides. It generates most of the boilerplate
|
||||||
|
code you need::
|
||||||
|
|
||||||
|
mkdir openstack_dashboard/dashboards/mydashboard
|
||||||
|
|
||||||
|
./run_tests.sh -m startdash mydashboard \
|
||||||
|
--target openstack_dashboard/dashboards/mydashboard
|
||||||
|
|
||||||
|
mkdir openstack_dashboard/dashboards/mydashboard/mypanel
|
||||||
|
|
||||||
|
./run_tests.sh -m startpanel mypanel \
|
||||||
|
--dashboard=openstack_dashboard.dashboards.mydashboard \
|
||||||
|
--target=openstack_dashboard/dashboards/mydashboard/mypanel
|
||||||
|
|
||||||
|
|
||||||
|
You will notice that the directory ``mydashboard`` gets automatically
|
||||||
|
populated with the files related to a dashboard and the ``mypanel`` directory
|
||||||
|
gets automatically populated with the files related to a panel.
|
||||||
|
|
||||||
|
|
||||||
|
Structure
|
||||||
|
---------
|
||||||
|
If you use the ``tree mydashboard`` command to list the ``mydashboard``
|
||||||
|
directory in ``openstack_dashboard/dashboards`` , you will see a directory
|
||||||
|
structure that looks like the following::
|
||||||
|
|
||||||
|
mydashboard
|
||||||
|
├── dashboard.py
|
||||||
|
├── dashboard.pyc
|
||||||
|
├── __init__.py
|
||||||
|
├── __init__.pyc
|
||||||
|
├── models.py
|
||||||
|
├── mypanel
|
||||||
|
│ ├── __init__.py
|
||||||
|
│ ├── models.py
|
||||||
|
│ ├── panel.py
|
||||||
|
│ ├── templates
|
||||||
|
│ │ └── mypanel
|
||||||
|
│ │ └── index.html
|
||||||
|
│ ├── tests.py
|
||||||
|
│ ├── urls.py
|
||||||
|
│ └── views.py
|
||||||
|
├── static
|
||||||
|
│ └── mydashboard
|
||||||
|
│ ├── css
|
||||||
|
│ │ └── mydashboard.css
|
||||||
|
│ └── js
|
||||||
|
│ └── mydashboard.js
|
||||||
|
└── templates
|
||||||
|
└── mydashboard
|
||||||
|
└── base.html
|
||||||
|
|
||||||
|
|
||||||
|
For this tutorial, we will not deal with the static directory, the ``models.py``
|
||||||
|
file and tests.py file. Leave them as they are.
|
||||||
|
|
||||||
|
With the rest of the files and directories in place, we can move on to add our
|
||||||
|
own dashboard.
|
||||||
|
|
||||||
|
|
||||||
|
Defining a dashboard
|
||||||
|
--------------------
|
||||||
|
|
||||||
|
Open the ``dashboard.py`` file. You will notice the following code has been
|
||||||
|
automatically generated::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
|
||||||
|
class Mydashboard(horizon.Dashboard):
|
||||||
|
name = _("Mydashboard")
|
||||||
|
slug = "mydashboard"
|
||||||
|
panels = () # Add your panels here.
|
||||||
|
default_panel = '' # Specify the slug of the dashboard's default panel.
|
||||||
|
|
||||||
|
|
||||||
|
horizon.register(Mydashboard)
|
||||||
|
|
||||||
|
|
||||||
|
If you want the dashboard name to be something else, you can change the ``name``
|
||||||
|
attribute in the ``dashboard.py`` file . For example, you can change it
|
||||||
|
to be ``My Dashboard`` ::
|
||||||
|
|
||||||
|
name = _("My Dashboard")
|
||||||
|
|
||||||
|
|
||||||
|
A dashboard class will usually contain a ``name`` attribute (the display name of
|
||||||
|
the dashboard), a ``slug`` attribute (the internal name that could be referenced
|
||||||
|
by other components), a list of panels, default panel, etc. We will cover how
|
||||||
|
to add a panel in the next section.
|
||||||
|
|
||||||
|
|
||||||
|
Creating a panel
|
||||||
|
================
|
||||||
|
|
||||||
|
We'll create a panel and call it ``My Panel``.
|
||||||
|
|
||||||
|
Structure
|
||||||
|
---------
|
||||||
|
|
||||||
|
As described above, the ``mypanel`` directory under
|
||||||
|
``openstack_dashboard/dashboards/mydashboard`` should look like the following::
|
||||||
|
|
||||||
|
mypanel
|
||||||
|
├── __init__.py
|
||||||
|
├── models.py
|
||||||
|
├── panel.py
|
||||||
|
├── templates
|
||||||
|
│ └── mypanel
|
||||||
|
│ └── index.html
|
||||||
|
├── tests.py
|
||||||
|
├── urls.py
|
||||||
|
└── views.py
|
||||||
|
|
||||||
|
|
||||||
|
Defining a panel
|
||||||
|
----------------
|
||||||
|
|
||||||
|
The ``panel.py`` file referenced above has a special meaning. Within a dashboard,
|
||||||
|
any module name listed in the ``panels`` attribute on the dashboard class will
|
||||||
|
be auto-discovered by looking for the ``panel.py`` file in a corresponding
|
||||||
|
directory (the details are a bit magical, but have been thoroughly vetted in
|
||||||
|
Django's admin codebase).
|
||||||
|
|
||||||
|
Open the ``panel.py`` file, you will have the following auto-generated code::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard import dashboard
|
||||||
|
|
||||||
|
|
||||||
|
class Mypanel(horizon.Panel):
|
||||||
|
name = _("Mypanel")
|
||||||
|
slug = "mypanel"
|
||||||
|
|
||||||
|
|
||||||
|
dashboard.Mydashboard.register(Mypanel)
|
||||||
|
|
||||||
|
|
||||||
|
If you want the panel name to be something else, you can change the ``name``
|
||||||
|
attribute in the ``panel.py`` file . For example, you can change it to be
|
||||||
|
``My Panel``::
|
||||||
|
|
||||||
|
name = _("My Panel")
|
||||||
|
|
||||||
|
|
||||||
|
Open the ``dashboard.py`` file again, insert the following code above the
|
||||||
|
``Mydashboard`` class. This code defines the ``Mygroup`` class and adds a panel
|
||||||
|
called ``mypanel``::
|
||||||
|
|
||||||
|
class Mygroup(horizon.PanelGroup):
|
||||||
|
slug = "mygroup"
|
||||||
|
name = _("My Group")
|
||||||
|
panels = ('mypanel',)
|
||||||
|
|
||||||
|
|
||||||
|
Modify the ``Mydashboard`` class to include ``Mygroup`` and add ``mypanel`` as
|
||||||
|
the default panel::
|
||||||
|
|
||||||
|
class Mydashboard(horizon.Dashboard):
|
||||||
|
name = _("My Dashboard")
|
||||||
|
slug = "mydashboard"
|
||||||
|
panels = (Mygroup,) # Add your panels here.
|
||||||
|
default_panel = 'mypanel' # Specify the slug of the default panel.
|
||||||
|
|
||||||
|
|
||||||
|
The completed ``dashboard.py`` file should look like
|
||||||
|
the following::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
|
||||||
|
class Mygroup(horizon.PanelGroup):
|
||||||
|
slug = "mygroup"
|
||||||
|
name = _("My Group")
|
||||||
|
panels = ('mypanel',)
|
||||||
|
|
||||||
|
|
||||||
|
class Mydashboard(horizon.Dashboard):
|
||||||
|
name = _("My Dashboard")
|
||||||
|
slug = "mydashboard"
|
||||||
|
panels = (Mygroup,) # Add your panels here.
|
||||||
|
default_panel = 'mypanel' # Specify the slug of the default panel.
|
||||||
|
|
||||||
|
|
||||||
|
horizon.register(Mydashboard)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Tables, Tabs, and Views
|
||||||
|
-----------------------
|
||||||
|
|
||||||
|
We'll start with the table, combine that with the tabs, and then build our
|
||||||
|
view from the pieces.
|
||||||
|
|
||||||
|
Defining a table
|
||||||
|
~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon provides a :class:`~horizon.forms.SelfHandlingForm` :class:`~horizon.tables.DataTable` class which simplifies
|
||||||
|
the vast majority of displaying data to an end-user. We're just going to skim
|
||||||
|
the surface here, but it has a tremendous number of capabilities.
|
||||||
|
|
||||||
|
Create a ``tables.py`` file under the ``mypanel`` directory and add the
|
||||||
|
following code::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
|
||||||
|
class InstancesTable(tables.DataTable):
|
||||||
|
name = tables.Column("name", verbose_name=_("Name"))
|
||||||
|
status = tables.Column("status", verbose_name=_("Status"))
|
||||||
|
zone = tables.Column('availability_zone',
|
||||||
|
verbose_name=_("Availability Zone"))
|
||||||
|
image_name = tables.Column('image_name', verbose_name=_("Image Name"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
name = "instances"
|
||||||
|
verbose_name = _("Instances")
|
||||||
|
|
||||||
|
|
||||||
|
There are several things going on here... we created a table subclass,
|
||||||
|
and defined four columns that we want to retrieve data and display.
|
||||||
|
Each of those columns defines what attribute it accesses on the instance object
|
||||||
|
as the first argument, and since we like to make everything translatable,
|
||||||
|
we give each column a ``verbose_name`` that's marked for translation.
|
||||||
|
|
||||||
|
Lastly, we added a ``Meta`` class which indicates the meta object that describes
|
||||||
|
the ``instances`` table.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
This is a slight simplification from the reality of how the instance
|
||||||
|
object is actually structured. In reality, accessing other attributes
|
||||||
|
requires an additional step.
|
||||||
|
|
||||||
|
Adding actions to a table
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Horizon provides three types of basic action classes which can be taken
|
||||||
|
on a table's data:
|
||||||
|
|
||||||
|
- :class:`~horizon.tables.Action`
|
||||||
|
- :class:`~horizon.tables.LinkAction`
|
||||||
|
- :class:`~horizon.tables.FilterAction`
|
||||||
|
|
||||||
|
|
||||||
|
There are also additional actions which are extensions of the basic Action classes:
|
||||||
|
|
||||||
|
- :class:`~horizon.tables.BatchAction`
|
||||||
|
- :class:`~horizon.tables.DeleteAction`
|
||||||
|
- :class:`~horizon.tables.UpdateAction`
|
||||||
|
- :class:`~horizon.tables.FixedFilterAction`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
Now let's create and add a filter action to the table. To do so, we will need
|
||||||
|
to edit the ``tables.py`` file used above. To add a filter action which will
|
||||||
|
only show rows which contain the string entered in the filter field, we
|
||||||
|
must first define the action::
|
||||||
|
|
||||||
|
class MyFilterAction(tables.FilterAction):
|
||||||
|
name = "myfilter"
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
The action specified above will default the ``filter_type`` to be ``"query"``.
|
||||||
|
This means that the filter will use the client side table sorter.
|
||||||
|
|
||||||
|
Then, we add that action to the table actions for our table.::
|
||||||
|
|
||||||
|
class InstancesTable:
|
||||||
|
class Meta:
|
||||||
|
table_actions = (MyFilterAction,)
|
||||||
|
|
||||||
|
|
||||||
|
The completed ``tables.py`` file should look like the following::
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import tables
|
||||||
|
|
||||||
|
|
||||||
|
class MyFilterAction(tables.FilterAction):
|
||||||
|
name = "myfilter"
|
||||||
|
|
||||||
|
|
||||||
|
class InstancesTable(tables.DataTable):
|
||||||
|
name = tables.Column('name', \
|
||||||
|
verbose_name=_("Name"))
|
||||||
|
status = tables.Column('status', \
|
||||||
|
verbose_name=_("Status"))
|
||||||
|
zone = tables.Column('availability_zone', \
|
||||||
|
verbose_name=_("Availability Zone"))
|
||||||
|
image_name = tables.Column('image_name', \
|
||||||
|
verbose_name=_("Image Name"))
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
name = "instances"
|
||||||
|
verbose_name = _("Instances")
|
||||||
|
table_actions = (MyFilterAction,)
|
||||||
|
|
||||||
|
|
||||||
|
Defining tabs
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
So we have a table, ready to receive our data. We could go straight to a view
|
||||||
|
from here, but in this case we're also going to use Horizon's
|
||||||
|
:class:`~horizon.tabs.TabGroup` class.
|
||||||
|
|
||||||
|
Create a ``tabs.py`` file under the ``mypanel`` directory. Let's make a tab
|
||||||
|
group which has one tab. The completed code should look like the following::
|
||||||
|
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
from openstack_dashboard import api
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel import tables
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceTab(tabs.TableTab):
|
||||||
|
name = _("Instances Tab")
|
||||||
|
slug = "instances_tab"
|
||||||
|
table_classes = (tables.InstancesTable,)
|
||||||
|
template_name = ("horizon/common/_detail_table.html")
|
||||||
|
preload = False
|
||||||
|
|
||||||
|
def has_more_data(self, table):
|
||||||
|
return self._has_more
|
||||||
|
|
||||||
|
def get_instances_data(self):
|
||||||
|
try:
|
||||||
|
marker = self.request.GET.get(
|
||||||
|
tables.InstancesTable._meta.pagination_param, None)
|
||||||
|
|
||||||
|
instances, self._has_more = api.nova.server_list(
|
||||||
|
self.request,
|
||||||
|
search_opts={'marker': marker, 'paginate': True})
|
||||||
|
|
||||||
|
return instances
|
||||||
|
except Exception:
|
||||||
|
self._has_more = False
|
||||||
|
error_message = _('Unable to get instances')
|
||||||
|
exceptions.handle(self.request, error_message)
|
||||||
|
|
||||||
|
return []
|
||||||
|
|
||||||
|
class MypanelTabs(tabs.TabGroup):
|
||||||
|
slug = "mypanel_tabs"
|
||||||
|
tabs = (InstanceTab,)
|
||||||
|
sticky = True
|
||||||
|
|
||||||
|
|
||||||
|
This tab gets a little more complicated. The tab handles data tables (and
|
||||||
|
all their associated features), and it also uses the ``preload`` attribute to
|
||||||
|
specify that this tab shouldn't be loaded by default. It will instead be loaded
|
||||||
|
via AJAX when someone clicks on it, saving us on API calls in the vast majority
|
||||||
|
of cases.
|
||||||
|
|
||||||
|
Additionally, the displaying of the table is handled by a reusable template,
|
||||||
|
``horizon/common/_detail_table.html``. Some simple pagination code was added
|
||||||
|
to handle large instance lists.
|
||||||
|
|
||||||
|
Lastly, this code introduces the concept of error handling in Horizon.
|
||||||
|
The :func:`horizon.exceptions.handle` function is a centralized error
|
||||||
|
handling mechanism that takes all the guess-work and inconsistency out of
|
||||||
|
dealing with exceptions from the API. Use it everywhere.
|
||||||
|
|
||||||
|
Tying it together in a view
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
There are lots of pre-built class-based views in Horizon. We try to provide
|
||||||
|
the starting points for all the common combinations of components.
|
||||||
|
|
||||||
|
Open the ``views.py`` file, the auto-generated code is like the following::
|
||||||
|
|
||||||
|
from horizon import views
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(views.APIView):
|
||||||
|
# A very simple class-based view...
|
||||||
|
template_name = 'mydashboard/mypanel/index.html'
|
||||||
|
|
||||||
|
def get_data(self, request, context, *args, **kwargs):
|
||||||
|
# Add data to the context here...
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
In this case we want a starting view type that works with both tabs and
|
||||||
|
tables... that'd be the :class:`~horizon.tabs.TabbedTableView` class. It takes
|
||||||
|
the best of the dynamic delayed-loading capabilities tab groups provide and
|
||||||
|
mixes in the actions and AJAX-updating that tables are capable of with almost
|
||||||
|
no work on the user's end. Change ``views.APIView`` to be
|
||||||
|
``tabs.TabbedTableView`` and add ``MypanelTabs`` as the tab group class in the
|
||||||
|
``IndexView`` class::
|
||||||
|
|
||||||
|
class IndexView(tabs.TabbedTableView):
|
||||||
|
tab_group_class = mydashboard_tabs.MypanelTabs
|
||||||
|
|
||||||
|
|
||||||
|
After importing the proper package, the completed ``views.py`` file now looks like
|
||||||
|
the following::
|
||||||
|
|
||||||
|
from horizon import tabs
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel \
|
||||||
|
import tabs as mydashboard_tabs
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(tabs.TabbedTableView):
|
||||||
|
tab_group_class = mydashboard_tabs.MypanelTabs
|
||||||
|
template_name = 'mydashboard/mypanel/index.html'
|
||||||
|
|
||||||
|
def get_data(self, request, context, *args, **kwargs):
|
||||||
|
# Add data to the context here...
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
URLs
|
||||||
|
----
|
||||||
|
The auto-generated ``urls.py`` file is like::
|
||||||
|
|
||||||
|
from django.conf.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel.views \
|
||||||
|
import IndexView
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
url(r'^$', IndexView.as_view(), name='index'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
Adjust the import of ``IndexView`` to make the code readable::
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel import views
|
||||||
|
|
||||||
|
|
||||||
|
Replace the existing ``url`` pattern with the following line::
|
||||||
|
|
||||||
|
url(r'^$',
|
||||||
|
views.IndexView.as_view(), name='index'),
|
||||||
|
|
||||||
|
|
||||||
|
The completed ``urls.py`` file should look like the following::
|
||||||
|
|
||||||
|
from django.conf.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from openstack_dashboard.dashboards.mydashboard.mypanel import views
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^$',
|
||||||
|
views.IndexView.as_view(), name='index'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
The template
|
||||||
|
~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Open the ``index.html`` file in the ``mydashboard/mypanel/templates/mypanel``
|
||||||
|
directory, the auto-generated code is like the following::
|
||||||
|
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "Mypanel" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("Mypanel") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
The ``main`` block must be modified to insert the following code::
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{{ tab_group.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
If you want to change the title of the ``index.html`` file to be something else,
|
||||||
|
you can change it. For example, change it to be ``My Panel`` in the
|
||||||
|
``block title`` section. If you want the ``title`` in the ``block page_header``
|
||||||
|
section to be something else, you can change it. For example, change it to be
|
||||||
|
``My Panel``. The updated code could be like::
|
||||||
|
|
||||||
|
{% extends 'base.html' %}
|
||||||
|
{% load i18n %}
|
||||||
|
{% block title %}{% trans "My Panel" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block page_header %}
|
||||||
|
{% include "horizon/common/_page_header.html" with title=_("My Panel") %}
|
||||||
|
{% endblock page_header %}
|
||||||
|
|
||||||
|
{% block main %}
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-sm-12">
|
||||||
|
{{ tab_group.render }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
This gives us a custom page title, a header, and renders our tab group provided
|
||||||
|
by the view.
|
||||||
|
|
||||||
|
With all our code in place, the only thing left to do is to integrate it into
|
||||||
|
our OpenStack Dashboard site.
|
||||||
|
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
For more information about Django views, URLs and templates, please refer
|
||||||
|
to the `Django documentation`_.
|
||||||
|
|
||||||
|
.. _Django documentation: https://docs.djangoproject.com/en/1.6/
|
||||||
|
|
||||||
|
|
||||||
|
Enable and show the dashboard
|
||||||
|
=============================
|
||||||
|
|
||||||
|
In order to make ``My Dashboard`` show up along with the existing dashboards
|
||||||
|
like ``Project`` or ``Admin`` on Horizon, you need to create a file called
|
||||||
|
``_50_mydashboard.py`` under ``openstack_dashboard/enabled`` and add the
|
||||||
|
following::
|
||||||
|
|
||||||
|
# The name of the dashboard to be added to HORIZON['dashboards']. Required.
|
||||||
|
DASHBOARD = 'mydashboard'
|
||||||
|
|
||||||
|
# If set to True, this dashboard will not be added to the settings.
|
||||||
|
DISABLED = False
|
||||||
|
|
||||||
|
# A list of applications to be added to INSTALLED_APPS.
|
||||||
|
ADD_INSTALLED_APPS = [
|
||||||
|
'openstack_dashboard.dashboards.mydashboard',
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
Run and check the dashboard
|
||||||
|
===========================
|
||||||
|
|
||||||
|
Everything is in place, now run ``Horizon`` on the different port::
|
||||||
|
|
||||||
|
./run_tests.sh --runserver 0.0.0.0:8877
|
||||||
|
|
||||||
|
|
||||||
|
Go to ``http://<your server>:8877`` using a browser. After login as an admin
|
||||||
|
you should be able see ``My Dashboard`` shows up at the left side on Horizon.
|
||||||
|
Click it, ``My Group`` will expand with ``My Panel``. Click on ``My Panel``,
|
||||||
|
the right side panel will display an ``Instances Tab`` which has an
|
||||||
|
``Instances`` table.
|
||||||
|
|
||||||
|
If you don't see any instance data, you haven't created any instances yet. Go to
|
||||||
|
dashboard ``Project`` -> ``Images``, select a small image, for example,
|
||||||
|
``crioos-0.3.1-x86_64-uec`` , click ``Launch`` and enter an ``Instance Name``,
|
||||||
|
click the button ``Launch``. It should create an instance if the openstack or
|
||||||
|
devstack is correctly set up. Once the creation of an instance is successful, go
|
||||||
|
to ``My Dashboard`` again to check the data.
|
||||||
|
|
||||||
|
|
||||||
|
Adding a complex action to a table
|
||||||
|
==================================
|
||||||
|
|
||||||
|
For a more detailed look into adding a table action, one that requires forms for
|
||||||
|
gathering data, you can walk through :doc:`Adding a complex action to a table
|
||||||
|
</topics/table_actions>` tutorial.
|
||||||
|
|
||||||
|
|
||||||
|
Conclusion
|
||||||
|
==========
|
||||||
|
|
||||||
|
What you've learned here is the fundamentals of how to write interfaces for
|
||||||
|
your own project based on the components Horizon provides.
|
||||||
|
|
||||||
|
If you have feedback on how this tutorial could be improved, please feel free
|
||||||
|
to submit a bug against ``Horizon`` in `launchpad`_.
|
||||||
|
|
||||||
|
.. _launchpad: https://bugs.launchpad.net/horizon
|
|
@ -0,0 +1,134 @@
|
||||||
|
======================
|
||||||
|
Workflows Topic Guide
|
||||||
|
======================
|
||||||
|
|
||||||
|
One of the most challenging aspects of building a compelling user experience
|
||||||
|
is crafting complex multi-part workflows. Horizon's ``workflows`` module
|
||||||
|
aims to bring that capability within everyday reach.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
|
||||||
|
For a detailed API information check out the :doc:`Workflows Reference
|
||||||
|
Guide </ref/workflows>`.
|
||||||
|
|
||||||
|
Workflows
|
||||||
|
=========
|
||||||
|
|
||||||
|
Workflows are complex forms with tabs, each workflow must consist of classes
|
||||||
|
extending the :class:`~horizon.workflows.Workflow`,
|
||||||
|
:class:`~horizon.workflows.Step` and :class:`~horizon.workflows.Action`
|
||||||
|
|
||||||
|
Complex example of workflow
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
The following is a complex example of how data are exchanged between
|
||||||
|
urls, views, workflows and templates:
|
||||||
|
|
||||||
|
#. In ``urls.py``, we have the named parameter. E.g. ``resource_class_id``. ::
|
||||||
|
|
||||||
|
RESOURCE_CLASS = r'^(?P<resource_class_id>[^/]+)/%s$'
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
url(RESOURCE_CLASS % 'update', UpdateView.as_view(), name='update'))
|
||||||
|
|
||||||
|
#. In ``views.py``, we pass data to the template and to the action(form)
|
||||||
|
(action can also pass data to the ``get_context_data`` method and to the
|
||||||
|
template). ::
|
||||||
|
|
||||||
|
class UpdateView(workflows.WorkflowView):
|
||||||
|
workflow_class = UpdateResourceClass
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(UpdateView, self).get_context_data(**kwargs)
|
||||||
|
# Data from URL are always in self.kwargs, here we pass the data
|
||||||
|
# to the template.
|
||||||
|
context["resource_class_id"] = self.kwargs['resource_class_id']
|
||||||
|
# Data contributed by Workflow's Steps are in the
|
||||||
|
# context['workflow'].context list. We can use that in the
|
||||||
|
# template too.
|
||||||
|
return context
|
||||||
|
|
||||||
|
def _get_object(self, *args, **kwargs):
|
||||||
|
# Data from URL are always in self.kwargs, we can use them here
|
||||||
|
# to load our object of interest.
|
||||||
|
resource_class_id = self.kwargs['resource_class_id']
|
||||||
|
# Code omitted, this method should return some object obtained
|
||||||
|
# from API.
|
||||||
|
|
||||||
|
def get_initial(self):
|
||||||
|
resource_class = self._get_object()
|
||||||
|
# This data will be available in the Action's methods and
|
||||||
|
# Workflow's handle method.
|
||||||
|
# But only if the steps will depend on them.
|
||||||
|
return {'resource_class_id': resource_class.id,
|
||||||
|
'name': resource_class.name,
|
||||||
|
'service_type': resource_class.service_type}
|
||||||
|
|
||||||
|
#. In ``workflows.py`` we process the data, it is just more complex django
|
||||||
|
form. ::
|
||||||
|
|
||||||
|
class ResourcesAction(workflows.Action):
|
||||||
|
# The name field will be automatically available in all action's
|
||||||
|
# methods.
|
||||||
|
# If we want this field to be used in the another Step or Workflow,
|
||||||
|
# it has to be contributed by this step, then depend on in another
|
||||||
|
# step.
|
||||||
|
name = forms.CharField(max_length=255,
|
||||||
|
label=_("Testing Name"),
|
||||||
|
help_text="",
|
||||||
|
required=True)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
pass
|
||||||
|
# If we want to use some data from the URL, the Action's step
|
||||||
|
# has to depend on them. It's then available in
|
||||||
|
# self.initial['resource_class_id'] or data['resource_class_id'].
|
||||||
|
# In other words, resource_class_id has to be passed by view's
|
||||||
|
# get_initial and listed in step's depends_on list.
|
||||||
|
|
||||||
|
# We can also use here the data from the other steps. If we want
|
||||||
|
# the data from the other step, the step needs to contribute the
|
||||||
|
# data and the steps needs to be ordered properly.
|
||||||
|
|
||||||
|
class UpdateResources(workflows.Step):
|
||||||
|
# This passes data from Workflow context to action methods
|
||||||
|
# (handle, clean). Workflow context consists of URL data and data
|
||||||
|
# contributed by other steps.
|
||||||
|
depends_on = ("resource_class_id",)
|
||||||
|
|
||||||
|
# By contributing, the data on these indexes will become available to
|
||||||
|
# Workflow and to other Steps (if they will depend on them). Notice,
|
||||||
|
# that the resources_object_ids key has to be manually added in
|
||||||
|
# contribute method first.
|
||||||
|
contributes = ("resources_object_ids", "name")
|
||||||
|
|
||||||
|
def contribute(self, data, context):
|
||||||
|
# We can obtain the http request from workflow.
|
||||||
|
request = self.workflow.request
|
||||||
|
if data:
|
||||||
|
# Only fields defined in Action are automatically
|
||||||
|
# available for contribution. If we want to contribute
|
||||||
|
# something else, We need to override the contribute method
|
||||||
|
# and manually add it to the dictionary.
|
||||||
|
context["resources_object_ids"] =\
|
||||||
|
request.POST.getlist("resources_object_ids")
|
||||||
|
|
||||||
|
# We have to merge new context with the passed data or let
|
||||||
|
# the superclass do this.
|
||||||
|
context.update(data)
|
||||||
|
return context
|
||||||
|
|
||||||
|
class UpdateResourceClass(workflows.Workflow):
|
||||||
|
default_steps = (UpdateResources,)
|
||||||
|
|
||||||
|
def handle(self, request, data):
|
||||||
|
pass
|
||||||
|
# This method is called as last (after all Action's handle
|
||||||
|
# methods). All data that are listed in step's 'contributes='
|
||||||
|
# and 'depends_on=' are available here.
|
||||||
|
# It can be easier to have the saving logic only here if steps
|
||||||
|
# are heavily connected or complex.
|
||||||
|
|
||||||
|
# data["resources_object_ids"], data["name"] and
|
||||||
|
# data["resources_class_id"] are available here.
|
|
@ -0,0 +1,66 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
""" The Horizon interface.
|
||||||
|
|
||||||
|
Contains the core Horizon classes--:class:`~horizon.Dashboard` and
|
||||||
|
:class:`horizon.Panel`--the dynamic URLconf for Horizon, and common interface
|
||||||
|
methods like :func:`~horizon.register` and :func:`~horizon.unregister`.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Because this module is compiled by setup.py before Django may be installed
|
||||||
|
# in the environment we try importing Django and issue a warning but move on
|
||||||
|
# should that fail.
|
||||||
|
Horizon = None
|
||||||
|
try:
|
||||||
|
from horizon.base import Dashboard # noqa
|
||||||
|
from horizon.base import Horizon # noqa
|
||||||
|
from horizon.base import Panel # noqa
|
||||||
|
from horizon.base import PanelGroup # noqa
|
||||||
|
except ImportError:
|
||||||
|
import warnings
|
||||||
|
|
||||||
|
def simple_warn(message, category, filename, lineno, file=None, line=None):
|
||||||
|
return '%s: %s' % (category.__name__, message)
|
||||||
|
|
||||||
|
msg = ("Could not import Horizon dependencies. "
|
||||||
|
"This is normal during installation.\n")
|
||||||
|
warnings.formatwarning = simple_warn
|
||||||
|
warnings.warn(msg, Warning)
|
||||||
|
|
||||||
|
if Horizon:
|
||||||
|
register = Horizon.register
|
||||||
|
unregister = Horizon.unregister
|
||||||
|
get_absolute_url = Horizon.get_absolute_url
|
||||||
|
get_user_home = Horizon.get_user_home
|
||||||
|
get_dashboard = Horizon.get_dashboard
|
||||||
|
get_default_dashboard = Horizon.get_default_dashboard
|
||||||
|
get_dashboards = Horizon.get_dashboards
|
||||||
|
urls = Horizon._lazy_urls
|
||||||
|
|
||||||
|
# silence flake8 about unused imports here:
|
||||||
|
__all__ = [
|
||||||
|
"Dashboard",
|
||||||
|
"Horizon",
|
||||||
|
"Panel",
|
||||||
|
"PanelGroup",
|
||||||
|
"register",
|
||||||
|
"unregister",
|
||||||
|
"get_absolute_url",
|
||||||
|
"get_user_home",
|
||||||
|
"get_dashboard",
|
||||||
|
"get_default_dashboard",
|
||||||
|
"get_dashboards",
|
||||||
|
"urls",
|
||||||
|
]
|
|
@ -0,0 +1,982 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Contains the core classes and functionality that makes Horizon what it is.
|
||||||
|
This module is considered internal, and should not be relied on directly.
|
||||||
|
|
||||||
|
Public APIs are made available through the :mod:`horizon` module and
|
||||||
|
the classes contained therein.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import collections
|
||||||
|
import copy
|
||||||
|
import inspect
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.conf.urls import include
|
||||||
|
from django.conf.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
from django.core.exceptions import ImproperlyConfigured # noqa
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.datastructures import SortedDict
|
||||||
|
from django.utils.functional import SimpleLazyObject # noqa
|
||||||
|
from django.utils.importlib import import_module # noqa
|
||||||
|
from django.utils.module_loading import module_has_submodule # noqa
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import conf
|
||||||
|
from horizon.decorators import _current_component # noqa
|
||||||
|
from horizon.decorators import require_auth # noqa
|
||||||
|
from horizon.decorators import require_perms # noqa
|
||||||
|
from horizon import loaders
|
||||||
|
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
def _decorate_urlconf(urlpatterns, decorator, *args, **kwargs):
|
||||||
|
for pattern in urlpatterns:
|
||||||
|
if getattr(pattern, 'callback', None):
|
||||||
|
pattern._callback = decorator(pattern.callback, *args, **kwargs)
|
||||||
|
if getattr(pattern, 'url_patterns', []):
|
||||||
|
_decorate_urlconf(pattern.url_patterns, decorator, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
def access_cached(func):
|
||||||
|
def inner(self, context):
|
||||||
|
session = context['request'].session
|
||||||
|
try:
|
||||||
|
if session['allowed']['valid_for'] != session.get('token'):
|
||||||
|
raise KeyError()
|
||||||
|
except KeyError:
|
||||||
|
session['allowed'] = {"valid_for": session.get('token')}
|
||||||
|
|
||||||
|
key = "%s.%s" % (self.__class__.__module__, self.__class__.__name__)
|
||||||
|
if key not in session['allowed']:
|
||||||
|
session['allowed'][key] = func(self, context)
|
||||||
|
session.modified = True
|
||||||
|
return session['allowed'][key]
|
||||||
|
return inner
|
||||||
|
|
||||||
|
|
||||||
|
class NotRegistered(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HorizonComponent(object):
|
||||||
|
policy_rules = None
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(HorizonComponent, self).__init__()
|
||||||
|
if not self.slug:
|
||||||
|
raise ImproperlyConfigured('Every %s must have a slug.'
|
||||||
|
% self.__class__)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
name = getattr(self, 'name', u"Unnamed %s" % self.__class__.__name__)
|
||||||
|
return unicode(name)
|
||||||
|
|
||||||
|
def _get_default_urlpatterns(self):
|
||||||
|
package_string = '.'.join(self.__module__.split('.')[:-1])
|
||||||
|
if getattr(self, 'urls', None):
|
||||||
|
try:
|
||||||
|
mod = import_module('.%s' % self.urls, package_string)
|
||||||
|
except ImportError:
|
||||||
|
mod = import_module(self.urls)
|
||||||
|
urlpatterns = mod.urlpatterns
|
||||||
|
else:
|
||||||
|
# Try importing a urls.py from the dashboard package
|
||||||
|
if module_has_submodule(import_module(package_string), 'urls'):
|
||||||
|
urls_mod = import_module('.urls', package_string)
|
||||||
|
urlpatterns = urls_mod.urlpatterns
|
||||||
|
else:
|
||||||
|
urlpatterns = patterns('')
|
||||||
|
return urlpatterns
|
||||||
|
|
||||||
|
@access_cached
|
||||||
|
def can_access(self, context):
|
||||||
|
"""Return whether the user has role based access to this component.
|
||||||
|
|
||||||
|
This method is not intended to be overridden.
|
||||||
|
The result of the method is stored in per-session cache.
|
||||||
|
"""
|
||||||
|
return self.allowed(context)
|
||||||
|
|
||||||
|
def allowed(self, context):
|
||||||
|
"""Checks if the user is allowed to access this component.
|
||||||
|
|
||||||
|
This method should be overridden to return the result of
|
||||||
|
any policy checks required for the user to access this component
|
||||||
|
when more complex checks are required.
|
||||||
|
"""
|
||||||
|
return self._can_access(context['request'])
|
||||||
|
|
||||||
|
def _can_access(self, request):
|
||||||
|
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)
|
||||||
|
|
||||||
|
# this check is an OR check rather than an AND check that is the
|
||||||
|
# default in the policy engine, so calling each rule individually
|
||||||
|
if policy_check and self.policy_rules:
|
||||||
|
for rule in self.policy_rules:
|
||||||
|
if policy_check((rule,), request):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
# default to allowed
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
class Registry(object):
|
||||||
|
def __init__(self):
|
||||||
|
self._registry = {}
|
||||||
|
if not getattr(self, '_registerable_class', None):
|
||||||
|
raise ImproperlyConfigured('Subclasses of Registry must set a '
|
||||||
|
'"_registerable_class" property.')
|
||||||
|
|
||||||
|
def _register(self, cls):
|
||||||
|
"""Registers the given class.
|
||||||
|
|
||||||
|
If the specified class is already registered then it is ignored.
|
||||||
|
"""
|
||||||
|
if not inspect.isclass(cls):
|
||||||
|
raise ValueError('Only classes may be registered.')
|
||||||
|
elif not issubclass(cls, self._registerable_class):
|
||||||
|
raise ValueError('Only %s classes or subclasses may be registered.'
|
||||||
|
% self._registerable_class.__name__)
|
||||||
|
|
||||||
|
if cls not in self._registry:
|
||||||
|
cls._registered_with = self
|
||||||
|
self._registry[cls] = cls()
|
||||||
|
|
||||||
|
return self._registry[cls]
|
||||||
|
|
||||||
|
def _unregister(self, cls):
|
||||||
|
"""Unregisters the given class.
|
||||||
|
|
||||||
|
If the specified class isn't registered, ``NotRegistered`` will
|
||||||
|
be raised.
|
||||||
|
"""
|
||||||
|
if not issubclass(cls, self._registerable_class):
|
||||||
|
raise ValueError('Only %s classes or subclasses may be '
|
||||||
|
'unregistered.' % self._registerable_class)
|
||||||
|
|
||||||
|
if cls not in self._registry.keys():
|
||||||
|
raise NotRegistered('%s is not registered' % cls)
|
||||||
|
|
||||||
|
del self._registry[cls]
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def _registered(self, cls):
|
||||||
|
if inspect.isclass(cls) and issubclass(cls, self._registerable_class):
|
||||||
|
found = self._registry.get(cls, None)
|
||||||
|
if found:
|
||||||
|
return found
|
||||||
|
else:
|
||||||
|
# Allow for fetching by slugs as well.
|
||||||
|
for registered in self._registry.values():
|
||||||
|
if registered.slug == cls:
|
||||||
|
return registered
|
||||||
|
class_name = self._registerable_class.__name__
|
||||||
|
if hasattr(self, "_registered_with"):
|
||||||
|
parent = self._registered_with._registerable_class.__name__
|
||||||
|
raise NotRegistered('%(type)s with slug "%(slug)s" is not '
|
||||||
|
'registered with %(parent)s "%(name)s".'
|
||||||
|
% {"type": class_name,
|
||||||
|
"slug": cls,
|
||||||
|
"parent": parent,
|
||||||
|
"name": self.slug})
|
||||||
|
else:
|
||||||
|
slug = getattr(cls, "slug", cls)
|
||||||
|
raise NotRegistered('%(type)s with slug "%(slug)s" is not '
|
||||||
|
'registered.' % {"type": class_name,
|
||||||
|
"slug": slug})
|
||||||
|
|
||||||
|
|
||||||
|
class Panel(HorizonComponent):
|
||||||
|
"""A base class for defining Horizon dashboard panels.
|
||||||
|
|
||||||
|
All Horizon dashboard panels should extend from this class. It provides
|
||||||
|
the appropriate hooks for automatically constructing URLconfs, and
|
||||||
|
providing permission-based access control.
|
||||||
|
|
||||||
|
.. attribute:: name
|
||||||
|
|
||||||
|
The name of the panel. This will be displayed in the
|
||||||
|
auto-generated navigation and various other places.
|
||||||
|
Default: ``''``.
|
||||||
|
|
||||||
|
.. attribute:: slug
|
||||||
|
|
||||||
|
A unique "short name" for the panel. The slug is used as
|
||||||
|
a component of the URL path for the panel. Default: ``''``.
|
||||||
|
|
||||||
|
.. attribute:: permissions
|
||||||
|
|
||||||
|
A list of permission names, all of which a user must possess in order
|
||||||
|
to access any view associated with this panel. This attribute
|
||||||
|
is combined cumulatively with any permissions required on the
|
||||||
|
``Dashboard`` class with which it is registered.
|
||||||
|
|
||||||
|
.. attribute:: urls
|
||||||
|
|
||||||
|
Path to a URLconf of views for this panel using dotted Python
|
||||||
|
notation. If no value is specified, a file called ``urls.py``
|
||||||
|
living in the same package as the ``panel.py`` file is used.
|
||||||
|
Default: ``None``.
|
||||||
|
|
||||||
|
.. attribute:: nav
|
||||||
|
.. method:: nav(context)
|
||||||
|
|
||||||
|
The ``nav`` attribute can be either boolean value or a callable
|
||||||
|
which accepts a ``RequestContext`` object as a single argument
|
||||||
|
to control whether or not this panel should appear in
|
||||||
|
automatically-generated navigation. Default: ``True``.
|
||||||
|
|
||||||
|
.. attribute:: index_url_name
|
||||||
|
|
||||||
|
The ``name`` argument for the URL pattern which corresponds to
|
||||||
|
the index view for this ``Panel``. This is the view that
|
||||||
|
:meth:`.Panel.get_absolute_url` will attempt to reverse.
|
||||||
|
"""
|
||||||
|
name = ''
|
||||||
|
slug = ''
|
||||||
|
urls = None
|
||||||
|
nav = True
|
||||||
|
index_url_name = "index"
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Panel: %s>" % self.slug
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
"""Returns the default URL for this panel.
|
||||||
|
|
||||||
|
The default URL is defined as the URL pattern with ``name="index"`` in
|
||||||
|
the URLconf for this panel.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return reverse('horizon:%s:%s:%s' % (self._registered_with.slug,
|
||||||
|
self.slug,
|
||||||
|
self.index_url_name))
|
||||||
|
except Exception as exc:
|
||||||
|
# Logging here since this will often be called in a template
|
||||||
|
# where the exception would be hidden.
|
||||||
|
LOG.info("Error reversing absolute URL for %s: %s" % (self, exc))
|
||||||
|
raise
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _decorated_urls(self):
|
||||||
|
urlpatterns = self._get_default_urlpatterns()
|
||||||
|
|
||||||
|
# Apply access controls to all views in the patterns
|
||||||
|
permissions = getattr(self, 'permissions', [])
|
||||||
|
_decorate_urlconf(urlpatterns, require_perms, permissions)
|
||||||
|
_decorate_urlconf(urlpatterns, _current_component, panel=self)
|
||||||
|
|
||||||
|
# Return the three arguments to django.conf.urls.include
|
||||||
|
return urlpatterns, self.slug, self.slug
|
||||||
|
|
||||||
|
|
||||||
|
class PanelGroup(object):
|
||||||
|
"""A container for a set of :class:`~horizon.Panel` classes.
|
||||||
|
|
||||||
|
When iterated, it will yield each of the ``Panel`` instances it
|
||||||
|
contains.
|
||||||
|
|
||||||
|
.. attribute:: slug
|
||||||
|
|
||||||
|
A unique string to identify this panel group. Required.
|
||||||
|
|
||||||
|
.. attribute:: name
|
||||||
|
|
||||||
|
A user-friendly name which will be used as the group heading in
|
||||||
|
places such as the navigation. Default: ``None``.
|
||||||
|
|
||||||
|
.. attribute:: panels
|
||||||
|
|
||||||
|
A list of panel module names which should be contained within this
|
||||||
|
grouping.
|
||||||
|
"""
|
||||||
|
def __init__(self, dashboard, slug=None, name=None, panels=None):
|
||||||
|
self.dashboard = dashboard
|
||||||
|
self.slug = slug or getattr(self, "slug", "default")
|
||||||
|
self.name = name or getattr(self, "name", None)
|
||||||
|
# Our panels must be mutable so it can be extended by others.
|
||||||
|
self.panels = list(panels or getattr(self, "panels", []))
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<%s: %s>" % (self.__class__.__name__, self.slug)
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
panel_instances = []
|
||||||
|
for name in self.panels:
|
||||||
|
try:
|
||||||
|
panel_instances.append(self.dashboard.get_panel(name))
|
||||||
|
except NotRegistered as e:
|
||||||
|
LOG.debug(e)
|
||||||
|
return iter(panel_instances)
|
||||||
|
|
||||||
|
|
||||||
|
class Dashboard(Registry, HorizonComponent):
|
||||||
|
"""A base class for defining Horizon dashboards.
|
||||||
|
|
||||||
|
All Horizon dashboards should extend from this base class. It provides the
|
||||||
|
appropriate hooks for automatic discovery of :class:`~horizon.Panel`
|
||||||
|
modules, automatically constructing URLconfs, and providing
|
||||||
|
permission-based access control.
|
||||||
|
|
||||||
|
.. attribute:: name
|
||||||
|
|
||||||
|
The name of the dashboard. This will be displayed in the
|
||||||
|
auto-generated navigation and various other places.
|
||||||
|
Default: ``''``.
|
||||||
|
|
||||||
|
.. attribute:: slug
|
||||||
|
|
||||||
|
A unique "short name" for the dashboard. The slug is used as
|
||||||
|
a component of the URL path for the dashboard. Default: ``''``.
|
||||||
|
|
||||||
|
.. attribute:: panels
|
||||||
|
|
||||||
|
The ``panels`` attribute can be either a flat list containing the name
|
||||||
|
of each panel **module** which should be loaded as part of this
|
||||||
|
dashboard, or a list of :class:`~horizon.PanelGroup` classes which
|
||||||
|
define groups of panels as in the following example::
|
||||||
|
|
||||||
|
class SystemPanels(horizon.PanelGroup):
|
||||||
|
slug = "syspanel"
|
||||||
|
name = _("System")
|
||||||
|
panels = ('overview', 'instances', ...)
|
||||||
|
|
||||||
|
class Syspanel(horizon.Dashboard):
|
||||||
|
panels = (SystemPanels,)
|
||||||
|
|
||||||
|
Automatically generated navigation will use the order of the
|
||||||
|
modules in this attribute.
|
||||||
|
|
||||||
|
Default: ``[]``.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
The values for this attribute should not correspond to the
|
||||||
|
:attr:`~.Panel.name` attributes of the ``Panel`` classes.
|
||||||
|
They should be the names of the Python modules in which the
|
||||||
|
``panel.py`` files live. This is used for the automatic
|
||||||
|
loading and registration of ``Panel`` classes much like
|
||||||
|
Django's ``ModelAdmin`` machinery.
|
||||||
|
|
||||||
|
Panel modules must be listed in ``panels`` in order to be
|
||||||
|
discovered by the automatic registration mechanism.
|
||||||
|
|
||||||
|
.. attribute:: default_panel
|
||||||
|
|
||||||
|
The name of the panel which should be treated as the default
|
||||||
|
panel for the dashboard, i.e. when you visit the root URL
|
||||||
|
for this dashboard, that's the panel that is displayed.
|
||||||
|
Default: ``None``.
|
||||||
|
|
||||||
|
.. attribute:: permissions
|
||||||
|
|
||||||
|
A list of permission names, all of which a user must possess in order
|
||||||
|
to access any panel registered with this dashboard. This attribute
|
||||||
|
is combined cumulatively with any permissions required on individual
|
||||||
|
:class:`~horizon.Panel` classes.
|
||||||
|
|
||||||
|
.. attribute:: urls
|
||||||
|
|
||||||
|
Optional path to a URLconf of additional views for this dashboard
|
||||||
|
which are not connected to specific panels. Default: ``None``.
|
||||||
|
|
||||||
|
.. attribute:: nav
|
||||||
|
.. method:: nav(context)
|
||||||
|
|
||||||
|
The ``nav`` attribute can be either boolean value or a callable
|
||||||
|
which accepts a ``RequestContext`` object as a single argument
|
||||||
|
to control whether or not this dashboard should appear in
|
||||||
|
automatically-generated navigation. Default: ``True``.
|
||||||
|
|
||||||
|
.. attribute:: public
|
||||||
|
|
||||||
|
Boolean value to determine whether this dashboard can be viewed
|
||||||
|
without being logged in. Defaults to ``False``.
|
||||||
|
|
||||||
|
"""
|
||||||
|
_registerable_class = Panel
|
||||||
|
name = ''
|
||||||
|
slug = ''
|
||||||
|
urls = None
|
||||||
|
panels = []
|
||||||
|
default_panel = None
|
||||||
|
nav = True
|
||||||
|
public = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<Dashboard: %s>" % self.slug
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(Dashboard, self).__init__(*args, **kwargs)
|
||||||
|
self._panel_groups = None
|
||||||
|
|
||||||
|
def get_panel(self, panel):
|
||||||
|
"""Returns the specified :class:`~horizon.Panel` instance registered
|
||||||
|
with this dashboard.
|
||||||
|
"""
|
||||||
|
return self._registered(panel)
|
||||||
|
|
||||||
|
def get_panels(self):
|
||||||
|
"""Returns the :class:`~horizon.Panel` instances registered with this
|
||||||
|
dashboard in order, without any panel groupings.
|
||||||
|
"""
|
||||||
|
all_panels = []
|
||||||
|
panel_groups = self.get_panel_groups()
|
||||||
|
for panel_group in panel_groups.values():
|
||||||
|
all_panels.extend(panel_group)
|
||||||
|
return all_panels
|
||||||
|
|
||||||
|
def get_panel_group(self, slug):
|
||||||
|
"""Returns the specified :class:~horizon.PanelGroup
|
||||||
|
or None if not registered
|
||||||
|
"""
|
||||||
|
return self._panel_groups.get(slug)
|
||||||
|
|
||||||
|
def get_panel_groups(self):
|
||||||
|
registered = copy.copy(self._registry)
|
||||||
|
panel_groups = []
|
||||||
|
|
||||||
|
# Gather our known panels
|
||||||
|
if self._panel_groups is not None:
|
||||||
|
for panel_group in self._panel_groups.values():
|
||||||
|
for panel in panel_group:
|
||||||
|
registered.pop(panel.__class__)
|
||||||
|
panel_groups.append((panel_group.slug, panel_group))
|
||||||
|
|
||||||
|
# Deal with leftovers (such as add-on registrations)
|
||||||
|
if len(registered):
|
||||||
|
slugs = [panel.slug for panel in registered.values()]
|
||||||
|
new_group = PanelGroup(self,
|
||||||
|
slug="other",
|
||||||
|
name=_("Other"),
|
||||||
|
panels=slugs)
|
||||||
|
panel_groups.append((new_group.slug, new_group))
|
||||||
|
return SortedDict(panel_groups)
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
"""Returns the default URL for this dashboard.
|
||||||
|
|
||||||
|
The default URL is defined as the URL pattern with ``name="index"``
|
||||||
|
in the URLconf for the :class:`~horizon.Panel` specified by
|
||||||
|
:attr:`~horizon.Dashboard.default_panel`.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
return self._registered(self.default_panel).get_absolute_url()
|
||||||
|
except Exception:
|
||||||
|
# Logging here since this will often be called in a template
|
||||||
|
# where the exception would be hidden.
|
||||||
|
LOG.exception("Error reversing absolute URL for %s." % self)
|
||||||
|
raise
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _decorated_urls(self):
|
||||||
|
urlpatterns = self._get_default_urlpatterns()
|
||||||
|
|
||||||
|
default_panel = None
|
||||||
|
|
||||||
|
# Add in each panel's views except for the default view.
|
||||||
|
for panel in self._registry.values():
|
||||||
|
if panel.slug == self.default_panel:
|
||||||
|
default_panel = panel
|
||||||
|
continue
|
||||||
|
url_slug = panel.slug.replace('.', '/')
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
url(r'^%s/' % url_slug,
|
||||||
|
include(panel._decorated_urls)))
|
||||||
|
# Now the default view, which should come last
|
||||||
|
if not default_panel:
|
||||||
|
raise NotRegistered('The default panel "%s" is not registered.'
|
||||||
|
% self.default_panel)
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
url(r'',
|
||||||
|
include(default_panel._decorated_urls)))
|
||||||
|
|
||||||
|
# Require login if not public.
|
||||||
|
if not self.public:
|
||||||
|
_decorate_urlconf(urlpatterns, require_auth)
|
||||||
|
# Apply access controls to all views in the patterns
|
||||||
|
permissions = getattr(self, 'permissions', [])
|
||||||
|
_decorate_urlconf(urlpatterns, require_perms, permissions)
|
||||||
|
_decorate_urlconf(urlpatterns, _current_component, dashboard=self)
|
||||||
|
|
||||||
|
# Return the three arguments to django.conf.urls.include
|
||||||
|
return urlpatterns, self.slug, self.slug
|
||||||
|
|
||||||
|
def _autodiscover(self):
|
||||||
|
"""Discovers panels to register from the current dashboard module."""
|
||||||
|
if getattr(self, "_autodiscover_complete", False):
|
||||||
|
return
|
||||||
|
|
||||||
|
panels_to_discover = []
|
||||||
|
panel_groups = []
|
||||||
|
# If we have a flat iterable of panel names, wrap it again so
|
||||||
|
# we have a consistent structure for the next step.
|
||||||
|
if all([isinstance(i, basestring) for i in self.panels]):
|
||||||
|
self.panels = [self.panels]
|
||||||
|
|
||||||
|
# Now iterate our panel sets.
|
||||||
|
for panel_set in self.panels:
|
||||||
|
# Instantiate PanelGroup classes.
|
||||||
|
if not isinstance(panel_set, collections.Iterable) and \
|
||||||
|
issubclass(panel_set, PanelGroup):
|
||||||
|
panel_group = panel_set(self)
|
||||||
|
# Check for nested tuples, and convert them to PanelGroups
|
||||||
|
elif not isinstance(panel_set, PanelGroup):
|
||||||
|
panel_group = PanelGroup(self, panels=panel_set)
|
||||||
|
|
||||||
|
# Put our results into their appropriate places
|
||||||
|
panels_to_discover.extend(panel_group.panels)
|
||||||
|
panel_groups.append((panel_group.slug, panel_group))
|
||||||
|
|
||||||
|
self._panel_groups = SortedDict(panel_groups)
|
||||||
|
|
||||||
|
# Do the actual discovery
|
||||||
|
package = '.'.join(self.__module__.split('.')[:-1])
|
||||||
|
mod = import_module(package)
|
||||||
|
for panel in panels_to_discover:
|
||||||
|
try:
|
||||||
|
before_import_registry = copy.copy(self._registry)
|
||||||
|
import_module('.%s.panel' % panel, package)
|
||||||
|
except Exception:
|
||||||
|
self._registry = before_import_registry
|
||||||
|
if module_has_submodule(mod, panel):
|
||||||
|
raise
|
||||||
|
self._autodiscover_complete = True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def register(cls, panel):
|
||||||
|
"""Registers a :class:`~horizon.Panel` with this dashboard."""
|
||||||
|
panel_class = Horizon.register_panel(cls, panel)
|
||||||
|
# Support template loading from panel template directories.
|
||||||
|
panel_mod = import_module(panel.__module__)
|
||||||
|
panel_dir = os.path.dirname(panel_mod.__file__)
|
||||||
|
template_dir = os.path.join(panel_dir, "templates")
|
||||||
|
if os.path.exists(template_dir):
|
||||||
|
key = os.path.join(cls.slug, panel.slug)
|
||||||
|
loaders.panel_template_dirs[key] = template_dir
|
||||||
|
return panel_class
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def unregister(cls, panel):
|
||||||
|
"""Unregisters a :class:`~horizon.Panel` from this dashboard."""
|
||||||
|
success = Horizon.unregister_panel(cls, panel)
|
||||||
|
if success:
|
||||||
|
# Remove the panel's template directory.
|
||||||
|
key = os.path.join(cls.slug, panel.slug)
|
||||||
|
if key in loaders.panel_template_dirs:
|
||||||
|
del loaders.panel_template_dirs[key]
|
||||||
|
return success
|
||||||
|
|
||||||
|
def allowed(self, context):
|
||||||
|
"""Checks for role based access for this dashboard.
|
||||||
|
|
||||||
|
Checks for access to any panels in the dashboard and of the the
|
||||||
|
dashboard itself.
|
||||||
|
|
||||||
|
This method should be overridden to return the result of
|
||||||
|
any policy checks required for the user to access this dashboard
|
||||||
|
when more complex checks are required.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# if the dashboard has policy rules, honor those above individual
|
||||||
|
# panels
|
||||||
|
if not self._can_access(context['request']):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# check if access is allowed to a single panel,
|
||||||
|
# the default for each panel is True
|
||||||
|
for panel in self.get_panels():
|
||||||
|
if panel.can_access(context):
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Workflow(object):
|
||||||
|
pass
|
||||||
|
|
||||||
|
try:
|
||||||
|
from django.utils.functional import empty # noqa
|
||||||
|
except ImportError:
|
||||||
|
# Django 1.3 fallback
|
||||||
|
empty = None
|
||||||
|
|
||||||
|
|
||||||
|
class LazyURLPattern(SimpleLazyObject):
|
||||||
|
def __iter__(self):
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup()
|
||||||
|
return iter(self._wrapped)
|
||||||
|
|
||||||
|
def __reversed__(self):
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup()
|
||||||
|
return reversed(self._wrapped)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup()
|
||||||
|
return len(self._wrapped)
|
||||||
|
|
||||||
|
def __getitem__(self, idx):
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup()
|
||||||
|
return self._wrapped[idx]
|
||||||
|
|
||||||
|
|
||||||
|
class Site(Registry, HorizonComponent):
|
||||||
|
"""The overarching class which encompasses all dashboards and panels."""
|
||||||
|
|
||||||
|
# Required for registry
|
||||||
|
_registerable_class = Dashboard
|
||||||
|
|
||||||
|
name = "Horizon"
|
||||||
|
namespace = 'horizon'
|
||||||
|
slug = 'horizon'
|
||||||
|
urls = 'horizon.site_urls'
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return u"<Site: %s>" % self.slug
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _conf(self):
|
||||||
|
return conf.HORIZON_CONFIG
|
||||||
|
|
||||||
|
@property
|
||||||
|
def dashboards(self):
|
||||||
|
return self._conf['dashboards']
|
||||||
|
|
||||||
|
@property
|
||||||
|
def default_dashboard(self):
|
||||||
|
return self._conf['default_dashboard']
|
||||||
|
|
||||||
|
def register(self, dashboard):
|
||||||
|
"""Registers a :class:`~horizon.Dashboard` with Horizon."""
|
||||||
|
return self._register(dashboard)
|
||||||
|
|
||||||
|
def unregister(self, dashboard):
|
||||||
|
"""Unregisters a :class:`~horizon.Dashboard` from Horizon."""
|
||||||
|
return self._unregister(dashboard)
|
||||||
|
|
||||||
|
def registered(self, dashboard):
|
||||||
|
return self._registered(dashboard)
|
||||||
|
|
||||||
|
def register_panel(self, dashboard, panel):
|
||||||
|
dash_instance = self.registered(dashboard)
|
||||||
|
return dash_instance._register(panel)
|
||||||
|
|
||||||
|
def unregister_panel(self, dashboard, panel):
|
||||||
|
dash_instance = self.registered(dashboard)
|
||||||
|
if not dash_instance:
|
||||||
|
raise NotRegistered("The dashboard %s is not registered."
|
||||||
|
% dashboard)
|
||||||
|
return dash_instance._unregister(panel)
|
||||||
|
|
||||||
|
def get_dashboard(self, dashboard):
|
||||||
|
"""Returns the specified :class:`~horizon.Dashboard` instance."""
|
||||||
|
return self._registered(dashboard)
|
||||||
|
|
||||||
|
def get_dashboards(self):
|
||||||
|
"""Returns an ordered tuple of :class:`~horizon.Dashboard` modules.
|
||||||
|
|
||||||
|
Orders dashboards according to the ``"dashboards"`` key in
|
||||||
|
``HORIZON_CONFIG`` or else returns all registered dashboards
|
||||||
|
in alphabetical order.
|
||||||
|
|
||||||
|
Any remaining :class:`~horizon.Dashboard` classes registered with
|
||||||
|
Horizon but not listed in ``HORIZON_CONFIG['dashboards']``
|
||||||
|
will be appended to the end of the list alphabetically.
|
||||||
|
"""
|
||||||
|
if self.dashboards:
|
||||||
|
registered = copy.copy(self._registry)
|
||||||
|
dashboards = []
|
||||||
|
for item in self.dashboards:
|
||||||
|
dashboard = self._registered(item)
|
||||||
|
dashboards.append(dashboard)
|
||||||
|
registered.pop(dashboard.__class__)
|
||||||
|
if len(registered):
|
||||||
|
extra = registered.values()
|
||||||
|
extra.sort()
|
||||||
|
dashboards.extend(extra)
|
||||||
|
return dashboards
|
||||||
|
else:
|
||||||
|
dashboards = self._registry.values()
|
||||||
|
dashboards.sort()
|
||||||
|
return dashboards
|
||||||
|
|
||||||
|
def get_default_dashboard(self):
|
||||||
|
"""Returns the default :class:`~horizon.Dashboard` instance.
|
||||||
|
|
||||||
|
If ``"default_dashboard"`` is specified in ``HORIZON_CONFIG``
|
||||||
|
then that dashboard will be returned. If not, the first dashboard
|
||||||
|
returned by :func:`~horizon.get_dashboards` will be returned.
|
||||||
|
"""
|
||||||
|
if self.default_dashboard:
|
||||||
|
return self._registered(self.default_dashboard)
|
||||||
|
elif len(self._registry):
|
||||||
|
return self.get_dashboards()[0]
|
||||||
|
else:
|
||||||
|
raise NotRegistered("No dashboard modules have been registered.")
|
||||||
|
|
||||||
|
def get_user_home(self, user):
|
||||||
|
"""Returns the default URL for a particular user.
|
||||||
|
|
||||||
|
This method can be used to customize where a user is sent when
|
||||||
|
they log in, etc. By default it returns the value of
|
||||||
|
:meth:`get_absolute_url`.
|
||||||
|
|
||||||
|
An alternative function can be supplied to customize this behavior
|
||||||
|
by specifying a either a URL or a function which returns a URL via
|
||||||
|
the ``"user_home"`` key in ``HORIZON_CONFIG``. Each of these
|
||||||
|
would be valid::
|
||||||
|
|
||||||
|
{"user_home": "/home",} # A URL
|
||||||
|
{"user_home": "my_module.get_user_home",} # Path to a function
|
||||||
|
{"user_home": lambda user: "/" + user.name,} # A function
|
||||||
|
{"user_home": None,} # Will always return the default dashboard
|
||||||
|
|
||||||
|
This can be useful if the default dashboard may not be accessible
|
||||||
|
to all users. When user_home is missing from HORIZON_CONFIG,
|
||||||
|
it will default to the settings.LOGIN_REDIRECT_URL value.
|
||||||
|
"""
|
||||||
|
user_home = self._conf['user_home']
|
||||||
|
if user_home:
|
||||||
|
if callable(user_home):
|
||||||
|
return user_home(user)
|
||||||
|
elif isinstance(user_home, basestring):
|
||||||
|
# Assume we've got a URL if there's a slash in it
|
||||||
|
if '/' in user_home:
|
||||||
|
return user_home
|
||||||
|
else:
|
||||||
|
mod, func = user_home.rsplit(".", 1)
|
||||||
|
return getattr(import_module(mod), func)(user)
|
||||||
|
# If it's not callable and not a string, it's wrong.
|
||||||
|
raise ValueError('The user_home setting must be either a string '
|
||||||
|
'or a callable object (e.g. a function).')
|
||||||
|
else:
|
||||||
|
return self.get_absolute_url()
|
||||||
|
|
||||||
|
def get_absolute_url(self):
|
||||||
|
"""Returns the default URL for Horizon's URLconf.
|
||||||
|
|
||||||
|
The default URL is determined by calling
|
||||||
|
:meth:`~horizon.Dashboard.get_absolute_url`
|
||||||
|
on the :class:`~horizon.Dashboard` instance returned by
|
||||||
|
:meth:`~horizon.get_default_dashboard`.
|
||||||
|
"""
|
||||||
|
return self.get_default_dashboard().get_absolute_url()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def _lazy_urls(self):
|
||||||
|
"""Lazy loading for URL patterns.
|
||||||
|
|
||||||
|
This method avoids problems associated with attempting to evaluate
|
||||||
|
the URLconf before the settings module has been loaded.
|
||||||
|
"""
|
||||||
|
def url_patterns():
|
||||||
|
return self._urls()[0]
|
||||||
|
|
||||||
|
return LazyURLPattern(url_patterns), self.namespace, self.slug
|
||||||
|
|
||||||
|
def _urls(self):
|
||||||
|
"""Constructs the URLconf for Horizon from registered Dashboards."""
|
||||||
|
urlpatterns = self._get_default_urlpatterns()
|
||||||
|
self._autodiscover()
|
||||||
|
|
||||||
|
# Discover each dashboard's panels.
|
||||||
|
for dash in self._registry.values():
|
||||||
|
dash._autodiscover()
|
||||||
|
|
||||||
|
# Load the plugin-based panel configuration
|
||||||
|
self._load_panel_customization()
|
||||||
|
|
||||||
|
# Allow for override modules
|
||||||
|
if self._conf.get("customization_module", None):
|
||||||
|
customization_module = self._conf["customization_module"]
|
||||||
|
bits = customization_module.split('.')
|
||||||
|
mod_name = bits.pop()
|
||||||
|
package = '.'.join(bits)
|
||||||
|
mod = import_module(package)
|
||||||
|
try:
|
||||||
|
before_import_registry = copy.copy(self._registry)
|
||||||
|
import_module('%s.%s' % (package, mod_name))
|
||||||
|
except Exception:
|
||||||
|
self._registry = before_import_registry
|
||||||
|
if module_has_submodule(mod, mod_name):
|
||||||
|
raise
|
||||||
|
|
||||||
|
# Compile the dynamic urlconf.
|
||||||
|
for dash in self._registry.values():
|
||||||
|
urlpatterns += patterns('',
|
||||||
|
url(r'^%s/' % dash.slug,
|
||||||
|
include(dash._decorated_urls)))
|
||||||
|
|
||||||
|
# Return the three arguments to django.conf.urls.include
|
||||||
|
return urlpatterns, self.namespace, self.slug
|
||||||
|
|
||||||
|
def _autodiscover(self):
|
||||||
|
"""Discovers modules to register from ``settings.INSTALLED_APPS``.
|
||||||
|
|
||||||
|
This makes sure that the appropriate modules get imported to register
|
||||||
|
themselves with Horizon.
|
||||||
|
"""
|
||||||
|
if not getattr(self, '_registerable_class', None):
|
||||||
|
raise ImproperlyConfigured('You must set a '
|
||||||
|
'"_registerable_class" property '
|
||||||
|
'in order to use autodiscovery.')
|
||||||
|
# Discover both dashboards and panels, in that order
|
||||||
|
for mod_name in ('dashboard', 'panel'):
|
||||||
|
for app in settings.INSTALLED_APPS:
|
||||||
|
mod = import_module(app)
|
||||||
|
try:
|
||||||
|
before_import_registry = copy.copy(self._registry)
|
||||||
|
import_module('%s.%s' % (app, mod_name))
|
||||||
|
except Exception:
|
||||||
|
self._registry = before_import_registry
|
||||||
|
if module_has_submodule(mod, mod_name):
|
||||||
|
raise
|
||||||
|
|
||||||
|
def _load_panel_customization(self):
|
||||||
|
"""Applies the plugin-based panel configurations.
|
||||||
|
|
||||||
|
This method parses the panel customization from the ``HORIZON_CONFIG``
|
||||||
|
and make changes to the dashboard accordingly.
|
||||||
|
|
||||||
|
It supports adding, removing and setting default panels on the
|
||||||
|
dashboard. It also support registering a panel group.
|
||||||
|
"""
|
||||||
|
panel_customization = self._conf.get("panel_customization", [])
|
||||||
|
|
||||||
|
# Process all the panel groups first so that they exist before panels
|
||||||
|
# are added to them and Dashboard._autodiscover() doesn't wipe out any
|
||||||
|
# panels previously added when its panel groups are instantiated.
|
||||||
|
panel_configs = []
|
||||||
|
for config in panel_customization:
|
||||||
|
if config.get('PANEL'):
|
||||||
|
panel_configs.append(config)
|
||||||
|
elif config.get('PANEL_GROUP'):
|
||||||
|
self._process_panel_group_configuration(config)
|
||||||
|
else:
|
||||||
|
LOG.warning("Skipping %s because it doesn't have PANEL or "
|
||||||
|
"PANEL_GROUP defined.", config.__name__)
|
||||||
|
# Now process the panels.
|
||||||
|
for config in panel_configs:
|
||||||
|
self._process_panel_configuration(config)
|
||||||
|
|
||||||
|
def _process_panel_configuration(self, config):
|
||||||
|
"""Add, remove and set default panels on the dashboard."""
|
||||||
|
try:
|
||||||
|
dashboard = config.get('PANEL_DASHBOARD')
|
||||||
|
if not dashboard:
|
||||||
|
LOG.warning("Skipping %s because it doesn't have "
|
||||||
|
"PANEL_DASHBOARD defined.", config.__name__)
|
||||||
|
return
|
||||||
|
panel_slug = config.get('PANEL')
|
||||||
|
dashboard_cls = self.get_dashboard(dashboard)
|
||||||
|
panel_group = config.get('PANEL_GROUP')
|
||||||
|
default_panel = config.get('DEFAULT_PANEL')
|
||||||
|
|
||||||
|
# Set the default panel
|
||||||
|
if default_panel:
|
||||||
|
dashboard_cls.default_panel = default_panel
|
||||||
|
|
||||||
|
# Remove the panel
|
||||||
|
if config.get('REMOVE_PANEL', False):
|
||||||
|
for panel in dashboard_cls.get_panels():
|
||||||
|
if panel_slug == panel.slug:
|
||||||
|
dashboard_cls.unregister(panel.__class__)
|
||||||
|
elif config.get('ADD_PANEL', None):
|
||||||
|
# Add the panel to the dashboard
|
||||||
|
panel_path = config['ADD_PANEL']
|
||||||
|
mod_path, panel_cls = panel_path.rsplit(".", 1)
|
||||||
|
try:
|
||||||
|
mod = import_module(mod_path)
|
||||||
|
except ImportError:
|
||||||
|
LOG.warning("Could not load panel: %s", mod_path)
|
||||||
|
return
|
||||||
|
panel = getattr(mod, panel_cls)
|
||||||
|
dashboard_cls.register(panel)
|
||||||
|
if panel_group:
|
||||||
|
dashboard_cls.get_panel_group(panel_group).\
|
||||||
|
panels.append(panel.slug)
|
||||||
|
else:
|
||||||
|
panels = list(dashboard_cls.panels)
|
||||||
|
panels.append(panel)
|
||||||
|
dashboard_cls.panels = tuple(panels)
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning('Could not process panel %(panel)s: %(exc)s',
|
||||||
|
{'panel': panel_slug, 'exc': e})
|
||||||
|
|
||||||
|
def _process_panel_group_configuration(self, config):
|
||||||
|
"""Adds a panel group to the dashboard."""
|
||||||
|
panel_group_slug = config.get('PANEL_GROUP')
|
||||||
|
try:
|
||||||
|
dashboard = config.get('PANEL_GROUP_DASHBOARD')
|
||||||
|
if not dashboard:
|
||||||
|
LOG.warning("Skipping %s because it doesn't have "
|
||||||
|
"PANEL_GROUP_DASHBOARD defined.", config.__name__)
|
||||||
|
return
|
||||||
|
dashboard_cls = self.get_dashboard(dashboard)
|
||||||
|
|
||||||
|
panel_group_name = config.get('PANEL_GROUP_NAME')
|
||||||
|
if not panel_group_name:
|
||||||
|
LOG.warning("Skipping %s because it doesn't have "
|
||||||
|
"PANEL_GROUP_NAME defined.", config.__name__)
|
||||||
|
return
|
||||||
|
# Create the panel group class
|
||||||
|
panel_group = type(panel_group_slug,
|
||||||
|
(PanelGroup, ),
|
||||||
|
{'slug': panel_group_slug,
|
||||||
|
'name': panel_group_name,
|
||||||
|
'panels': []},)
|
||||||
|
# Add the panel group to dashboard
|
||||||
|
panels = list(dashboard_cls.panels)
|
||||||
|
panels.append(panel_group)
|
||||||
|
dashboard_cls.panels = tuple(panels)
|
||||||
|
# Trigger the autodiscovery to completely load the new panel group
|
||||||
|
dashboard_cls._autodiscover_complete = False
|
||||||
|
dashboard_cls._autodiscover()
|
||||||
|
except Exception as e:
|
||||||
|
LOG.warning('Could not process panel group %(panel_group)s: '
|
||||||
|
'%(exc)s',
|
||||||
|
{'panel_group': panel_group_slug, 'exc': e})
|
||||||
|
|
||||||
|
|
||||||
|
class HorizonSite(Site):
|
||||||
|
"""A singleton implementation of Site such that all dealings with horizon
|
||||||
|
get the same instance no matter what. There can be only one.
|
||||||
|
"""
|
||||||
|
_instance = None
|
||||||
|
|
||||||
|
def __new__(cls, *args, **kwargs):
|
||||||
|
if not cls._instance:
|
||||||
|
cls._instance = super(Site, cls).__new__(cls, *args, **kwargs)
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
|
||||||
|
# The one true Horizon
|
||||||
|
Horizon = HorizonSite()
|
|
@ -0,0 +1,17 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
# Importing non-modules that are not used explicitly
|
||||||
|
from horizon.browsers.base import ResourceBrowser # noqa
|
||||||
|
from horizon.browsers.views import ResourceBrowserView # noqa
|
|
@ -0,0 +1,145 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon.browsers.breadcrumb import Breadcrumb # noqa
|
||||||
|
from horizon.tables import DataTable # noqa
|
||||||
|
from horizon.utils import html
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceBrowser(html.HTMLElement):
|
||||||
|
"""A class which defines a browser for displaying data.
|
||||||
|
|
||||||
|
.. attribute:: name
|
||||||
|
|
||||||
|
A short name or slug for the browser.
|
||||||
|
|
||||||
|
.. attribute:: verbose_name
|
||||||
|
|
||||||
|
A more verbose name for the browser meant for display purposes.
|
||||||
|
|
||||||
|
.. attribute:: navigation_table_class
|
||||||
|
|
||||||
|
This table displays data on the left side of the browser.
|
||||||
|
Set the ``navigation_table_class`` attribute with
|
||||||
|
the desired :class:`~horizon.tables.DataTable` class.
|
||||||
|
This table class must set browser_table attribute in Meta to
|
||||||
|
``"navigation"``.
|
||||||
|
|
||||||
|
.. attribute:: content_table_class
|
||||||
|
|
||||||
|
This table displays data on the right side of the browser.
|
||||||
|
Set the ``content_table_class`` attribute with
|
||||||
|
the desired :class:`~horizon.tables.DataTable` class.
|
||||||
|
This table class must set browser_table attribute in Meta to
|
||||||
|
``"content"``.
|
||||||
|
|
||||||
|
.. attribute:: navigation_kwarg_name
|
||||||
|
|
||||||
|
This attribute represents the key of the navigatable items in the
|
||||||
|
kwargs property of this browser's view.
|
||||||
|
Defaults to ``"navigation_kwarg"``.
|
||||||
|
|
||||||
|
.. attribute:: content_kwarg_name
|
||||||
|
|
||||||
|
This attribute represents the key of the content items in the
|
||||||
|
kwargs property of this browser's view.
|
||||||
|
Defaults to ``"content_kwarg"``.
|
||||||
|
|
||||||
|
.. attribute:: template
|
||||||
|
|
||||||
|
String containing the template which should be used to render
|
||||||
|
the browser. Defaults to ``"horizon/common/_resource_browser.html"``.
|
||||||
|
|
||||||
|
.. attribute:: context_var_name
|
||||||
|
|
||||||
|
The name of the context variable which will contain the browser when
|
||||||
|
it is rendered. Defaults to ``"browser"``.
|
||||||
|
|
||||||
|
.. attribute:: has_breadcrumb
|
||||||
|
|
||||||
|
Indicates if the content table of the browser would have breadcrumb.
|
||||||
|
Defaults to false.
|
||||||
|
|
||||||
|
.. attribute:: breadcrumb_template
|
||||||
|
|
||||||
|
This is a template used to render the breadcrumb.
|
||||||
|
Defaults to ``"horizon/common/_breadcrumb.html"``.
|
||||||
|
"""
|
||||||
|
name = None
|
||||||
|
verbose_name = None
|
||||||
|
navigation_table_class = None
|
||||||
|
content_table_class = None
|
||||||
|
navigation_kwarg_name = "navigation_kwarg"
|
||||||
|
content_kwarg_name = "content_kwarg"
|
||||||
|
navigable_item_name = _("Navigation Item")
|
||||||
|
template = "horizon/common/_resource_browser.html"
|
||||||
|
context_var_name = "browser"
|
||||||
|
has_breadcrumb = False
|
||||||
|
breadcrumb_template = "horizon/common/_breadcrumb.html"
|
||||||
|
breadcrumb_url = None
|
||||||
|
|
||||||
|
def __init__(self, request, tables_dict=None, attrs=None, **kwargs):
|
||||||
|
super(ResourceBrowser, self).__init__()
|
||||||
|
self.name = self.name or self.__class__.__name__
|
||||||
|
self.verbose_name = self.verbose_name or self.name.title()
|
||||||
|
self.request = request
|
||||||
|
self.kwargs = kwargs
|
||||||
|
self.has_breadcrumb = getattr(self, "has_breadcrumb")
|
||||||
|
if self.has_breadcrumb:
|
||||||
|
self.breadcrumb_template = getattr(self, "breadcrumb_template")
|
||||||
|
self.breadcrumb_url = getattr(self, "breadcrumb_url")
|
||||||
|
if not self.breadcrumb_url:
|
||||||
|
raise ValueError("You must specify a breadcrumb_url "
|
||||||
|
"if the has_breadcrumb is set to True.")
|
||||||
|
self.attrs.update(attrs or {})
|
||||||
|
self.check_table_class(self.content_table_class, "content_table_class")
|
||||||
|
self.check_table_class(self.navigation_table_class,
|
||||||
|
"navigation_table_class")
|
||||||
|
if tables_dict:
|
||||||
|
self.set_tables(tables_dict)
|
||||||
|
|
||||||
|
def check_table_class(self, cls, attr_name):
|
||||||
|
if not cls or not issubclass(cls, DataTable):
|
||||||
|
raise ValueError("You must specify a DataTable subclass for "
|
||||||
|
"the %s attribute on %s."
|
||||||
|
% (attr_name, self.__class__.__name__))
|
||||||
|
|
||||||
|
def set_tables(self, tables):
|
||||||
|
"""Sets the table instances on the browser from a dictionary mapping
|
||||||
|
table names to table instances (as constructed by MultiTableView).
|
||||||
|
"""
|
||||||
|
self.navigation_table = tables[self.navigation_table_class._meta.name]
|
||||||
|
self.content_table = tables[self.content_table_class._meta.name]
|
||||||
|
navigation_item = self.kwargs.get(self.navigation_kwarg_name)
|
||||||
|
content_path = self.kwargs.get(self.content_kwarg_name)
|
||||||
|
if self.has_breadcrumb:
|
||||||
|
self.prepare_breadcrumb(tables, navigation_item, content_path)
|
||||||
|
|
||||||
|
def prepare_breadcrumb(self, tables, navigation_item, content_path):
|
||||||
|
if self.has_breadcrumb and navigation_item and content_path:
|
||||||
|
for table in tables.values():
|
||||||
|
table.breadcrumb = Breadcrumb(self.request,
|
||||||
|
self.breadcrumb_template,
|
||||||
|
navigation_item,
|
||||||
|
content_path,
|
||||||
|
self.breadcrumb_url)
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
browser_template = template.loader.get_template(self.template)
|
||||||
|
extra_context = {self.context_var_name: self}
|
||||||
|
context = template.RequestContext(self.request, extra_context)
|
||||||
|
return browser_template.render(context)
|
|
@ -0,0 +1,46 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django import template
|
||||||
|
|
||||||
|
from horizon.utils import html
|
||||||
|
|
||||||
|
|
||||||
|
class Breadcrumb(html.HTMLElement):
|
||||||
|
def __init__(self, request, template, root,
|
||||||
|
subfolder_path, url, attr=None):
|
||||||
|
super(Breadcrumb, self).__init__()
|
||||||
|
self.template = template
|
||||||
|
self.request = request
|
||||||
|
self.root = root
|
||||||
|
self.subfolder_path = subfolder_path
|
||||||
|
self.url = url
|
||||||
|
self._subfolders = []
|
||||||
|
|
||||||
|
def get_subfolders(self):
|
||||||
|
if self.subfolder_path and not self._subfolders:
|
||||||
|
(parent, slash, folder) = self.subfolder_path.strip('/') \
|
||||||
|
.rpartition('/')
|
||||||
|
while folder:
|
||||||
|
path = "%s%s%s/" % (parent, slash, folder)
|
||||||
|
self._subfolders.insert(0, (folder, path))
|
||||||
|
(parent, slash, folder) = parent.rpartition('/')
|
||||||
|
return self._subfolders
|
||||||
|
|
||||||
|
def render(self):
|
||||||
|
"""Renders the table using the template from the table options."""
|
||||||
|
breadcrumb_template = template.loader.get_template(self.template)
|
||||||
|
extra_context = {"breadcrumb": self}
|
||||||
|
context = template.RequestContext(self.request, extra_context)
|
||||||
|
return breadcrumb_template.render(context)
|
|
@ -0,0 +1,58 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon.tables import MultiTableView # noqa
|
||||||
|
from horizon.utils import memoized
|
||||||
|
|
||||||
|
|
||||||
|
class ResourceBrowserView(MultiTableView):
|
||||||
|
browser_class = None
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
if not self.browser_class:
|
||||||
|
raise ValueError("You must specify a ResourceBrowser subclass "
|
||||||
|
"for the browser_class attribute on %s."
|
||||||
|
% self.__class__.__name__)
|
||||||
|
self.table_classes = (self.browser_class.navigation_table_class,
|
||||||
|
self.browser_class.content_table_class)
|
||||||
|
self.navigation_selection = False
|
||||||
|
super(ResourceBrowserView, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
@memoized.memoized_method
|
||||||
|
def get_browser(self):
|
||||||
|
browser = self.browser_class(self.request, **self.kwargs)
|
||||||
|
browser.set_tables(self.get_tables())
|
||||||
|
if not self.navigation_selection:
|
||||||
|
ct = browser.content_table
|
||||||
|
item = browser.navigable_item_name.lower()
|
||||||
|
ct._no_data_message = _("Select a %s to browse.") % item
|
||||||
|
return browser
|
||||||
|
|
||||||
|
def get_tables(self):
|
||||||
|
tables = super(ResourceBrowserView, self).get_tables()
|
||||||
|
# Tells the navigation table what is selected.
|
||||||
|
navigation_table = tables[
|
||||||
|
self.browser_class.navigation_table_class._meta.name]
|
||||||
|
navigation_item = self.kwargs.get(
|
||||||
|
self.browser_class.navigation_kwarg_name)
|
||||||
|
navigation_table.current_item_id = navigation_item
|
||||||
|
return tables
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ResourceBrowserView, self).get_context_data(**kwargs)
|
||||||
|
browser = self.get_browser()
|
||||||
|
context["%s_browser" % browser.name] = browser
|
||||||
|
return context
|
|
@ -0,0 +1,47 @@
|
||||||
|
# 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 copy
|
||||||
|
|
||||||
|
from django.utils.functional import empty # noqa
|
||||||
|
from django.utils.functional import LazyObject # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class LazySettings(LazyObject):
|
||||||
|
def _setup(self, name=None):
|
||||||
|
from django.conf import settings
|
||||||
|
from horizon.conf.default import HORIZON_CONFIG as DEFAULT_CONFIG # noqa
|
||||||
|
HORIZON_CONFIG = copy.copy(DEFAULT_CONFIG)
|
||||||
|
HORIZON_CONFIG.update(settings.HORIZON_CONFIG)
|
||||||
|
|
||||||
|
# Ensure we always have our exception configuration...
|
||||||
|
for exc_category in ['unauthorized', 'not_found', 'recoverable']:
|
||||||
|
if exc_category not in HORIZON_CONFIG['exceptions']:
|
||||||
|
default_exc_config = DEFAULT_CONFIG['exceptions'][exc_category]
|
||||||
|
HORIZON_CONFIG['exceptions'][exc_category] = default_exc_config
|
||||||
|
|
||||||
|
# Ensure our password validator always exists...
|
||||||
|
if 'regex' not in HORIZON_CONFIG['password_validator']:
|
||||||
|
default_pw_regex = DEFAULT_CONFIG['password_validator']['regex']
|
||||||
|
HORIZON_CONFIG['password_validator']['regex'] = default_pw_regex
|
||||||
|
if 'help_text' not in HORIZON_CONFIG['password_validator']:
|
||||||
|
default_pw_help = DEFAULT_CONFIG['password_validator']['help_text']
|
||||||
|
HORIZON_CONFIG['password_validator']['help_text'] = default_pw_help
|
||||||
|
|
||||||
|
self._wrapped = HORIZON_CONFIG
|
||||||
|
|
||||||
|
def __getitem__(self, name, fallback=None):
|
||||||
|
if self._wrapped is empty:
|
||||||
|
self._setup(name)
|
||||||
|
return self._wrapped.get(name, fallback)
|
||||||
|
|
||||||
|
HORIZON_CONFIG = LazySettings()
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
|
||||||
|
|
||||||
|
class {{ dash_name|title }}(horizon.Dashboard):
|
||||||
|
name = _("{{ dash_name|title }}")
|
||||||
|
slug = "{{ dash_name|slugify }}"
|
||||||
|
panels = () # Add your panels here.
|
||||||
|
default_panel = '' # Specify the slug of the dashboard's default panel.
|
||||||
|
|
||||||
|
|
||||||
|
horizon.register({{ dash_name|title }})
|
|
@ -0,0 +1,3 @@
|
||||||
|
"""
|
||||||
|
Stub file to work around django bug: https://code.djangoproject.com/ticket/7198
|
||||||
|
"""
|
|
@ -0,0 +1 @@
|
||||||
|
/* Additional CSS for {{ dash_name }}. */
|
|
@ -0,0 +1 @@
|
||||||
|
/* Additional JavaScript for {{ dash_name }}. */
|
|
@ -0,0 +1,11 @@
|
||||||
|
{% load horizon %}{% jstemplate %}[% extends 'base.html' %]
|
||||||
|
|
||||||
|
[% block sidebar %]
|
||||||
|
[% include 'horizon/common/_sidebar.html' %]
|
||||||
|
[% endblock %]
|
||||||
|
|
||||||
|
[% block main %]
|
||||||
|
[% include "horizon/_messages.html" %]
|
||||||
|
[% block {{ dash_name }}_main %][% endblock %]
|
||||||
|
[% endblock %]
|
||||||
|
{% endjstemplate %}
|
|
@ -0,0 +1,47 @@
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
# Default configuration dictionary. Do not mutate.
|
||||||
|
HORIZON_CONFIG = {
|
||||||
|
# Allow for ordering dashboards; list or tuple if provided.
|
||||||
|
'dashboards': None,
|
||||||
|
|
||||||
|
# Name of a default dashboard; defaults to first alphabetically if None
|
||||||
|
'default_dashboard': None,
|
||||||
|
|
||||||
|
# Default redirect url for users' home
|
||||||
|
'user_home': settings.LOGIN_REDIRECT_URL,
|
||||||
|
|
||||||
|
# AJAX settings for JavaScript
|
||||||
|
'ajax_queue_limit': 10,
|
||||||
|
'ajax_poll_interval': 2500,
|
||||||
|
|
||||||
|
# URL for additional help with this site.
|
||||||
|
'help_url': None,
|
||||||
|
|
||||||
|
# Exception configuration.
|
||||||
|
'exceptions': {'unauthorized': [],
|
||||||
|
'not_found': [],
|
||||||
|
'recoverable': []},
|
||||||
|
|
||||||
|
# Password configuration.
|
||||||
|
'password_validator': {'regex': '.*',
|
||||||
|
'help_text': _("Password is not accepted")},
|
||||||
|
|
||||||
|
'password_autocomplete': 'off',
|
||||||
|
|
||||||
|
# Enable or disable simplified floating IP address management.
|
||||||
|
'simple_ip_management': True
|
||||||
|
}
|
|
@ -0,0 +1,11 @@
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
import horizon
|
||||||
|
{% if dashboard %}from {{ dash_path }} import dashboard{% endif %}
|
||||||
|
|
||||||
|
class {{ panel_name|title }}(horizon.Panel):
|
||||||
|
name = _("{{ panel_name|title }}")
|
||||||
|
slug = "{{ panel_name|slugify }}"
|
||||||
|
{% if dashboard %}
|
||||||
|
|
||||||
|
dashboard.{{ dash_name|title }}.register({{ panel_name|title }}){% endif %}
|
|
@ -0,0 +1,12 @@
|
||||||
|
{% load horizon %}{% jstemplate %}[% extends 'base.html' %]
|
||||||
|
[% load i18n %]
|
||||||
|
[% block title %][% trans "{{ panel_name|title }}" %][% endblock %]
|
||||||
|
|
||||||
|
[% block page_header %]
|
||||||
|
[% include "horizon/common/_page_header.html" with title=_("{{ panel_name|title }}") %]
|
||||||
|
[% endblock page_header %]
|
||||||
|
|
||||||
|
[% block main %]
|
||||||
|
[% endblock %]
|
||||||
|
|
||||||
|
{% endjstemplate %}
|
|
@ -0,0 +1,7 @@
|
||||||
|
from horizon.test import helpers as test
|
||||||
|
|
||||||
|
|
||||||
|
class {{ panel_name|title }}Tests(test.TestCase):
|
||||||
|
# Unit tests for {{ panel_name }}.
|
||||||
|
def test_me(self):
|
||||||
|
self.assertTrue(1 + 1 == 2)
|
|
@ -0,0 +1,11 @@
|
||||||
|
from django.conf.urls import patterns
|
||||||
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from {{ dash_path }}.{{ panel_name }}.views \
|
||||||
|
import IndexView
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = patterns(
|
||||||
|
'',
|
||||||
|
url(r'^$', IndexView.as_view(), name='index'),
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
from horizon import views
|
||||||
|
|
||||||
|
|
||||||
|
class IndexView(views.APIView):
|
||||||
|
# A very simple class-based view...
|
||||||
|
template_name = '{{ dash_name }}/{{ panel_name }}/index.html'
|
||||||
|
|
||||||
|
def get_data(self, request, context, *args, **kwargs):
|
||||||
|
# Add data to the context here...
|
||||||
|
return context
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
"""
|
||||||
|
Context processors used by Horizon.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from horizon import conf
|
||||||
|
|
||||||
|
|
||||||
|
def horizon(request):
|
||||||
|
"""The main Horizon context processor. Required for Horizon to function.
|
||||||
|
|
||||||
|
It adds the Horizon config to the context as well as setting the names
|
||||||
|
``True`` and ``False`` in the context to their boolean equivalents
|
||||||
|
for convenience.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Don't put API calls in context processors; they will be called once
|
||||||
|
for each template/template fragment which takes context that is used
|
||||||
|
to render the complete output.
|
||||||
|
"""
|
||||||
|
context = {"HORIZON_CONFIG": conf.HORIZON_CONFIG,
|
||||||
|
"True": True,
|
||||||
|
"False": False}
|
||||||
|
|
||||||
|
return context
|
|
@ -0,0 +1,68 @@
|
||||||
|
# Copyright 2014 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
# Map Horizon languages to datepicker locales
|
||||||
|
LOCALE_MAPPING = {
|
||||||
|
'ar': 'ar',
|
||||||
|
'az': 'az',
|
||||||
|
'bg': 'bg',
|
||||||
|
'ca': 'ca',
|
||||||
|
'cs': 'cs',
|
||||||
|
'cy': 'cy',
|
||||||
|
'da': 'da',
|
||||||
|
'de': 'de',
|
||||||
|
'el': 'el',
|
||||||
|
'es': 'es',
|
||||||
|
'et': 'et',
|
||||||
|
'fa': 'fa',
|
||||||
|
'fi': 'fi',
|
||||||
|
'fr': 'fr',
|
||||||
|
'gl': 'gl',
|
||||||
|
'he': 'he',
|
||||||
|
'hr': 'hr',
|
||||||
|
'hu': 'hu',
|
||||||
|
'id': 'id',
|
||||||
|
'is': 'is',
|
||||||
|
'it': 'it',
|
||||||
|
'ja': 'ja',
|
||||||
|
'ka': 'ka',
|
||||||
|
'kk': 'kk',
|
||||||
|
'ko': 'kr', # difference between horizon and datepicker
|
||||||
|
'lt': 'lt',
|
||||||
|
'lv': 'lv',
|
||||||
|
'mk': 'mk',
|
||||||
|
'ms': 'ms',
|
||||||
|
'nb': 'nb',
|
||||||
|
'nl-be': 'nl-BE',
|
||||||
|
'nl': 'nl',
|
||||||
|
'no': 'no',
|
||||||
|
'pl': 'pl',
|
||||||
|
'pt-br': 'pt-BR',
|
||||||
|
'pt': 'pt',
|
||||||
|
'ro': 'ro',
|
||||||
|
'rs-latin': 'rs-latin',
|
||||||
|
'sr': 'rs', # difference between horizon and datepicker
|
||||||
|
'ru': 'ru',
|
||||||
|
'sk': 'sk',
|
||||||
|
'sl': 'sl',
|
||||||
|
'sq': 'sq',
|
||||||
|
'sv': 'sv',
|
||||||
|
'sw': 'sw',
|
||||||
|
'th': 'th',
|
||||||
|
'tr': 'tr',
|
||||||
|
'ua': 'ua',
|
||||||
|
'vi': 'vi',
|
||||||
|
'zh-cn': 'zh-CN',
|
||||||
|
'zh-tw': 'zh-TW',
|
||||||
|
}
|
|
@ -0,0 +1,92 @@
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2012 CRS4
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
General-purpose decorators for use with Horizon.
|
||||||
|
"""
|
||||||
|
import functools
|
||||||
|
|
||||||
|
from django.utils.decorators import available_attrs # noqa
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
def _current_component(view_func, dashboard=None, panel=None):
|
||||||
|
"""Sets the currently-active dashboard and/or panel on the request."""
|
||||||
|
@functools.wraps(view_func, assigned=available_attrs(view_func))
|
||||||
|
def dec(request, *args, **kwargs):
|
||||||
|
if dashboard:
|
||||||
|
request.horizon['dashboard'] = dashboard
|
||||||
|
if panel:
|
||||||
|
request.horizon['panel'] = panel
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
def require_auth(view_func):
|
||||||
|
"""Performs user authentication check.
|
||||||
|
|
||||||
|
Similar to Django's `login_required` decorator, except that this throws
|
||||||
|
:exc:`~horizon.exceptions.NotAuthenticated` exception if the user is not
|
||||||
|
signed-in.
|
||||||
|
"""
|
||||||
|
from horizon.exceptions import NotAuthenticated # noqa
|
||||||
|
|
||||||
|
@functools.wraps(view_func, assigned=available_attrs(view_func))
|
||||||
|
def dec(request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
raise NotAuthenticated(_("Please log in to continue."))
|
||||||
|
return dec
|
||||||
|
|
||||||
|
|
||||||
|
def require_perms(view_func, required):
|
||||||
|
"""Enforces permission-based access controls.
|
||||||
|
|
||||||
|
:param list required: A tuple of permission names, all of which the request
|
||||||
|
user must possess in order access the decorated view.
|
||||||
|
|
||||||
|
Example usage::
|
||||||
|
|
||||||
|
from horizon.decorators import require_perms
|
||||||
|
|
||||||
|
|
||||||
|
@require_perms(['foo.admin', 'foo.member'])
|
||||||
|
def my_view(request):
|
||||||
|
...
|
||||||
|
|
||||||
|
Raises a :exc:`~horizon.exceptions.NotAuthorized` exception if the
|
||||||
|
requirements are not met.
|
||||||
|
"""
|
||||||
|
from horizon.exceptions import NotAuthorized # noqa
|
||||||
|
# We only need to check each permission once for a view, so we'll use a set
|
||||||
|
current_perms = getattr(view_func, '_required_perms', set([]))
|
||||||
|
view_func._required_perms = current_perms | set(required)
|
||||||
|
|
||||||
|
@functools.wraps(view_func, assigned=available_attrs(view_func))
|
||||||
|
def dec(request, *args, **kwargs):
|
||||||
|
if request.user.is_authenticated():
|
||||||
|
if request.user.has_perms(view_func._required_perms):
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
raise NotAuthorized(_("You are not authorized to access %s")
|
||||||
|
% request.path)
|
||||||
|
|
||||||
|
# If we don't have any permissions, just return the original view.
|
||||||
|
if required:
|
||||||
|
return dec
|
||||||
|
else:
|
||||||
|
return view_func
|
|
@ -0,0 +1,364 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Exceptions raised by the Horizon code and the machinery for handling them.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import six
|
||||||
|
|
||||||
|
from django.core.management import color_style # noqa
|
||||||
|
from django.http import HttpRequest # noqa
|
||||||
|
from django.utils import encoding
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.debug import CLEANSED_SUBSTITUTE # noqa
|
||||||
|
from django.views.debug import SafeExceptionReporterFilter # noqa
|
||||||
|
|
||||||
|
from horizon.conf import HORIZON_CONFIG # noqa
|
||||||
|
from horizon import messages
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class HorizonReporterFilter(SafeExceptionReporterFilter):
|
||||||
|
"""Error report filter that's always active, even in DEBUG mode."""
|
||||||
|
def is_active(self, request):
|
||||||
|
return True
|
||||||
|
|
||||||
|
# TODO(gabriel): This bugfix is cribbed from Django's code. When 1.4.1
|
||||||
|
# is available we can remove this code.
|
||||||
|
def get_traceback_frame_variables(self, request, tb_frame):
|
||||||
|
"""Replaces the values of variables marked as sensitive with
|
||||||
|
stars (*********).
|
||||||
|
"""
|
||||||
|
# Loop through the frame's callers to see if the sensitive_variables
|
||||||
|
# decorator was used.
|
||||||
|
current_frame = tb_frame.f_back
|
||||||
|
sensitive_variables = None
|
||||||
|
while current_frame is not None:
|
||||||
|
if (current_frame.f_code.co_name == 'sensitive_variables_wrapper'
|
||||||
|
and 'sensitive_variables_wrapper'
|
||||||
|
in current_frame.f_locals):
|
||||||
|
# The sensitive_variables decorator was used, so we take note
|
||||||
|
# of the sensitive variables' names.
|
||||||
|
wrapper = current_frame.f_locals['sensitive_variables_wrapper']
|
||||||
|
sensitive_variables = getattr(wrapper,
|
||||||
|
'sensitive_variables',
|
||||||
|
None)
|
||||||
|
break
|
||||||
|
current_frame = current_frame.f_back
|
||||||
|
|
||||||
|
cleansed = []
|
||||||
|
if self.is_active(request) and sensitive_variables:
|
||||||
|
if sensitive_variables == '__ALL__':
|
||||||
|
# Cleanse all variables
|
||||||
|
for name, value in tb_frame.f_locals.items():
|
||||||
|
cleansed.append((name, CLEANSED_SUBSTITUTE))
|
||||||
|
return cleansed
|
||||||
|
else:
|
||||||
|
# Cleanse specified variables
|
||||||
|
for name, value in tb_frame.f_locals.items():
|
||||||
|
if name in sensitive_variables:
|
||||||
|
value = CLEANSED_SUBSTITUTE
|
||||||
|
elif isinstance(value, HttpRequest):
|
||||||
|
# Cleanse the request's POST parameters.
|
||||||
|
value = self.get_request_repr(value)
|
||||||
|
cleansed.append((name, value))
|
||||||
|
return cleansed
|
||||||
|
else:
|
||||||
|
# Potentially cleanse only the request if it's one of the
|
||||||
|
# frame variables.
|
||||||
|
for name, value in tb_frame.f_locals.items():
|
||||||
|
if isinstance(value, HttpRequest):
|
||||||
|
# Cleanse the request's POST parameters.
|
||||||
|
value = self.get_request_repr(value)
|
||||||
|
cleansed.append((name, value))
|
||||||
|
return cleansed
|
||||||
|
|
||||||
|
|
||||||
|
class HorizonException(Exception):
|
||||||
|
"""Base exception class for distinguishing our own exception classes."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Http302(HorizonException):
|
||||||
|
"""Error class which can be raised from within a handler to cause an
|
||||||
|
early bailout and redirect at the middleware level.
|
||||||
|
"""
|
||||||
|
status_code = 302
|
||||||
|
|
||||||
|
def __init__(self, location, message=None):
|
||||||
|
self.location = location
|
||||||
|
self.message = message
|
||||||
|
|
||||||
|
|
||||||
|
class NotAuthorized(HorizonException):
|
||||||
|
"""Raised whenever a user attempts to access a resource which they do not
|
||||||
|
have permission-based access to (such as when failing the
|
||||||
|
:func:`~horizon.decorators.require_perms` decorator).
|
||||||
|
|
||||||
|
The included :class:`~horizon.middleware.HorizonMiddleware` catches
|
||||||
|
``NotAuthorized`` and handles it gracefully by displaying an error
|
||||||
|
message and redirecting the user to a login page.
|
||||||
|
"""
|
||||||
|
status_code = 401
|
||||||
|
|
||||||
|
|
||||||
|
class NotAuthenticated(HorizonException):
|
||||||
|
"""Raised when a user is trying to make requests and they are not logged
|
||||||
|
in.
|
||||||
|
|
||||||
|
The included :class:`~horizon.middleware.HorizonMiddleware` catches
|
||||||
|
``NotAuthenticated`` and handles it gracefully by displaying an error
|
||||||
|
message and redirecting the user to a login page.
|
||||||
|
"""
|
||||||
|
status_code = 403
|
||||||
|
|
||||||
|
|
||||||
|
class NotFound(HorizonException):
|
||||||
|
"""Generic error to replace all "Not Found"-type API errors."""
|
||||||
|
status_code = 404
|
||||||
|
|
||||||
|
|
||||||
|
class Conflict(HorizonException):
|
||||||
|
"""Generic error to replace all "Conflict"-type API errors."""
|
||||||
|
status_code = 409
|
||||||
|
|
||||||
|
|
||||||
|
class RecoverableError(HorizonException):
|
||||||
|
"""Generic error to replace any "Recoverable"-type API errors."""
|
||||||
|
status_code = 100 # HTTP status code "Continue"
|
||||||
|
|
||||||
|
|
||||||
|
class ServiceCatalogException(HorizonException):
|
||||||
|
"""Raised when a requested service is not available in the
|
||||||
|
``ServiceCatalog`` returned by Keystone.
|
||||||
|
"""
|
||||||
|
def __init__(self, service_name):
|
||||||
|
message = 'Invalid service catalog service: %s' % service_name
|
||||||
|
super(ServiceCatalogException, self).__init__(message)
|
||||||
|
|
||||||
|
|
||||||
|
class AlreadyExists(HorizonException):
|
||||||
|
"""Exception to be raised when trying to create an API resource which
|
||||||
|
already exists.
|
||||||
|
"""
|
||||||
|
def __init__(self, name, resource_type):
|
||||||
|
self.attrs = {"name": name, "resource": resource_type}
|
||||||
|
self.msg = _('A %(resource)s with the name "%(name)s" already exists.')
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.msg % self.attrs
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.msg % self.attrs
|
||||||
|
|
||||||
|
def __unicode__(self):
|
||||||
|
return self.msg % self.attrs
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationError(HorizonException):
|
||||||
|
"""Exception to be raised when invalid settings have been provided."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NotAvailable(HorizonException):
|
||||||
|
"""Exception to be raised when something is not available."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowError(HorizonException):
|
||||||
|
"""Exception to be raised when something goes wrong in a workflow."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class WorkflowValidationError(HorizonException):
|
||||||
|
"""Exception raised during workflow validation if required data is missing,
|
||||||
|
or existing data is not valid.
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HandledException(HorizonException):
|
||||||
|
"""Used internally to track exceptions that have gone through
|
||||||
|
:func:`horizon.exceptions.handle` more than once.
|
||||||
|
"""
|
||||||
|
def __init__(self, wrapped):
|
||||||
|
self.wrapped = wrapped
|
||||||
|
|
||||||
|
|
||||||
|
UNAUTHORIZED = tuple(HORIZON_CONFIG['exceptions']['unauthorized'])
|
||||||
|
NOT_FOUND = tuple(HORIZON_CONFIG['exceptions']['not_found'])
|
||||||
|
RECOVERABLE = (AlreadyExists, Conflict, NotAvailable, ServiceCatalogException)
|
||||||
|
RECOVERABLE += tuple(HORIZON_CONFIG['exceptions']['recoverable'])
|
||||||
|
|
||||||
|
|
||||||
|
def error_color(msg):
|
||||||
|
return color_style().ERROR_OUTPUT(msg)
|
||||||
|
|
||||||
|
|
||||||
|
def check_message(keywords, message):
|
||||||
|
"""Checks an exception for given keywords and raises a new ``ActionError``
|
||||||
|
with the desired message if the keywords are found. This allows selective
|
||||||
|
control over API error messages.
|
||||||
|
"""
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
if set(str(exc_value).split(" ")).issuperset(set(keywords)):
|
||||||
|
exc_value._safe_message = message
|
||||||
|
raise
|
||||||
|
|
||||||
|
|
||||||
|
def handle_unauthorized(request, message, redirect, ignore, escalate, handled,
|
||||||
|
force_silence, force_log,
|
||||||
|
log_method, log_entry, log_level):
|
||||||
|
if ignore:
|
||||||
|
return NotAuthorized
|
||||||
|
if not force_silence and not handled:
|
||||||
|
log_method(error_color("Unauthorized: %s" % log_entry))
|
||||||
|
if not handled:
|
||||||
|
if message:
|
||||||
|
message = _("Unauthorized: %s") % message
|
||||||
|
# We get some pretty useless error messages back from
|
||||||
|
# some clients, so let's define our own fallback.
|
||||||
|
fallback = _("Unauthorized. Please try logging in again.")
|
||||||
|
messages.error(request, message or fallback)
|
||||||
|
# Escalation means logging the user out and raising NotAuthorized
|
||||||
|
# so the middleware will redirect them appropriately.
|
||||||
|
if escalate:
|
||||||
|
# Prevents creation of circular import. django.contrib.auth
|
||||||
|
# requires openstack_dashboard.settings to be loaded (by trying to
|
||||||
|
# access settings.CACHES in in django.core.caches) while
|
||||||
|
# openstack_dashboard.settings requires django.contrib.auth to be
|
||||||
|
# loaded while importing openstack_auth.utils
|
||||||
|
from django.contrib.auth import logout # noqa
|
||||||
|
logout(request)
|
||||||
|
raise NotAuthorized
|
||||||
|
# Otherwise continue and present our "unauthorized" error message.
|
||||||
|
return NotAuthorized
|
||||||
|
|
||||||
|
|
||||||
|
def handle_notfound(request, message, redirect, ignore, escalate, handled,
|
||||||
|
force_silence, force_log,
|
||||||
|
log_method, log_entry, log_level):
|
||||||
|
if not force_silence and not handled and (not ignore or force_log):
|
||||||
|
log_method(error_color("Not Found: %s" % log_entry))
|
||||||
|
if not ignore and not handled:
|
||||||
|
messages.error(request, message or log_entry)
|
||||||
|
if redirect:
|
||||||
|
raise Http302(redirect)
|
||||||
|
if not escalate:
|
||||||
|
return NotFound # return to normal code flow
|
||||||
|
|
||||||
|
|
||||||
|
def handle_recoverable(request, message, redirect, ignore, escalate, handled,
|
||||||
|
force_silence, force_log,
|
||||||
|
log_method, log_entry, log_level):
|
||||||
|
if not force_silence and not handled and (not ignore or force_log):
|
||||||
|
# Default recoverable error to WARN log level
|
||||||
|
log_method = getattr(LOG, log_level or "warning")
|
||||||
|
log_method(error_color("Recoverable error: %s" % log_entry))
|
||||||
|
if not ignore and not handled:
|
||||||
|
messages.error(request, message or log_entry)
|
||||||
|
if redirect:
|
||||||
|
raise Http302(redirect)
|
||||||
|
if not escalate:
|
||||||
|
return RecoverableError # return to normal code flow
|
||||||
|
|
||||||
|
|
||||||
|
HANDLE_EXC_METHODS = [
|
||||||
|
{'exc': UNAUTHORIZED, 'handler': handle_unauthorized, 'set_wrap': False},
|
||||||
|
{'exc': NOT_FOUND, 'handler': handle_notfound, 'set_wrap': True},
|
||||||
|
{'exc': RECOVERABLE, 'handler': handle_recoverable, 'set_wrap': True},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def handle(request, message=None, redirect=None, ignore=False,
|
||||||
|
escalate=False, log_level=None, force_log=None):
|
||||||
|
"""Centralized error handling for Horizon.
|
||||||
|
|
||||||
|
Because Horizon consumes so many different APIs with completely
|
||||||
|
different ``Exception`` types, it's necessary to have a centralized
|
||||||
|
place for handling exceptions which may be raised.
|
||||||
|
|
||||||
|
Exceptions are roughly divided into 3 types:
|
||||||
|
|
||||||
|
#. ``UNAUTHORIZED``: Errors resulting from authentication or authorization
|
||||||
|
problems. These result in being logged out and sent to the login screen.
|
||||||
|
#. ``NOT_FOUND``: Errors resulting from objects which could not be
|
||||||
|
located via the API. These generally result in a user-facing error
|
||||||
|
message, but are otherwise returned to the normal code flow. Optionally
|
||||||
|
a redirect value may be passed to the error handler so users are
|
||||||
|
returned to a different view than the one requested in addition to the
|
||||||
|
error message.
|
||||||
|
#. RECOVERABLE: Generic API errors which generate a user-facing message
|
||||||
|
but drop directly back to the regular code flow.
|
||||||
|
|
||||||
|
All other exceptions bubble the stack as normal unless the ``ignore``
|
||||||
|
argument is passed in as ``True``, in which case only unrecognized
|
||||||
|
errors are bubbled.
|
||||||
|
|
||||||
|
If the exception is not re-raised, an appropriate wrapper exception
|
||||||
|
class indicating the type of exception that was encountered will be
|
||||||
|
returned.
|
||||||
|
"""
|
||||||
|
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||||
|
log_method = getattr(LOG, log_level or "exception")
|
||||||
|
force_log = force_log or os.environ.get("HORIZON_TEST_RUN", False)
|
||||||
|
force_silence = getattr(exc_value, "silence_logging", False)
|
||||||
|
|
||||||
|
# Because the same exception may travel through this method more than
|
||||||
|
# once (if it's re-raised) we may want to treat it differently
|
||||||
|
# the second time (e.g. no user messages/logging).
|
||||||
|
handled = issubclass(exc_type, HandledException)
|
||||||
|
wrap = False
|
||||||
|
|
||||||
|
# Restore our original exception information, but re-wrap it at the end
|
||||||
|
if handled:
|
||||||
|
exc_type, exc_value, exc_traceback = exc_value.wrapped
|
||||||
|
wrap = True
|
||||||
|
|
||||||
|
log_entry = encoding.force_text(exc_value)
|
||||||
|
|
||||||
|
# We trust messages from our own exceptions
|
||||||
|
if issubclass(exc_type, HorizonException):
|
||||||
|
message = exc_value
|
||||||
|
# Check for an override message
|
||||||
|
elif getattr(exc_value, "_safe_message", None):
|
||||||
|
message = exc_value._safe_message
|
||||||
|
# If the message has a placeholder for the exception, fill it in
|
||||||
|
elif message and "%(exc)s" in message:
|
||||||
|
message = encoding.force_text(message) % {"exc": log_entry}
|
||||||
|
if message:
|
||||||
|
message = encoding.force_text(message)
|
||||||
|
|
||||||
|
for exc_handler in HANDLE_EXC_METHODS:
|
||||||
|
if issubclass(exc_type, exc_handler['exc']):
|
||||||
|
if exc_handler['set_wrap']:
|
||||||
|
wrap = True
|
||||||
|
handler = exc_handler['handler']
|
||||||
|
ret = handler(request, message, redirect, ignore, escalate,
|
||||||
|
handled, force_silence, force_log,
|
||||||
|
log_method, log_entry, log_level)
|
||||||
|
if ret:
|
||||||
|
return ret # return to normal code flow
|
||||||
|
|
||||||
|
# If we've gotten here, time to wrap and/or raise our exception.
|
||||||
|
if wrap:
|
||||||
|
raise HandledException([exc_type, exc_value, exc_traceback])
|
||||||
|
|
||||||
|
six.reraise(exc_type, exc_value, exc_traceback)
|
|
@ -0,0 +1,76 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
# Importing non-modules that are not used explicitly
|
||||||
|
|
||||||
|
# FIXME(gabriel): Legacy imports for API compatibility.
|
||||||
|
from django.core.exceptions import ValidationError # noqa
|
||||||
|
from django.forms.fields import * # noqa
|
||||||
|
from django.forms.forms import * # noqa
|
||||||
|
from django.forms import widgets
|
||||||
|
from django.forms.widgets import * # noqa
|
||||||
|
|
||||||
|
|
||||||
|
# Convenience imports for public API components.
|
||||||
|
from horizon.forms.base import DateForm # noqa
|
||||||
|
from horizon.forms.base import SelfHandlingForm # noqa
|
||||||
|
from horizon.forms.base import SelfHandlingMixin # noqa
|
||||||
|
from horizon.forms.fields import DynamicChoiceField # noqa
|
||||||
|
from horizon.forms.fields import DynamicTypedChoiceField # noqa
|
||||||
|
from horizon.forms.fields import IPField # noqa
|
||||||
|
from horizon.forms.fields import IPv4 # noqa
|
||||||
|
from horizon.forms.fields import IPv6 # noqa
|
||||||
|
from horizon.forms.fields import MultiIPField # noqa
|
||||||
|
from horizon.forms.fields import SelectWidget # noqa
|
||||||
|
from horizon.forms.views import ModalFormMixin # noqa
|
||||||
|
from horizon.forms.views import ModalFormView # noqa
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"SelfHandlingMixin",
|
||||||
|
"SelfHandlingForm",
|
||||||
|
"DateForm",
|
||||||
|
"ModalFormView",
|
||||||
|
"ModalFormMixin",
|
||||||
|
"DynamicTypedChoiceField",
|
||||||
|
"DynamicChoiceField",
|
||||||
|
"IPField",
|
||||||
|
"IPv4",
|
||||||
|
"IPv6",
|
||||||
|
"MultiIPField",
|
||||||
|
"SelectWidget"
|
||||||
|
|
||||||
|
# From django.forms
|
||||||
|
"ValidationError",
|
||||||
|
|
||||||
|
# From django.forms.fields
|
||||||
|
'Field', 'CharField', 'IntegerField', 'DateField', 'TimeField',
|
||||||
|
'DateTimeField', 'TimeField', 'RegexField', 'EmailField', 'FileField',
|
||||||
|
'ImageField', 'URLField', 'BooleanField', 'NullBooleanField',
|
||||||
|
'ChoiceField', 'MultipleChoiceField', 'ComboField', 'MultiValueField',
|
||||||
|
'FloatField', 'DecimalField', 'SplitDateTimeField', 'IPAddressField',
|
||||||
|
'GenericIPAddressField', 'FilePathField', 'SlugField', 'TypedChoiceField',
|
||||||
|
'TypedMultipleChoiceField',
|
||||||
|
|
||||||
|
# From django.forms.widgets
|
||||||
|
"widgets",
|
||||||
|
'Media', 'MediaDefiningClass', 'Widget', 'TextInput', 'PasswordInput',
|
||||||
|
'HiddenInput', 'MultipleHiddenInput', 'ClearableFileInput', 'FileInput',
|
||||||
|
'DateInput', 'DateTimeInput', 'TimeInput', 'Textarea', 'CheckboxInput',
|
||||||
|
'Select', 'NullBooleanSelect', 'SelectMultiple', 'RadioSelect',
|
||||||
|
'CheckboxSelectMultiple', 'MultiWidget', 'SplitDateTimeWidget',
|
||||||
|
|
||||||
|
# From django.forms.forms
|
||||||
|
'BaseForm', 'Form',
|
||||||
|
]
|
|
@ -0,0 +1,62 @@
|
||||||
|
# Copyright 2012 United States Government as represented by the
|
||||||
|
# Administrator of the National Aeronautics and Space Administration.
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from django import forms
|
||||||
|
from django.forms.forms import NON_FIELD_ERRORS # noqa
|
||||||
|
|
||||||
|
|
||||||
|
class SelfHandlingMixin(object):
|
||||||
|
def __init__(self, request, *args, **kwargs):
|
||||||
|
self.request = request
|
||||||
|
if not hasattr(self, "handle"):
|
||||||
|
raise NotImplementedError("%s does not define a handle method."
|
||||||
|
% self.__class__.__name__)
|
||||||
|
super(SelfHandlingMixin, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
|
class SelfHandlingForm(SelfHandlingMixin, forms.Form):
|
||||||
|
"""A base :class:`Form <django:django.forms.Form>` class which includes
|
||||||
|
processing logic in its subclasses.
|
||||||
|
"""
|
||||||
|
required_css_class = 'required'
|
||||||
|
|
||||||
|
def api_error(self, message):
|
||||||
|
"""Adds an error to the form's error dictionary after validation
|
||||||
|
based on problems reported via the API. This is useful when you
|
||||||
|
wish for API errors to appear as errors on the form rather than
|
||||||
|
using the messages framework.
|
||||||
|
"""
|
||||||
|
self._errors[NON_FIELD_ERRORS] = self.error_class([message])
|
||||||
|
|
||||||
|
def set_warning(self, message):
|
||||||
|
"""Sets a warning on the form.
|
||||||
|
|
||||||
|
Unlike NON_FIELD_ERRORS, this doesn't fail form validation.
|
||||||
|
"""
|
||||||
|
self.warnings = self.error_class([message])
|
||||||
|
|
||||||
|
|
||||||
|
class DateForm(forms.Form):
|
||||||
|
"""A simple form for selecting a range of time."""
|
||||||
|
start = forms.DateField(input_formats=("%Y-%m-%d",))
|
||||||
|
end = forms.DateField(input_formats=("%Y-%m-%d",))
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super(DateForm, self).__init__(*args, **kwargs)
|
||||||
|
self.fields['start'].widget.attrs['data-date-format'] = "yyyy-mm-dd"
|
||||||
|
self.fields['end'].widget.attrs['data-date-format'] = "yyyy-mm-dd"
|
|
@ -0,0 +1,253 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import re
|
||||||
|
|
||||||
|
import netaddr
|
||||||
|
|
||||||
|
from django.core.exceptions import ValidationError # noqa
|
||||||
|
from django.core import urlresolvers
|
||||||
|
from django.forms import fields
|
||||||
|
from django.forms.util import flatatt # noqa
|
||||||
|
from django.forms import widgets
|
||||||
|
from django.utils.encoding import force_text
|
||||||
|
from django.utils.functional import Promise # noqa
|
||||||
|
from django.utils import html
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
ip_allowed_symbols_re = re.compile(r'^[a-fA-F0-9:/\.]+$')
|
||||||
|
IPv4 = 1
|
||||||
|
IPv6 = 2
|
||||||
|
|
||||||
|
|
||||||
|
class IPField(fields.Field):
|
||||||
|
"""Form field for entering IP/range values, with validation.
|
||||||
|
Supports IPv4/IPv6 in the format:
|
||||||
|
.. xxx.xxx.xxx.xxx
|
||||||
|
.. xxx.xxx.xxx.xxx/zz
|
||||||
|
.. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
|
||||||
|
.. ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff/zz
|
||||||
|
and all compressed forms. Also the short forms
|
||||||
|
are supported:
|
||||||
|
xxx/yy
|
||||||
|
xxx.xxx/yy
|
||||||
|
|
||||||
|
.. attribute:: version
|
||||||
|
|
||||||
|
Specifies which IP version to validate,
|
||||||
|
valid values are 1 (fields.IPv4), 2 (fields.IPv6) or
|
||||||
|
both - 3 (fields.IPv4 | fields.IPv6).
|
||||||
|
Defaults to IPv4 (1)
|
||||||
|
|
||||||
|
.. attribute:: mask
|
||||||
|
|
||||||
|
Boolean flag to validate subnet masks along with IP address.
|
||||||
|
E.g: 10.0.0.1/32
|
||||||
|
|
||||||
|
.. attribute:: mask_range_from
|
||||||
|
Subnet range limitation, e.g. 16
|
||||||
|
That means the input mask will be checked to be in the range
|
||||||
|
16:max_value. Useful to limit the subnet ranges
|
||||||
|
to A/B/C-class networks.
|
||||||
|
"""
|
||||||
|
invalid_format_message = _("Incorrect format for IP address")
|
||||||
|
invalid_version_message = _("Invalid version for IP address")
|
||||||
|
invalid_mask_message = _("Invalid subnet mask")
|
||||||
|
max_v4_mask = 32
|
||||||
|
max_v6_mask = 128
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
self.mask = kwargs.pop("mask", None)
|
||||||
|
self.min_mask = kwargs.pop("mask_range_from", 0)
|
||||||
|
self.version = kwargs.pop('version', IPv4)
|
||||||
|
|
||||||
|
super(IPField, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
|
def validate(self, value):
|
||||||
|
super(IPField, self).validate(value)
|
||||||
|
if not value and not self.required:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
if self.mask:
|
||||||
|
self.ip = netaddr.IPNetwork(value)
|
||||||
|
else:
|
||||||
|
self.ip = netaddr.IPAddress(value)
|
||||||
|
except Exception:
|
||||||
|
raise ValidationError(self.invalid_format_message)
|
||||||
|
|
||||||
|
if not any([self.version & IPv4 > 0 and self.ip.version == 4,
|
||||||
|
self.version & IPv6 > 0 and self.ip.version == 6]):
|
||||||
|
raise ValidationError(self.invalid_version_message)
|
||||||
|
|
||||||
|
if self.mask:
|
||||||
|
if self.ip.version == 4 and \
|
||||||
|
not self.min_mask <= self.ip.prefixlen <= self.max_v4_mask:
|
||||||
|
raise ValidationError(self.invalid_mask_message)
|
||||||
|
|
||||||
|
if self.ip.version == 6 and \
|
||||||
|
not self.min_mask <= self.ip.prefixlen <= self.max_v6_mask:
|
||||||
|
raise ValidationError(self.invalid_mask_message)
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
super(IPField, self).clean(value)
|
||||||
|
return str(getattr(self, "ip", ""))
|
||||||
|
|
||||||
|
|
||||||
|
class MultiIPField(IPField):
|
||||||
|
"""Extends IPField to allow comma-separated lists of addresses."""
|
||||||
|
def validate(self, value):
|
||||||
|
self.addresses = []
|
||||||
|
if value:
|
||||||
|
addresses = value.split(',')
|
||||||
|
for ip in addresses:
|
||||||
|
super(MultiIPField, self).validate(ip)
|
||||||
|
self.addresses.append(ip)
|
||||||
|
else:
|
||||||
|
super(MultiIPField, self).validate(value)
|
||||||
|
|
||||||
|
def clean(self, value):
|
||||||
|
super(MultiIPField, self).clean(value)
|
||||||
|
return str(','.join(getattr(self, "addresses", [])))
|
||||||
|
|
||||||
|
|
||||||
|
class SelectWidget(widgets.Select):
|
||||||
|
"""Customizable select widget, that allows to render
|
||||||
|
data-xxx attributes from choices. This widget also
|
||||||
|
allows user to specify additional html attributes
|
||||||
|
for choices.
|
||||||
|
|
||||||
|
.. attribute:: data_attrs
|
||||||
|
|
||||||
|
Specifies object properties to serialize as
|
||||||
|
data-xxx attribute. If passed ('id', ),
|
||||||
|
this will be rendered as:
|
||||||
|
<option data-id="123">option_value</option>
|
||||||
|
where 123 is the value of choice_value.id
|
||||||
|
|
||||||
|
.. attribute:: transform
|
||||||
|
|
||||||
|
A callable used to render the display value
|
||||||
|
from the option object.
|
||||||
|
|
||||||
|
.. attribute:: transform_html_attrs
|
||||||
|
|
||||||
|
A callable used to render additional HTML attributes
|
||||||
|
for the option object. It returns a dictionary
|
||||||
|
containing the html attributes and their values.
|
||||||
|
For example, to define a title attribute for the
|
||||||
|
choices:
|
||||||
|
|
||||||
|
helpText = { 'Apple': 'This is a fruit',
|
||||||
|
'Carrot': 'This is a vegetable' }
|
||||||
|
|
||||||
|
def get_title(data):
|
||||||
|
text = helpText.get(data, None)
|
||||||
|
if text:
|
||||||
|
return {'title': text}
|
||||||
|
else:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
....
|
||||||
|
....
|
||||||
|
|
||||||
|
widget=forms.SelectWidget( attrs={'class': 'switchable',
|
||||||
|
'data-slug': 'source'},
|
||||||
|
transform_html_attrs=get_title )
|
||||||
|
|
||||||
|
self.fields[<field name>].choices =
|
||||||
|
([
|
||||||
|
('apple','Apple'),
|
||||||
|
('carrot','Carrot')
|
||||||
|
])
|
||||||
|
"""
|
||||||
|
def __init__(self, attrs=None, choices=(), data_attrs=(), transform=None,
|
||||||
|
transform_html_attrs=None):
|
||||||
|
self.data_attrs = data_attrs
|
||||||
|
self.transform = transform
|
||||||
|
self.transform_html_attrs = transform_html_attrs
|
||||||
|
super(SelectWidget, self).__init__(attrs, choices)
|
||||||
|
|
||||||
|
def render_option(self, selected_choices, option_value, option_label):
|
||||||
|
option_value = force_text(option_value)
|
||||||
|
other_html = (u' selected="selected"'
|
||||||
|
if option_value in selected_choices else '')
|
||||||
|
|
||||||
|
if callable(self.transform_html_attrs):
|
||||||
|
html_attrs = self.transform_html_attrs(option_label)
|
||||||
|
other_html += flatatt(html_attrs)
|
||||||
|
|
||||||
|
if not isinstance(option_label, (basestring, Promise)):
|
||||||
|
for data_attr in self.data_attrs:
|
||||||
|
data_value = html.conditional_escape(
|
||||||
|
force_text(getattr(option_label,
|
||||||
|
data_attr, "")))
|
||||||
|
other_html += ' data-%s="%s"' % (data_attr, data_value)
|
||||||
|
|
||||||
|
if callable(self.transform):
|
||||||
|
option_label = self.transform(option_label)
|
||||||
|
|
||||||
|
return u'<option value="%s"%s>%s</option>' % (
|
||||||
|
html.escape(option_value), other_html,
|
||||||
|
html.conditional_escape(force_text(option_label)))
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicSelectWidget(widgets.Select):
|
||||||
|
"""A subclass of the ``Select`` widget which renders extra attributes for
|
||||||
|
use in callbacks to handle dynamic changes to the available choices.
|
||||||
|
"""
|
||||||
|
_data_add_url_attr = "data-add-item-url"
|
||||||
|
|
||||||
|
def render(self, *args, **kwargs):
|
||||||
|
add_item_url = self.get_add_item_url()
|
||||||
|
if add_item_url is not None:
|
||||||
|
self.attrs[self._data_add_url_attr] = add_item_url
|
||||||
|
return super(DynamicSelectWidget, self).render(*args, **kwargs)
|
||||||
|
|
||||||
|
def get_add_item_url(self):
|
||||||
|
if callable(self.add_item_link):
|
||||||
|
return self.add_item_link()
|
||||||
|
try:
|
||||||
|
if self.add_item_link_args:
|
||||||
|
return urlresolvers.reverse(self.add_item_link,
|
||||||
|
args=self.add_item_link_args)
|
||||||
|
else:
|
||||||
|
return urlresolvers.reverse(self.add_item_link)
|
||||||
|
except urlresolvers.NoReverseMatch:
|
||||||
|
return self.add_item_link
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicChoiceField(fields.ChoiceField):
|
||||||
|
"""A subclass of ``ChoiceField`` with additional properties that make
|
||||||
|
dynamically updating its elements easier.
|
||||||
|
|
||||||
|
Notably, the field declaration takes an extra argument, ``add_item_link``
|
||||||
|
which may be a string or callable defining the URL that should be used
|
||||||
|
for the "add" link associated with the field.
|
||||||
|
"""
|
||||||
|
widget = DynamicSelectWidget
|
||||||
|
|
||||||
|
def __init__(self,
|
||||||
|
add_item_link=None,
|
||||||
|
add_item_link_args=None,
|
||||||
|
*args,
|
||||||
|
**kwargs):
|
||||||
|
super(DynamicChoiceField, self).__init__(*args, **kwargs)
|
||||||
|
self.widget.add_item_link = add_item_link
|
||||||
|
self.widget.add_item_link_args = add_item_link_args
|
||||||
|
|
||||||
|
|
||||||
|
class DynamicTypedChoiceField(DynamicChoiceField, fields.TypedChoiceField):
|
||||||
|
"""Simple mix of ``DynamicChoiceField`` and ``TypedChoiceField``."""
|
||||||
|
pass
|
|
@ -0,0 +1,196 @@
|
||||||
|
# Copyright 2012 Nebula, Inc.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django import http
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
from horizon import exceptions
|
||||||
|
from horizon import views
|
||||||
|
|
||||||
|
|
||||||
|
ADD_TO_FIELD_HEADER = "HTTP_X_HORIZON_ADD_TO_FIELD"
|
||||||
|
|
||||||
|
|
||||||
|
class ModalBackdropMixin(object):
|
||||||
|
"""This mixin class is to be used for together with ModalFormView and
|
||||||
|
WorkflowView classes to augment them with modal_backdrop context data.
|
||||||
|
|
||||||
|
.. attribute: modal_backdrop (optional)
|
||||||
|
|
||||||
|
The appearance and behavior of backdrop under the modal element.
|
||||||
|
Possible options are:
|
||||||
|
* 'true' - show backdrop element outside the modal, close the modal
|
||||||
|
after clicking on backdrop (the default one);
|
||||||
|
* 'false' - do not show backdrop element, do not close the modal after
|
||||||
|
clicking outside of it;
|
||||||
|
* 'static' - show backdrop element outside the modal, do not close
|
||||||
|
the modal after clicking on backdrop.
|
||||||
|
"""
|
||||||
|
modal_backdrop = 'static'
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
super(ModalBackdropMixin, self).__init__()
|
||||||
|
config = getattr(settings, 'HORIZON_CONFIG', {})
|
||||||
|
if 'modal_backdrop' in config:
|
||||||
|
self.modal_backdrop = config['modal_backdrop']
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ModalBackdropMixin, self).get_context_data(**kwargs)
|
||||||
|
context['modal_backdrop'] = self.modal_backdrop
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ModalFormMixin(object):
|
||||||
|
def get_template_names(self):
|
||||||
|
if self.request.is_ajax():
|
||||||
|
if not hasattr(self, "ajax_template_name"):
|
||||||
|
# Transform standard template name to ajax name (leading "_")
|
||||||
|
bits = list(os.path.split(self.template_name))
|
||||||
|
bits[1] = "".join(("_", bits[1]))
|
||||||
|
self.ajax_template_name = os.path.join(*bits)
|
||||||
|
template = self.ajax_template_name
|
||||||
|
else:
|
||||||
|
template = self.template_name
|
||||||
|
return template
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ModalFormMixin, self).get_context_data(**kwargs)
|
||||||
|
if self.request.is_ajax():
|
||||||
|
context['hide'] = True
|
||||||
|
if ADD_TO_FIELD_HEADER in self.request.META:
|
||||||
|
context['add_to_field'] = self.request.META[ADD_TO_FIELD_HEADER]
|
||||||
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
class ModalFormView(ModalBackdropMixin, ModalFormMixin, views.HorizonFormView):
|
||||||
|
"""The main view class from which all views which handle forms in Horizon
|
||||||
|
should inherit. It takes care of all details with processing
|
||||||
|
:class:`~horizon.forms.base.SelfHandlingForm` classes, and modal concerns
|
||||||
|
when the associated template inherits from
|
||||||
|
`horizon/common/_modal_form.html`.
|
||||||
|
|
||||||
|
Subclasses must define a ``form_class`` and ``template_name`` attribute
|
||||||
|
at minimum.
|
||||||
|
|
||||||
|
See Django's documentation on the `FormView <https://docs.djangoproject.com
|
||||||
|
/en/dev/ref/class-based-views/generic-editing/#formview>`_ class for
|
||||||
|
more details.
|
||||||
|
|
||||||
|
.. attribute: modal_id (recommended)
|
||||||
|
|
||||||
|
The HTML element id of this modal.
|
||||||
|
|
||||||
|
.. attribute: modal_header (recommended)
|
||||||
|
|
||||||
|
The title of this modal.
|
||||||
|
|
||||||
|
.. attribute: form_id (recommended)
|
||||||
|
|
||||||
|
The HTML element id of the form in this modal.
|
||||||
|
|
||||||
|
.. attribute: submit_url (required)
|
||||||
|
|
||||||
|
The url for a submit action.
|
||||||
|
|
||||||
|
.. attribute: submit_label (optional)
|
||||||
|
|
||||||
|
The label for the submit button. This label defaults to ``Submit``.
|
||||||
|
This button should only be visible if the action_url is defined.
|
||||||
|
Clicking on this button will post to the action_url.
|
||||||
|
|
||||||
|
.. attribute: cancel_label (optional)
|
||||||
|
|
||||||
|
The label for the cancel button. This label defaults to ``Cancel``.
|
||||||
|
Clicking on this button will redirect user to the cancel_url.
|
||||||
|
|
||||||
|
.. attribute: cancel_url (optional)
|
||||||
|
|
||||||
|
The url for a cancel action. This url defaults to the success_url
|
||||||
|
if omitted. Note that the cancel_url redirect is nullified when
|
||||||
|
shown in a modal dialog.
|
||||||
|
"""
|
||||||
|
|
||||||
|
modal_id = None
|
||||||
|
modal_header = ""
|
||||||
|
form_id = None
|
||||||
|
submit_url = None
|
||||||
|
submit_label = _("Submit")
|
||||||
|
cancel_label = _("Cancel")
|
||||||
|
cancel_url = None
|
||||||
|
|
||||||
|
def get_context_data(self, **kwargs):
|
||||||
|
context = super(ModalFormView, self).get_context_data(**kwargs)
|
||||||
|
context['modal_id'] = self.modal_id
|
||||||
|
context['modal_header'] = self.modal_header
|
||||||
|
context['form_id'] = self.form_id
|
||||||
|
context['submit_url'] = self.submit_url
|
||||||
|
context['submit_label'] = self.submit_label
|
||||||
|
context['cancel_label'] = self.cancel_label
|
||||||
|
context['cancel_url'] = self.get_cancel_url()
|
||||||
|
return context
|
||||||
|
|
||||||
|
def get_cancel_url(self):
|
||||||
|
return self.cancel_url or self.success_url
|
||||||
|
|
||||||
|
def get_object_id(self, obj):
|
||||||
|
"""For dynamic insertion of resources created in modals, this method
|
||||||
|
returns the id of the created object. Defaults to returning the ``id``
|
||||||
|
attribute.
|
||||||
|
"""
|
||||||
|
return obj.id
|
||||||
|
|
||||||
|
def get_object_display(self, obj):
|
||||||
|
"""For dynamic insertion of resources created in modals, this method
|
||||||
|
returns the display name of the created object. Defaults to returning
|
||||||
|
the ``name`` attribute.
|
||||||
|
"""
|
||||||
|
return obj.name
|
||||||
|
|
||||||
|
def get_form(self, form_class):
|
||||||
|
"""Returns an instance of the form to be used in this view."""
|
||||||
|
return form_class(self.request, **self.get_form_kwargs())
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
try:
|
||||||
|
handled = form.handle(self.request, form.cleaned_data)
|
||||||
|
except Exception:
|
||||||
|
handled = None
|
||||||
|
exceptions.handle(self.request)
|
||||||
|
|
||||||
|
if handled:
|
||||||
|
if ADD_TO_FIELD_HEADER in self.request.META:
|
||||||
|
field_id = self.request.META[ADD_TO_FIELD_HEADER]
|
||||||
|
data = [self.get_object_id(handled),
|
||||||
|
self.get_object_display(handled)]
|
||||||
|
response = http.HttpResponse(json.dumps(data))
|
||||||
|
response["X-Horizon-Add-To-Field"] = field_id
|
||||||
|
elif isinstance(handled, http.HttpResponse):
|
||||||
|
return handled
|
||||||
|
else:
|
||||||
|
success_url = self.get_success_url()
|
||||||
|
response = http.HttpResponseRedirect(success_url)
|
||||||
|
# TODO(gabriel): This is not a long-term solution to how
|
||||||
|
# AJAX should be handled, but it's an expedient solution
|
||||||
|
# until the blueprint for AJAX handling is architected
|
||||||
|
# and implemented.
|
||||||
|
response['X-Horizon-Location'] = success_url
|
||||||
|
return response
|
||||||
|
else:
|
||||||
|
# If handled didn't return, we can assume something went
|
||||||
|
# wrong, and we should send back the form as-is.
|
||||||
|
return self.form_invalid(form)
|
|
@ -0,0 +1,68 @@
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Wrapper for loading templates from "templates" directories in panel modules.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
import django
|
||||||
|
from django.conf import settings
|
||||||
|
from django.template.base import TemplateDoesNotExist # noqa
|
||||||
|
|
||||||
|
if django.get_version() >= '1.8':
|
||||||
|
from django.template.engine import Engine
|
||||||
|
from django.template.loaders.base import Loader as tLoaderCls
|
||||||
|
else:
|
||||||
|
from django.template.loader import BaseLoader as tLoaderCls # noqa
|
||||||
|
|
||||||
|
from django.utils._os import safe_join # noqa
|
||||||
|
|
||||||
|
# Set up a cache of the panel directories to search.
|
||||||
|
panel_template_dirs = {}
|
||||||
|
|
||||||
|
|
||||||
|
class TemplateLoader(tLoaderCls):
|
||||||
|
is_usable = True
|
||||||
|
|
||||||
|
def get_template_sources(self, template_name):
|
||||||
|
bits = template_name.split('/', 2)
|
||||||
|
if len(bits) == 3:
|
||||||
|
dash_name, panel_name, remainder = bits
|
||||||
|
key = os.path.join(dash_name, panel_name)
|
||||||
|
if key in panel_template_dirs:
|
||||||
|
template_dir = panel_template_dirs[key]
|
||||||
|
try:
|
||||||
|
yield safe_join(template_dir, panel_name, remainder)
|
||||||
|
except UnicodeDecodeError:
|
||||||
|
# The template dir name wasn't valid UTF-8.
|
||||||
|
raise
|
||||||
|
except ValueError:
|
||||||
|
# The joined path was located outside of template_dir.
|
||||||
|
pass
|
||||||
|
|
||||||
|
def load_template_source(self, template_name, template_dirs=None):
|
||||||
|
for path in self.get_template_sources(template_name):
|
||||||
|
try:
|
||||||
|
with open(path) as file:
|
||||||
|
return (file.read().decode(settings.FILE_CHARSET), path)
|
||||||
|
except IOError:
|
||||||
|
pass
|
||||||
|
raise TemplateDoesNotExist(template_name)
|
||||||
|
|
||||||
|
|
||||||
|
if django.get_version() >= '1.8':
|
||||||
|
e = Engine()
|
||||||
|
_loader = TemplateLoader(e)
|
||||||
|
else:
|
||||||
|
_loader = TemplateLoader()
|
|
@ -0,0 +1,512 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Andreas Jaeger <jaegerandi@gmail.com>, 2014
|
||||||
|
# Robert Simai, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-01 22:52+0000\n"
|
||||||
|
"Last-Translator: Robert Simai\n"
|
||||||
|
"Language-Team: German (http://www.transifex.com/projects/p/horizon/language/de/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Andere"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Navigations-Eintrag"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Wähle %s zum Durchsuchen."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Password wurde nicht akzeptiert"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Bitte melden Sie sich an um fortzufahren."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "Sie sind nicht berechtigt für den Zugriff auf %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "Ein(e) %(resource)s mit dem Namen \"%(name)s\" existiert bereits."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "Nicht berechtigt: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Nicht autorisiert. Bitte melden Sie sich erneut an."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Ungültiges Format der IP-Adresse"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Ungültige Version der IP-Adresse"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Ungültige Subnetzmaske"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Abschicken"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Abbrechen"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "Die Sitzung ist abgelaufen."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filter"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "Diese Aktion kann nicht rückgängig gemacht werden."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "Sie haben keine Berechtigung für %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Nicht möglich: %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Löschen"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Gelöscht"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Aktualisieren"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Aktualisiert"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "Das Attribut %(attr)s existiert nicht für %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Keine Einträge zum anzeigen."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Aktionen"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "Keine Übereinstimmung für die ID \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Bitte wählen Sie vor dem Ausführen dieser Aktion eine Zeile aus."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "Nicht verfügbar"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Angemeldet als: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Hilfe"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Abmelden"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\nWenn Sie nicht sicher sind, welche Authentifizierungsmethode zu verwenden ist, kontaktieren Sie Ihren Administrator."
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Anmelden"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "Sie haben keine Zugriffsrechte auf die Ressource:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Melden Sie sich als anderer Benutzer an oder gehen Sie zurück auf die <a href=\"%(home_url)s\"> Startseite</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Anmelden"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Verbinden"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Login"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Information:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Warnung:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Erfolg:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Fehler:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Zusammenfassung"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Zeige %(counter)s Eintrag"
|
||||||
|
msgstr[1] "Zeige %(counter)s Einträge"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Zurück"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Weiter »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "Weitere Aktionen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Eine Zeile hinzufügen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Übersicht Begrenzungen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instanzen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(used)s </span> von <span> %(available)s </span> benutzt"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPUs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "Floating IPs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(used)s </span> von <span> %(available)s </span> zugewiesen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Sicherheitsgruppen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Datenträger"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Datenträger-Speicher"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Speichern"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Zeige %(nav_items)s Eintrag"
|
||||||
|
msgstr[1] "Zeige %(nav_items)s Einträge"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Zeige %(content_items)s Eintrag"
|
||||||
|
msgstr[1] "Zeige %(content_items)s Einträge"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Nutzungsübersicht"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Wählen Sie einen Zeitbereich, um die Auslastung abzurufen:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n <label>Von:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>Bis:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "Das Datum sollte im YYYY-mm-dd Format sein."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Aktive Instanzen:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "Aktiver RAM:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "VCPU-Stunden in diesem Zeitraum:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "GB-Stunden in diesem Zeitraum:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "RAM-Stunden in diesem Zeitraum:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Zurück"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Weiter"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "Keine Begrenzung"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Verfügbar"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d Byte"
|
||||||
|
msgstr[1] "%(size)d Bytes"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Bytes"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "Sell Puppy"
|
||||||
|
msgstr[1] "Sell Puppies"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "Sold Puppy"
|
||||||
|
msgstr[1] "Sold Puppies"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Fälschung"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Niemals"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Keine gültige Port-Nummer"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Keine gültige IP-Protokollnummer"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "Im Port-Bereich ist nur ein Doppelpunkt erlaubt"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Port-Nummer muss ganzzahlig sein"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "Die Zeichenkette darf nur druckbare ASCII-Zeichen enthalten."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Verarbeite..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "Alle verfügbaren"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Mitglieder"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "Keine verfügbar."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "Keine Mitglieder."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s erfolgreich abgeschlossen."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s nicht abgeschlossen."
|
|
@ -0,0 +1,621 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Andreas Jaeger <jaegerandi@gmail.com>, 2015
|
||||||
|
# Ettore Atalan <atalanttore@googlemail.com>, 2015
|
||||||
|
# Robert Simai, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-11 11:47-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-12 03:25+0000\n"
|
||||||
|
"Last-Translator: Ettore Atalan <atalanttore@googlemail.com>\n"
|
||||||
|
"Language-Team: German (http://www.transifex.com/projects/p/horizon/language/de/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: de\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "Die Aktion kann nicht ausgeführt werden. Der Inhalt dieser Spalte enthält Fehler oder es fehlen Informationen."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Detail-Informationen"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "Sie können Ressourcen-Metadaten bestimmen, indem Sie Positionen aus der linken Spalte in die rechte bewegen. In der linken Spalte finden Sie Metadaten-Definitionen aus dem Glace Metadaten-Katalog. Verwenden Sie die \"Andere\" Option, um Metadaten mit dem Schlüssel Ihrer Wahl hinzuzufügen."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Min"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Max"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Minimale Länge"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Maximale Länge"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Muster-Übereinstimmungsfehler"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Ganzzahl erforderlich"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Dezimalzahl erforderlich"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Erforderlich"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Doppelte Schlüssel sind nicht erlaubt"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filter"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Verfügbare Metadaten"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Vorhandene Metadaten"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Angepasst"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "Keine Metadaten vorhanden"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "Keine existierenden Metadaten"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Abschicken"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Abbrechen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Zugewiesen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Verfügbar"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Eines auswählen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Wählen Sie einen Eintrag aus den verfügbaren Positionen aus"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "Keine verfügbaren Positionen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Erweitern, um zugewiesene Positionen zu sehen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Erweitern, um verfügbare Positionen zu sehen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Klicken zum zeigen oder verbergen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "Umsortieren der Positionen durch ziehen und ablegen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Klicken um mehr Details zu sehen"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "%(found)s von %(total)s gefunden"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Klicken Sie hier um die Zeile zu erweitern und die Fehler zu sehen."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Zurück"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Weiter"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Beenden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "Verbinde"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Öffnen"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "Schließen"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Geschlossen"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "Status: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Ja"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "Nein"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s Bytes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Zeige %s Eintrag"
|
||||||
|
msgstr[1] "Zeige %s Einträge"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "Datenträger können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "Datenträger-Schattenkopien können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "Benutzerkonfiguration kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "Admin-Konfiguration kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "Einstellungen können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Einstellung ist nicht aktiviert: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "Abbild kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "Abbilder können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "Namensräume können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "Benutzer können nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "Benutzer kann nicht angelegt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "Benutzer können nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "Aktuelle Benutzersitzung kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "Benutzer kann nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "Benutzer kann nicht bearbeitet werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "Benutzer kann nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Rolle kann nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Die Rolle kann nicht erstellt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Die Rollen können nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Die Rolle kann nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Die Rolle kann nicht bearbeitet werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Die Rolle kann nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "Domänen können nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "Die Domäne kann nicht erstellt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "Die Domänen können nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "Die Domäne kann nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "Die Domäne kann nicht bearbeitet werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "Die Domäne kann nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "Projekte können nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "Das Projekt kann nicht erstellt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "Die Projekte können nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "Das Projekt kann nicht abgerufen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "Das Projekt kann nicht bearbeitet werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "Das Projekt kann nicht gelöscht werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Die Rolle kann nicht gewährt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "Die Dienstekatalog kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Dienst-Typ ist nicht aktiviert: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Dienstekatalog kann nicht von Keystone abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "Netzwerke können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "Netzwerk kann nicht angelegt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "Subnetze können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "Subnetz kann nicht erzeugt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "Ports können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Schlüsselpaare können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "Das Schlüsselpaar kann nicht importiert werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Das Schlüsselpaar kann nicht erstellt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "Verfügbarkeitszonen können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "Begrenzungen können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "Der Server kann nicht erstellt werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "Server kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "Erweiterungen können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Varianten können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Variante kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Extraspezifikationen zur Variante können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "Nicht aktivierte Erweiterung: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Liste der Nova-Erweiterungen kann nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "Richtlinienüberprüfung fehlgeschlagen."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "Sicherheitsgruppen können nicht abgerufen werden."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Lade"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "Keine Daten verfügbar."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "Ein Fehler ist aufgetreten. Bitte versuchen Sie es später noch einmal."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "Es gab ein Problem bei der Kommunikation mit dem Server. Bitte versuchen Sie es noch einmal."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "Die Datei konnte nicht gelesen werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "Das Passwort konnte nicht entschlüsselt werden"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "Keine Rollen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Rollen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Gefahr:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Warnung:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Nachricht:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Erfolg:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Fehler:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "In Arbeit"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "Es gab einen Fehler beim Abschicken des Formulars. Bitte versuchen Sie es noch einmal."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Keine"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Löschen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUS"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Schnittstellen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Schnittstelle löschen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Konsole öffnen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "Details anzeigen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Router löschen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "Router-Details anzeigen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Schnittstelle hinzufügen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Instanz terminieren"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "Instanz-Details anzeigen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Kein Eintrag zum anzeigen."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "Ein Fehler ist beim Aktualisieren aufgetreten."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "Sie haben %s ausgewählt."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "%s bestätigen"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Bitte bestätigen Sie Ihre Auswahl."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "Keine Berechtigung für diese Aktion."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "Die Passwörter stimmen nicht überein."
|
|
@ -0,0 +1,510 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your "
|
||||||
|
"administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home page</a>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr ""
|
|
@ -0,0 +1,618 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
#, fuzzy
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||||
|
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"Language: \n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr ""
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr ""
|
|
@ -0,0 +1,511 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Tom Fifield <tom@openstack.org>, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-20 07:07+0000\n"
|
||||||
|
"Last-Translator: Tom Fifield <tom@openstack.org>\n"
|
||||||
|
"Language-Team: English (Australia) (http://www.transifex.com/projects/p/horizon/language/en_AU/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: en_AU\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Other"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Navigation Item"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Select a %s to browse."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Password is not accepted"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Please log in to continue."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "You are not authorised to access %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "Unauthorised: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Unauthorised. Please try logging in again."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Incorrect format for IP address"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Invalid version for IP address"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Invalid subnet mask"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Submit"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancel"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "Session timed out."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filter"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "This action cannot be undone."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Unable to %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Delete"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Deleted"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Update"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Updated"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "No items to display."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Actions"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "No match returned for the id \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Please select a row before taking that action."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/A"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Logged in as: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Help"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Sign Out"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\n If you are not sure which authentication method to use, contact your administrator.\n "
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Log In"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "You do not have permission to access the resource:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Login as different user or go back to <a href=\"%(home_url)s\"> home page</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Sign In"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Connect"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Login"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Info: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Warning: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Success: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Error: "
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Summary"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Displaying %(counter)s item"
|
||||||
|
msgstr[1] "Displaying %(counter)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Prev"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Next »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "More Actions"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Add a row"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Limit Summary"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instances"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPUs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "Floating IPs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Security Groups"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Volumes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Volume Storage"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Save"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Displaying %(nav_items)s item"
|
||||||
|
msgstr[1] "Displaying %(nav_items)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Displaying %(content_items)s item"
|
||||||
|
msgstr[1] "Displaying %(content_items)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Usage Summary"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Select a period of time to query its usage:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n <label>From:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>To:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "The date should be in YYYY-mm-dd format."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Active Instances:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "Active RAM:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "This Period's VCPU-Hours:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "This Period's GB-Hours:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "This Period's RAM-Hours:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Back"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Next"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "No Limit"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Available"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d Byte"
|
||||||
|
msgstr[1] "%(size)d Bytes"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Bytes"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "Sell Puppy"
|
||||||
|
msgstr[1] "Sell Puppies"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "Sold Puppy"
|
||||||
|
msgstr[1] "Sold Puppies"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Fake"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Never"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Not a valid port number"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Not a valid IP protocol number"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "One colon allowed in port range"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Port number must be integer"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "The string may only contain ASCII printable characters."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Processing..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "All available"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Members"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "None available."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "No members."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s completed successfully."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s did not complete."
|
|
@ -0,0 +1,619 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Tom Fifield <tom@openstack.org>, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
|
||||||
|
"Last-Translator: Tom Fifield <tom@openstack.org>\n"
|
||||||
|
"Language-Team: English (Australia) (http://www.transifex.com/projects/p/horizon/language/en_AU/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: en_AU\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "The action cannot be performed. The contents of this row have errors or are missing information."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Detail Information"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "You can specify resource metadata by moving items from the left column to the right column. In the left columns there are metadata definitions from the Glance Metadata Catalog. Use the \"Other\" option to add metadata with the key of your choice."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Min"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Max"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Min length"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Max length"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Pattern mismatch"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Integer required"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Decimal required"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Required"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Duplicate keys are not allowed"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filter"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Available Metadata"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Existing Metadata"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Custom"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "No available metadata"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "No existing metadata"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Submit"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancel"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Allocated"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Available"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Select one"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Select an item from Available items below"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "No available items"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Expand to see allocated items"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Expand to see available items"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Click to show or hide"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "Re-order items using drag and drop"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Click to see more details"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "Found %(found)s of %(total)s"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Click here to expand the row and view the errors."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Back"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Next"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Finish"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "Connecting"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Open"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "Closing"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Closed"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "Status: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Yes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s bytes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Displaying %s item"
|
||||||
|
msgstr[1] "Displaying %s items"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "Unable to retrieve volumes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "Unable to retrieve volume snapshots."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "Unable to retrieve user configuration."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "Unable to retrieve admin configuration."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "Unable to retrieve settings."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Setting is not enabled: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "Unable to retrieve image."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "Unable to retrieve images."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "Unable to retrieve namespaces."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "Unable to retrieve users"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "Unable to create the user."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "Unable to delete the users."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "Unable to retrieve the current user session."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "Unable to retrieve the user"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "Unable to edit the user."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "Unable to delete the user."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Unable to retrieve role"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Unable to create the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Unable to delete the roles."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Unable to retrieve the role"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Unable to edit the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Unable to delete the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "Unable to retrieve domains"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "Unable to create the domain."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "Unable to delete the domains."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "Unable to retrieve the domain"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "Unable to edit the domain."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "Unable to delete the domain."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "Unable to retrieve projects"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "Unable to create the project."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "Unable to delete the projects."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "Unable to retrieve the project"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "Unable to edit the project."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "Unable to delete the project."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Unable to grant the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "Unable to fetch the service catalog."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Service type is not enabled: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Cannot get service catalog from keystone."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "Unable to retrieve networks."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "Unable to create the network."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "Unable to retrieve subnets."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "Unable to create the subnet."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "Unable to retrieve ports."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Unable to retrieve keypairs."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "Unable to import the keypair."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Unable to create the keypair."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "Unable to retrieve availability zones."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "Unable to retrieve limits."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "Unable to create the server."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "Unable to retrieve server."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "Unable to retrieve extensions."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Unable to retrieve flavors."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Unable to retrieve flavor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Unable to retrieve flavor extra specs."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "Extension is not enabled: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Cannot get nova extension list."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "Policy check failed."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "Unable to retrieve security groups."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Loading"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "No data available."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "An error occurred. Please try again later."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "There was a problem communicating with the server, please try again."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "Could not read the file"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "Could not decrypt the password"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "No roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Danger: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Warning: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Notice: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Success: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Error: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "Working"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "There was an error submitting the form. Please try again."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "None"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Delete"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUS"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Interfaces"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Delete Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Open Console"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "View Details"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Delete Router"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "View Router Details"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Add Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Terminate Instance"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "View Instance Details"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "No items to display."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "An error occurred while updating."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "You have selected %s. "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "Confirm %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Please confirm your selection. "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "Not authorised to do this operation."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "Passwords do not match."
|
|
@ -0,0 +1,512 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Andi Chandler <andi@gowling.com>, 2014
|
||||||
|
# Rob Cresswell <robert.cresswell@outlook.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-05 15:01+0000\n"
|
||||||
|
"Last-Translator: Rob Cresswell <robert.cresswell@outlook.com>\n"
|
||||||
|
"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/horizon/language/en_GB/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: en_GB\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Other"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Navigation Item"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Select a %s to browse."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Password is not accepted"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Please log in to continue."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "You are not authorised to access %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "Unauthorised: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Unauthorised. Please try logging in again."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Incorrect format for IP address"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Invalid version for IP address"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Invalid subnet mask"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Submit"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancel"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "Session timed out."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filter"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "This action cannot be undone."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Unable to %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Delete"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Deleted"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Update"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Updated"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "The attribute %(attr)s does not exist on %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "No items to display."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Actions"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "No match returned for the id \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Please select a row before taking that action."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/A"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Logged in as: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Help"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Sign Out"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\n If you are not sure which authentication method to use, contact your administrator.\n "
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Log In"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "You do not have permission to access the resource:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Login as different user or go back to <a href=\"%(home_url)s\"> home page</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Sign In"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Connect"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Login"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Info: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Warning: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Success: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Error: "
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Summary"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Displaying %(counter)s item"
|
||||||
|
msgstr[1] "Displaying %(counter)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Prev"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Next »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "More Actions"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Add a row"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Limit Summary"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instances"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPUs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "Floating IPs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Security Groups"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Volumes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Volume Storage"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Save"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Displaying %(nav_items)s item"
|
||||||
|
msgstr[1] "Displaying %(nav_items)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Displaying %(content_items)s item"
|
||||||
|
msgstr[1] "Displaying %(content_items)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Usage Summary"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Select a period of time to query its usage:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n <label>From:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>To:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "The date should be in YYYY-mm-dd format."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Active Instances:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "Active RAM:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "This Period's VCPU-Hours:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "This Period's GB-Hours:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "This Period's RAM-Hours:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Back"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Next"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "No Limit"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Available"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d Byte"
|
||||||
|
msgstr[1] "%(size)d Bytes"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Bytes"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] "Sell Puppies"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] "Sold Puppies"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Fake"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Never"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Not a valid port number"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Not a valid IP protocol number"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "One colon allowed in port range"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Port number must be integer"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "The string may only contain ASCII printable characters."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Processing..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "All available"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Members"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "None available."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "No members."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s completed successfully."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s did not complete."
|
|
@ -0,0 +1,620 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Andi Chandler <andi@gowling.com>, 2014
|
||||||
|
# Rob Cresswell <robert.cresswell@outlook.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 16:36-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-13 07:52+0000\n"
|
||||||
|
"Last-Translator: Rob Cresswell <robert.cresswell@outlook.com>\n"
|
||||||
|
"Language-Team: English (United Kingdom) (http://www.transifex.com/projects/p/horizon/language/en_GB/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: en_GB\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "The action cannot be performed. The contents of this row have errors or are missing information."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Detail Information"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "You can specify resource metadata by moving items from the left column to the right column. In the left columns there are metadata definitions from the Glance Metadata Catalog. Use the \"Other\" option to add metadata with the key of your choice."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Min"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Max"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Min length"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Max length"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Pattern mismatch"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Integer required"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Decimal required"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Required"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Duplicate keys are not allowed"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filter"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Available Metadata"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Existing Metadata"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Custom"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "No available metadata"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "No existing metadata"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Submit"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancel"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Allocated"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Available"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Select one"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Select an item from Available items below"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "No available items"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Expand to see allocated items"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Expand to see available items"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Click to show or hide"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "Re-order items using drag and drop"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Click to see more details"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "Found %(found)s of %(total)s"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Click here to expand the row and view the errors."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Back"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Next"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Finish"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "Connecting"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Open"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "Closing"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Closed"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "Status: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Yes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s bytes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Displaying %s item"
|
||||||
|
msgstr[1] "Displaying %s items"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "Unable to retrieve volumes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "Unable to retrieve volume snapshots."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "Unable to retrieve user configuration."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "Unable to retrieve admin configuration."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "Unable to retrieve settings."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Setting is not enabled: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "Unable to retrieve image."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "Unable to retrieve images."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "Unable to retrieve namespaces."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "Unable to retrieve users"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "Unable to create the user."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "Unable to delete the users."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "Unable to retrieve the current user session."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "Unable to retrieve the user"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "Unable to edit the user."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "Unable to delete the user."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Unable to retrieve role"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Unable to create the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Unable to delete the roles."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Unable to retrieve the role"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Unable to edit the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Unable to delete the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "Unable to retrieve domains"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "Unable to create the domain."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "Unable to delete the domains."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "Unable to retrieve the domain"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "Unable to edit the domain."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "Unable to delete the domain."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "Unable to retrieve projects"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "Unable to create the project."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "Unable to delete the projects."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "Unable to retrieve the project"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "Unable to edit the project."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "Unable to delete the project."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Unable to grant the role."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "Unable to fetch the service catalogue."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Service type is not enabled: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Cannot get service catalogue from keystone."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "Unable to retrieve networks."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "Unable to create the network."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "Unable to retrieve subnets."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "Unable to create the subnet."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "Unable to retrieve ports."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Unable to retrieve keypairs."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "Unable to import the keypair."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Unable to create the keypair."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "Unable to retrieve availability zones."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "Unable to retrieve limits."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "Unable to create the server."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "Unable to retrieve server."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "Unable to retrieve extensions."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Unable to retrieve flavours."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Unable to retrieve flavour."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Unable to retrieve flavour extra specs."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "Extension is not enabled: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Cannot get nova extension list."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "Policy check failed."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "Unable to retrieve security groups."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Loading"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "No data available."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "An error occurred. Please try again later."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "There was a problem communicating with the server, please try again."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "Could not read the file"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "Could not decrypt the password"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "No roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Danger: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Warning: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Notice: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Success: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Error: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "Working"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "There was an error submitting the form. Please try again."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "None"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Delete"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUS"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Interfaces"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Delete Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Open Console"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "View Details"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Delete Router"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "View Router Details"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Add Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Terminate Instance"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "View Instance Details"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "No items to display."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "An error occurred while updating."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "You have selected %s. "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "Confirm %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Please confirm your selection. "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "Not authorised to do this operation."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "Passwords do not match."
|
|
@ -0,0 +1,514 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Alberto Molina Coballes <alb.molina@gmail.com>, 2015
|
||||||
|
# cametiope <aldohcasa@hotmail.com>, 2014
|
||||||
|
# Orizhial <contact@prunier.es>, 2015
|
||||||
|
# Marian Tort <marian.tort@gmail.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-20 07:07+0000\n"
|
||||||
|
"Last-Translator: Alberto Molina Coballes <alb.molina@gmail.com>\n"
|
||||||
|
"Language-Team: Spanish (http://www.transifex.com/projects/p/horizon/language/es/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Otro"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Ítem de navegación"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Seleccione una %s para navegar."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "La contraseña no se ha aceptado"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Inicie sesión para continuar."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "No está autorizado para acceder a %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "Un %(resource)s con el nombre \"%(name)s\" ya existe."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "No autorizado: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "No autorizado. Inicie sesión de nuevo."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Formato de dirección IP incorrecto"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Versión de dirección IP no válida"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Máscara de red no válida"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Enviar"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancelar "
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "La sesión ha expirado."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtrar"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "No se puede deshacer esta acción."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "No le está permitido %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "No ha sido posible %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Eliminar"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Eliminado"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Actualizar"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Actualizada"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "El atributo %(attr)s no existe en %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "No hay ítems que mostrar."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Acciones"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "Ninguna coincidencia para el id \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Seleccione una fila antes de realizar la acción."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/A"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Sesión iniciada como: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Ayuda"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Salir"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\nSi no está seguro del método de autenticación a utilizar, contacte con su administrador."
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Iniciar sesión"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "No tiene permisos para acceder al recurso:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Inicie sesión con otro usuario o volver a la <a href=\"%(home_url)s\"> página de inicio</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Iniciar sesión"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Conectar"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Usuario"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Info:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Advertencia:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Correcto:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Error: "
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Resumen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Mostrando %(counter)s articulo"
|
||||||
|
msgstr[1] "Mostrando %(counter)s articulos"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Prev"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Siguiente »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "Más acciones"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Agregar una fila"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Resumen"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instancias"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(used)s </span> usado de <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPU"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "IPs flotantes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Asignados <span> %(used)s </span> de <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Grupos de seguridad"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Volúmenes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Almacenamiento de volúmenes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Guardar"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Mostrando %(nav_items)s articulo"
|
||||||
|
msgstr[1] "Mostrando %(nav_items)s articulos"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Mostrando %(content_items)s articulo"
|
||||||
|
msgstr[1] "Mostrando %(content_items)s articulos"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Resumen del uso"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Seleccione un periodo de tiempo para consultar su uso: "
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n <label>De:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>A:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "La fecha debe estar en formato AAAA-MM-DD."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Instancias activas:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "RAM activa:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "Este periodo en horas VCPU:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "Este periodo en horas GB:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "Horas-RAM de este periodo:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Anterior"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Siguiente"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "Sin límite"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Disponible"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d bite"
|
||||||
|
msgstr[1] "%(size)d bites"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Bytes"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "Vender Mascota"
|
||||||
|
msgstr[1] "Vender Mascotas"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "Mascota Vendida"
|
||||||
|
msgstr[1] "Mascotas Vendidas"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Falso"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Nunca"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Número de puerto no válido"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Número de protocolo IP no válido"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "Un punto permitido en el rango de puerto"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "El número de puerto debe ser un entero"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "La cadena sólo puede incluir caracteres imprimibles ASCII."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Procesando..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "Todos los disponibles"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Miembros"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "Ninguno disponible."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "Sin miembros."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s completado correctamente."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s no completado."
|
|
@ -0,0 +1,621 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Alberto Molina Coballes <alb.molina@gmail.com>, 2015
|
||||||
|
# cametiope <aldohcasa@hotmail.com>, 2014
|
||||||
|
# Marian Tort <marian.tort@gmail.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
|
||||||
|
"Last-Translator: Alberto Molina Coballes <alb.molina@gmail.com>\n"
|
||||||
|
"Language-Team: Spanish (http://www.transifex.com/projects/p/horizon/language/es/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: es\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "Esta acción no se puede llevar a cabo. Esta columna contiene errores o carece de información."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Información detallada"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "Puede especificar los metadatos de los recursos moviendo los ítems de la columna de la izquierda a la de la derecha. En las columnas de la izquierda hay definiciones de metadatos del Glance Metadata Catalog. Utilice la opcion \"Otro\" para añadir metadatos con la clave que desee. "
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Mín."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Máx."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Longitud mín."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Longitud máx."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Discrepancia en el patrón"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Entero obligatorio"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Decimal obligatorio"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Obligatorio"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Las claves duplicadas no están permitidas"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtrar"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Metadatos disponibles"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Metadatos existentes"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Personalizar"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "Metadatos no disponibles"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "No hay metadatos existentes"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Enviar"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancelar "
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Asignados"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Disponible"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Seleccione uno"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Seleccione un ítem de los disponibles abajo"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "No hay ítems disponibles"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Expandir para ver los ítems asociados"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Expandir para ver los ítems disponibles"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Haga click para mostrar u ocultar"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "Reordene los ítems arrastrando y soltando."
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Haga click para ver más detalles"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "%(found)s de %(total)s encontrado"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Haga click aquí para ver los errores."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Anterior"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Siguiente"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Finalizar"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "Conectando"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Abierta"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "Cerrando"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Cerrada"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "Estado: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Sí"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "No"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s bytes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Mostrando %s articulo"
|
||||||
|
msgstr[1] "Mostrando %s articulos"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "No ha sido posible obtener volúmenes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "No ha sido posible obtener las snapshots de volúmenes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "No ha sido posible obtener la configuración de usuario."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "No ha sido posible obtener la configuración de admin."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "No ha sido posible obtener los ajustes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Ajuste no habilitado: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "No ha sido posible obtener la imagen."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "No ha sido posible obtener las imágenes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "No ha sido posible obtener los espacios de nombres."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "No ha sido posible obtener los usuarios"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "No ha sido posible crear el usuario."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "No ha sido posible borrar los usuarios."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "No ha sido posible obtener la sesión de usuario actual."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "No ha sido posible obtener el usuario"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "No ha sido posible editar el usuario."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "No ha sido posible borrar el usuario."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "No ha sido posible obtener el rol"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "No ha sido posible crear el rol."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "No ha sido posible borrar los roles."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "No ha sido posible obtener el rol"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "No ha sido posible editar el rol."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "No ha sido posible borrar el rol."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "No ha sido posible obtener los dominios"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "No ha sido posible crear el dominio."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "No ha sido posible borrar los dominios."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "No ha sido posible obtener el dominio"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "No ha sido posible editar el dominio."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "No ha sido posible borrar el dominio."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "No ha sido posible obtener los proyectos"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "No ha sido posible crear el proyecto."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "No ha sido posible borrar los proyectos."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "No ha sido posible obtener el proyecto"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "No ha sido posible editar el proyecto."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "No ha sido posible borrar el proyecto."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "No ha sido posible asignar el rol."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "No ha sido posible obtener el catálogo de servicios."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Tipo de servicio no habilitado: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "No se puede obtener el catálogo de servicios desde keystone."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "No ha sido posible obtener las redes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "No ha sido posible crear la red."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "No ha sido posible obtener las subredes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "No ha sido posible crear la subred."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "No ha sido posible obtener los puertos."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "No ha sido posible obtener los pares de claves."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "No ha sido posible importar el par de claves."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "No ha sido posible crear el par de claves."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "No ha sido posible obtener las zonas de disponibilidad."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "No ha sido posible obtener los límites."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "No ha sido posible crear el servidor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "No ha sido posible obtener el servidor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "No ha sido posible obtener las extensiones."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "No ha sido posible obtener los sabores."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "No ha sido posible obtener el sabor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "No ha sido posible obtener las especificaciones extra del sabor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "Extensión no habilitada: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "No se puede obtener la lista de extensiones de nova."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "Ha fallado la comprobación de política."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "No ha sido posible obtener los grupos de seguridad."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Cargando"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "No hay datos disponibles."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "Ha ocurrido un error. Inténtelo de nuevo más tarde."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "Ha ocurrido un problema en la comunicación con el servidor, inténtelo de nuevo."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "No se ha podido leer el fichero"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "No se ha podido descifrar la contraseña"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "Sin roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Peligro:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Advertencia:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Aviso:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Correcto:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Error: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "Trabajando"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "Ha ocurrido un error al enviar el formulario. Inténtelo de nuevo."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Ninguno"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Eliminar"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "ESTADO"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Interfaces"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Eliminar interfaz"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Abrir consola"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "Ver detalles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Eliminar router"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "Ver detalles del router"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Añadir interfaz"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Terminar instancia"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "Ver detalles de la instancia"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "No hay ítems que mostrar."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "Ha ocurrido un error durante la actualización."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "Ha seleccionado %s."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "Confirmar %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Confirme su selección."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "No tiene autorización para realizar esta operación."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "Las contraseñas no coinciden."
|
|
@ -0,0 +1,514 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Bruno Cornec <bruno.cornec@hp.com>, 2015
|
||||||
|
# François Bureau, 2015
|
||||||
|
# Frédéric <frosmont@free.fr>, 2014
|
||||||
|
# Maxime COQUEREL <max.coquerel@gmail.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-03 13:28+0000\n"
|
||||||
|
"Last-Translator: François Bureau\n"
|
||||||
|
"Language-Team: French (http://www.transifex.com/projects/p/horizon/language/fr/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Autre"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Elément de navigation"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Sélectionner une %s à parcourir."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Le mot de passe n'est pas accepté"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Merci de vous connecter pour continuer."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "Vous n'êtes pas autorisé à accéder à %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "Des %(resource)s avec le nom \"%(name)s\" existent déjà."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "%s : non autorisé"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Accès non autorisé. Merci de vous reconnecter."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Format d'adresse IP incorrect"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Version d'adresse IP invalide"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Masque de sous-réseau invalide"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Envoyer"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Annuler"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "La session a expiré."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtrer"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "Cette action ne peut pas être réalisée."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "Vous n'êtes pas autorisé à %(action)s : %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Impossible de %(action)s : %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s : %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Supprimer"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Supprimée"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Mettre à jour"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Mis à jour"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "L'attribut %(attr)s n'existe pas sur %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Aucun élément à afficher."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Actions"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "Aucun résultat retourné pour l'id \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Merci de sélectionner une ligne avant de faire cette action."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/D"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Utilisateur connecté : %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Aide"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Se déconnecter"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\nSi vous n'êtes pas sûr de la méthode d'identification à utiliser, veuillez contacter votre administrateur"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Se connecter"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "Vous n'avez pas la permission d'accéder à la ressource :"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Connectez-vous avec un autre nom d'utilisateur ou revenez à <a href=\"%(home_url)s\"> la page d’accueil</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Se connecter"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Connection"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Identifiant"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Information :"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Avertissement :"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Succès :"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Erreur :"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Résumé"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Affichage de %(counter)s item"
|
||||||
|
msgstr[1] "Affichage de %(counter)s items"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Aperçu"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Suivant »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "Plus d'Actions"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s : "
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Ajouter une ligne"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s : %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Synthèse des Quotas"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instances"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(used)s </span> utilisé(es) sur <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPUs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "IP flottantes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Alloué <span> %(used)s </span> de <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Groupes de sécurité"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Volumes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Stockage de volumes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Enregistrer"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Affichage de l'item %(nav_items)s"
|
||||||
|
msgstr[1] "Affichage des items %(nav_items)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Affichage de l'item %(content_items)s"
|
||||||
|
msgstr[1] "Affichage des items %(content_items)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Résumé de l'Utilisation"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Sélectionnez une période de temps pour interroger son utilisation :"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n<label>Du :</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n<label>au :</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "La date doit être au format AAAA-mm-jj"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Instances Actives :"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "RAM Active :"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "VCPU-Heures de cette Période :"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "GB-Heures de cette période :"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "Dans cette période RAM-Heures:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Retour"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Suivant"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "Pas de limite"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "disponible(s)"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d Octet"
|
||||||
|
msgstr[1] "%(size)d Octets"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s Ko"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s Mo"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s Go"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s To"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s Po"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Octets"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "Chiot à vendre"
|
||||||
|
msgstr[1] "Chiots à vendre"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "Chiot vendu"
|
||||||
|
msgstr[1] "Chiots Vendus"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Faux"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Jamais"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Numéro de port invalide"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Numéro de protocole IP invalide "
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "Un seul caractère deux-points autorisé dans une plage de ports"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Le numéro de port doit être un nombre entier"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "La chaîne ne peut contenir que des caractères ASCII imprimables."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Traitement en cours..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "Disponibles"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Membres"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "Aucun disponible."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "Aucun membre."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s terminé avec succès."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s ne s'est pas terminé."
|
|
@ -0,0 +1,627 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# alexandre ignjatovic <alexandre.ignjatovic@gmail.com>, 2015
|
||||||
|
# Bruno Cornec <bruno.cornec@hp.com>, 2015
|
||||||
|
# Corina Roe <croe@redhat.com>, 2015
|
||||||
|
# EVEILLARD <stephane.eveillard@gmail.com>, 2015
|
||||||
|
# François Bureau, 2015
|
||||||
|
# Frédéric <frosmont@free.fr>, 2014
|
||||||
|
# JF Taltavull <jftalta@gmail.com>, 2015
|
||||||
|
# Maxime COQUEREL <max.coquerel@gmail.com>, 2015
|
||||||
|
# Patte D <pattedeph@gmail.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-23 12:36+0000\n"
|
||||||
|
"Last-Translator: JF Taltavull <jftalta@gmail.com>\n"
|
||||||
|
"Language-Team: French (http://www.transifex.com/projects/p/horizon/language/fr/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: fr\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "L'action n'a pas pu être effectuée. Le contenu de cette ligne contient des erreurs ou des informations manquantes."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Informations détaillées"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "Vous pouvez spécifier les ressources de métadonnées en déplaçant les éléments de la colonne de gauche vers la colonne de droite. Les colonnes de gauche contiennent des définitions de métadonnées du Catalogue de métadonnées Glance. Utiliser l'option \"Autre\" pour ajouter des métadonnées avec la clé de votre choix."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Min"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Max"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Longueur mini"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Longueur max"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Modèle ne correspondant pas"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Entier requis"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Nombre décimal requis"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Requis"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Les clés dupliquées ne sont pas autorisées"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtrer"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Métadonnées disponibles"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Métadonnées existantes"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Personnaliser"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "Pas de métadonnées disponibles"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "Pas de métadonnées existantes"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Envoyer"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Annuler"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Alloué"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "disponible(s)"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Sélectionner un"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Sélectionner un élément depuis les éléments disponibles ci-dessous"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "Pas d'élément disponible"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Développer pour voir les éléments alloués"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Développer pour voir les éléments disponibles"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Cliquer pour montrer ou cacher"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "Ordonnancer les éléments à nouveau en utilisant l'opération glisser-déplacer"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Cliquer pour une vue plus détaillée"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "Résultat %(found)s parmi %(total)s"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Cliquez ici pour étendre la ligne et voir les erreurs"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Retour"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Suivant"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Terminer"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "En cours de connexion"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Ouvert"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "En cours de fermeture"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Fermé"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "État : %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Oui"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "Non"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s Go"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s Mo"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s To"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s Ko"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s octets"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Affichage de %s élément"
|
||||||
|
msgstr[1] "Affichage de %s éléments"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "Impossible de récupérer les volumes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "Impossible de récupérer les instantanés de volume."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "Impossible de récupérer la configuration utilisateur."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "Impossible d'extraire la configuration admin."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "Impossible de récupérer les paramètres."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Le paramètre n'est pas activé : %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "Impossible de récupérer l'image."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "Impossible de récupérer les images."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "Impossible de récupérer les espaces de nommage"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "Impossible de récupérer les utilisateurs"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "Impossible de créer l'utilisateur."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "Impossible de supprimer les utilisateurs."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "Impossible de récupérer les informations de session de l'utilisateur courant."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "Impossible de récupérer l'utilisateur"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "Impossible d'éditer l'utilisateur"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "Impossible de supprimer l'utilisateur."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Impossible de récupérer le rôle."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Impossible de créer le rôle."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Impossible de supprimer les rôles."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Impossible de récupérer le rôle."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Impossible d'éditer le rôle."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Impossible de supprimer le rôle."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "Impossible de récupérer les domaines."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "Impossible de créer le domaine."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "Impossible de supprimer les domaines."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "Impossible de récupérer le domaine."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "Impossible d'éditer le domaine."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "Impossible de supprimer le domaine."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "Impossible de récupérer les projets."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "Impossible de créer le projet."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "Impossible de supprimer les projets."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "Impossible de récupérer le projet."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "Impossible d'éditer le projet."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "Impossible de supprimer le projet."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Impossible d'accorder le rôle"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "Impossible d'extraire le catalogue de services."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Type de service non activé: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Impossible d'obtenir le catalogue de services Keystone"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "Impossible de récupérer les réseaux."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "Impossible de créer le réseau."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "Impossible de récupérer les sous-réseaux."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "Impossible de créer le sous-réseau."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "Impossible de récupérer les ports."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Impossible de récupérer les paires de clés."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "Impossible d'importer la paire de clés."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Impossible de créer la paire de clés"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "Impossible de récupérer les zones de disponibilité."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "Impossible de récupérer les limites."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "Impossible de créer le serveur."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "Impossible de récupérer le serveur."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "Impossible d'obtenir les extensions."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Impossible de récupérer les gabarits."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Impossible de récupérer le gabarit."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Impossible de récupérer les paramètres supplémentaires du gabarit."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "L'extension n'est pas activée : %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Impossible d'obtenir la liste des extensions Nova."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "La vérification de la stratégie a échoué."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "Impossible de récupérer les groupes de sécurité."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Chargement..."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "Pas de données disponibles."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "Une erreur s'est produite. Veuillez réessayer ultérieurement."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "Problème de communication avec le serveur, veuillez réessayer."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "Le fichier n'a pu être lu"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "Le mot de passe n'a pu être déchiffré"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "Aucun rôle"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Rôles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Danger :"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Avertissement :"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Notification :"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Succès :"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Erreur :"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "Traitement en cours..."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "Erreur lors de la soumission du formulaire. Veuillez réessayer. "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Aucun"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Supprimer"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUT"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Interfaces"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Supprimer l'Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Ouvrir une Console"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "Voir les détails"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Supprimer un Routeur"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "Voir les Détails du Routeur"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Ajouter une interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Terminer l'Instance"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "Voir les Détails de l'Instance"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Aucun élément à afficher."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "Une erreur s'est produite durant la mise à jour."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "Vous avez sélectionné %s."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "Confirmez %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Merci de confirmer votre sélection."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "Vous n'êtes pas autorisé à effectuer cette opération."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "Les mots de passe ne correspondent pas."
|
|
@ -0,0 +1,507 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Akihiro Motoki <amotoki@gmail.com>, 2014-2015
|
||||||
|
# myamamot <myamamot@redhat.com>, 2014-2015
|
||||||
|
# ykatabam <ykatabam@redhat.com>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-06 23:21+0000\n"
|
||||||
|
"Last-Translator: myamamot <myamamot@redhat.com>\n"
|
||||||
|
"Language-Team: Japanese (http://www.transifex.com/projects/p/horizon/language/ja/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: ja\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "その他"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "ナビゲーション項目"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "表示する %s を選択してください。"
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "パスワードを受け付けられません"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "続行するには、ログインしてください。"
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "%s へのアクセスが許可されていません。"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "名前が \"%(name)s\" の %(resource)s がすでに存在します。"
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "権限がありません: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "認証されていません。もう一度ログインしてください。"
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "不正な形式の IP アドレス"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "無効な IP アドレスのバージョン"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "無効なサブネットマスク"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "送信"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "取り消し"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "セッションがタイムアウトしました。"
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "フィルター"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "この操作は取り消せません。"
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(data_type)sの%(action)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(data_type)sの%(action)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s は許可されていません: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s を実行できません: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "削除"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "削除"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "更新"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "更新日時"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "%(obj)s に 属性 %(attr)s が存在しません。"
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "表示する項目がありません"
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "アクション"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "ID \"%s\" に一致するものがありません。"
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "このアクションを実行する前に、対象を選択してください。"
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/A"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "%(username)s としてログイン中"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "ヘルプ"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "ログアウト"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\n 使用する認証メソッドが不明な場合は、管理者に問い合わせてください。\n "
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "ログイン"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "リソースにアクセスする権限がありません:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "他のユーザーとしてログインするか、<a href=\"%(home_url)s\">ホームページ</a>に戻ってください。"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "ログイン"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "接続"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "ログイン"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "情報: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "警告: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "成功: "
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "エラー: "
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "概要"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "%(counter)s件表示"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« 前へ"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "次へ »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "その他のアクション"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "行の追加"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "利用可能リソース概要"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "インスタンス"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span>%(used)s</span> / <span>%(available)s</span> 使用中"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "仮想 CPU"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "メモリー"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "Floating IP"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(used)s </span> / <span> %(available)s </span> 割り当て済み"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "セキュリティーグループ"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "ボリューム"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "ボリューム容量"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "保存"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "%(nav_items)s件表示"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "%(content_items)s件表示"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "使用状況の概要"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "使用状況を照会する期間を選択してください:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n%(start)s <label>から</label>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n%(end)s <label>まで</label>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "日付は YYYY-mm-dd 形式にする必要があります。"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "稼働中のインスタンス:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "使用中のメモリー:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "指定期間中の仮想 CPU 時間:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "指定期間中の GB 時間:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "指定期間中のメモリー時間"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "戻る"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "次へ"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "制限なし"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "利用可能"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)dB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 バイト"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "子犬を売る"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "子犬を売りました"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Fake"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "なし"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "有効なポート番号ではありません"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "有効な IP プロトコル番号ではありません"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "ポート範囲で使用できるコロンは 1 つだけです。"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "ポート番号は整数でなければなりません"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "文字列に含めることができるのは ASCII 印字可能文字のみです。"
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "処理中..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "利用可能な全項目"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "メンバー"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "利用可能な項目がありません。"
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "メンバーがいません。"
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s が正常に完了しました。"
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s が完了しませんでした。"
|
|
@ -0,0 +1,621 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Akihiro Motoki <amotoki@gmail.com>, 2014-2015
|
||||||
|
# myamamot <myamamot@redhat.com>, 2015
|
||||||
|
# Tom Fifield <tom@openstack.org>, 2015
|
||||||
|
# ykatabam <ykatabam@redhat.com>, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
|
||||||
|
"Last-Translator: ykatabam <ykatabam@redhat.com>\n"
|
||||||
|
"Language-Team: Japanese (http://www.transifex.com/projects/p/horizon/language/ja/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: ja\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "操作を実行できません。この行の内容にエラーがあるか、情報が見つかりません。"
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "詳細情報"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "左の列から右の列にアイテムを移動して、リソースのメタデータを指定できます。左の列には、Glance のメタデータカタログに登録されているメタデータの定義が表示されています。任意のキーのメタデータを追加するには、\"Other\" オプションを使ってください。"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "最小値"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "最大値"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "最小長"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "最大長"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "パターンが一致しません。"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "整数を指定する必要があります。"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "十進数を指定する必要があります。"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "必須"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "重複するキーは使用できません"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "フィルター"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "利用可能なメタデータ"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "選択済みのメタデータ"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "カスタム"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "利用可能なメタデータはありません"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "選択済みのメタデータはありません"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "送信"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "取り消し"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "割り当て済み"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "利用可能"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "1 つ選択してください。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "以下の利用可能なアイテムから 1 つ選択してください。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "利用できるアイテムがありません。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "割り当て済みのアイテムを表示するには展開してください。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "利用可能なアイテムを表示するには展開してください。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "クリックして表示/非表示を切り替えてください。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "順番を並べ替えるには、アイテムをドラッグ&ドロップしてください。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "クリックすると詳しい情報が表示されます。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "%(total)s 中 %(found)s 見つかりました。"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "行を展開してエラーを表示するには、ここをクリックしてください。"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "戻る"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "次へ"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "完了"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "接続中"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "オープン"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "切断中"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "切断済み"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "ステータス: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "はい"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "いいえ"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s バイト"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "%s件表示"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "ボリューム情報を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "ボリュームスナップショットの一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "ユーザーの設定を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "管理者の設定を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "設定を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "設定 %(setting)s が有効になっていません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "イメージ情報を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "イメージ一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "名前空間を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "ユーザー一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "ユーザーを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "ユーザーを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "現在のユーザーセッションを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "ユーザーを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "ユーザーを編集できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "ユーザーを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "ロールを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "ロールを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "ロールを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "ロールを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "ロールを編集できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "ロールを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "ドメイン一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "ドメインを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "ドメインを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "ドメインを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "ドメインを編集できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "ドメインを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "プロジェクト一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "プロジェクトを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "プロジェクトを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "プロジェクトを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "プロジェクトを編集できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "プロジェクトを削除できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "ロールを許可できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "サービスカタログを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "サービスタイプ %(desiredType)s が有効になっていません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "keystone からサービスカタログを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "ネットワーク一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "ネットワークを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "サブネットを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "サブネットを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "ポートを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "キーペアの一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "キーペアをインポートできません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "キーペアを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "アベイラビリティーゾーンを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "上限を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "サーバーを作成できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "サーバーを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "拡張機能を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "フレーバーの一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "フレーバーを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "フレーバーの追加スペックを取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "拡張機能 %(extension)s が有効ではありません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "nova の拡張機能一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "ポリシーチェックに失敗しました。"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "セキュリティーグループの一覧を取得できません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "読み込み中"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "データがありません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "エラーが発生しました。後からもう一度お試しください。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "サーバーとの通信中に問題がありました。再度お試しください。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "ファイルを読み取ることができませんでした。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "パスワードの復号化できませんでした。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "ロールがありません"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "ロール"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "危険:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "警告: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "注意:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "成功: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "エラー: "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "反映中"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "フォームの送信中にエラーが発生しました。再度お試しください。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "なし"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "削除"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "ステータス"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "インターフェース"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "インタフェースの削除"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "コンソールを開く"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "詳細の表示"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "ルーターの削除"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "ルーターの詳細の表示"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "インターフェースの追加"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "インスタンスの終了"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "インスタンスの詳細の表示"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "表示する項目がありません"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "更新中にエラーが発生しました。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "%s を選択しました。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "%sの確認"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "選択内容を確認してください。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "この操作を行う権限がありません。"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "パスワードが一致しません。"
|
|
@ -0,0 +1,508 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
|
||||||
|
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
|
||||||
|
# jaekwon.park <jaekwon.park@rockplace.co.kr>, 2014
|
||||||
|
# Sungjin Kang <potopro@gmail.com>, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-06 17:01+0000\n"
|
||||||
|
"Last-Translator: Sungjin Kang <potopro@gmail.com>\n"
|
||||||
|
"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/horizon/language/ko_KR/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: ko_KR\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "기타"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "네비게이션 항목"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "브라우져에서 %s를 선택하십시오."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "허용되지 않는 비밀번호입니다."
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "계속 진행하려면 로그인하십시오."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "%s에 접근 권한이 없습니다. "
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "이름 \"%(name)s\"과 %(resource)s이 이미 존재합니다."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "권한이 없습니다: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "권한이 없습니다. 다시 로그인 해주십시오."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "IP 주소 형식이 잘못 되었습니다."
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "IP 주소 버전이 잘못되었습니다."
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "서브넷 마스크가 잘못되었습니다."
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "제출"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "취소"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "세션 타임 아웃."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "필터"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "이 작업은 취소할 수 없습니다."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(data_type)s %(action)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(data_type)s %(action)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr " %(action)s 이(가) 허용되지 않습니다: %(objs)s "
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s을(를) 할 수 없습니다.: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "삭제"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "삭제 완료"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "업데이트"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "업데이트 완료"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "%(obj)s 에 %(attr)s 속성이 없습니다."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "표시할 항목이 없습니다."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "작업"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "ID \"%s\" 에 일치되는 항목이 없습니다."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "해당 작업을 실행하기 전에 열(row)을 선택하십시오."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/A"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "로그인됨: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "도움말"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "로그아웃"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\n당신이 인증 방법을 모르는경우, 관리자에게 문의하십시오."
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "로그인"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "당신은 리소스에 접근할 권한이 없습니다.:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "다른 사용자로 로그인하거나 <a href=\"%(home_url)s\">홈페이지</a>로 되돌아가기"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "등록"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "연결"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "로그인"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "정보:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "경고:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "완료:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "오류:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "요약"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "%(counter)s 항목 표시"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« 이전"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "다음 »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "기타 작업"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "열 추가"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "간략한 요약"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "인스턴스"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(available)s </span> 중에서 <span> %(used)s </span> 사용"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPUs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "유동 IP"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "<span> %(available)s </span> 중에서 <span> %(used)s </span> 할당"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "보안 그룹"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "볼륨"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "볼륨 스토리지"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "저장"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "%(nav_items)s 항목 표시"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "%(content_items)s 항목 표시"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "사용량 요약"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "사용량을 조회할 기간을 선택하세요:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n<label>시작:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n<label>끝:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "날짜는 YYYY-mm-dd 형식이어야 합니다."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "동작 중인 인스턴스:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "사용 중인 RAM:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "이 기간의 VCPU 사용 시간:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "이 기간의 GB-Hours 사용 시간:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "이 기간의 RAM 사용 시간:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "뒤로"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "다음"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "제한 없음"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "사용 가능"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d 바이트"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Bytes"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "강아지 판매"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "강아지 판매함"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "페이크"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "없음"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "유효하지 않은 포트 번호"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "유효하지 않은 IP 프로토콜 번호"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "포트 범위에서 콜론은 하나만 사용할 수 있습니다."
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "포트 번호는 정수이어야 합니다."
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "문자열은 ASCII 인쇄 문자를 포함할 수 있습니다."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "작업 중..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "모두 사용가능"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "구성원"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "사용할 수 있는 것이 없음."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "구성원이 없습니다."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s가 성공적으로 완료되었습니다."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s가 완료되지 않았습니다."
|
|
@ -0,0 +1,621 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Ian Y. Choi <ianyrchoi@gmail.com>, 2015
|
||||||
|
# jaekwon.park <jaekwon.park@rockplace.co.kr>, 2014
|
||||||
|
# jaekwon.park <jaekwon.park@rockplace.co.kr>, 2014
|
||||||
|
# Sungjin Kang <potopro@gmail.com>, 2014-2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-11 11:47-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-12 02:21+0000\n"
|
||||||
|
"Last-Translator: Sungjin Kang <potopro@gmail.com>\n"
|
||||||
|
"Language-Team: Korean (Korea) (http://www.transifex.com/projects/p/horizon/language/ko_KR/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: ko_KR\n"
|
||||||
|
"Plural-Forms: nplurals=1; plural=0;\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "작업을 진행할 수 없습니다. 이 행의 내용에 오류가 있거나 정보가 누락되어 있습니다."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "상세 정보"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "왼쪽 열에서 오른쪽 열로 항목을 이동하여 리소스 메타데이터를 지정할 수 있습니다. 왼쪽 열에서 Glance 메타데이터 카탈로그에서 메타데이터를 정의할 수 있습니다. \"Other\" 옵션을 사용하여 선택한 키를 메타데이터에 추가할 수 있습니다."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "최소"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "최대"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "최소 길이"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "최대 길이"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "패턴 불일치"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "정수 필요"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "실수 필요"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "필요"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "중복 키를 허용하지 않습니다."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "필터"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "사용 가능한 메타데이터"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "기존 메타데이터"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "사용자 지정"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "사용 가능한 메타데이터가 없습니다."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "기존 메타데이터가 없습니다."
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "제출"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "취소"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "할당됨"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "사용 가능"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "하나 선택"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "아래의 사용 가능한 항목에서 항목을 선택"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "사용 가능한 항목 없음"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "할당된 항목을 볼 수 있도록 확장"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "사용 가능한 항목을 볼 수 있도록 확장"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "클릭하여 보기 또는 감추기"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "다시 주문할 아이템은 드래그 앤 드롭으로 가져옵니다."
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "클릭하여 세부 사항 보기"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "%(total)s 의 %(found)s 찾기"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "클릭하여 열을 확장하고 에러를 확인할 수 있습니다."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "뒤로"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "다음"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "완료"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "연결중"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "열림"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "닫는중"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "닫음"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "상태: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "예"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "아니오"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s 바이트"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "%s 항목 표시"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "볼륨을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "볼륨 스냅샷을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "사용자 구성을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "관리자 구성을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "설정을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "설정을 활성하지 못했습니다: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "이미지를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "이미지를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "네임스페이스를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "사용자를 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "사용자를 만들지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "사용자를 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "현재 사용자 섹션을 찾을 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "사용자를 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "사용자를 수정하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "사용자를 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Role을 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Role을 만들지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Role을 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Role을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Role을 수정하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Role을 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "도메인을 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "도메인을 만들지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "도메인을 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "도메인을 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "도메인을 수정하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "도메인을 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "프로젝트를 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "프로젝트를 만들지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "프로젝트를 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "프로젝트를 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "프로젝트를 수정하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "프로젝트를 삭제하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Role을 부여하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "서비스 카탈로그를 가져올 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "서비스 타입을 활성화하지 못 했습니다: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Keystone으로부터 서비스 목록을 가져올 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "네트워크를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "네트워크를 생성하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "서브넷을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "서브넷을 생성하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "포트를 검색하지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Keypair를 찾을 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "키페어를 가져올 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Keypair를 생성하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "가용성 존에 대한 정보를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "제한을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "서버를 생성하지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "서버를 찾지 못했습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "확장을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Flavor를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Flavor를 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Flavor 확장 스팩을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "확장을 활성화하지 못 했습니다: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Nova 확장 목록을 가져올 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "정책 확인 실패함."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "시큐리티 그룹을 찾지 못했습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "불러오는 중"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "데이터가 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "오류가 발생했습니다. 나중에 다시 시도하십시오."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "서버와의 통신에 문제가 발생하였으니, 다시 시도하세요."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "파일을 읽을 수 없습니다"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "암호를 해독할 수 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "Role 없음"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Roles"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "위험:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "경고:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "주의:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "완료:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "오류:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "작동 중"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "양식을 제출하는 동안 오류가 발생하였습니다. 다시 시도하세요."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "None"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "삭제"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUS"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "인터페이스"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "인터페이스 삭제"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "콘솔 열기"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "세부 정보 보기"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "라우터 삭제"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "라우터 정보 자세히 보기"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "인터페이스 추가"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "인스턴스 종료"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "인스턴스 정보 자세히 보기"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "표시할 항목이 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "업데이트 도중 오류가 발생하였습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "%s를 선택하였습니다. "
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "%s를 확인하세요."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "선택 사항을 확인하십시오."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "이 작업에 대한 권한이 없습니다."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "비밀번호가 일치하지 않습니다."
|
|
@ -0,0 +1,517 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Łukasz Jernaś <deejay1@srem.org>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-07 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-21 12:54+0000\n"
|
||||||
|
"Last-Translator: Łukasz Jernaś <deejay1@srem.org>\n"
|
||||||
|
"Language-Team: Polish (Poland) (http://www.transifex.com/projects/p/horizon/language/pl_PL/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: pl_PL\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Inne"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Pozycja nawigacyjna"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Wybierz %s by przeglądać."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Hasło nie zostało zaakceptowane"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Należy się zalogować, aby można było kontynuować."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "Brak uprawnień dostępu do %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "%(resource)s o nazwie \"%(name)s\" już istnieje."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "Nieupoważniono: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Brak uprawnień. Proszę spróbować się wylogować i zalogować ponownie."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Błędny format adresu IP"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Błędna wersja adresu IP"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Błędna maska podsieci"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Wyślij"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Anuluj"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "Sesja wygasła."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtr"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "Tej czynności nie można cofnąć."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "Brak uprawnień do %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Nie można %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Usuń"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Usunięto"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Aktualizuj"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Zaktualizowany"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "Atrybut %(attr)s nie istnieje dla %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Brak pozycji do wyświetlenia."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Czynności"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "Nie znaleziono wyników dla identyfikatora „%s”."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Należy wybrać wiersz przed wykonaniem tej czynności."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "n.d."
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Zalogowano jako: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Pomoc"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Wyloguj"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\nJeśli nie wiadomo, którego sposobu uwierzytelniania użyć, należy skontaktować się z administratorem."
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Zaloguj"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "Brak uprawnień do zasobu:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Należy zalogować się jako inny użytkownik lub wrócić do <a href=\"%(home_url)s\"> strony domowej</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Wpisz się"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Połącz"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Login"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Informacja:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Ostrzeżenie:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Powodzenie:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Błąd:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Podsumowanie"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Wyświetlanie %(counter)s pozycji"
|
||||||
|
msgstr[1] "Wyświetlanie %(counter)s pozycji"
|
||||||
|
msgstr[2] "Wyświetlanie %(counter)s pozycji"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Poprzednie"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Następne »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "Pozostałe akcje"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Dodaj wiersz"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Podsumowanie limitów"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instancje"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Wykorzystano <span> %(used)s </span> z <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPU"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "Pływające adresy IP"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Przydzielono <span> %(used)s </span> z <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Grupy zabezpieczeń"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Wolumeny"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Przechowywanie danych"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Zapisz"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Wyświetlanie %(nav_items)s pozycję"
|
||||||
|
msgstr[1] "Wyświetlanie %(nav_items)s pozycji"
|
||||||
|
msgstr[2] "Wyświetlanie %(nav_items)s pozycji"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Wyświetlanie %(content_items)s pozycji"
|
||||||
|
msgstr[1] "Wyświetlanie %(content_items)s pozycji"
|
||||||
|
msgstr[2] "Wyświetlanie %(content_items)s pozycji"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Podsumowanie wykorzystania"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Wybierz okres, za który należy wyświetlić wykorzystanie:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n<label>Od:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>Do:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "Data powinna być podana w formacie YYYY-mm-dd."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Aktywne instancje:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "Aktywna pamięć RAM:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "VCPU-godziny w tym okresie:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "GB-godziny w tym okresie:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "RAM-godziny w tym okresie:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Wstecz"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Następny"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "Bez ograniczeń"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Dostępne"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d bajt"
|
||||||
|
msgstr[1] "%(size)d bajty"
|
||||||
|
msgstr[2] "%(size)d bajtów"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 bajtów"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "Sprzedaj pieska"
|
||||||
|
msgstr[1] "Sprzedaj pieski"
|
||||||
|
msgstr[2] "Sprzedaj pieski"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "Sprzedano pieska"
|
||||||
|
msgstr[1] "Sprzedano pieski"
|
||||||
|
msgstr[2] "Sprzedano pieski"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Fałszywe"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Nigdy"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Błędny numer portu"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Błędny numer protokołu IP"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "Tylko jeden dwukropek jest dozwolony w zakresie portów"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Numer portu musi być liczbą całkowitą"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "Ciąg może zawierać wyłącznie drukowalne znaki ASCII."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Przetwarzanie…"
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "Całość dostępna"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Członkowie"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "Całość wykorzystana"
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "Brak członków."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s zakończona pomyślnie."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s nie została zakończona."
|
|
@ -0,0 +1,620 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Łukasz Jernaś <deejay1@srem.org>, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 01:16-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-20 07:06+0000\n"
|
||||||
|
"Last-Translator: Łukasz Jernaś <deejay1@srem.org>\n"
|
||||||
|
"Language-Team: Polish (Poland) (http://www.transifex.com/projects/p/horizon/language/pl_PL/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: pl_PL\n"
|
||||||
|
"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "Nie można wykonać czynności. Zawartość tego wiersza zawiera błędy lub brakujące dane."
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Szczegółowe informacje"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "Można podać metadane zasobów przesuwając pozycje z lewej kolumny do prawej. W lewej kolumnie znajdują się definicje metadanych z Katalogu Metadanych Glance. Można podać metadane z własnym kluczem, po wybraniu opcji „Inne”."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Min."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Maks."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Min. długość"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Maks. długość"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Szablon się nie zgadza"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Wymagana liczba całkowita"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Wymagana liczba dziesiętna"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Wymagane"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Ponowne użycie nazwy klucza jest niedozwolone."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtr"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Dostępne metadane"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Istniejące metadane"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Własne"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "Metadane niedostępne"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "Brak metadanych"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Wyślij"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Anuluj"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Przydzielono"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Dostępne"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Wybierz jedno"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Należy wybrać pozycję z listy dostępnych pozycji poniżej"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "Brak dostępnych pozycji"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Rozszerz, aby zobaczyć przydzielone pozycje"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Rozszerz, aby zobaczyć dostepne pozycje"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Kliknij by pokazać lub ukryć"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "Zmień kolejność pozycji poprzez przesuwanie i upuszczanie"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Kliknij, aby wyświetlić szczegóły"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "Znaleziono %(found)s z %(total)s"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Kliknij tutaj, aby rozszerzyć wiersz i wyświetlić błędy."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Wstecz"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Następny"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Zakończ"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "Łączenie"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Otwórz"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "Zamykanie"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Zamknięto"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "Stan: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Tak"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "Nie"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s bajtów"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Wyświetlanie %s pozycji"
|
||||||
|
msgstr[1] "Wyświetlanie %s pozycji"
|
||||||
|
msgstr[2] "Wyświetlanie %s pozycji"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "Nie można pobrać wolumenów."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "Nie można pobrać migawek wolumenów."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "Nie można pobrać ustawień użytkownika."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "Nie można pobrać ustawień administratora."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "Nie można pobrać ustawień."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Ustawienie jest wyłączone: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "Nie można pobrać obrazu."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "Nie można pobrać obrazów"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "Nie można pobrać przestrzeni nazw."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "Nie można pobrać użytkowników"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "Nie można utworzyć użytkownika."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "Nie mozna usunąć użytkowników."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "Nie można pobrać bieżącej sesji użytkownika."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "Nie można pobrać użytkownika"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "Nie można edytować użytkownika."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "Nie można usunąć użytkownika."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Nie można pobrać roli"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Nie można utworzyć roli."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Nie mozna usunąć ról."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Nie można pobrać roli"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Nie można edytować roli."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Nie można usunąć roli."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "Nie można pobrać domen"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "Nie można utworzyć domeny."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "Nie można usunąć domen."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "Nie można pobrać domeny"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "Nie można edytować domeny."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "Nie można usunąć domeny."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "Nie można pobrać projektów"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "Nie można utworzyć projektu."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "Nie można usunąć projektów."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "Nie można pobrać projektu."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "Nie można edytować projektu."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "Nie można usunąć projektu."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Nie można nadać roli."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "Nie można pobrać katalogu usług."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Typ usługi nie jest włączony: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Nie można pobrać katalogu usług z keystone."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "Nie można pobrać sieci."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "Nie można utworzyć sieci."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "Nie można pobrać podsieci."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "Nie można utworzyć podsieci."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "Nie można pobrać portów."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Nie można pobrać par kluczy."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "Nie można zaimportować pary kluczy."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Nie można utworzyć pary kluczy."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "Nie można pobrać stref dostępności."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "Nie można pobrać ograniczeń."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "Nie można utworzyć serwera."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "Nie można pobrać serwera."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "Nie można pobrać rozszerzeń."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Nie można pobrać odmian."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Nie można pobrać odmiany."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Nie można pobrać dodatkowych danych odmiany."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "Rozszerzenie nie jest włączone: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Nie można pobrać listy rozszerzeń novy."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "Sprawdzenie polityki się nie powiodło."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "Nie można pobrać grup zabezpieczeń."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Wczytywanie"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "Brak dostępnych danych."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "Wystąpił błąd. Proszę spróbować później."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "Wystąpił problem w komunikacji z serwerem, proszę spróbować ponownie."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "Nie można odczytać pliku"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "Nie można odszyfrować hasła"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "Brak ról"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Role"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Zagrożenie:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Ostrzeżenie:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Powiadomienie:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Powodzenie:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Błąd:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "Praca"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "Wystąpił błąd podczas wysyłania formularza. Proszę spróbować ponownie."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Brak"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Usuń"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUS"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Interfejsy"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Usuń interfejs"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Otwórz konsolę"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "Wyświetl szczegóły"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Usuń router"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "Wyświetl szczegóły routera"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Dodaj interfejs"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Zniszcz instancję"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "Wyświetl szczegóły instancji"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Brak pozycji do wyświetlenia."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "Wystąpił błąd podczas aktualizacji."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "Wybrano %s."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "Potwierdź %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Należy potwierdzić wybór."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "Nie upoważniono do przeprowadzenie tej czynności."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "Hasła nie pasują"
|
|
@ -0,0 +1,514 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Fernando F. Rodrigues <rodrigues_fernando@hotmail.com>, 2015
|
||||||
|
# Gabriel Wainer, 2015
|
||||||
|
# Remulo Carvalho <remulo@gmail.com>, 2015
|
||||||
|
# Rodrigo Felix de Almeida <rodrigofelixdealmeida@gmail.com>, 2014
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-06 21:07-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-07 15:52+0000\n"
|
||||||
|
"Last-Translator: Remulo Carvalho <remulo@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/horizon/language/pt_BR/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Outro"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Item de Navegação"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Selecione um %s para navegar."
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Senha não foi aceita"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Por favor faça login para continuar."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "Você não está autorizado a acessar %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "Um %(resource)s com o nome \"%(name)s\" já existe."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "Não autorizado: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Não autorizado. Por favor tente efetuar login novamente."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Formato incorreto para o endereço IP"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Versão inválida para o endereço IP"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Máscara de sub-rede inválida"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Enviar"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancelar"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "Tempo limite da sessão esgotou."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtro"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "Esta ação não pode ser desfeita."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "Você não possui permissão para %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Não foi possível %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Excluir"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Excluído"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Atualizar"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Atualizado"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "O atributo %(attr)s não existe em %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Sem itens para exibir."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Ações"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "Não foi encontrada correspondência para o ID \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Por favor selecione uma linha antes de realizar esta ação."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "N/A"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Logado como: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Ajuda"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Sair"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\nSe você não tem certeza do método de autenticação a ser utilizado, entre em contato com o seu administrador."
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Entrar"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "Você não tem permissão para acessar o recurso:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Faça login com um usuário diferente ou volte para a <a href=\"%(home_url)s\"> página inicial</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Entrar"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Conectado"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Logar"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Informação:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Alerta:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Sucesso:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Erro:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Resumo"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Exibindo %(counter)s item"
|
||||||
|
msgstr[1] "Exibindo %(counter)s itens"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Prev"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Next »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "Mais Ações"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Adicionar uma linha"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Resumo de Limites"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Instâncias"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Utilizado <span> %(used)s </span> de <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "vCPUs"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "IPs Flutuantes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Alocado <span> %(used)s </span> de <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Grupos de Segurança"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Volumes"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Armazenamento de volume"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Salvar"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Exibindo %(nav_items)s item"
|
||||||
|
msgstr[1] "Exibindo %(nav_items)s itens"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Exibindo %(content_items)s item"
|
||||||
|
msgstr[1] "Exibindo %(content_items)s itens"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Resumo de Utilização"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Selecione um período de tempo para consultar seu uso:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n <label>De:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>Para:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "A data deve estar no formato YYYY-mm-dd."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Instâncias ativas:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "RAM ativa:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "VCPU-Horas desse Período:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "GB-Horas desse Período:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "Quantidade de RAM-Horas deste período:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Voltar"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Próximo"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "Sem Limite"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Disponível"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d Byte"
|
||||||
|
msgstr[1] "%(size)d Bytes"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s PB"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 Bytes"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] "Vender Puppy"
|
||||||
|
msgstr[1] "Vender Puppies"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] "Puppy Vendido"
|
||||||
|
msgstr[1] "Puppies Vendidos"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Falso"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Nunca"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Não é um número de porta válido"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Não é um número de protocolo IP válido"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "Uma pontução de dois pontos permitida no intervalo de portas"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Número de porta deve ser inteiro"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "A string somente deve conter caracteres ASCII imprimíveis."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Processando..."
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "Tudo disponível"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Membros"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "Nenhum disponível."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "Sem membros."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s concluído com sucesso."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s não completou."
|
|
@ -0,0 +1,622 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Fernando Pimenta <fernando.c.pimenta@gmail.com>, 2015
|
||||||
|
# Gabriel Wainer, 2015
|
||||||
|
# maurosr <maurosmrodrigues@gmail.com>, 2015
|
||||||
|
# Rodrigo Felix de Almeida <rodrigofelixdealmeida@gmail.com>, 2014
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 16:36-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-14 01:40+0000\n"
|
||||||
|
"Last-Translator: Fernando Pimenta <fernando.c.pimenta@gmail.com>\n"
|
||||||
|
"Language-Team: Portuguese (Brazil) (http://www.transifex.com/projects/p/horizon/language/pt_BR/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: pt_BR\n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1);\n"
|
||||||
|
|
||||||
|
#: static/angular/action-list/button-tooltip.js:15
|
||||||
|
msgid ""
|
||||||
|
"The action cannot be performed. The contents of this row have errors or are "
|
||||||
|
"missing information."
|
||||||
|
msgstr "A ação não pôde ser executada. O conteúdo desta linha possui erros ou informações indisponíveis"
|
||||||
|
|
||||||
|
#: static/angular/metadata-display/metadata-display.js:33
|
||||||
|
msgid "Detail Information"
|
||||||
|
msgstr "Informações delhadas"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:35
|
||||||
|
msgid ""
|
||||||
|
"You can specify resource metadata by moving items from the left column to "
|
||||||
|
"the right column. In the left columns there are metadata definitions from "
|
||||||
|
"the Glance Metadata Catalog. Use the \"Other\" option to add metadata with "
|
||||||
|
"the key of your choice."
|
||||||
|
msgstr "Você pode especificar metadados de recurso movendo itens da coluna da esquerda para a coluna da direita. Nas colunas da esquerda existem definições de metadados do catálogo de metadados do Glance. Utilize a opção \"Outros\" para adicionar metadados com a chave de sua escolha."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:36
|
||||||
|
msgid "Min"
|
||||||
|
msgstr "Mín. "
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:37
|
||||||
|
msgid "Max"
|
||||||
|
msgstr "Máx."
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:38
|
||||||
|
msgid "Min length"
|
||||||
|
msgstr "Tamanho mínimo"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:39
|
||||||
|
msgid "Max length"
|
||||||
|
msgstr "Tamanho máximo"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:40
|
||||||
|
msgid "Pattern mismatch"
|
||||||
|
msgstr "Incompatibilidade de padrão"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:41
|
||||||
|
msgid "Integer required"
|
||||||
|
msgstr "Inteiro requerido"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:42
|
||||||
|
msgid "Decimal required"
|
||||||
|
msgstr "Decimal requerido"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:43
|
||||||
|
msgid "Required"
|
||||||
|
msgstr "Requerido"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:44
|
||||||
|
msgid "Duplicate keys are not allowed"
|
||||||
|
msgstr "Chaves duplicadas não são permitidas"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:45
|
||||||
|
#: static/angular/table/basic-table.js:6
|
||||||
|
#: static/horizon/js/horizon.forms.js:184
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Filtro"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:46
|
||||||
|
msgid "Available Metadata"
|
||||||
|
msgstr "Metadados disponível"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:47
|
||||||
|
msgid "Existing Metadata"
|
||||||
|
msgstr "Metadados existente"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:48
|
||||||
|
msgid "Custom"
|
||||||
|
msgstr "Customizado"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:49
|
||||||
|
msgid "No available metadata"
|
||||||
|
msgstr "Metadados não disponível"
|
||||||
|
|
||||||
|
#: static/angular/metadata-tree/metadata-tree.js:50
|
||||||
|
msgid "No existing metadata"
|
||||||
|
msgstr "Metadados não existente"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:83
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Enviar"
|
||||||
|
|
||||||
|
#: static/angular/modal/modal.js:84 static/angular/wizard/wizard.js:11
|
||||||
|
#: static/horizon/js/horizon.modals.js:33
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Cancelar"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:39
|
||||||
|
msgid "Allocated"
|
||||||
|
msgstr "Alocado"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:40
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Disponível"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:41
|
||||||
|
msgid "Select one"
|
||||||
|
msgstr "Selecione"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:42
|
||||||
|
msgid "Select an item from Available items below"
|
||||||
|
msgstr "Selecione um item dentre os disponíveis abaixo"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:43
|
||||||
|
msgid "No available items"
|
||||||
|
msgstr "Itens indisponíveis"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:44
|
||||||
|
msgid "Expand to see allocated items"
|
||||||
|
msgstr "Expanda para ver os itens alocados"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:45
|
||||||
|
msgid "Expand to see available items"
|
||||||
|
msgstr "Expanda para ver os itens disponíveis"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:46
|
||||||
|
msgid "Click to show or hide"
|
||||||
|
msgstr "Clique para exibir ou esconder"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:47
|
||||||
|
msgid "Re-order items using drag and drop"
|
||||||
|
msgstr "reordene os itens clicando em um deles e arrastando-o"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:48
|
||||||
|
msgid "Click to see more details"
|
||||||
|
msgstr "Clique para ver mais detalhes."
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:100
|
||||||
|
msgid "Found %(found)s of %(total)s"
|
||||||
|
msgstr "Encontrado %(found)s de %(total)s"
|
||||||
|
|
||||||
|
#: static/angular/transfer-table/transfer-table.js:166
|
||||||
|
msgid "Click here to expand the row and view the errors."
|
||||||
|
msgstr "Clique aqui para expandir a linha e ver os erros."
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:12
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Voltar"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:13
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Próximo"
|
||||||
|
|
||||||
|
#: static/angular/wizard/wizard.js:14
|
||||||
|
msgid "Finish"
|
||||||
|
msgstr "Encerrar"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Connecting"
|
||||||
|
msgstr "Conectando"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Open"
|
||||||
|
msgstr "Abrir"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closing"
|
||||||
|
msgstr "Encerramento"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:23
|
||||||
|
msgid "Closed"
|
||||||
|
msgstr "Encerrado"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/directives/serialConsole.js:85
|
||||||
|
#, c-format
|
||||||
|
msgid "Status: %s"
|
||||||
|
msgstr "Status: %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "Yes"
|
||||||
|
msgstr "Sim"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:37
|
||||||
|
msgid "No"
|
||||||
|
msgstr "Não"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:53
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:140
|
||||||
|
#, c-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s GB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:70
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:142
|
||||||
|
#, c-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s MB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:138
|
||||||
|
#, c-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s TB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:144
|
||||||
|
#, c-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s KB"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:146
|
||||||
|
#, c-format
|
||||||
|
msgid "%s bytes"
|
||||||
|
msgstr "%s bytes"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/filters/filters.js:163
|
||||||
|
#: static/horizon/js/horizon.tables.js:393
|
||||||
|
#, c-format
|
||||||
|
msgid "Displaying %s item"
|
||||||
|
msgid_plural "Displaying %s items"
|
||||||
|
msgstr[0] "Exibindo %s item"
|
||||||
|
msgstr[1] "Exibindo %s itens"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:47
|
||||||
|
msgid "Unable to retrieve volumes."
|
||||||
|
msgstr "Não foi possível obter a lista de volumes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.cinder.js:74
|
||||||
|
msgid "Unable to retrieve volume snapshots."
|
||||||
|
msgstr "Não é possível recuperar snapshots de volume."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:43
|
||||||
|
msgid "Unable to retrieve user configuration."
|
||||||
|
msgstr "Não foi possível recuperar as configurações de usuário"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:62
|
||||||
|
msgid "Unable to retrieve admin configuration."
|
||||||
|
msgstr "Não foi possível recuperar as configurações de administrador"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:105
|
||||||
|
msgid "Unable to retrieve settings."
|
||||||
|
msgstr "Não é possível recuperar as configurações."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.config.js:289
|
||||||
|
msgid "Setting is not enabled: %(setting)s"
|
||||||
|
msgstr "Configuração não está habilitada: %(setting)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:38
|
||||||
|
msgid "Unable to retrieve image."
|
||||||
|
msgstr "Não foi possível recuperar a imagem."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:81
|
||||||
|
msgid "Unable to retrieve images."
|
||||||
|
msgstr "Não foi possível obter as imagems"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.glance.js:144
|
||||||
|
msgid "Unable to retrieve namespaces."
|
||||||
|
msgstr "Não é possível recuperar namespaces."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:24
|
||||||
|
msgid "Unable to retrieve users"
|
||||||
|
msgstr "Não foi possível obter os usuários"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:31
|
||||||
|
msgid "Unable to create the user."
|
||||||
|
msgstr "Não foi possível criar o usuário."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:38
|
||||||
|
msgid "Unable to delete the users."
|
||||||
|
msgstr "Não foi possível excluir os usuários."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:73
|
||||||
|
msgid "Unable to retrieve the current user session."
|
||||||
|
msgstr "Não é possível recuperar a sessão atual do usuário."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:80
|
||||||
|
msgid "Unable to retrieve the user"
|
||||||
|
msgstr "Não foi possível obter o usuário"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:88
|
||||||
|
msgid "Unable to edit the user."
|
||||||
|
msgstr "Não foi possível editar o usuário."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:95
|
||||||
|
msgid "Unable to delete the user."
|
||||||
|
msgstr "Não foi possível excluir o usuário."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:103
|
||||||
|
msgid "Unable to retrieve role"
|
||||||
|
msgstr "Não foi possível obter o papel"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:110
|
||||||
|
msgid "Unable to create the role."
|
||||||
|
msgstr "Não foi possível criar o papel."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:117
|
||||||
|
msgid "Unable to delete the roles."
|
||||||
|
msgstr "Não foi possível excluir os papéis."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:124
|
||||||
|
msgid "Unable to retrieve the role"
|
||||||
|
msgstr "Não foi possível obter o papel"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:132
|
||||||
|
msgid "Unable to edit the role."
|
||||||
|
msgstr "Não foi possível editar o papel."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:139
|
||||||
|
msgid "Unable to delete the role."
|
||||||
|
msgstr "Não foi possível excluir o papel."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:147
|
||||||
|
msgid "Unable to retrieve domains"
|
||||||
|
msgstr "Não foi possível obter os domínios"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:154
|
||||||
|
msgid "Unable to create the domain."
|
||||||
|
msgstr "Não foi possível criar o domínio."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:161
|
||||||
|
msgid "Unable to delete the domains."
|
||||||
|
msgstr "Não foi possível excluir os domínios."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:168
|
||||||
|
msgid "Unable to retrieve the domain"
|
||||||
|
msgstr "Não foi possível obter o domínio"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:176
|
||||||
|
msgid "Unable to edit the domain."
|
||||||
|
msgstr "Não foi possível editar o domínio."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:183
|
||||||
|
msgid "Unable to delete the domain."
|
||||||
|
msgstr "Não foi possível excluir o domínio."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:192
|
||||||
|
msgid "Unable to retrieve projects"
|
||||||
|
msgstr "Não foi possível obter os projetos"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:199
|
||||||
|
msgid "Unable to create the project."
|
||||||
|
msgstr "Não foi possível criar o projeto."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:206
|
||||||
|
msgid "Unable to delete the projects."
|
||||||
|
msgstr "Não foi possível excluir os projetos."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:213
|
||||||
|
msgid "Unable to retrieve the project"
|
||||||
|
msgstr "Não foi possível obter o projeto"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:221
|
||||||
|
msgid "Unable to edit the project."
|
||||||
|
msgstr "Não foi possível editar o projeto."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:228
|
||||||
|
msgid "Unable to delete the project."
|
||||||
|
msgstr "Não foi possível excluir o projeto."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:236
|
||||||
|
msgid "Unable to grant the role."
|
||||||
|
msgstr "Não foi possível permitir o papel."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:250
|
||||||
|
msgid "Unable to fetch the service catalog."
|
||||||
|
msgstr "Não é possível obter o catálogo de serviços."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:385
|
||||||
|
msgid "Service type is not enabled: %(desiredType)s"
|
||||||
|
msgstr "Tipo de serviço não está habilitado: %(desiredType)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.keystone.js:392
|
||||||
|
msgid "Cannot get service catalog from keystone."
|
||||||
|
msgstr "Não é possível obter o catálogo de serviços a partir do keystone."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:39
|
||||||
|
msgid "Unable to retrieve networks."
|
||||||
|
msgstr "Não é possível recuperar redes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:88
|
||||||
|
msgid "Unable to create the network."
|
||||||
|
msgstr "Não foi possível criar a rede"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:108
|
||||||
|
msgid "Unable to retrieve subnets."
|
||||||
|
msgstr "Não é possível recuperar a subredes."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:172
|
||||||
|
msgid "Unable to create the subnet."
|
||||||
|
msgstr "Não foi possível criar sub-rede"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.neutron.js:192
|
||||||
|
msgid "Unable to retrieve ports."
|
||||||
|
msgstr "Não foi possível obter lista de portas."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:40
|
||||||
|
msgid "Unable to retrieve keypairs."
|
||||||
|
msgstr "Não é possível recuperar pares de chaves."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:62
|
||||||
|
msgid "Unable to import the keypair."
|
||||||
|
msgstr "Não é possível importar o par de chaves."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:64
|
||||||
|
msgid "Unable to create the keypair."
|
||||||
|
msgstr "Não foi possível criar o par de chaves."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:83
|
||||||
|
msgid "Unable to retrieve availability zones."
|
||||||
|
msgstr "Não é possível recuperar todas zonas de disponibilidade."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:121
|
||||||
|
msgid "Unable to retrieve limits."
|
||||||
|
msgstr "Não foi possível obter informações de limite."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:148
|
||||||
|
msgid "Unable to create the server."
|
||||||
|
msgstr "Não foi possível criar a instância"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:162
|
||||||
|
msgid "Unable to retrieve server."
|
||||||
|
msgstr "Não é possível recuperar servidor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:192
|
||||||
|
msgid "Unable to retrieve extensions."
|
||||||
|
msgstr "Não é possível recuperar extensões."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:237
|
||||||
|
msgid "Unable to retrieve flavors."
|
||||||
|
msgstr "Não foi possível recuperar flavors."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:255
|
||||||
|
msgid "Unable to retrieve flavor."
|
||||||
|
msgstr "Não é possível recuperar flavor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:269
|
||||||
|
msgid "Unable to retrieve flavor extra specs."
|
||||||
|
msgstr "Não é possível recuperar especificações extras do flavor."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:311
|
||||||
|
msgid "Extension is not enabled: %(extension)s"
|
||||||
|
msgstr "Extensão não está habilitada: %(extension)s"
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.nova.js:318
|
||||||
|
msgid "Cannot get nova extension list."
|
||||||
|
msgstr "Não é possível obter lista de extensão do nova."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.policy.js:65
|
||||||
|
msgid "Policy check failed."
|
||||||
|
msgstr "Verificação de política falhou."
|
||||||
|
|
||||||
|
#: static/horizon/js/angular/services/hz.api.security-group.js:64
|
||||||
|
msgid "Unable to retrieve security groups."
|
||||||
|
msgstr "Não é possível recuperar grupos de segurança"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.accordion_nav.js:78
|
||||||
|
#: static/horizon/js/horizon.modals.js:315
|
||||||
|
#: static/horizon/js/horizon.tabs.js:21
|
||||||
|
msgid "Loading"
|
||||||
|
msgstr "Carregando"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:394
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:404
|
||||||
|
msgid "No data available."
|
||||||
|
msgstr "Não há dados disponíveis."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.d3linechart.js:410
|
||||||
|
#: static/horizon/js/horizon.modals.js:334
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:94
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:157
|
||||||
|
msgid "An error occurred. Please try again later."
|
||||||
|
msgstr "Um erro ocorreu. Por favor tente novamente mais tarde."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.firewalls.js:32
|
||||||
|
#: static/horizon/js/horizon.instances.js:31
|
||||||
|
msgid "There was a problem communicating with the server, please try again."
|
||||||
|
msgstr "Houve um problema ao comunicar-se com o servidor, por favor tente novamente."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:273
|
||||||
|
msgid "Could not read the file"
|
||||||
|
msgstr "Não foi possivel ler o arquivo"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.instances.js:279
|
||||||
|
#: static/horizon/js/horizon.instances.js:308
|
||||||
|
msgid "Could not decrypt the password"
|
||||||
|
msgstr "Não foi possível descriptografar a senha"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:190
|
||||||
|
msgid "No roles"
|
||||||
|
msgstr "Sem papéis"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.membership.js:222
|
||||||
|
msgid "Roles"
|
||||||
|
msgstr "Papéis"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:9
|
||||||
|
msgid "Danger: "
|
||||||
|
msgstr "Perigo:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:10
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Alerta:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:11
|
||||||
|
msgid "Notice: "
|
||||||
|
msgstr "Notificação:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:12
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Sucesso:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.messages.js:13
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Erro:"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:229
|
||||||
|
#: static/horizon/js/horizon.tables.js:218
|
||||||
|
msgid "Working"
|
||||||
|
msgstr "Trabalhando"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.modals.js:263
|
||||||
|
msgid "There was an error submitting the form. Please try again."
|
||||||
|
msgstr "Houve um erro ao enviar o formulário. Por favor tente novamente."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:530
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:536
|
||||||
|
msgid "None"
|
||||||
|
msgstr "Nenhum"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:549
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Excluir"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:552
|
||||||
|
msgid "STATUS"
|
||||||
|
msgstr "STATUS"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:553
|
||||||
|
msgid "ID"
|
||||||
|
msgstr "ID"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:554
|
||||||
|
msgid "Interfaces"
|
||||||
|
msgstr "Interfaces"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:555
|
||||||
|
msgid "Delete Interface"
|
||||||
|
msgstr "Excluir Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:556
|
||||||
|
msgid "Open Console"
|
||||||
|
msgstr "Abrir Console"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:557
|
||||||
|
msgid "View Details"
|
||||||
|
msgstr "Ver Detalhes"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:560
|
||||||
|
msgid "Delete Router"
|
||||||
|
msgstr "Excluir Roteador"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:561
|
||||||
|
msgid "View Router Details"
|
||||||
|
msgstr "Ver Detalhes do Roteador"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:564
|
||||||
|
msgid "Add Interface"
|
||||||
|
msgstr "Adicionar Interface"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:570
|
||||||
|
msgid "Terminate Instance"
|
||||||
|
msgstr "Terminar Instância"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.networktopology.js:571
|
||||||
|
msgid "View Instance Details"
|
||||||
|
msgstr "Ver Detalhes da Instância"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:39
|
||||||
|
#: static/horizon/js/horizon.tables.js:406
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Sem itens para exibir."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:52
|
||||||
|
#: static/horizon/js/horizon.tables.js:120
|
||||||
|
msgid "An error occurred while updating."
|
||||||
|
msgstr "Um erro ocorreu ao atualizar."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:201
|
||||||
|
#, c-format
|
||||||
|
msgid "You have selected %s. "
|
||||||
|
msgstr "Você selecionou %s."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:203
|
||||||
|
#, c-format
|
||||||
|
msgid "Confirm %s"
|
||||||
|
msgstr "Confirma %s"
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables.js:204
|
||||||
|
msgid "Please confirm your selection. "
|
||||||
|
msgstr "Por favor confirme a sua seleção."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:88
|
||||||
|
#: static/horizon/js/horizon.tables_inline_edit.js:151
|
||||||
|
msgid "Not authorized to do this operation."
|
||||||
|
msgstr "Não autorizado para realizar esta operação."
|
||||||
|
|
||||||
|
#: static/horizon/js/horizon.users.js:18
|
||||||
|
msgid "Passwords do not match."
|
||||||
|
msgstr "As senhas não conferem."
|
|
@ -0,0 +1,518 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
#
|
||||||
|
# Translators:
|
||||||
|
# Ilya Alekseyev <ilyaalekseyev@acm.org>, 2015
|
||||||
|
# Nikita Burtsev, 2015
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: Horizon\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2015-04-12 16:36-0500\n"
|
||||||
|
"PO-Revision-Date: 2015-04-13 12:42+0000\n"
|
||||||
|
"Last-Translator: Ilya Alekseyev <ilyaalekseyev@acm.org>\n"
|
||||||
|
"Language-Team: Russian (http://www.transifex.com/projects/p/horizon/language/ru/)\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: ru\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"
|
||||||
|
|
||||||
|
#: base.py:475
|
||||||
|
msgid "Other"
|
||||||
|
msgstr "Другое"
|
||||||
|
|
||||||
|
#: browsers/base.py:88
|
||||||
|
msgid "Navigation Item"
|
||||||
|
msgstr "Элемент навигации"
|
||||||
|
|
||||||
|
#: browsers/views.py:41
|
||||||
|
#, python-format
|
||||||
|
msgid "Select a %s to browse."
|
||||||
|
msgstr "Выберите %s для просмотра"
|
||||||
|
|
||||||
|
#: conf/default.py:41
|
||||||
|
msgid "Password is not accepted"
|
||||||
|
msgstr "Пароль не принят"
|
||||||
|
|
||||||
|
#: decorators.py:53
|
||||||
|
msgid "Please log in to continue."
|
||||||
|
msgstr "Войдите в систему для продолжения."
|
||||||
|
|
||||||
|
#: decorators.py:85
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not authorized to access %s"
|
||||||
|
msgstr "Нет права доступа к %s"
|
||||||
|
|
||||||
|
#: exceptions.py:163
|
||||||
|
#, python-format
|
||||||
|
msgid "A %(resource)s with the name \"%(name)s\" already exists."
|
||||||
|
msgstr "%(resource)s с именем\"%(name)s\" уже существует."
|
||||||
|
|
||||||
|
#: exceptions.py:235
|
||||||
|
#, python-format
|
||||||
|
msgid "Unauthorized: %s"
|
||||||
|
msgstr "Не авторизован: %s"
|
||||||
|
|
||||||
|
#: exceptions.py:238
|
||||||
|
msgid "Unauthorized. Please try logging in again."
|
||||||
|
msgstr "Вы не авторизованы. Попробуйте войти в систему еще раз."
|
||||||
|
|
||||||
|
#: forms/fields.py:64
|
||||||
|
msgid "Incorrect format for IP address"
|
||||||
|
msgstr "Неправильный формат IP-адреса"
|
||||||
|
|
||||||
|
#: forms/fields.py:65
|
||||||
|
msgid "Invalid version for IP address"
|
||||||
|
msgstr "Неправильная версия IP-адреса"
|
||||||
|
|
||||||
|
#: forms/fields.py:66
|
||||||
|
msgid "Invalid subnet mask"
|
||||||
|
msgstr "Неправильная маска подсети"
|
||||||
|
|
||||||
|
#: forms/views.py:132 templates/horizon/common/_usage_summary.html:16
|
||||||
|
msgid "Submit"
|
||||||
|
msgstr "Отправить"
|
||||||
|
|
||||||
|
#: forms/views.py:133
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:25
|
||||||
|
#: templates/horizon/common/_workflow.html:49
|
||||||
|
msgid "Cancel"
|
||||||
|
msgstr "Отмена"
|
||||||
|
|
||||||
|
#: middleware.py:103
|
||||||
|
msgid "Session timed out."
|
||||||
|
msgstr "Время сеанса истекло."
|
||||||
|
|
||||||
|
#: tables/actions.py:460
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:21
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:33
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:14
|
||||||
|
#: templates/horizon/common/_workflow_step_update_members.html:23
|
||||||
|
msgid "Filter"
|
||||||
|
msgstr "Фильтр"
|
||||||
|
|
||||||
|
#: tables/actions.py:645
|
||||||
|
msgid "This action cannot be undone."
|
||||||
|
msgstr "Это действие не может быть отменено."
|
||||||
|
|
||||||
|
#: tables/actions.py:767
|
||||||
|
#, python-format
|
||||||
|
msgctxt "past"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:769
|
||||||
|
#, python-format
|
||||||
|
msgctxt "present"
|
||||||
|
msgid "%(action)s %(data_type)s"
|
||||||
|
msgstr "%(action)s %(data_type)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:832
|
||||||
|
#, python-format
|
||||||
|
msgid "You are not allowed to %(action)s: %(objs)s"
|
||||||
|
msgstr "Вам не разрешено выполнение: %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:839
|
||||||
|
#, python-format
|
||||||
|
msgid "Unable to %(action)s: %(objs)s"
|
||||||
|
msgstr "Невозможно выполнить %(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:845
|
||||||
|
#, python-format
|
||||||
|
msgid "%(action)s: %(objs)s"
|
||||||
|
msgstr "%(action)s: %(objs)s"
|
||||||
|
|
||||||
|
#: tables/actions.py:915
|
||||||
|
msgid "Delete"
|
||||||
|
msgstr "Удалить"
|
||||||
|
|
||||||
|
#: tables/actions.py:917
|
||||||
|
msgid "Deleted"
|
||||||
|
msgstr "Удалено"
|
||||||
|
|
||||||
|
#: tables/actions.py:948
|
||||||
|
msgid "Update"
|
||||||
|
msgstr "Обновить"
|
||||||
|
|
||||||
|
#: tables/actions.py:949
|
||||||
|
msgid "Updated"
|
||||||
|
msgstr "Обновлено"
|
||||||
|
|
||||||
|
#: tables/base.py:305
|
||||||
|
msgid "-"
|
||||||
|
msgstr "-"
|
||||||
|
|
||||||
|
#: tables/base.py:361
|
||||||
|
#, python-format
|
||||||
|
msgid "The attribute %(attr)s doesn't exist on %(obj)s."
|
||||||
|
msgstr "Атрибут %(attr)s не существует для %(obj)s."
|
||||||
|
|
||||||
|
#: tables/base.py:990
|
||||||
|
msgid "No items to display."
|
||||||
|
msgstr "Нет элементов для отображения."
|
||||||
|
|
||||||
|
#: tables/base.py:1099
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:47
|
||||||
|
msgid "Actions"
|
||||||
|
msgstr "Действия"
|
||||||
|
|
||||||
|
#: tables/base.py:1329
|
||||||
|
#, python-format
|
||||||
|
msgid "No match returned for the id \"%s\"."
|
||||||
|
msgstr "Нет совпадений для id \"%s\"."
|
||||||
|
|
||||||
|
#: tables/base.py:1486
|
||||||
|
msgid "Please select a row before taking that action."
|
||||||
|
msgstr "Выберите строку перед выполнением этого действия."
|
||||||
|
|
||||||
|
#: tables/base.py:1570
|
||||||
|
msgid "N/A"
|
||||||
|
msgstr "Н/Д"
|
||||||
|
|
||||||
|
#: templates/_header.html:5
|
||||||
|
#, python-format
|
||||||
|
msgid "Logged in as: %(username)s"
|
||||||
|
msgstr "Пользователь: %(username)s"
|
||||||
|
|
||||||
|
#: templates/_header.html:7
|
||||||
|
msgid "Help"
|
||||||
|
msgstr "Помощь"
|
||||||
|
|
||||||
|
#: templates/_header.html:9
|
||||||
|
msgid "Sign Out"
|
||||||
|
msgstr "Выход"
|
||||||
|
|
||||||
|
#: templates/auth/_description.html:9
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" If you are not sure which authentication method to use, contact your administrator.\n"
|
||||||
|
" "
|
||||||
|
msgstr "\n Если вы не уверены какой метод аутентификации выбрать, свяжитесь с вашим системным администратором.\n "
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:5
|
||||||
|
msgid "Log In"
|
||||||
|
msgstr "Войти"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:27
|
||||||
|
msgid "You do not have permission to access the resource:"
|
||||||
|
msgstr "Вы не имеете права на доступ к ресурсу:"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"Login as different user or go back to <a href=\"%(home_url)s\"> home "
|
||||||
|
"page</a>"
|
||||||
|
msgstr "Войдите под другим пользователем или вернитесь на<a href=\"%(home_url)s\"> домашнюю страницу</a>"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:45
|
||||||
|
msgid "Sign In"
|
||||||
|
msgstr "Вход"
|
||||||
|
|
||||||
|
#: templates/auth/_login.html:46
|
||||||
|
msgid "Connect"
|
||||||
|
msgstr "Подключиться"
|
||||||
|
|
||||||
|
#: templates/auth/login.html:4
|
||||||
|
msgid "Login"
|
||||||
|
msgstr "Имя пользователя"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:7
|
||||||
|
msgid "Info: "
|
||||||
|
msgstr "Информация:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:13
|
||||||
|
msgid "Warning: "
|
||||||
|
msgstr "Внимание:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:19
|
||||||
|
msgid "Success: "
|
||||||
|
msgstr "Успешно:"
|
||||||
|
|
||||||
|
#: templates/horizon/_messages.html:25
|
||||||
|
msgid "Error: "
|
||||||
|
msgstr "Ошибка:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:63
|
||||||
|
msgid "Summary"
|
||||||
|
msgstr "Итого"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:72
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(counter)s item"
|
||||||
|
msgid_plural "Displaying %(counter)s items"
|
||||||
|
msgstr[0] "Показан %(counter)s элемент"
|
||||||
|
msgstr[1] "Показано %(counter)s элементов"
|
||||||
|
msgstr[2] "Показано %(counter)s элементов"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:77
|
||||||
|
msgid "« Prev"
|
||||||
|
msgstr "« Предыдущее"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table.html:80
|
||||||
|
msgid "Next »"
|
||||||
|
msgstr "Следующее »"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_data_table_table_actions.html:45
|
||||||
|
msgid "More Actions"
|
||||||
|
msgstr "Еще действия"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_domain_page_header.html:6
|
||||||
|
#, python-format
|
||||||
|
msgid "%(context_name)s:"
|
||||||
|
msgstr "%(context_name)s:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table.html:35
|
||||||
|
msgid "Add a row"
|
||||||
|
msgstr "Добавить строку"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_formset_table_row.html:15
|
||||||
|
#, python-format
|
||||||
|
msgid "%(name)s: %(error)s"
|
||||||
|
msgstr "%(name)s: %(error)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:4
|
||||||
|
msgid "Limit Summary"
|
||||||
|
msgstr "Сводка лимитов"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:7
|
||||||
|
msgid "Instances"
|
||||||
|
msgstr "Машины"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:8
|
||||||
|
#: templates/horizon/common/_limit_summary.html:15
|
||||||
|
#: templates/horizon/common/_limit_summary.html:22
|
||||||
|
#: templates/horizon/common/_limit_summary.html:36
|
||||||
|
#: templates/horizon/common/_limit_summary.html:43
|
||||||
|
#: templates/horizon/common/_limit_summary.html:50
|
||||||
|
#, python-format
|
||||||
|
msgid "Used <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Использовано <span> %(used)s </span> из <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:14
|
||||||
|
msgid "VCPUs"
|
||||||
|
msgstr "VCPU"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:21
|
||||||
|
msgid "RAM"
|
||||||
|
msgstr "RAM"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:28
|
||||||
|
msgid "Floating IPs"
|
||||||
|
msgstr "Назначаемые IP"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:29
|
||||||
|
#, python-format
|
||||||
|
msgid "Allocated <span> %(used)s </span> of <span> %(available)s </span>"
|
||||||
|
msgstr "Выделено <span> %(used)s </span> из <span> %(available)s </span>"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:35
|
||||||
|
msgid "Security Groups"
|
||||||
|
msgstr "Группы безопасности"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:42
|
||||||
|
msgid "Volumes"
|
||||||
|
msgstr "Диски"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_limit_summary.html:49
|
||||||
|
msgid "Volume Storage"
|
||||||
|
msgstr "Хранилище"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_modal_form_update_metadata.html:24
|
||||||
|
#: workflows/base.py:594
|
||||||
|
msgid "Save"
|
||||||
|
msgstr "Сохранить"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:10
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(nav_items)s item"
|
||||||
|
msgid_plural "Displaying %(nav_items)s items"
|
||||||
|
msgstr[0] "Показан %(nav_items)s элемент"
|
||||||
|
msgstr[1] "Показано %(nav_items)s элементов"
|
||||||
|
msgstr[2] "Показано %(nav_items)s элементов"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_resource_browser.html:11
|
||||||
|
#, python-format
|
||||||
|
msgid "Displaying %(content_items)s item"
|
||||||
|
msgid_plural "Displaying %(content_items)s items"
|
||||||
|
msgstr[0] "Показан %(content_items)s элемент"
|
||||||
|
msgstr[1] "Показано %(content_items)s элементов"
|
||||||
|
msgstr[2] "Показано %(content_items)s элементов"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:3
|
||||||
|
msgid "Usage Summary"
|
||||||
|
msgstr "Сводка использования"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:7
|
||||||
|
msgid "Select a period of time to query its usage:"
|
||||||
|
msgstr "Выберите временной интервал для запроса использования:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:9
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>From:</label> %(start)s"
|
||||||
|
msgstr "\n <label>От:</label> %(start)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:13
|
||||||
|
#, python-format
|
||||||
|
msgid ""
|
||||||
|
"\n"
|
||||||
|
" <label>To:</label>%(end)s"
|
||||||
|
msgstr "\n <label>До:</label>%(end)s"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:17
|
||||||
|
msgid "The date should be in YYYY-mm-dd format."
|
||||||
|
msgstr "Дата должна иметь формат YYYY-mm-dd."
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:20
|
||||||
|
msgid "Active Instances:"
|
||||||
|
msgstr "Активные машины:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:21
|
||||||
|
msgid "Active RAM:"
|
||||||
|
msgstr "Используемая RAM:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:22
|
||||||
|
msgid "This Period's VCPU-Hours:"
|
||||||
|
msgstr "vCPU-часов за период:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:23
|
||||||
|
msgid "This Period's GB-Hours:"
|
||||||
|
msgstr "ГБ-часов за период:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_usage_summary.html:24
|
||||||
|
msgid "This Period's RAM-Hours:"
|
||||||
|
msgstr "RAM-часов за период:"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:40
|
||||||
|
msgid "Back"
|
||||||
|
msgstr "Назад"
|
||||||
|
|
||||||
|
#: templates/horizon/common/_workflow.html:43
|
||||||
|
msgid "Next"
|
||||||
|
msgstr "Следующий"
|
||||||
|
|
||||||
|
#: templatetags/branding.py:34
|
||||||
|
msgid "Horizon"
|
||||||
|
msgstr "Horizon"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:137 templatetags/horizon.py:148
|
||||||
|
msgid "No Limit"
|
||||||
|
msgstr "Без ограничений"
|
||||||
|
|
||||||
|
#: templatetags/horizon.py:140 templatetags/horizon.py:142
|
||||||
|
msgid "Available"
|
||||||
|
msgstr "Доступно"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:51 templatetags/sizeformat.py:56
|
||||||
|
#, python-format
|
||||||
|
msgid "%(size)d Byte"
|
||||||
|
msgid_plural "%(size)d Bytes"
|
||||||
|
msgstr[0] "%(size)d байт"
|
||||||
|
msgstr[1] "%(size)d байт"
|
||||||
|
msgstr[2] "%(size)d байт"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:59
|
||||||
|
#, python-format
|
||||||
|
msgid "%s KB"
|
||||||
|
msgstr "%s КБ"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:61
|
||||||
|
#, python-format
|
||||||
|
msgid "%s MB"
|
||||||
|
msgstr "%s МБ"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:63
|
||||||
|
#, python-format
|
||||||
|
msgid "%s GB"
|
||||||
|
msgstr "%s ГБ"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:65
|
||||||
|
#, python-format
|
||||||
|
msgid "%s TB"
|
||||||
|
msgstr "%s ТБ"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:66
|
||||||
|
#, python-format
|
||||||
|
msgid "%s PB"
|
||||||
|
msgstr "%s ПБ"
|
||||||
|
|
||||||
|
#: templatetags/sizeformat.py:74
|
||||||
|
msgid "0 Bytes"
|
||||||
|
msgstr "0 байт"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:31
|
||||||
|
msgid "Sell Puppy"
|
||||||
|
msgid_plural "Sell Puppies"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
msgstr[2] "Sell Puppies"
|
||||||
|
|
||||||
|
#. Translators: test code, don't really have to translate
|
||||||
|
#: test/test_dashboards/dogs/puppies/tables.py:40
|
||||||
|
msgid "Sold Puppy"
|
||||||
|
msgid_plural "Sold Puppies"
|
||||||
|
msgstr[0] ""
|
||||||
|
msgstr[1] ""
|
||||||
|
msgstr[2] "Sold Puppies"
|
||||||
|
|
||||||
|
#: test/tests/views.py:59
|
||||||
|
msgid "Fake"
|
||||||
|
msgstr "Fake"
|
||||||
|
|
||||||
|
#: utils/filters.py:49
|
||||||
|
msgid "Never"
|
||||||
|
msgstr "Никогда"
|
||||||
|
|
||||||
|
#: utils/validators.py:26 utils/validators.py:50
|
||||||
|
msgid "Not a valid port number"
|
||||||
|
msgstr "Недопустимый номер порта"
|
||||||
|
|
||||||
|
#: utils/validators.py:31
|
||||||
|
msgid "Not a valid IP protocol number"
|
||||||
|
msgstr "Недопустимый номер IP-протокола"
|
||||||
|
|
||||||
|
#: utils/validators.py:45
|
||||||
|
msgid "One colon allowed in port range"
|
||||||
|
msgstr "В списке портов допустима одна запятая"
|
||||||
|
|
||||||
|
#: utils/validators.py:52
|
||||||
|
msgid "Port number must be integer"
|
||||||
|
msgstr "Номер порта должен быть целым числом"
|
||||||
|
|
||||||
|
#: utils/validators.py:59
|
||||||
|
msgid "The string may only contain ASCII printable characters."
|
||||||
|
msgstr "Строка может содержать только печатные ASCII символы."
|
||||||
|
|
||||||
|
#: workflows/base.py:71
|
||||||
|
msgid "Processing..."
|
||||||
|
msgstr "Обработка…"
|
||||||
|
|
||||||
|
#: workflows/base.py:475
|
||||||
|
msgid "All available"
|
||||||
|
msgstr "Все доступные"
|
||||||
|
|
||||||
|
#: workflows/base.py:476
|
||||||
|
msgid "Members"
|
||||||
|
msgstr "Участники"
|
||||||
|
|
||||||
|
#: workflows/base.py:477
|
||||||
|
msgid "None available."
|
||||||
|
msgstr "Нет доступных."
|
||||||
|
|
||||||
|
#: workflows/base.py:478
|
||||||
|
msgid "No members."
|
||||||
|
msgstr "Нет участников."
|
||||||
|
|
||||||
|
#: workflows/base.py:595
|
||||||
|
#, python-format
|
||||||
|
msgid "%s completed successfully."
|
||||||
|
msgstr "%s успешно завершено."
|
||||||
|
|
||||||
|
#: workflows/base.py:596
|
||||||
|
#, python-format
|
||||||
|
msgid "%s did not complete."
|
||||||
|
msgstr "%s не завершено."
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue