summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.bzrignore7
-rw-r--r--README49
-rw-r--r--django-nova/LICENSE176
-rw-r--r--django-nova/README42
-rw-r--r--django-nova/bootstrap.py260
-rw-r--r--django-nova/buildout.cfg19
-rw-r--r--django-nova/setup.py29
-rw-r--r--django-nova/src/django_nova/__init__.py (renamed from dashboard/__init__.py)0
-rw-r--r--django-nova/src/django_nova/adminclient.py498
-rw-r--r--django-nova/src/django_nova/connection.py38
-rw-r--r--django-nova/src/django_nova/exceptions.py95
-rw-r--r--django-nova/src/django_nova/forms.py262
-rw-r--r--django-nova/src/django_nova/management/__init__.py (renamed from local/__init__.py)0
-rw-r--r--django-nova/src/django_nova/management/commands/__init__.py0
-rw-r--r--django-nova/src/django_nova/management/commands/createnovausers.py37
-rw-r--r--django-nova/src/django_nova/manager.py340
-rw-r--r--django-nova/src/django_nova/models.py121
-rw-r--r--django-nova/src/django_nova/shortcuts.py131
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html45
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html69
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html16
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html3
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html25
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html25
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html95
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html71
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html42
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html76
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html87
-rw-r--r--django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html39
-rw-r--r--django-nova/src/django_nova/templates/django_nova/_messages.html41
-rw-r--r--django-nova/src/django_nova/templates/django_nova/base.html85
-rw-r--r--django-nova/src/django_nova/templates/django_nova/credentials/expired.html17
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/_launch_form.html7
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/_list.html112
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/base.html7
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/detail_list.html207
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/edit.html35
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/index.html70
-rw-r--r--django-nova/src/django_nova/templates/django_nova/images/launch.html32
-rw-r--r--django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html104
-rw-r--r--django-nova/src/django_nova/templates/django_nova/instances/base.html7
-rw-r--r--django-nova/src/django_nova/templates/django_nova/instances/detail_list.html33
-rw-r--r--django-nova/src/django_nova/templates/django_nova/instances/edit.html34
-rw-r--r--django-nova/src/django_nova/templates/django_nova/instances/index.html98
-rw-r--r--django-nova/src/django_nova/templates/django_nova/instances/performance.html58
-rw-r--r--django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html5
-rw-r--r--django-nova/src/django_nova/templates/django_nova/keypairs/_list.html31
-rw-r--r--django-nova/src/django_nova/templates/django_nova/keypairs/base.html7
-rw-r--r--django-nova/src/django_nova/templates/django_nova/keypairs/index.html77
-rw-r--r--django-nova/src/django_nova/templates/django_nova/projects/edit_user.html72
-rw-r--r--django-nova/src/django_nova/templates/django_nova/projects/index.html26
-rw-r--r--django-nova/src/django_nova/templates/django_nova/projects/manage.html45
-rw-r--r--django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html5
-rw-r--r--django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html5
-rw-r--r--django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html3
-rw-r--r--django-nova/src/django_nova/templates/django_nova/securitygroups/base.html7
-rw-r--r--django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html62
-rw-r--r--django-nova/src/django_nova/templates/django_nova/securitygroups/index.html59
-rw-r--r--django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html5
-rw-r--r--django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html5
-rw-r--r--django-nova/src/django_nova/templates/django_nova/volumes/base.html7
-rw-r--r--django-nova/src/django_nova/templates/django_nova/volumes/index.html84
-rw-r--r--django-nova/src/django_nova/templatetags/__init__.py0
-rw-r--r--django-nova/src/django_nova/templatetags/admin_extras.py50
-rw-r--r--django-nova/src/django_nova/templatetags/django_nova_tags.py37
-rw-r--r--django-nova/src/django_nova/templatetags/project_tags.py39
-rw-r--r--django-nova/src/django_nova/templatetags/region_tags.py40
-rw-r--r--django-nova/src/django_nova/templatetags/sidebar_tags.py46
-rw-r--r--django-nova/src/django_nova/templatetags/truncate_filter.py31
-rw-r--r--django-nova/src/django_nova/tests/__init__.py1
-rw-r--r--django-nova/src/django_nova/tests/templates/base-sidebar.html0
-rw-r--r--django-nova/src/django_nova/tests/urls.py36
-rw-r--r--django-nova/src/django_nova/tests/view_tests/__init__.py7
-rw-r--r--django-nova/src/django_nova/tests/view_tests/base.py90
-rw-r--r--django-nova/src/django_nova/tests/view_tests/credential_tests.py70
-rw-r--r--django-nova/src/django_nova/tests/view_tests/image_tests.py232
-rw-r--r--django-nova/src/django_nova/tests/view_tests/instance_tests.py67
-rw-r--r--django-nova/src/django_nova/tests/view_tests/keypair_tests.py93
-rw-r--r--django-nova/src/django_nova/tests/view_tests/region_tests.py43
-rw-r--r--django-nova/src/django_nova/tests/view_tests/volume_tests.py170
-rw-r--r--django-nova/src/django_nova/testsettings.py21
-rw-r--r--django-nova/src/django_nova/urls/__init__.py0
-rw-r--r--django-nova/src/django_nova/urls/admin_project.py55
-rw-r--r--django-nova/src/django_nova/urls/admin_roles.py32
-rw-r--r--django-nova/src/django_nova/urls/project.py129
-rw-r--r--django-nova/src/django_nova/urls/region.py29
-rw-r--r--django-nova/src/django_nova/views/__init__.py0
-rw-r--r--django-nova/src/django_nova/views/admin.py326
-rw-r--r--django-nova/src/django_nova/views/credentials.py46
-rw-r--r--django-nova/src/django_nova/views/images.py229
-rw-r--r--django-nova/src/django_nova/views/instances.py203
-rw-r--r--django-nova/src/django_nova/views/keypairs.py122
-rw-r--r--django-nova/src/django_nova/views/projects.py107
-rw-r--r--django-nova/src/django_nova/views/regions.py36
-rw-r--r--django-nova/src/django_nova/views/securitygroups.py180
-rw-r--r--django-nova/src/django_nova/views/volumes.py151
-rw-r--r--openstack-dashboard/README49
-rw-r--r--openstack-dashboard/dashboard/__init__.py0
-rwxr-xr-xopenstack-dashboard/dashboard/manage.py (renamed from dashboard/manage.py)0
-rw-r--r--openstack-dashboard/dashboard/settings.py (renamed from dashboard/settings.py)0
-rw-r--r--openstack-dashboard/dashboard/templates/403.html (renamed from dashboard/templates/403.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/404.html (renamed from dashboard/templates/404.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/500.html (renamed from dashboard/templates/500.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/admin/index.html (renamed from dashboard/templates/admin/index.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/base-root.html (renamed from dashboard/templates/base-root.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/base-sidebar.html (renamed from dashboard/templates/base-sidebar.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/base.html (renamed from dashboard/templates/base.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/index.html (renamed from dashboard/templates/index.html)4
-rw-r--r--openstack-dashboard/dashboard/templates/permission_denied.html (renamed from dashboard/templates/permission_denied.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/activate.html (renamed from dashboard/templates/registration/activate.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/activation_email.txt (renamed from dashboard/templates/registration/activation_email.txt)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/activation_email_subject.txt (renamed from dashboard/templates/registration/activation_email_subject.txt)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/login.html (renamed from dashboard/templates/registration/login.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/logout.html (renamed from dashboard/templates/registration/logout.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_change_done.html (renamed from dashboard/templates/registration/password_change_done.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_change_form.html (renamed from dashboard/templates/registration/password_change_form.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_reset_complete.html (renamed from dashboard/templates/registration/password_reset_complete.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_reset_confirm.html (renamed from dashboard/templates/registration/password_reset_confirm.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_reset_done.html (renamed from dashboard/templates/registration/password_reset_done.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_reset_email.html (renamed from dashboard/templates/registration/password_reset_email.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/password_reset_form.html (renamed from dashboard/templates/registration/password_reset_form.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/registration_complete.html (renamed from dashboard/templates/registration/registration_complete.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/registration/registration_form.html (renamed from dashboard/templates/registration/registration_form.html)0
-rw-r--r--openstack-dashboard/dashboard/templates/unavailable.html (renamed from dashboard/templates/unavailable.html)0
-rw-r--r--openstack-dashboard/dashboard/urls.py (renamed from dashboard/urls.py)0
-rw-r--r--openstack-dashboard/dashboard/views.py (renamed from dashboard/views.py)0
-rw-r--r--openstack-dashboard/dashboard/wsgi/django.wsgi (renamed from dashboard/wsgi/django.wsgi)0
-rw-r--r--openstack-dashboard/local/__init__.py0
-rw-r--r--openstack-dashboard/local/local_settings.py.example (renamed from local/local_settings.py.example)0
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png (renamed from media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png)bin251 -> 251 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png (renamed from media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png)bin181 -> 181 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png (renamed from media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png)bin158 -> 158 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png (renamed from media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png)bin131 -> 131 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png (renamed from media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png)bin124 -> 124 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png (renamed from media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png)bin103 -> 103 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png (renamed from media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png)bin118 -> 118 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png (renamed from media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png)bin104 -> 104 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png (renamed from media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png)bin119 -> 119 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png (renamed from media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png)bin4369 -> 4369 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png (renamed from media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png)bin4369 -> 4369 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png (renamed from media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png)bin4369 -> 4369 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png (renamed from media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png)bin4369 -> 4369 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png (renamed from media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png)bin4369 -> 4369 bytes
-rw-r--r--openstack-dashboard/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css (renamed from media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css)0
-rw-r--r--openstack-dashboard/media/dashboard/css/django-admin-widgets.css (renamed from media/dashboard/css/django-admin-widgets.css)0
-rwxr-xr-xopenstack-dashboard/media/dashboard/css/ie7.css (renamed from media/dashboard/css/ie7.css)0
-rw-r--r--openstack-dashboard/media/dashboard/css/openstack.css (renamed from media/dashboard/css/openstack.css)0
-rw-r--r--openstack-dashboard/media/dashboard/css/reset.css (renamed from media/dashboard/css/reset.css)0
-rw-r--r--openstack-dashboard/media/dashboard/img/body_bg.gif (renamed from media/dashboard/img/body_bg.gif)bin12957 -> 12957 bytes
-rwxr-xr-xopenstack-dashboard/media/dashboard/img/body_bg.png (renamed from media/dashboard/img/body_bg.png)bin2217 -> 2217 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/book_icon.png (renamed from media/dashboard/img/book_icon.png)bin741 -> 741 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/bread_crumb.gif (renamed from media/dashboard/img/bread_crumb.gif)bin2027 -> 2027 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/btn_bg.png (renamed from media/dashboard/img/btn_bg.png)bin1043 -> 1043 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/chat_icon.png (renamed from media/dashboard/img/chat_icon.png)bin847 -> 847 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/content_bg.gif (renamed from media/dashboard/img/content_bg.gif)bin105 -> 105 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/content_shadow.png (renamed from media/dashboard/img/content_shadow.png)bin2836 -> 2836 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/dashboard_nav_bg.png (renamed from media/dashboard/img/dashboard_nav_bg.png)bin255 -> 255 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/foot_back.png (renamed from media/dashboard/img/foot_back.png)bin2524 -> 2524 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/gears.png (renamed from media/dashboard/img/gears.png)bin4701 -> 4701 bytes
-rwxr-xr-xopenstack-dashboard/media/dashboard/img/header_bg.png (renamed from media/dashboard/img/header_bg.png)bin966 -> 966 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/home_head_back.png (renamed from media/dashboard/img/home_head_back.png)bin4459 -> 4459 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/image_detail.png (renamed from media/dashboard/img/image_detail.png)bin47257 -> 47257 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/logged_in_box_bg.gif (renamed from media/dashboard/img/logged_in_box_bg.gif)bin1100 -> 1100 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/login_bg.png (renamed from media/dashboard/img/login_bg.png)bin476 -> 476 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/login_btn.png (renamed from media/dashboard/img/login_btn.png)bin282 -> 282 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/logo.gif (renamed from media/dashboard/img/logo.gif)bin3098 -> 3098 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/nav_arrow.png (renamed from media/dashboard/img/nav_arrow.png)bin471 -> 471 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/nav_bg.png (renamed from media/dashboard/img/nav_bg.png)bin1554 -> 1554 bytes
-rwxr-xr-xopenstack-dashboard/media/dashboard/img/nav_highlight.png (renamed from media/dashboard/img/nav_highlight.png)bin598 -> 598 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/page_header.png (renamed from media/dashboard/img/page_header.png)bin782 -> 782 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/projects_bg.png (renamed from media/dashboard/img/projects_bg.png)bin408 -> 408 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/server_icon.png (renamed from media/dashboard/img/server_icon.png)bin433 -> 433 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/spinner.gif (renamed from media/dashboard/img/spinner.gif)bin2545 -> 2545 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/sub-head-back.png (renamed from media/dashboard/img/sub-head-back.png)bin426 -> 426 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/sub_head_back.png (renamed from media/dashboard/img/sub_head_back.png)bin3104 -> 3104 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/table_header_bg.png (renamed from media/dashboard/img/table_header_bg.png)bin157 -> 157 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/table_heading_bg.png (renamed from media/dashboard/img/table_heading_bg.png)bin968 -> 968 bytes
-rw-r--r--openstack-dashboard/media/dashboard/img/title-blank-short-foot.png (renamed from media/dashboard/img/title-blank-short-foot.png)bin435 -> 435 bytes
-rw-r--r--openstack-dashboard/media/dashboard/js/dashboard.js (renamed from media/dashboard/js/dashboard.js)0
-rw-r--r--openstack-dashboard/media/dashboard/js/django-admin.multiselect.js (renamed from media/dashboard/js/django-admin.multiselect.js)0
-rwxr-xr-xopenstack-dashboard/media/dashboard/js/jquery-ui.min.js (renamed from media/dashboard/js/jquery-ui.min.js)0
-rw-r--r--openstack-dashboard/media/dashboard/js/jquery.form.js (renamed from media/dashboard/js/jquery.form.js)0
-rw-r--r--openstack-dashboard/media/dashboard/js/jquery.min.js (renamed from media/dashboard/js/jquery.min.js)0
-rwxr-xr-xopenstack-dashboard/run_tests.sh (renamed from run_tests.sh)0
-rw-r--r--openstack-dashboard/tools/install_venv.py (renamed from tools/install_venv.py)24
-rw-r--r--openstack-dashboard/tools/pip-requires (renamed from tools/pip-requires)0
-rwxr-xr-xopenstack-dashboard/tools/with_venv.sh (renamed from tools/with_venv.sh)0
188 files changed, 6872 insertions, 72 deletions
diff --git a/.bzrignore b/.bzrignore
index ea457b59..03f01515 100644
--- a/.bzrignore
+++ b/.bzrignore
@@ -1,3 +1,4 @@
-.dashboard-venv
-local/dashboard_openstack.sqlite3
-local/local_settings.py
+django-nova/src/django_nova.egg-info
+openstack-dashboard/.dashboard-venv
+openstack-dashboard/local/dashboard_openstack.sqlite3
+openstack-dashboard/local/local_settings.py
diff --git a/README b/README
index f4b1cfcb..e69de29b 100644
--- a/README
+++ b/README
@@ -1,49 +0,0 @@
-OpenStack Dashboard
--------------------
-
-The OpenStack Dashboard is a reference implementation of a Django site that
-uses the Django-Nova project to provide web based interactions with the
-OpenStack Nova cloud controller.
-
-For more information about the Django-Nova project, please visit:
-
- http://launchpad.net/django-nova
-
-
-Getting Started
----------------
-
-The first step is to obtain a local copy of the django-nova project:
-
- $ mkdir django-nova
- $ cd django-nova
- $ bzr init-repo .
- $ bzr branch lp:django-nova/trunk
-
-
-Next we will create the virtualenv for local development. A tool is included to
-create one for you:
-
- $ python tools/install_venv.py <path to django-nova/trunk>
-
-
-Now that the virtualenv is created, you need to configure your local
-environment. To do this, create a local_settings.py file in the local/
-directory. There is a local_settings.py.example file there that may be used
-as a template.
-
-Finally, issue the django syncdb command:
-
- $ tools/with_venv.sh dashboard/manage.py syncdb
-
-If after you have specified the admin user the script appears to hang, it
-probably means the installation of Nova being referred to in local_settings.py
-is unavailable.
-
-
-If all is well you should now able to run the server locally:
-
- $ tools/with_venv.sh dashboard/manage.py runserver
-
-
-
diff --git a/django-nova/LICENSE b/django-nova/LICENSE
new file mode 100644
index 00000000..68c771a0
--- /dev/null
+++ b/django-nova/LICENSE
@@ -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.
+
diff --git a/django-nova/README b/django-nova/README
new file mode 100644
index 00000000..cc89b6cf
--- /dev/null
+++ b/django-nova/README
@@ -0,0 +1,42 @@
+OpenStack Django-Nova
+---------------------
+
+The Django-Nova project is a Django module that is used to provide web based
+interactions with the OpenStack Nova cloud controller.
+
+There is a reference implementation that uses this module located at:
+
+ http://launchpad.net/openstack-dashboard
+
+It is highly recommended that you make use of this reference implementation
+so that changes you make can be visualized effectively and are consistent.
+Using this reference implementation as a development environment will greatly
+simplify development of the django-nova module.
+
+Of course, if you are developing your own Django site using django-nova, then
+you can disregard this advice.
+
+
+
+Getting Started
+---------------
+
+Django-Nova uses Buildout (http://www.buildout.org/) to manage local
+development. To configure your local Buildout environment:
+
+ $ python bootstrap.py
+ $ bin/buildout
+
+This will install all the dependencies of django-nova and provide some useful
+scripts in the bin/ directory:
+
+ bin/python provides a python shell for the current buildout.
+ bin/django provides django functions for the current buildout.
+
+
+You should now be able to run unit tests as follows:
+
+ $ bin/django test
+
+
+
diff --git a/django-nova/bootstrap.py b/django-nova/bootstrap.py
new file mode 100644
index 00000000..5f2cb083
--- /dev/null
+++ b/django-nova/bootstrap.py
@@ -0,0 +1,260 @@
+##############################################################################
+#
+# Copyright (c) 2006 Zope Foundation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Bootstrap a buildout-based project
+
+Simply run this script in a directory containing a buildout.cfg.
+The script accepts buildout command-line options, so you can
+use the -c option to specify an alternate configuration file.
+"""
+
+import os, shutil, sys, tempfile, textwrap, urllib, urllib2, subprocess
+from optparse import OptionParser
+
+if sys.platform == 'win32':
+ def quote(c):
+ if ' ' in c:
+ return '"%s"' % c # work around spawn lamosity on windows
+ else:
+ return c
+else:
+ quote = str
+
+# See zc.buildout.easy_install._has_broken_dash_S for motivation and comments.
+stdout, stderr = subprocess.Popen(
+ [sys.executable, '-Sc',
+ 'try:\n'
+ ' import ConfigParser\n'
+ 'except ImportError:\n'
+ ' print 1\n'
+ 'else:\n'
+ ' print 0\n'],
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
+has_broken_dash_S = bool(int(stdout.strip()))
+
+# In order to be more robust in the face of system Pythons, we want to
+# run without site-packages loaded. This is somewhat tricky, in
+# particular because Python 2.6's distutils imports site, so starting
+# with the -S flag is not sufficient. However, we'll start with that:
+if not has_broken_dash_S and 'site' in sys.modules:
+ # We will restart with python -S.
+ args = sys.argv[:]
+ args[0:0] = [sys.executable, '-S']
+ args = map(quote, args)
+ os.execv(sys.executable, args)
+# Now we are running with -S. We'll get the clean sys.path, import site
+# because distutils will do it later, and then reset the path and clean
+# out any namespace packages from site-packages that might have been
+# loaded by .pth files.
+clean_path = sys.path[:]
+import site
+sys.path[:] = clean_path
+for k, v in sys.modules.items():
+ if k in ('setuptools', 'pkg_resources') or (
+ hasattr(v, '__path__') and
+ len(v.__path__)==1 and
+ not os.path.exists(os.path.join(v.__path__[0],'__init__.py'))):
+ # This is a namespace package. Remove it.
+ sys.modules.pop(k)
+
+is_jython = sys.platform.startswith('java')
+
+setuptools_source = 'http://peak.telecommunity.com/dist/ez_setup.py'
+distribute_source = 'http://python-distribute.org/distribute_setup.py'
+
+# parsing arguments
+def normalize_to_url(option, opt_str, value, parser):
+ if value:
+ if '://' not in value: # It doesn't smell like a URL.
+ value = 'file://%s' % (
+ urllib.pathname2url(
+ os.path.abspath(os.path.expanduser(value))),)
+ if opt_str == '--download-base' and not value.endswith('/'):
+ # Download base needs a trailing slash to make the world happy.
+ value += '/'
+ else:
+ value = None
+ name = opt_str[2:].replace('-', '_')
+ setattr(parser.values, name, value)
+
+usage = '''\
+[DESIRED PYTHON FOR BUILDOUT] bootstrap.py [options]
+
+Bootstraps a buildout-based project.
+
+Simply run this script in a directory containing a buildout.cfg, using the
+Python that you want bin/buildout to use.
+
+Note that by using --setup-source and --download-base to point to
+local resources, you can keep this script from going over the network.
+'''
+
+parser = OptionParser(usage=usage)
+parser.add_option("-v", "--version", dest="version",
+ help="use a specific zc.buildout version")
+parser.add_option("-d", "--distribute",
+ action="store_true", dest="use_distribute", default=False,
+ help="Use Distribute rather than Setuptools.")
+parser.add_option("--setup-source", action="callback", dest="setup_source",
+ callback=normalize_to_url, nargs=1, type="string",
+ help=("Specify a URL or file location for the setup file. "
+ "If you use Setuptools, this will default to " +
+ setuptools_source + "; if you use Distribute, this "
+ "will default to " + distribute_source +"."))
+parser.add_option("--download-base", action="callback", dest="download_base",
+ callback=normalize_to_url, nargs=1, type="string",
+ help=("Specify a URL or directory for downloading "
+ "zc.buildout and either Setuptools or Distribute. "
+ "Defaults to PyPI."))
+parser.add_option("--eggs",
+ help=("Specify a directory for storing eggs. Defaults to "
+ "a temporary directory that is deleted when the "
+ "bootstrap script completes."))
+parser.add_option("-t", "--accept-buildout-test-releases",
+ dest='accept_buildout_test_releases',
+ action="store_true", default=False,
+ help=("Normally, if you do not specify a --version, the "
+ "bootstrap script and buildout gets the newest "
+ "*final* versions of zc.buildout and its recipes and "
+ "extensions for you. If you use this flag, "
+ "bootstrap and buildout will get the newest releases "
+ "even if they are alphas or betas."))
+parser.add_option("-c", None, action="store", dest="config_file",
+ help=("Specify the path to the buildout configuration "
+ "file to be used."))
+
+options, args = parser.parse_args()
+
+# if -c was provided, we push it back into args for buildout's main function
+if options.config_file is not None:
+ args += ['-c', options.config_file]
+
+if options.eggs:
+ eggs_dir = os.path.abspath(os.path.expanduser(options.eggs))
+else:
+ eggs_dir = tempfile.mkdtemp()
+
+if options.setup_source is None:
+ if options.use_distribute:
+ options.setup_source = distribute_source
+ else:
+ options.setup_source = setuptools_source
+
+if options.accept_buildout_test_releases:
+ args.append('buildout:accept-buildout-test-releases=true')
+args.append('bootstrap')
+
+try:
+ import pkg_resources
+ import setuptools # A flag. Sometimes pkg_resources is installed alone.
+ if not hasattr(pkg_resources, '_distribute'):
+ raise ImportError
+except ImportError:
+ ez_code = urllib2.urlopen(
+ options.setup_source).read().replace('\r\n', '\n')
+ ez = {}
+ exec ez_code in ez
+ setup_args = dict(to_dir=eggs_dir, download_delay=0)
+ if options.download_base:
+ setup_args['download_base'] = options.download_base
+ if options.use_distribute:
+ setup_args['no_fake'] = True
+ ez['use_setuptools'](**setup_args)
+ if 'pkg_resources' in sys.modules:
+ reload(sys.modules['pkg_resources'])
+ import pkg_resources
+ # This does not (always?) update the default working set. We will
+ # do it.
+ for path in sys.path:
+ if path not in pkg_resources.working_set.entries:
+ pkg_resources.working_set.add_entry(path)
+
+cmd = [quote(sys.executable),
+ '-c',
+ quote('from setuptools.command.easy_install import main; main()'),
+ '-mqNxd',
+ quote(eggs_dir)]
+
+if not has_broken_dash_S:
+ cmd.insert(1, '-S')
+
+find_links = options.download_base
+if not find_links:
+ find_links = os.environ.get('bootstrap-testing-find-links')
+if find_links:
+ cmd.extend(['-f', quote(find_links)])
+
+if options.use_distribute:
+ setup_requirement = 'distribute'
+else:
+ setup_requirement = 'setuptools'
+ws = pkg_resources.working_set
+setup_requirement_path = ws.find(
+ pkg_resources.Requirement.parse(setup_requirement)).location
+env = dict(
+ os.environ,
+ PYTHONPATH=setup_requirement_path)
+
+requirement = 'zc.buildout'
+version = options.version
+if version is None and not options.accept_buildout_test_releases:
+ # Figure out the most recent final version of zc.buildout.
+ import setuptools.package_index
+ _final_parts = '*final-', '*final'
+ def _final_version(parsed_version):
+ for part in parsed_version:
+ if (part[:1] == '*') and (part not in _final_parts):
+ return False
+ return True
+ index = setuptools.package_index.PackageIndex(
+ search_path=[setup_requirement_path])
+ if find_links:
+ index.add_find_links((find_links,))
+ req = pkg_resources.Requirement.parse(requirement)
+ if index.obtain(req) is not None:
+ best = []
+ bestv = None
+ for dist in index[req.project_name]:
+ distv = dist.parsed_version
+ if _final_version(distv):
+ if bestv is None or distv > bestv:
+ best = [dist]
+ bestv = distv
+ elif distv == bestv:
+ best.append(dist)
+ if best:
+ best.sort()
+ version = best[-1].version
+if version:
+ requirement = '=='.join((requirement, version))
+cmd.append(requirement)
+
+if is_jython:
+ import subprocess
+ exitcode = subprocess.Popen(cmd, env=env).wait()
+else: # Windows prefers this, apparently; otherwise we would prefer subprocess
+ exitcode = os.spawnle(*([os.P_WAIT, sys.executable] + cmd + [env]))
+if exitcode != 0:
+ sys.stdout.flush()
+ sys.stderr.flush()
+ print ("An error occurred when trying to install zc.buildout. "
+ "Look above this message for any errors that "
+ "were output by easy_install.")
+ sys.exit(exitcode)
+
+ws.add_entry(eggs_dir)
+ws.require(requirement)
+import zc.buildout.buildout
+zc.buildout.buildout.main(args)
+if not options.eggs: # clean up temporary egg directory
+ shutil.rmtree(eggs_dir)
diff --git a/django-nova/buildout.cfg b/django-nova/buildout.cfg
new file mode 100644
index 00000000..c41ec9ae
--- /dev/null
+++ b/django-nova/buildout.cfg
@@ -0,0 +1,19 @@
+[buildout]
+parts = python django
+develop = .
+eggs = django-nova
+
+[python]
+recipe = zc.recipe.egg
+interpreter = python
+eggs = ${buildout:eggs}
+
+[django]
+recipe = djangorecipe
+version = 1.2.4
+project = django_nova
+projectegg = django_nova
+settings = testsettings
+test = django_nova
+eggs = ${buildout:eggs}
+
diff --git a/django-nova/setup.py b/django-nova/setup.py
new file mode 100644
index 00000000..838e652f
--- /dev/null
+++ b/django-nova/setup.py
@@ -0,0 +1,29 @@
+import os
+from setuptools import setup, find_packages
+
+def read(fname):
+ return open(os.path.join(os.path.dirname(__file__), fname)).read()
+
+setup(
+ name = "django-nova",
+ version = "0.1",
+ url = 'https://launchpad.net/django-nova/',
+ license = 'Apache 2.0',
+ description = "A Django interface for OpenStack Nova.",
+ long_description = read('README'),
+ author = 'Devin Carlen',
+ author_email = 'devin.carlen@gmail.com',
+ packages = find_packages('src'),
+ package_dir = {'': 'src'},
+ install_requires = ['setuptools', 'boto==1.9b', 'mox>=0.5.0'],
+ classifiers = [
+ 'Development Status :: 4 - Beta',
+ 'Framework :: Django',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: Apache License',
+ 'Operating System :: OS Independent',
+ 'Programming Language :: Python',
+ 'Topic :: Internet :: WWW/HTTP',
+ ]
+)
+
diff --git a/dashboard/__init__.py b/django-nova/src/django_nova/__init__.py
index e69de29b..e69de29b 100644
--- a/dashboard/__init__.py
+++ b/django-nova/src/django_nova/__init__.py
diff --git a/django-nova/src/django_nova/adminclient.py b/django-nova/src/django_nova/adminclient.py
new file mode 100644
index 00000000..a6af95ca
--- /dev/null
+++ b/django-nova/src/django_nova/adminclient.py
@@ -0,0 +1,498 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Nova User API client library.
+"""
+
+import base64
+import boto
+import boto.exception
+import httplib
+import re
+import string
+from boto.ec2.regioninfo import RegionInfo
+
+
+DEFAULT_CLC_URL='http://127.0.0.1:8773'
+DEFAULT_REGION='nova'
+DEFAULT_ACCESS_KEY='admin'
+DEFAULT_SECRET_KEY='admin'
+
+
+class UserInfo(object):
+ """
+ Information about a Nova user, as parsed through SAX
+ fields include:
+ username
+ accesskey
+ secretkey
+
+ and an optional field containing a zip with X509 cert & rc
+ file
+ """
+
+ def __init__(self, connection=None, username=None, endpoint=None):
+ self.connection = connection
+ self.username = username
+ self.endpoint = endpoint
+
+ def __repr__(self):
+ return 'UserInfo:%s' % self.username
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'username':
+ self.username = str(value)
+ elif name == 'file':
+ self.file = base64.b64decode(str(value))
+ elif name == 'accesskey':
+ self.accesskey = str(value)
+ elif name == 'secretkey':
+ self.secretkey = str(value)
+
+
+class UserRole(object):
+ """
+ Information about a Nova user's role, as parsed through SAX.
+ Fields include:
+ role
+ """
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.role = None
+
+ def __repr__(self):
+ return 'UserRole:%s' % self.role
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'role':
+ self.role = value
+ else:
+ setattr(self, name, str(value))
+
+
+class ProjectInfo(object):
+ """
+ Information about a Nova project, as parsed through SAX
+ Fields include:
+ projectname
+ description
+ projectManagerId
+ memberIds
+ """
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.projectname = None
+ self.description = None
+ self.projectManagerId = None
+ self.memberIds = []
+
+ def __repr__(self):
+ return 'ProjectInfo:%s' % self.projectname
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'projectname':
+ self.projectname = value
+ elif name == 'description':
+ self.description = value
+ elif name == 'projectManagerId':
+ self.projectManagerId = value
+ elif name == 'memberId':
+ self.memberIds.append(value)
+ else:
+ setattr(self, name, str(value))
+
+
+class ProjectMember(object):
+ """
+ Information about a Nova project member, as parsed through SAX.
+ Fields include:
+ memberId
+ """
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.memberId = None
+
+ def __repr__(self):
+ return 'ProjectMember:%s' % self.memberId
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'member':
+ self.memberId = value
+ else:
+ setattr(self, name, str(value))
+
+
+class HostInfo(object):
+ """
+ Information about a Nova Host, as parsed through SAX:
+ Hostname
+ Compute Service Status
+ Volume Service Status
+ """
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.hostname = None
+ self.compute = None
+ self.volume = None
+ self.instance_count = 0
+ self.volume_count = 0
+
+ def __repr__(self):
+ return 'Host:%s' % self.hostname
+
+ # this is needed by the sax parser, so ignore the ugly name
+ def startElement(self, name, attrs, connection):
+ return None
+
+ # this is needed by the sax parser, so ignore the ugly name
+ def endElement(self, name, value, connection):
+ fixed_name = string.lower(re.sub(r'([A-Z])', r'_\1', name))
+ setattr(self, fixed_name, value)
+
+
+class Vpn(object):
+ """
+ Information about a Vpn, as parsed through SAX
+ fields include:
+ instance_id
+ project_id
+ public_ip
+ public_port
+ created_at
+ internal_ip
+ state
+ """
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.instance_id = None
+ self.project_id = None
+
+ def __repr__(self):
+ return 'Vpn:%s:%s' % (self.project_id, self.instance_id)
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == 'instanceId':
+ self.instance_id = str(value)
+ elif name == 'projectId':
+ self.project_id = str(value)
+ elif name == 'publicIp':
+ self.public_ip = str(value)
+ elif name == 'publicPort':
+ self.public_port = str(value)
+ elif name == 'createdAt':
+ self.created_at = str(value)
+ elif name == 'internalIp':
+ self.internal_ip = str(value)
+ else:
+ setattr(self, name, str(value))
+
+
+class InstanceType(object):
+ """
+ Information about a Nova instance type, as parsed through SAX.
+
+ **Fields include**
+
+ * name
+ * vcpus
+ * disk_gb
+ * memory_mb
+ * flavor_id
+
+ """
+
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.name = None
+ self.vcpus = None
+ self.disk_gb = None
+ self.memory_mb = None
+ self.flavor_id = None
+
+ def __repr__(self):
+ return 'InstanceType:%s' % self.name
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ if name == "memoryMb":
+ self.memory_mb = str(value)
+ elif name == "flavorId":
+ self.flavor_id = str(value)
+ elif name == "diskGb":
+ self.disk_gb = str(value)
+ else:
+ setattr(self, name, str(value))
+
+
+class StatusResponse(object):
+ def __init__(self, connection=None):
+ self.connection = connection
+ self.status = None
+
+ def __repr__(self):
+ return 'Status:%s' % self.status
+
+ def startElement(self, name, attrs, connection):
+ return None
+
+ def endElement(self, name, value, connection):
+ setattr(self, name, str(value))
+
+
+class NovaAdminClient(object):
+ def __init__(self, clc_url=DEFAULT_CLC_URL, region=DEFAULT_REGION,
+ access_key=DEFAULT_ACCESS_KEY, secret_key=DEFAULT_SECRET_KEY,
+ **kwargs):
+ parts = self.split_clc_url(clc_url)
+
+ self.clc_url = clc_url
+ self.region = region
+ self.access = access_key
+ self.secret = secret_key
+ self.apiconn = boto.connect_ec2(aws_access_key_id=access_key,
+ aws_secret_access_key=secret_key,
+ is_secure=parts['is_secure'],
+ region=RegionInfo(None,
+ region,
+ parts['ip']),
+ port=parts['port'],
+ path='/services/Admin',
+ **kwargs)
+ self.apiconn.APIVersion = 'nova'
+
+ def connection_for(self, username, project, clc_url=None, region=None,
+ **kwargs):
+ """
+ Returns a boto ec2 connection for the given username.
+ """
+ if not clc_url:
+ clc_url = self.clc_url
+ if not region:
+ region = self.region
+ parts = self.split_clc_url(clc_url)
+ user = self.get_user(username)
+ access_key = '%s:%s' % (user.accesskey, project)
+ return boto.connect_ec2(aws_access_key_id=access_key,
+ aws_secret_access_key=user.secretkey,
+ is_secure=parts['is_secure'],
+ region=RegionInfo(None,
+ self.region,
+ parts['ip']),
+ port=parts['port'],
+ path='/services/Cloud',
+ **kwargs)
+
+ def split_clc_url(self, clc_url):
+ """
+ Splits a cloud controller endpoint url.
+ """
+ parts = httplib.urlsplit(clc_url)
+ is_secure = parts.scheme == 'https'
+ ip, port = parts.netloc.split(':')
+ return {'ip': ip, 'port': int(port), 'is_secure': is_secure}
+
+ def get_users(self):
+ """ grabs the list of all users """
+ return self.apiconn.get_list('DescribeUsers', {}, [('item', UserInfo)])
+
+ def get_user(self, name):
+ """ grab a single user by name """
+ try:
+ return self.apiconn.get_object('DescribeUser', {'Name': name}, UserInfo)
+ except boto.exception.BotoServerError, e:
+ if e.status == 400 and e.error_code == 'NotFound':
+ return None
+ raise
+
+ def has_user(self, username):
+ """ determine if user exists """
+ return self.get_user(username) != None
+
+ def create_user(self, username):
+ """ creates a new user, returning the userinfo object with access/secret """
+ return self.apiconn.get_object('RegisterUser', {'Name': username}, UserInfo)
+
+ def delete_user(self, username):
+ """ deletes a user """
+ return self.apiconn.get_object('DeregisterUser', {'Name': username}, UserInfo)
+
+ def get_roles(self, project_roles=True):
+ """Returns a list of available roles."""
+ return self.apiconn.get_list('DescribeRoles',
+ {'ProjectRoles': project_roles},
+ [('item', UserRole)])
+
+ def get_user_roles(self, user, project=None):
+ """Returns a list of roles for the given user.
+ Omitting project will return any global roles that the user has.
+ Specifying project will return only project specific roles.
+ """
+ params = {'User':user}
+ if project:
+ params['Project'] = project
+ return self.apiconn.get_list('DescribeUserRoles',
+ params,
+ [('item', UserRole)])
+
+ def add_user_role(self, user, role, project=None):
+ """
+ Add a role to a user either globally or for a specific project.
+ """
+ return self.modify_user_role(user, role, project=project,
+ operation='add')
+
+ def remove_user_role(self, user, role, project=None):
+ """
+ Remove a role from a user either globally or for a specific project.
+ """
+ return self.modify_user_role(user, role, project=project,
+ operation='remove')
+
+ def modify_user_role(self, user, role, project=None, operation='add',
+ **kwargs):
+ """
+ Add or remove a role for a user and project.
+ """
+ params = {'User': user,
+ 'Role': role,
+ 'Project': project,
+ 'Operation': operation}
+ return self.apiconn.get_status('ModifyUserRole', params)
+
+ def get_projects(self, user=None):
+ """
+ Returns a list of all projects.
+ """
+ if user:
+ params = {'User': user}
+ else:
+ params = {}
+ return self.apiconn.get_list('DescribeProjects',
+ params,
+ [('item', ProjectInfo)])
+
+ def get_project(self, name):
+ """
+ Returns a single project with the specified name.
+ """
+ project = self.apiconn.get_object('DescribeProject',
+ {'Name': name},
+ ProjectInfo)
+
+ if project.projectname != None:
+ return project
+
+ def create_project(self, projectname, manager_user, description=None,
+ member_users=None):
+ """
+ Creates a new project.
+ """
+ params = {'Name': projectname,
+ 'ManagerUser': manager_user,
+ 'Description': description,
+ 'MemberUsers': member_users}
+ return self.apiconn.get_object('RegisterProject', params, ProjectInfo)
+
+ def delete_project(self, projectname):
+ """
+ Permanently deletes the specified project.
+ """
+ return self.apiconn.get_object('DeregisterProject',
+ {'Name': projectname},
+ ProjectInfo)
+
+ def get_project_members(self, name):
+ """
+ Returns a list of members of a project.
+ """
+ return self.apiconn.get_list('DescribeProjectMembers',
+ {'Name': name},
+ [('item', ProjectMember)])
+
+ def add_project_member(self, user, project):
+ """
+ Adds a user to a project.
+ """
+ return self.modify_project_member(user, project, operation='add')
+
+ def remove_project_member(self, user, project):
+ """
+ Removes a user from a project.
+ """
+ return self.modify_project_member(user, project, operation='remove')
+
+ def modify_project_member(self, user, project, operation='add'):
+ """
+ Adds or removes a user from a project.
+ """
+ params = {'User': user,
+ 'Project': project,
+ 'Operation': operation}
+ return self.apiconn.get_status('ModifyProjectMember', params)
+
+ def get_zip(self, user, project):
+ """
+ Returns the content of a zip file containing novarc and access credentials.
+ """
+ params = {'Name': user, 'Project': project}
+ zip = self.apiconn.get_object('GenerateX509ForUser', params, UserInfo)
+ return zip.file
+
+ def start_vpn(self, project):
+ """
+ Starts the vpn for a user
+ """
+ return self.apiconn.get_object('StartVpn', {'Project': project}, Vpn)
+
+ def get_vpns(self):
+ """Return a list of vpn with project name"""
+ return self.apiconn.get_list('DescribeVpns', {}, [('item', Vpn)])
+
+ def get_hosts(self):
+ return self.apiconn.get_list('DescribeHosts', {}, [('item', HostInfo)])
+
+ def get_instance_types(self):
+ """Grabs the list of all users."""
+ return self.apiconn.get_list('DescribeInstanceTypes', {},
+ [('item', InstanceType)])
+
+ def disable_project_credentials(self, project):
+ """Revoke project credentials and kill the cloudpipe/vpn instance"""
+ return self.apiconn.get_object('DisableProjectCredentials',
+ {'Project': project}, StatusResponse)
diff --git a/django-nova/src/django_nova/connection.py b/django-nova/src/django_nova/connection.py
new file mode 100644
index 00000000..b29146ed
--- /dev/null
+++ b/django-nova/src/django_nova/connection.py
@@ -0,0 +1,38 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Manage connections to Nova's admin API.
+"""
+
+from django.conf import settings
+from django_nova import adminclient
+
+
+def get_nova_admin_connection():
+ """
+ Returns a Nova administration connection.
+ """
+ return adminclient.NovaAdminClient (
+ clc_url=settings.NOVA_DEFAULT_ENDPOINT,
+ region=settings.NOVA_DEFAULT_REGION,
+ access_key=settings.NOVA_ACCESS_KEY,
+ secret_key=settings.NOVA_SECRET_KEY
+ )
+
+
+
diff --git a/django-nova/src/django_nova/exceptions.py b/django-nova/src/django_nova/exceptions.py
new file mode 100644
index 00000000..03f3a986
--- /dev/null
+++ b/django-nova/src/django_nova/exceptions.py
@@ -0,0 +1,95 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Better wrappers for errors from Nova's admin api.
+"""
+
+import boto.exception
+from django.shortcuts import redirect
+from django.core import exceptions as core_exceptions
+
+
+class NovaServerError(Exception):
+ """
+ Consumes a BotoServerError and gives more meaningful errors.
+ """
+ def __init__(self, ec2error):
+ self.status = ec2error.status
+ self.message = ec2error.reason
+
+ def __str__(self):
+ return self.message
+
+
+class NovaApiError(Exception):
+ """
+ Used when Nova returns a 400 Bad Request status.
+ """
+ def __init__(self, ec2error):
+ self.message = ec2error.error_message
+
+ def __str__(self):
+ return self.message
+
+
+class NovaUnavailableError(NovaServerError):
+ """
+ Used when Nova returns a 503 Service Unavailable status.
+ """
+ pass
+
+
+class NovaUnauthorizedError(core_exceptions.PermissionDenied):
+ """
+ Used when Nova returns a 401 Not Authorized status.
+ """
+ pass
+
+
+def wrap_nova_error(func):
+ """
+ Used to decorate a function that interacts with boto. It will catch
+ and convert boto server errors and reraise as a more specific nova error.
+ """
+ def decorator(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except boto.exception.BotoServerError, e:
+ if e.status == 400 and e.error_code == 'ApiError':
+ raise NovaApiError(e)
+ elif e.status == 401:
+ raise NovaUnauthorizedError()
+ elif e.status == 503:
+ raise NovaUnavailableError(e)
+ raise NovaServerError(e)
+ return decorator
+
+
+def handle_nova_error(func):
+ """
+ Decorator for handling nova errors in a generalized way.
+ """
+ def decorator(*args, **kwargs):
+ try:
+ return func(*args, **kwargs)
+ except NovaUnavailableError:
+ return redirect('nova_unavailable')
+ return decorator
+
+
diff --git a/django-nova/src/django_nova/forms.py b/django-nova/src/django_nova/forms.py
new file mode 100644
index 00000000..cc8ae265
--- /dev/null
+++ b/django-nova/src/django_nova/forms.py
@@ -0,0 +1,262 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Forms used by various views.
+"""
+
+import re
+
+from django import forms
+from django.contrib.auth import models as auth_models
+from django_nova.connection import get_nova_admin_connection
+from django_nova.exceptions import wrap_nova_error
+
+
+# TODO: Store this in settings.
+MAX_VOLUME_SIZE = 100
+
+alphanumeric_re = re.compile(r'^\w+$')
+
+
+@wrap_nova_error
+def get_instance_type_choices():
+ """
+ Returns list of instance types from nova admin api
+ """
+ nova = get_nova_admin_connection()
+ instance_types = nova.get_instance_types()
+ rv = []
+ for t in instance_types:
+ rv.append((t.name, "%s (%sMB memory, %s cpu, %sGB space)" % \
+ (t.name, t.memory_mb, t.vcpus, t.disk_gb)))
+ return rv
+
+def get_instance_choices(project):
+ choices = [(i.id, i.id) for i in project.get_instances()]
+ if not len(choices):
+ choices = [('', 'none available')]
+ return choices
+
+def get_key_pair_choices(project):
+ choices = [(k.name, k.name) for k in project.get_key_pairs()]
+ if not len(choices):
+ choices = [('', 'none available')]
+ return choices
+
+#def get_security_group_choices(project):
+# choices = [(g.name, g.description) for g in project.get_security_groups()]
+# if len(choices) == 0:
+# choices = [('', 'none available')]
+# return choices
+
+def get_available_volume_choices(project):
+ choices = [(v.id, '%s %s - %dGB' % (v.id, v.displayName, v.size)) for v in \
+ project.get_volumes() if v.status != "in-use"]
+ if not len(choices):
+ choices = [('', 'none available')]
+ return choices
+
+def get_protocols():
+ return (
+ ('tcp', 'tcp'),
+ ('udp', 'udp'),
+ )
+
+@wrap_nova_error
+def get_roles(project_roles=True):
+ nova = get_nova_admin_connection()
+ roles = nova.get_roles(project_roles=project_roles)
+ return [(role.role, role.role) for role in roles]
+
+@wrap_nova_error
+def get_members(project):
+ nova = get_nova_admin_connection()
+ members = nova.get_project_members(project)
+ return [str(user.memberId) for user in members]
+
+@wrap_nova_error
+def set_project_roles(projectname, username, roles):
+ nova = get_nova_admin_connection()
+ # hacky work around to interface correctly with multiple select form
+ _remove_roles(projectname, username)
+
+ for role in roles:
+ nova.add_user_role(username, str(role), projectname)
+
+def _remove_roles(project, username):
+ nova = get_nova_admin_connection()
+ userroles = nova.get_user_roles(username, project)
+ roles = [str(role.role) for role in userroles]
+
+ for role in roles:
+ if role == "developer":
+ nova.remove_user_role(username, "developer", project)
+ if role == "sysadmin":
+ nova.remove_user_role(username, "sysadmin", project)
+ if role == "netadmin":
+ nova.remove_user_role(username, "netadmin", project)
+
+
+class ProjectFormBase(forms.Form):
+ def __init__(self, project, *args, **kwargs):
+ self.project = project
+ super(ProjectFormBase, self).__init__(*args, **kwargs)
+
+
+class LaunchInstanceForm(forms.Form):
+ # nickname = forms.CharField()
+ # description = forms.CharField()
+
+ count = forms.ChoiceField(choices=[(x, x) for x in range(1, 6)])
+ size = forms.ChoiceField()
+ key_name = forms.ChoiceField()
+ #security_group = forms.ChoiceField()
+ user_data = forms.CharField(required=False, widget=forms.widgets.Textarea(attrs={'rows': 4}))
+
+ def __init__(self, project, *args, **kwargs):
+ forms.Form.__init__(self, *args, **kwargs)
+ #self.fields['security_group'].choices = get_security_group_choices(project)
+ self.fields['key_name'].choices = get_key_pair_choices(project)
+ self.fields['size'].choices = get_instance_type_choices()
+
+
+class UpdateInstanceForm(forms.Form):
+ nickname = forms.CharField(required=False, label="Name")
+ description = forms.CharField(required=False, widget=forms.Textarea, max_length=70)
+
+ def __init__(self, instance, *args, **kwargs):
+ forms.Form.__init__(self, *args, **kwargs)
+ self.fields['nickname'].initial = instance.displayName
+ self.fields['description'].initial = instance.displayDescription
+
+
+class UpdateImageForm(forms.Form):
+ nickname = forms.CharField(required=False, label="Name")
+ description = forms.CharField(required=False, widget=forms.Textarea, max_length=70)
+
+ def __init__(self, image, *args, **kwargs):
+ forms.Form.__init__(self, *args, **kwargs)
+ self.fields['nickname'].initial = image.displayName
+ self.fields['description'].initial = image.description
+
+
+class CreateKeyPairForm(ProjectFormBase):
+ name = forms.RegexField(regex=alphanumeric_re)
+
+ def clean_name(self):
+ name = self.cleaned_data['name']
+
+ if self.project.has_key_pair(name):
+ raise forms.ValidationError('A key named %s already exists.' % name)
+
+ return name
+
+
+class CreateSecurityGroupForm(ProjectFormBase):
+ name = forms.RegexField(regex=alphanumeric_re)
+ description = forms.CharField()
+
+ def clean_name(self):
+ name = self.cleaned_data['name']
+
+ if self.project.has_security_group(name):
+ raise forms.ValidationError('A security group named %s already exists.' % name)
+
+ return name
+
+
+class AuthorizeSecurityGroupRuleForm(forms.Form):
+ protocol = forms.ChoiceField(choices=get_protocols())
+ from_port = forms.IntegerField(min_value=1, max_value=65535)
+ to_port = forms.IntegerField(min_value=1, max_value=65535)
+
+
+class CreateVolumeForm(forms.Form):
+ size = forms.IntegerField(label='Size (in GB)', min_value=1, max_value=MAX_VOLUME_SIZE)
+ nickname = forms.CharField()
+ description = forms.CharField()
+
+
+class AttachVolumeForm(ProjectFormBase):
+ volume = forms.ChoiceField()
+ instance = forms.ChoiceField()
+ device = forms.CharField(initial='/dev/vdb')
+
+ def __init__(self, project, *args, **kwargs):
+ super(AttachVolumeForm, self).__init__(project, *args, **kwargs)
+ self.fields['volume'].choices = get_available_volume_choices(project)
+ self.fields['instance'].choices = get_instance_choices(project)
+
+
+class ProjectForm(forms.Form):
+ projectname = forms.CharField(label="Project Name", max_length=20)
+ description = forms.CharField(label="Description",
+ widget=forms.widgets.Textarea())
+ manager = forms.ModelChoiceField(queryset=auth_models.User.objects.all(),
+ label="Project Manager")
+
+
+class GlobalRolesForm(forms.Form):
+ role = forms.MultipleChoiceField(label='Roles', required=False)
+
+ def __init__(self, *args, **kwargs):
+ super(GlobalRolesForm, self).__init__(*args, **kwargs)
+ self.fields['role'].choices = get_roles(project_roles=False)
+
+
+class ProjectUserForm(forms.Form):
+ role = forms.MultipleChoiceField(label='Roles', required=False)
+
+ def __init__(self, project, user, *args, **kwargs):
+ super(ProjectUserForm, self).__init__(*args, **kwargs)
+ self.project = project
+ self.user = user
+ self.fields['role'].choices = get_roles()
+
+ def save(self):
+ set_project_roles(self.project.projectname,
+ self.user.username,
+ self.cleaned_data['role'])
+
+
+class AddProjectUserForm(forms.Form):
+ username = forms.ModelChoiceField(queryset='',
+ label='Username',
+ empty_label='Select a Username')
+ role = forms.MultipleChoiceField(label='Roles')
+
+ def __init__(self, *args, **kwargs):
+ project = kwargs.pop('project')
+ super(AddProjectUserForm, self).__init__(*args, **kwargs)
+ members = get_members(project)
+
+ self.fields['username'].queryset = \
+ auth_models.User.objects.exclude(username__in=members)
+ self.fields['role'].choices = get_roles()
+
+
+class SendCredentialsForm(forms.Form):
+ users = forms.MultipleChoiceField(label='Users', required=True)
+
+ def __init__(self, *args, **kwargs):
+ query_list = kwargs.pop('query_list')
+ super(SendCredentialsForm, self).__init__(*args, **kwargs)
+
+ self.fields['users'].choices = [(choices, choices) for choices in query_list]
+
diff --git a/local/__init__.py b/django-nova/src/django_nova/management/__init__.py
index e69de29b..e69de29b 100644
--- a/local/__init__.py
+++ b/django-nova/src/django_nova/management/__init__.py
diff --git a/django-nova/src/django_nova/management/commands/__init__.py b/django-nova/src/django_nova/management/commands/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/django-nova/src/django_nova/management/commands/__init__.py
diff --git a/django-nova/src/django_nova/management/commands/createnovausers.py b/django-nova/src/django_nova/management/commands/createnovausers.py
new file mode 100644
index 00000000..18bb461d
--- /dev/null
+++ b/django-nova/src/django_nova/management/commands/createnovausers.py
@@ -0,0 +1,37 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Management commands for synchronizing the Django auth database and Nova
+users database.
+"""
+
+from django.core.management.base import NoArgsCommand
+from django.contrib.auth.models import User
+from django_nova.connection import get_nova_admin_connection
+
+class Command(NoArgsCommand):
+ help = 'Creates nova users for all users in the django auth database.'
+
+ def handle_noargs(self, **options):
+ nova = get_nova_admin_connection()
+ users = User.objects.all()
+ for user in users:
+ if not nova.has_user(user.username):
+ self.stdout.write('creating user %s... ' % user.username)
+ nova.create_user(user.username)
+ self.stdout.write('ok\n')
diff --git a/django-nova/src/django_nova/manager.py b/django-nova/src/django_nova/manager.py
new file mode 100644
index 00000000..4cdb892a
--- /dev/null
+++ b/django-nova/src/django_nova/manager.py
@@ -0,0 +1,340 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Simple API for interacting with Nova projects.
+"""
+
+import boto
+import boto.ec2.volume
+import boto.exception
+import boto.s3
+from django.conf import settings
+from django_nova.connection import get_nova_admin_connection
+from django_nova.exceptions import wrap_nova_error
+
+
+class ProjectManager(object):
+ def __init__(self, username, project, region):
+ self.username = username
+ self.projectname = project.projectname
+ self.projectManagerId = project.projectManagerId
+ self.region = region
+
+ def get_nova_connection(self):
+ """
+ Returns a boto connection for a user's project.
+ """
+ nova = get_nova_admin_connection()
+ return nova.connection_for(self.username,
+ self.projectname,
+ clc_url=self.region['endpoint'],
+ region=self.region['name'])
+
+ def get_zip(self):
+ """
+ Returns a buffer of a zip file containing signed credentials
+ for the project's Nova user.
+ """
+ nova = get_nova_admin_connection()
+ return nova.get_zip(self.username, self.projectname)
+
+ def get_images(self, image_ids=None):
+ conn = self.get_nova_connection()
+ images = conn.get_all_images(image_ids=image_ids)
+ sorted_images = [i for i in images if i.ownerId == self.username] + \
+ [i for i in images if i.ownerId != self.username]
+
+ return [i for i in sorted_images if i.type == 'machine' and i.location.split('/')[0] != 'nova']
+
+ def get_image(self, image_id):
+ try:
+ return self.get_images(image_ids=[image_id,])[0]
+ except IndexError:
+ return None
+
+ @wrap_nova_error
+ def deregister_image(self, image_id):
+ """
+ Removes the image's listing but leaves the image
+ and manifest in the object store in tact.
+ """
+ conn = self.get_nova_connection()
+ return conn.deregister_image(image_id)
+
+ @wrap_nova_error
+ def update_image(self, image_id, display_name=None, description=None):
+ conn = self.get_nova_connection()
+ params = {
+ 'ImageId': image_id,
+ 'DisplayName': display_name,
+ 'Description': description
+ }
+ return conn.get_object('UpdateImage', params, boto.ec2.image.Image)
+
+ @wrap_nova_error
+ def modify_image_attribute(self, image_id, attribute=None, operation=None,
+ groups='all'):
+ conn = self.get_nova_connection()
+ return conn.modify_image_attribute(image_id,
+ attribute='launchPermission',
+ operation='remove',
+ groups='all',)
+
+
+ @wrap_nova_error
+ def run_instances(self, image_id, **kwargs):
+ """
+ Runs instances of the specified image id.
+ """
+ conn = self.get_nova_connection()
+ return conn.run_instances(image_id, **kwargs)
+
+ def get_instance_count(self):
+ """
+ Returns the number of active instances in this project or None if unknown.
+ """
+ try:
+ return len(self.get_instances())
+ except:
+ return None
+
+ @wrap_nova_error
+ def get_instances(self):
+ """
+ Returns all instances in this project.
+ """
+ conn = self.get_nova_connection()
+ reservations = conn.get_all_instances()
+ instances = []
+ for reservation in reservations:
+ for instance in reservation.instances:
+ instances.append(instance)
+ return instances
+
+ @wrap_nova_error
+ def get_instance(self, instance_id):
+ """
+ Returns detail about the specified instance.
+ """
+ conn = self.get_nova_connection()
+ # TODO: Refactor this once nova's describe_instances filters by instance_id.
+ reservations = conn.get_all_instances()
+ for reservation in reservations:
+ for instance in reservation.instances:
+ if instance.id == instance_id:
+ return instance
+ return None
+
+ @wrap_nova_error
+ def update_instance(self, instance_id, updates):
+ conn = self.get_nova_connection()
+ params = {'InstanceId': instance_id, 'DisplayName': updates['nickname'],
+ 'DisplayDescription': updates['description']}
+ return conn.get_object('UpdateInstance', params,
+ boto.ec2.instance.Instance)
+
+ def get_instance_graph(self, region, instance_id, graph_name):
+ # TODO(devcamcar): Need better support for multiple regions.
+ # Need a way to get object store by region.
+ s3 = boto.s3.connection.S3Connection (
+ aws_access_key_id=settings.NOVA_ACCESS_KEY,
+ aws_secret_access_key=settings.NOVA_SECRET_KEY,
+ is_secure=False,
+ calling_format=boto.s3.connection.OrdinaryCallingFormat(),
+ port=3333,
+ host=settings.NOVA_CLC_IP
+ )
+ key = '_%s.monitor' % instance_id
+
+ try:
+ bucket = s3.get_bucket(key, validate=False)
+ except boto.exception.S3ResponseError, e:
+ if e.code == "NoSuchBucket":
+ return None
+ else:
+ raise e
+
+ key = bucket.get_key(graph_name)
+
+ return key.read()
+
+ @wrap_nova_error
+ def terminate_instance(self, instance_id):
+ """ Terminates the specified instance within this project. """
+ conn = self.get_nova_connection()
+ conn.terminate_instances([instance_id])
+
+ @wrap_nova_error
+ def get_security_groups(self):
+ """
+ Returns all security groups associated with this project.
+ """
+ conn = self.get_nova_connection()
+ groups = []
+
+ for g in conn.get_all_security_groups():
+ # Do not show vpn group.
+ #if g.name != 'vpn-secgroup':
+ groups.append(g)
+
+ return groups
+
+ @wrap_nova_error
+ def get_security_group(self, name):
+ """
+ Returns the specified security group for this project.
+ """
+ conn = self.get_nova_connection()
+
+ try:
+ return conn.get_all_security_groups(groupnames=name.encode('ASCII'))[0]
+ except IndexError:
+ return None
+
+ @wrap_nova_error
+ def has_security_group(self, name):
+ """
+ Indicates whether a security group with the specified name exists in this project.
+ """
+ return self.get_security_group(name) is not None
+
+ @wrap_nova_error
+ def create_security_group(self, name, description):
+ """
+ Creates a new security group in this project.
+ """
+ conn = self.get_nova_connection()
+ return conn.create_security_group(name, description)
+
+ @wrap_nova_error
+ def delete_security_group(self, name):
+ """
+ Deletes a security group from the project.
+ """
+ conn = self.get_nova_connection()
+ return conn.delete_security_group(name = name)
+
+ @wrap_nova_error
+ def authorize_security_group(self, group_name, ip_protocol, from_port, to_port):
+ """
+ Authorizes a rule for the specified security group.
+ """
+ conn = self.get_nova_connection()
+ return conn.authorize_security_group (
+ group_name = group_name,
+ ip_protocol = ip_protocol,
+ from_port = from_port,
+ to_port = to_port,
+ cidr_ip = '0.0.0.0/0'
+ )
+
+ @wrap_nova_error
+ def revoke_security_group(self, group_name, ip_protocol, from_port, to_port):
+ """
+ Revokes a rule for the specified security group.
+ """
+ conn = self.get_nova_connection()
+ return conn.revoke_security_group (
+ group_name = group_name,
+ ip_protocol = ip_protocol,
+ from_port = from_port,
+ to_port = to_port,
+ cidr_ip = '0.0.0.0/0'
+ )
+
+ @wrap_nova_error
+ def get_key_pairs(self):
+ """
+ Returns all key pairs associated with this project.
+ """
+ conn = self.get_nova_connection()
+ keys = []
+
+ for k in conn.get_all_key_pairs():
+ # Do not show vpn key.
+ if k.name != 'vpn-key':
+ keys.append(k)
+
+ return keys
+
+ @wrap_nova_error
+ def get_key_pair(self, name):
+ """
+ Returns the specified security group for this project.
+ """
+ conn = self.get_nova_connection()
+
+ try:
+ return conn.get_all_key_pairs(keynames=name.encode('ASCII'))[0]
+ except IndexError:
+ return None
+
+ @wrap_nova_error
+ def has_key_pair(self, name):
+ """
+ Indicates whether a key pair with the specified name exists in this project.
+ """
+ return self.get_key_pair(name) != None
+
+ @wrap_nova_error
+ def create_key_pair(self, name):
+ """
+ Creates a new key pair for this project.
+ """
+ conn = self.get_nova_connection()
+ return conn.create_key_pair(name)
+
+ @wrap_nova_error
+ def delete_key_pair(self, name):
+ """
+ Deletes a new key pair from this project.
+ """
+ conn = self.get_nova_connection()
+ conn.delete_key_pair(name)
+
+ @wrap_nova_error
+ def get_volumes(self):
+ """
+ Returns all volumes in this project.
+ """
+ conn = self.get_nova_connection()
+ return conn.get_all_volumes()
+
+ @wrap_nova_error
+ def create_volume(self, size, display_name=None, display_description=None,
+ snapshot=None):
+ conn = self.get_nova_connection()
+ params = {'Size': size, 'DisplayName': display_name,
+ 'DisplayDescription': display_description}
+ return conn.get_object('CreateVolume', params, boto.ec2.volume.Volume)
+
+ @wrap_nova_error
+ def delete_volume(self, volume_id):
+ conn = self.get_nova_connection()
+ return conn.delete_volume(volume_id)
+
+ @wrap_nova_error
+ def attach_volume(self, volume_id, instance_id, device):
+ conn = self.get_nova_connection()
+ return conn.attach_volume(volume_id, instance_id, device)
+
+ @wrap_nova_error
+ def detach_volume(self, volume_id):
+ conn = self.get_nova_connection()
+ return conn.detach_volume(volume_id)
+
diff --git a/django-nova/src/django_nova/models.py b/django-nova/src/django_nova/models.py
new file mode 100644
index 00000000..5f06f251
--- /dev/null
+++ b/django-nova/src/django_nova/models.py
@@ -0,0 +1,121 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Database models for authorization credentials and synchronizing Nova users.
+"""
+
+import datetime
+import random
+import re
+import sha
+from django.conf import settings
+from django.contrib.auth import models as auth_models
+from django.contrib.sites import models as site_models
+from django.core import mail
+from django.db import models
+from django.db.models.signals import post_save
+from django.template.loader import render_to_string
+from django_nova.connection import get_nova_admin_connection
+
+
+SHA1_RE=re.compile('^[a-f0-9]{40}$')
+
+
+class CredentialsAuthorization(models.Model):
+ username = models.CharField(max_length=128)
+ project = models.CharField(max_length=128)
+ auth_token = models.CharField(max_length=40)
+ auth_date = models.DateTimeField(auto_now_add=True)
+
+ def __str__(self):
+ return '%s/%s:%s' % (self.username, self.project, self.auth_token)
+
+ @classmethod
+ def get_by_token(cls, token):
+ if SHA1_RE.search(token):
+ try:
+ credentials = cls.objects.get(auth_token=token)
+ except cls.DoesNotExist:
+ return None
+ if not credentials.auth_token_expired():
+ return credentials
+ return None
+
+ @classmethod
+ def authorize(cls, username, project):
+ return cls.objects.create(username=username,
+ project=project,
+ auth_token=cls.create_auth_token(username))
+
+ @staticmethod
+ def create_auth_token(username):
+ salt = sha.new(str(random.random())).hexdigest()[:5]
+ return sha.new(salt+username).hexdigest()
+
+ def auth_token_expired(self):
+ expiration_date = datetime.timedelta(days=int(settings.CREDENTIAL_AUTHORIZATION_DAYS))
+
+ return self.auth_date + expiration_date <= datetime.datetime.now()
+
+ def get_download_url(self):
+ return settings.CREDENTIAL_DOWNLOAD_URL + self.auth_token
+
+ def get_zip(self):
+ nova = get_nova_admin_connection()
+ self.delete()
+ return nova.get_zip(self.username, self.project)
+
+
+def credentials_post_save(sender, instance, created, *args, **kwargs):
+ """
+ Creates a Nova User when a new Django User is created.
+ """
+ if created:
+ site = site_models.Site.objects.get_current()
+ user = auth_models.User.objects.get(username=instance.username)
+ context = {
+ 'user': user,
+ 'download_url': instance.get_download_url(),
+ 'dashboard_url': 'http://%s/' % site.domain
+ }
+ subject = render_to_string('credentials/credentials_email_subject.txt')
+ body = render_to_string('credentials/credentials_email.txt', context)
+
+ message = mail.EmailMessage(subject=subject, body=body, to=[user.email])
+ message.send(fail_silently=False)
+post_save.connect(credentials_post_save,
+ CredentialsAuthorization,
+ dispatch_uid='django_nova.CredentialsAuthorization.post_save')
+
+
+def user_post_save(sender, instance, created, *args, **kwargs):
+ """
+ Creates a Nova User when a new Django User is created.
+ """
+
+ # NOTE(devcamcar): If running unit tests, don't use a real endpoint.
+ if settings.NOVA_DEFAULT_ENDPOINT == 'none':
+ return
+
+ if created:
+ nova = get_nova_admin_connection()
+ if not nova.has_user(instance.username):
+ nova.create_user(instance.username)
+post_save.connect(user_post_save,
+ auth_models.User,
+ dispatch_uid='django_nova.User.post_save')
diff --git a/django-nova/src/django_nova/shortcuts.py b/django-nova/src/django_nova/shortcuts.py
new file mode 100644
index 00000000..15474d0b
--- /dev/null
+++ b/django-nova/src/django_nova/shortcuts.py
@@ -0,0 +1,131 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Helper methods for commonly used operations.
+"""
+
+from django.conf import settings
+from django.core.cache import cache
+from django.core.exceptions import PermissionDenied
+from django.http import Http404
+from django_nova import manager
+from django_nova.connection import get_nova_admin_connection
+from django_nova.exceptions import wrap_nova_error
+
+
+@wrap_nova_error
+def get_project_or_404(request, project_id):
+ """
+ Returns a project or 404s if it doesn't exist.
+ """
+
+ # Ensure that a connection is never attempted for a user that is unauthenticated.
+ if not request.user.is_authenticated:
+ raise PermissionDenied('User not authenticated')
+
+ nova = get_nova_admin_connection()
+ project = nova.get_project(project_id)
+ region = get_current_region(request)
+
+ if not project:
+ raise Http404('Project %s does not exist.' % project_id)
+
+ return manager.ProjectManager(request.user, project, region)
+
+
+@wrap_nova_error
+def get_projects(user):
+ """
+ Returns a list of projects for a user.
+ """
+ #key = 'projects.%s' % user
+ #projects = cache.get(key)
+
+ #if not projects:
+ # nova = get_nova_admin_connection()
+ # projects = nova.get_projects(user=user)
+ # cache.set(key, projects, 30)
+
+ #return projects
+ nova = get_nova_admin_connection()
+ return nova.get_projects(user=user)
+
+
+@wrap_nova_error
+def get_all_regions():
+ """
+ Returns a list of all regions.
+ """
+ regions = cache.get('regions')
+
+ if not regions:
+ nova = get_nova_admin_connection()
+ conn = nova.connection_for(settings.NOVA_ADMIN_USER, settings.NOVA_PROJECT)
+ results = conn.get_all_regions()
+ regions = [{'name': r.name, 'endpoint': r.endpoint} for r in results]
+ cache.set('regions', regions, 60 * 60 * 24)
+
+ return regions
+
+
+def get_region(region_name):
+ regions = get_all_regions()
+ try:
+ return [r for r in regions if r['name'] == region_name][0]
+ except IndexError:
+ return None
+
+
+def get_current_region(request):
+ """
+ Returns the currently selected region for a user.
+ """
+ region_name = request.session.get('region', settings.NOVA_DEFAULT_REGION)
+ return get_region(region_name)
+
+
+def set_current_region(request, region_name):
+ """
+ Sets the current region selection for a user.
+ """
+ request.session['region'] = region_name
+
+
+@wrap_nova_error
+def get_user_image_permissions(username, project_name):
+ """
+ Returns true if user is a sysadmin and can modify image attributes.
+ """
+ nova = get_nova_admin_connection()
+ user_has_modify_permissions = False
+
+ # checks global roles, if user is a sysadmin they can modify image attribtues.
+ if not user_has_modify_permissions:
+ for role in nova.get_user_roles(username):
+ if role.role == "sysadmin":
+ user_has_modify_permissions = True
+
+ # checks project roles, if user is a sysadmin they can modify image attribtues.
+ if not user_has_modify_permissions:
+ for role in nova.get_user_roles(username, project_name):
+ if role.role == "sysadmin":
+ user_has_modify_permissions = True
+
+ return user_has_modify_permissions
+
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html
new file mode 100644
index 00000000..e91d1889
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project.html
@@ -0,0 +1,45 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% load admin_modify adminmedia %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media }}
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block bodyclass %} change-form{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ <a href="../../projects">Projects</a> &rsaquo;
+ Add Project
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+ {% block object-tools %}
+ {% endblock %}
+ <form action="." method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <fieldset class="module aligned {{ fieldset.classes }}">
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field.label_tag }}{{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+
+ </fieldset>
+ <input type="submit" value="Save" />
+ </form>
+</div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html
new file mode 100644
index 00000000..4790a3ca
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/add_project_user.html
@@ -0,0 +1,69 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% load admin_modify adminmedia %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media }}
+
+<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
+<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
+
+<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
+<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
+
+<script type="text/javascript" charset="utf-8">
+ django.jQuery(function(){
+ django.jQuery.each(django.jQuery(".edit_user_roles select[multiple]"), function () {
+ // "Locations" can be any label you want
+ SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
+ });
+ })
+</script>
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block bodyclass %} change-form{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ <a href="{% url admin_projects %}">Projects</a> &rsaquo;
+ <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> &rsaquo;
+ User
+ {{form.ProjectUserForm}}
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+ {% block object-tools %}
+ {% endblock %}
+ <form class="edit_user_roles" action="." method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <fieldset class="module aligned {{ fieldset.classes }}">
+ <input type="hidden" name="username" value="{{user.id}}" id="username" />
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field.label_tag }}{{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+
+ </fieldset>
+ <div class="submit-row">
+ <p class="deletelink-box">
+ <a href="{% url admin_project_delete_user project.projectname user.username %}" class="deletelink">Delete</a>
+ </p>
+ <input type="submit" value="Save" class="default" />
+ </div>
+
+ </form>
+</div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html b/django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html
new file mode 100644
index 00000000..60759c6b
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/base_projects.html
@@ -0,0 +1,16 @@
+{% extends "admin/change_list.html" %}
+
+{% block extrastyle %}
+ {{block.super}}
+ <link rel="stylesheet" type="text/css" href="{{settings.MEDIA_URL}}/stylesheets/extra_admin.css" />
+{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">Home</a>&nbsp;&rsaquo;&nbsp;Projects</div>{% endblock %}
+{% block content %}
+ <div id="content-main">
+ <div class="module filtered" id="changelist">
+ <div id="toolbartable">
+ {% block innercontent %}{% endblock %}
+ </div>
+ </div>
+ </div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html b/django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html
new file mode 100644
index 00000000..ba0046d7
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/change_list.html
@@ -0,0 +1,3 @@
+{% extends "admin/change_list.html" %}
+{% load admin_extras %}
+{% block result_list %}{% project_result_list cl %}{% endblock %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html
new file mode 100644
index 00000000..53f180ef
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project.html
@@ -0,0 +1,25 @@
+{% extends "admin/change_list.html" %}
+
+{% block extrastyle %}
+ {{block.super}}
+ <link rel="stylesheet" type="text/css" href="{{settings.MEDIA_URL}}/stylesheets/extra_admin.css" />
+{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">Home</a>&nbsp;&rsaquo;&nbsp;<a href="/admin/projects">Projects</a>&nbsp;&rsaquo;&nbsp; <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a>&nbsp;&rsaquo;&nbsp;Delete</div>{% endblock %}
+{% block content %}
+ <div id="content-main">
+ <div class="module filtered" id="changelist">
+ <div id="toolbartable">
+ <h1>Delete Project</h1>
+ <p>Do you really want to delete this project?</p>
+ <ul>
+ <li><a href="{% url admin_project project.projectname %}">{{project.projectname}}</a></li>
+ </ul>
+
+ <form action="." method="post">
+ {% csrf_token %}
+ <p><input type="submit" value="Delete"></p>
+ </form>
+ </div>
+ </div>
+ </div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html
new file mode 100644
index 00000000..37071ee2
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/delete_project_user.html
@@ -0,0 +1,25 @@
+{% extends "admin/change_list.html" %}
+
+{% block extrastyle %}
+ {{block.super}}
+ <link rel="stylesheet" type="text/css" href="{{settings.MEDIA_URL}}/stylesheets/extra_admin.css" />
+{% endblock %}
+{% block breadcrumbs %}<div class="breadcrumbs"><a href="/admin/">Home</a>&nbsp;&rsaquo;&nbsp;<a href="/admin/projects">Projects</a>&nbsp;&rsaquo;&nbsp; <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a>&nbsp;&rsaquo;&nbsp;Delete</div>{% endblock %}
+{% block content %}
+ <div id="content-main">
+ <div class="module filtered" id="changelist">
+ <div id="toolbartable">
+ <h1>Remove User From Project</h1>
+ <p>Do you really want to remove this user from project?</p>
+ <ul>
+ <li><a href="{% url project_user project.projectname user.username %}">{{user.username}}</a> from <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a></li>
+ </ul>
+
+ <form action="." method="post">
+ {% csrf_token %}
+ <p><input type="submit" value="Delete"></p>
+ </form>
+ </div>
+ </div>
+ </div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html b/django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html
new file mode 100644
index 00000000..6952eb45
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/edit_project.html
@@ -0,0 +1,95 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% load admin_modify adminmedia %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media }}
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block bodyclass %} change-form{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ <a href="{% url admin_projects %}">Projects</a> &rsaquo;
+ <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> &rsaquo;
+ Edit Project
+ {{form.ProjectEditForm}}
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+ {% block object-tools %}
+ {% endblock %}
+ <form action="#" method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <fieldset class="module aligned {{ fieldset.classes }}">
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field.label_tag }}{{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+ <div class="form-row">
+ <label for="project_name">Project Name</label>
+ <span id="project_name" style="display: block; padding-top: 5px; padding-left: 5px; float: left;"><em>{{projectname}}</em></span>
+ </div>
+ <div class="form-row">
+ <label for="project_description">Description</label>
+ <span id="project_description" style="display: block; padding-top: 5px; padding-left: 5px; float: left;"><em>{{description}}</em></span>
+ </div>
+ <div class="form-row">
+ <label for="project_manager">Project Manager</label>
+ <span id="project_manager" style="display: block; padding-top: 5px; padding-left: 5px; float: left;"><em>{{manager}}</em></span>
+ </div>
+ </fieldset>
+ <div class="submit-row">
+ <p class="deletelink-box">
+ <a href="{% url delete_project project.projectname %}" class="deletelink">Delete Project</a>
+ </p>
+ {# <input type="submit" value="Save" class="default" /> #}
+ </div>
+
+ </form>
+
+
+ <table cellspacing="0" style="margin-top: 20px;">
+ <thead>
+ <tr>
+ <th>Username</th>
+ <th>Project Roles</th>
+ <th>Global Roles</th>
+ </tr>
+ </thead>
+ {% for user in users %}
+ <tr class="{% cycle 'row1' 'row2' %}">
+ <td>
+ <a href="{%url project_user project.projectname user.memberId %}">{{user.memberId}} {% if user.memberId == project.projectManagerId %}(<em>project manager</em>){%endif %}</a>
+
+ </td>
+ <td>
+ {{user.project_roles}}
+ </td>
+ <td>
+ {{user.global_roles}}
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ <ul class="object-tools">
+ <li>
+ <a class="addlink" href="{% url add_project_user project.projectname %}">Add User</a>
+ </li>
+ </ul>
+
+</div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html
new file mode 100644
index 00000000..d6d618af
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/global_edit_user.html
@@ -0,0 +1,71 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% load admin_modify adminmedia %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media }}
+
+<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
+<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
+
+<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
+<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
+
+<script type="text/javascript" charset="utf-8">
+ django.jQuery(function(){
+ django.jQuery.each(django.jQuery("#global_users select"), function () {
+ // "Locations" can be any label you want
+ SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
+ });
+ })
+</script>
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block bodyclass %} change-form{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ <a href="{% url admin_users_list %}">Global Roles</a> &rsaquo;
+ {{user.username}}
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+ {% block object-tools %}
+ {% endblock %}
+ <form action="." method="post" enctype="multipart/form-data" id="global_users">
+ {% csrf_token %}
+ <fieldset class="module aligned {{ fieldset.classes }}">
+ <div class="form-row">
+ <label for="id_username">Username</label>
+ <span>{{user.username}}</span>
+ </div>
+ <input type="hidden" name="username" value="{{user.id}}" id="username" />
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field.label_tag }}{{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+
+ </fieldset>
+ <div class="submit-row">
+ <p class="deletelink-box">
+ {# <a href="{% url admin_project_delete_user project.projectname user.username %}" class="deletelink">Delete</a> #}
+ </p>
+ <input type="submit" value="Save" class="default" />
+ </div>
+
+ </form>
+</div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html b/django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html
new file mode 100644
index 00000000..2159d777
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/project_list.html
@@ -0,0 +1,42 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% block extrahead %}
+ {{ block.super }}
+{% endblock %}
+{% block innercontent %}
+ <ul class="object-tools">
+ <li>
+ <a class="addlink" href="{% url add_project %}">Add Project</a>
+ </li>
+ </ul>
+ <table cellspacing="0" style="margin-top: 20px;">
+ <thead>
+ <tr>
+ <th>Name</th>
+ <th>Description</th>
+ <th>Project Manager</th>
+ <th>Send Credentials</th>
+ <th>Start VPN</th>
+ </tr>
+ </thead>
+ {% for project in projects %}
+ <tr class="{% cycle 'row1' 'row2' %}">
+ <td>
+ <a href="{%url admin_project project.projectname %}">{{project.projectname}}</a>
+ </td>
+ <td>
+ {{project.description}}
+ </td>
+ <td>
+ {{project.projectManagerId}}
+ </td>
+ <td>
+ <a href="{% url admin_project_sendcredentials project.projectname %}">Send Credentials</a>
+ </td>
+ <td>
+ <a href="{% url admin_project_start_vpn project.projectname %}">Start VPN</a>
+ </td>
+
+ </tr>
+ {% endfor %}
+ </table>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html b/django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html
new file mode 100644
index 00000000..7b19564c
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/project_user.html
@@ -0,0 +1,76 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% load admin_modify adminmedia %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media }}
+
+<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
+<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
+
+<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
+<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
+
+<script type="text/javascript" charset="utf-8">
+ django.jQuery(function(){
+ django.jQuery.each(django.jQuery(".edit_user_roles select[multiple]"), function () {
+ // "Locations" can be any label you want
+ SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
+ });
+ })
+</script>
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
+
+{% block coltype %}colMS{% endblock %}
+
+{% block bodyclass %} change-form{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ <a href="{% url admin_projects %}">Projects</a> &rsaquo;
+ <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> &rsaquo;
+ User
+ {{form.ProjectUserForm}}
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+ {% block object-tools %}
+ {% endblock %}
+ <form class="edit_user_roles" action="." method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <fieldset class="module aligned {{ fieldset.classes }}">
+ <div class="form-row">
+ <label for="id_username">Username</label>
+ <span>{{user.username}}</span>
+ </div>
+ <input type="hidden" name="username" value="{{user.id}}" id="username" />
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field.label_tag }}{{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+
+ </fieldset>
+ <div class="submit-row">
+ {% if project.projectManagerId != user.username %}
+ <p class="deletelink-box">
+ <a href="{% url admin_project_delete_user project.projectname user.username %}" class="deletelink">Remove User From Project</a>
+ </p>
+ {% endif %}
+
+ <input type="submit" value="Save" class="default" />
+ </div>
+
+ </form>
+</div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html b/django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html
new file mode 100644
index 00000000..1bd0294e
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/send_credentials.html
@@ -0,0 +1,87 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+
+{% block title %}Send project credentials{{ block.super }}{% endblock %}
+
+{% block extrahead %}
+{{ block.super }}
+{{ media }}
+
+<script type="text/javascript" src="/media/admin/js/jquery.min.js"></script>
+<script type="text/javascript" src="/media/admin/js/jquery.init.js"></script>
+
+<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
+<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
+
+<script type="text/javascript" charset="utf-8">
+ django.jQuery(function(){
+ django.jQuery.each(django.jQuery("#send_credentials select"), function () {
+ // "Locations" can be any label you want
+ SelectFilter.init(this.id, "Users", 0, "/media/admin/");
+ });
+ })
+</script>
+
+<style type="text/css" media="screen">
+ .errorlist, .successlist {background:#fcc;border:1px solid #c66;color:#600;list-style:none; padding: 10px 5px; margin: 25px 0 25px 0; float: left; width: 100%;}
+ .successlist {background: #CBFBD7; color: #1E5024; border-color: #6FBA5C;}
+</style>
+{% endblock %}
+
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ <a href="{% url admin_projects %}">Projects</a> &rsaquo;
+ <a href="{% url admin_project project.projectname %}">{{project.projectname}}</a> &rsaquo;
+ Send Credentials
+</div>
+{% endblock %}
+
+{% block content %}
+<div id="content-main">
+
+
+ {% if not success %}
+ <h1>Send Credentials</h1>
+ <h3>Select which users you would like to send credentials to from the '{{ project.projectname }}' project.</h3>
+ {% else %}
+ <h1>Credentials sent successfully</h1>
+ {% endif %}
+
+ <div class="status">
+ {% if error %}
+ <span class="errorlist">{{ error }}</span>
+ {% endif %}
+
+ {% if success %}
+ <span class="successlist">{{ success }}</span>
+ {% endif %}
+
+ </div>
+
+ {% if not success %}
+ <form id="send_credentials" action="{% url admin_project_sendcredentials project.projectname %}" method="post">
+ {% csrf_token %}
+ <fieldset class="module aligned">
+
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+
+ </fieldset>
+ <div class="submit-row">
+ <input style="margin-top:20px; margin-left:10px;" type="submit" value="Send Credentials" />
+ </div>
+
+ </form>
+ {% endif %}
+</div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html b/django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html
new file mode 100644
index 00000000..4bd59427
--- /dev/null
+++ b/django-nova/src/django_nova/templates/admin/django_nova/project/user_list.html
@@ -0,0 +1,39 @@
+{% extends "admin/django_nova/project/base_projects.html" %}
+{% block extrahead %}
+ {{ block.super }}
+{% endblock %}
+
+{% block breadcrumbs %}
+<div class="breadcrumbs">
+ <a href="/admin">Home</a> &rsaquo;
+ Global Roles
+</div>
+{% endblock %}
+
+{% block innercontent %}
+ <h1>Select a User</h1>
+
+ <table cellspacing="0" style="margin-top: 20px;">
+ <thead>
+ <tr>
+ <th>Username</th>
+ <th>Global Roles</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ {% for user in users %}
+ <tr class="{% cycle 'row1' 'row2' %}">
+ <td>
+ {{user.username}}
+ </td>
+ <td>
+ (temporarily hidden)
+ {#user.roles#}
+ </td>
+ <td>
+ <a href="{%url admin_user_roles user.username %}">Edit</a>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/_messages.html b/django-nova/src/django_nova/templates/django_nova/_messages.html
new file mode 100644
index 00000000..8cead94b
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/_messages.html
@@ -0,0 +1,41 @@
+{% for message in messages %}
+ <div class="message ui-widget">
+ {% if message.tags == "info" %}
+ <div class="ui-state-highlight ui-corner-all">
+ <span class="close ui-icon ui-icon-circle-close"></span>
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ {{ message }}
+ </p>
+ </div>
+ {% endif %}
+ {% if message.tags == "warning" %}
+ <div class="ui-state-highlight ui-corner-all" >
+ <span class="close ui-icon ui-icon-circle-close"></span>
+ <p>
+ <span class="ui-icon ui-icon-alert"></span>
+ {{ message }}
+ </p>
+ </div>
+ {% endif %}
+ {% if message.tags == "success" %}
+ <div class="ui-state-highlight ui-corner-all success" >
+ <span class="close ui-icon ui-icon-circle-close"></span>
+ <p>
+ <span class="ui-icon ui-icon-check"></span>
+ {{ message }}
+ </p>
+ </div>
+ {% endif %}
+ {% if message.tags == "error" %}
+ <div class="ui-state-error ui-corner-all" >
+ <span class="close ui-icon ui-icon-circle-close"></span>
+ <p>
+ <span class="ui-icon ui-icon-alert"></span>
+ {{ message }}
+ </p>
+ </div>
+ {% endif %}
+ </div>
+{% endfor %}
+
diff --git a/django-nova/src/django_nova/templates/django_nova/base.html b/django-nova/src/django_nova/templates/django_nova/base.html
new file mode 100644
index 00000000..d5551ae9
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/base.html
@@ -0,0 +1,85 @@
+{% extends "base-sidebar.html" %}
+{% load region_tags %}
+{% load project_tags %}
+
+{% block headerjs %}
+{{ block.super }}
+{% endblock %}
+
+{% block region %}
+ <div id="region_selector">
+ {% load_regions %}
+ <span id="project_name"><strong>Project:</strong> {{ project.projectname }}</span>
+
+ <form id="frm_region" method="post" action="{% url region_change %}">
+ {% csrf_token %}
+ <fieldset>
+ <input name="redirect_url" type="hidden" value="{{ request.get_full_path }}" />
+ <noscript>
+ <input id="btn_region_change" type="submit" value="Change" />
+ </noscript>
+
+ <div id="region_form">
+ <label for="sel_region">Region: </label>
+ <select id="sel_region" name="region">
+ {% for region in regions %}
+ <option{% if region.name == current_region.name %} selected{% endif %}>
+ {{ region.name }}
+ </option>
+ {% endfor %}
+ </select>
+ </div>
+
+ </fieldset>
+ </form>
+ </div>
+{% endblock %}
+
+{% block nav_projects %}
+ {% load_projects %}
+ <li>
+ <h3 class="active"><a href="/">Projects</a></h3>
+ <div id="projects">
+ {% for p in projects %}
+ <div id="{{ p.projectname }}" class="project{% if p.projectname == project.projectname %} active{% endif %}">
+ <h4>
+ <a class="project_link" href="/project/{{ p.projectname }}">{{ p.projectname }}</a>
+ {% if p.projectManagerId == user.username %}
+ <a id="manage_project_{{p.projectname}}"
+ class="manage_link"
+ href="{% url nova_project_manage p.projectname %}"
+ title="Manage User Roles">Manage Project</a>
+ {% endif %}
+ </h4>
+ {% if project.projectname == p.projectname %}
+ <ul>
+ <li {% if p.projectname == project.projectname and sidebar_selected == "instances" %}class="active"{% endif %}>
+ <a id="lnk_instances_{{p.projectname}}" href="{% url nova_instances p.projectname %}">Instances</a>
+ </li>
+ <li {% if p.projectname == project.projectname and sidebar_selected == "images" %}class="active"{% endif %}>
+ <a id="lnk_images_{{p.projectname}}" href="{% url nova_images p.projectname %}">Images</a>
+ </li>
+ <li {% if p.projectname == project.projectname and sidebar_selected == "keys" %}class="active"{% endif %}>
+ <a id="lnk_keypairs_{{p.projectname}}" href="{% url nova_keypairs p.projectname %}">Keys</a>
+ </li>
+ <li {% if p.projectname == project.projectname and sidebar_selected == "volumes" %}class="active"{% endif %}>
+ <a id="lnk_volumes_{{p.projectname}}" href="{% url nova_volumes p.projectname %}">Volumes</a>
+ </li>
+ </ul>
+ {% endif %}
+ </div>
+ {% endfor %}
+ </div>
+ </li>
+{% endblock %}
+
+{% block footerjs %}
+{{ block.super }}
+<script type="text/javascript">
+ $(function() {
+ $('#sel_region').change(function() {
+ $('#frm_region').submit();
+ });
+ });
+</script>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/credentials/expired.html b/django-nova/src/django_nova/templates/django_nova/credentials/expired.html
new file mode 100644
index 00000000..7fd741ff
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/credentials/expired.html
@@ -0,0 +1,17 @@
+{% load django_nova_tags %}
+<!DOCTYPE html>
+<html lang="en" xml:lang="en">
+ <head>
+ <meta http-equiv="content-type" content="text/html; charset=utf-8" />
+ <title>Expired Token</title>
+ </head>
+ <body>
+ <center>
+ <h1>The link you clicked has expired.</h1>
+ <p style="width:460px;">This credentials download link you have reached
+ is either invalid or has expired. Each link is only good for one use. If
+ you need to download your credentials again, please contact the
+ {% site_branding %} support team.</p>
+ </center>
+ </body>
+</html>
diff --git a/django-nova/src/django_nova/templates/django_nova/images/_launch_form.html b/django-nova/src/django_nova/templates/django_nova/images/_launch_form.html
new file mode 100644
index 00000000..3d5a01e5
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/_launch_form.html
@@ -0,0 +1,7 @@
+{% for field in form %}
+<div class="{% cycle 'odd' 'even'%}">
+ {{ field.label_tag }}
+ {% if field.errors %}{{ field.errors }}{% endif %}
+ {{ field }}
+</div>
+{% endfor %}
diff --git a/django-nova/src/django_nova/templates/django_nova/images/_list.html b/django-nova/src/django_nova/templates/django_nova/images/_list.html
new file mode 100644
index 00000000..ec25c521
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/_list.html
@@ -0,0 +1,112 @@
+ <h3 class="image_list_heading"> {{ heading }} </h3>
+ {% if images %}
+ <table id="image_launch">
+ <tr>
+ <th>ID</th>
+ <th>Description</th>
+ <th colspan="2">Owner</th>
+ </tr>
+ {% for image in images %}
+ {% if image.id == ami.id %}
+ <td class="detail_wrapper" colspan="4">
+ <div id="{{ ami.id }}" class="image_detail">
+ <div class="column">
+ <div class="image_detail_item">
+ <span class="label">Owner: </span>
+ <span class="data">{{ ami.ownerId }}</span>
+ </div>
+
+ <div class="image_detail_item">
+ <span class="label">Description: </span>
+ <span class="data">{{ ami.description }}</span>
+ </div>
+
+ <div class="image_detail_item">
+ <span class="label">Location: </span>
+ <span class="data">{{ ami.location }}</span>
+ </div>
+ </div>
+
+ <div class="column">
+ <div class="image_detail_item">
+ <span class="label">ID: </span>
+ <span class="data">{{ ami.id }}</span>
+ </div>
+ <div class="image_detail_item">
+ <span class="label">Name: </span>
+ <span class="data">{% if ami.displayName %}{{ ami.displayName }}{%else%}{{ ami.id }}{% endif %}</span>
+ </div>
+ <div class="image_detail_item">
+ <span class="label">Type: </span>
+ <span class="data">{{ ami.type }}</span>
+ </div>
+ <div class="image_detail_item">
+ <span class="label">Architecture: </span>
+ <span class="data">{{ ami.architecture }}</span>
+ </div>
+ </div>
+
+ <div id="last" class="column">
+ {% if ami.is_public %}
+ <div id="public" class="privacy">Public Image</div>
+ {% else %}
+ <div id="private" class="privacy">Private Image</div>
+ {% endif %}
+
+ <a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}" title="Click to launch image">Launch</a>
+ {% if can_modify or user.username == ami.ownerId %}
+ <a id="edit_image_link" href="{% url nova_images_update project.projectname ami.id %}">Edit Image</a>
+ {% endif %}
+
+ </div>
+
+ {% if can_modify or user.username == ami.ownerId %}
+ <span class="image_privacy">
+ <form id="privacy_{{ ami.id }}" action="{% url nova_images_privacy project.projectname ami.id %}" method="post" accept-charset="utf-8">
+ {% csrf_token %}
+ {% if ami.is_public %}
+ <input class="private" type="submit" value="Make Private" />
+ {% else %}
+ <input class="public" type="submit" value="Make Public" />
+ {% endif %}
+ </form>
+ </span>
+
+ <span class="delete">
+ <form id="delete_{{ ami.id }}" action="{% url nova_images_remove project.projectname ami.id %}" method="post" accept-charset="utf-8">
+ {% csrf_token %}
+ <input type="submit" value="Remove Image" />
+ </form>
+ </span>
+ {% endif %}
+ </div>
+ </td>
+ {% else %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ <td class="image_id">
+ <a href="{% url nova_images_detail project.projectname image.id %}">{% if image.displayName %}{{ image.displayName }}{%else%}{{ image.id }}{% endif %}</a>
+ </td>
+ <td class="image_location odd">
+ {% if image.description %}
+ {{ image.description }}
+ {% else %}
+ {{ image.location }}
+ {% endif %}
+ </td>
+ <td class="image_owner_id">{{ image.ownerId }}</td>
+ <td class="image_launch_btn odd"><a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}">Launch</a></td>
+ {#<td class="odd"><a class="ui-state-default ui-corner-all" onclick="$('#dlg_launch').dialog('open');">Launch</a></td>#}
+ </tr>
+ {% endif %}
+ {% endfor %}
+ </table>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No images currently available.
+ </p>
+ </div>
+ </div>
+ {% endif %}
diff --git a/django-nova/src/django_nova/templates/django_nova/images/base.html b/django-nova/src/django_nova/templates/django_nova/images/base.html
new file mode 100644
index 00000000..e9649723
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/base.html
@@ -0,0 +1,7 @@
+{% extends "django_nova/base.html" %}
+{% load sidebar_tags %}
+
+{% block nav_projects %}
+ {% sidebar_select images %}
+ {{ block.super }}
+{% endblock %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/django_nova/images/detail_list.html b/django-nova/src/django_nova/templates/django_nova/images/detail_list.html
new file mode 100644
index 00000000..9c979e2b
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/detail_list.html
@@ -0,0 +1,207 @@
+{% extends "django_nova/images/base.html" %}
+
+{% block title %} - Launch an Image{% endblock %}
+
+{% block headerjs %}
+<script type="text/javascript" src="/media/django_nova/js/jquery.form.js"></script>
+{% endblock %}
+
+
+{% block content %}
+ <div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Images</h2>
+ <p id="page_description">Images are snapshots of running systems which can easily be deployed to run one or more instances.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ {% if images %}
+ <table id="image_launch">
+ <tr>
+ <th>ID</th>
+ <th>Description</th>
+ <th colspan="2">Owner</th>
+ </tr>
+ {% for image in images %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ {% if image.id == ami.id %}
+ <td class="detail_wrapper" colspan="4">
+ <div id="{{ ami.id }}" class="image_detail">
+ <div class="column">
+ <div class="image_detail_item">
+ <span class="label">Owner: </span>
+ <span class="data">{{ ami.ownerId }}</span>
+ </div>
+
+ <div class="image_detail_item">
+ <span class="label">Description: </span>
+ <span class="data">{{ ami.description }}</span>
+ </div>
+
+ <div class="image_detail_item">
+ <span class="label">Location: </span>
+ <span class="data">{{ ami.location }}</span>
+ </div>
+ </div>
+
+ <div class="column">
+ <div class="image_detail_item">
+ <span class="label">ID: </span>
+ <span class="data">{{ ami.id }}</span>
+ </div>
+ <div class="image_detail_item">
+ <span class="label">Name: </span>
+ <span class="data">{% if ami.displayName %}{{ ami.displayName }}{%else%}{{ ami.id }}{% endif %}</span>
+ </div>
+ <div class="image_detail_item">
+ <span class="label">Type: </span>
+ <span class="data">{{ ami.type }}</span>
+ </div>
+ <div class="image_detail_item">
+ <span class="label">Architecture: </span>
+ <span class="data">{{ ami.architecture }}</span>
+ </div>
+ </div>
+
+ <div id="last" class="column">
+ {% if ami.is_public %}
+ <div id="public" class="privacy">Public Image</div>
+ {% else %}
+ <div id="private" class="privacy">Private Image</div>
+ {% endif %}
+
+ <a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}" title="Click to launch image">Launch</a>
+ {% if can_modify or user.username == ami.ownerId %}
+ <a id="edit_image_link" href="{% url nova_images_update project.projectname ami.id %}">Edit Image</a>
+ {% endif %}
+
+ </div>
+
+ {% if can_modify or user.username == ami.ownerId %}
+ <span class="image_privacy">
+ <form id="privacy_{{ ami.id }}" action="{% url nova_images_privacy project.projectname ami.id %}" method="post" accept-charset="utf-8">
+ {% csrf_token %}
+ {% if ami.is_public %}
+ <input class="private" type="submit" value="Make Private" />
+ {% else %}
+ <input class="public" type="submit" value="Make Public" />
+ {% endif %}
+ </form>
+ </span>
+
+ <span class="delete">
+ <form id="delete_{{ ami.id }}" action="{% url nova_images_remove project.projectname ami.id %}" method="post" accept-charset="utf-8">
+ {% csrf_token %}
+ <input type="submit" value="Remove Image" />
+ </form>
+ </span>
+ {% endif %}
+ </div>
+ </td>
+ {% else %}
+ <td class="image_id"><a href="{% url nova_images_detail project.projectname image.id %}">{{ image.id }}</a></td>
+ <td class="image_location odd">
+ {% if image.description %}
+ {{ image.description }}
+ {% else %}
+ {{ image.location }}
+ {% endif %}
+ </td>
+ <td class="image_owner_id">{{ image.ownerId }}</td>
+ <td class="image_launch_btn odd"><a id="launch_{{ image.id }}" class="launch" href="{% url nova_images_launch project.projectname image.id %}">Launch</a></td>
+ {#<td class="odd"><a class="ui-state-default ui-corner-all" onclick="$('#dlg_launch').dialog('open');">Launch</a></td>#}
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No images currently available.
+ </p>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+
+ <div id="dlg_launch" title="Launch Instance" style="display:none;">
+ <form id="frm_launch" action="url nova_images_launch project.projectname" method="post">
+ {% csrf_token %}
+ {% include "django_nova/images/_launch_form.html" %}
+ </form>
+ </div>
+
+ <div id="dlg_confirm" title="Confirm Termination">
+ <p>Are you sure you wish to unregister the <span id="ami_name"></span> image?</p>
+ </div>
+
+{% endblock %}
+
+{% block footerjs %}
+{{ block.super }}
+<script type="text/javascript">
+ var options = {
+ success: handleResponse,
+ beforeSubmit: showRequest,
+ dataType: 'json'
+ }
+
+ // TODO: On dialog open, reset form and validation.
+ $(function() {
+ $('#dlg_launch').dialog({
+ buttons: {
+ 'Ok': function() {
+ $('#frm_launch').ajaxSubmit(options);
+ },
+ 'Cancel': function() {
+ $(this).dialog('close');
+ }
+ },
+ autoOpen: false,
+ resizable: false,
+ width: 400,
+ height: 400
+ });
+ });
+
+ function showRequest(formData, jqForm, options) {
+ var queryString = $.param(formData);
+ alert('About to submit: \n\n' + queryString);
+ return true;
+ }
+
+ function handleResponse(data, statusText, xhr, $form) {
+ alert('status: ' + statusText + '\nsuccess:\n\n' + data.success);
+ }
+
+ $(function(){
+ $('.delete form').submit(function() {
+ ami_name = $(this).parent().parent().attr("id");
+ $('#ami_name').text(ami_name);
+ $('#dlg_confirm').dialog('open');
+ return false;
+ });
+
+ $('#dlg_confirm').dialog({
+ buttons: {
+ 'Ok': onConfirmOK,
+ 'Cancel': function() { $(this).dialog('close'); }
+ },
+ autoOpen: false,
+ resizable: false,
+ width: 500,
+ height: 200
+ });
+ })
+
+ function onConfirmOK() {
+ $(this).dialog('close');
+ form = document.getElementById('delete_' + ami_name);
+ if(form) form.submit();
+ }
+
+</script>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/images/edit.html b/django-nova/src/django_nova/templates/django_nova/images/edit.html
new file mode 100644
index 00000000..800cfbb3
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/edit.html
@@ -0,0 +1,35 @@
+{% extends "django_nova/images/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block headerjs %}
+<script type="text/javascript" src="{{ COMMON_MEDIA_PREFIX }}js/jquery.form.js"></script>
+{% endblock %}
+
+{% block content %}
+<div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Edit Image</h2>
+ <p id="page_description">From this page you can edit the name and description of an image that belongs to you.</p>
+ </div>
+
+ <div class="dash_block first">
+ <h3 class="image_id">Edit Image: {{ ami.id }}</h3>
+
+ <form class="edit_image" id="rename_{{ ami.id }}" action="{% url nova_images_update project.projectname ami.id %}" method="post">
+ <fieldset>
+ {% csrf_token %}
+ {% for field in form %}
+
+ {{ field.label_tag }}
+ {% if field.errors %}{{ field.errors }}{% endif %}
+ {{ field }}
+ {% endfor %}
+ <input type="submit" value="Update Image" />
+ </fieldset>
+ </form>
+
+ </div>
+</div>
+
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/images/index.html b/django-nova/src/django_nova/templates/django_nova/images/index.html
new file mode 100644
index 00000000..913bf012
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/index.html
@@ -0,0 +1,70 @@
+{% extends "django_nova/images/base.html" %}
+
+{% block title %} - Launch an Image{% endblock %}
+
+{% block headerjs %}
+<script type="text/javascript" src="/media/django_nova/js/jquery.form.js"></script>
+{% endblock %}
+
+
+{% block content %}
+ <div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Images</h2>
+ <p id="page_description">Images are snapshots of running systems which can easily be deployed to run one or more instances.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ {% for heading, images in image_lists.items %}
+ {% include "django_nova/images/_list.html" %}
+ {% endfor %}
+
+ </div>
+
+ <div id="dlg_launch" title="Launch Instance" style="display:none;">
+ <form id="frm_launch" action="#" method="post">
+ {% csrf_token %}
+ {% include "django_nova/images/_launch_form.html" %}
+ </form>
+ </div>
+{% endblock %}
+
+{% block footerjs %}
+{{ block.super }}
+<script type="text/javascript">
+ var options = {
+ success: handleResponse,
+ beforeSubmit: showRequest,
+ dataType: 'json'
+ }
+
+ // TODO: On dialog open, reset form and validation.
+ $(function() {
+ $('#dlg_launch').dialog({
+ buttons: {
+ 'Ok': function() {
+ $('#frm_launch').ajaxSubmit(options);
+ },
+ 'Cancel': function() {
+ $(this).dialog('close');
+ }
+ },
+ autoOpen: false,
+ resizable: false,
+ width: 400,
+ height: 400
+ });
+ });
+
+ function showRequest(formData, jqForm, options) {
+ var queryString = $.param(formData);
+ alert('About to submit: \n\n' + queryString);
+ return true;
+ }
+
+ function handleResponse(data, statusText, xhr, $form) {
+ alert('status: ' + statusText + '\nsuccess:\n\n' + data.success);
+ }
+</script>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/images/launch.html b/django-nova/src/django_nova/templates/django_nova/images/launch.html
new file mode 100644
index 00000000..6e3990e7
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/images/launch.html
@@ -0,0 +1,32 @@
+{% extends "django_nova/images/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block headerjs %}
+<script type="text/javascript" src="{{ COMMON_MEDIA_PREFIX }}js/jquery.form.js"></script>
+{% endblock %}
+
+{% block content %}
+<div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Launch Image</h2>
+ <p id="page_description">You can launch up to five instances of an image at a time. Some images allow for custom configuration to be passed in via User data. (<a href="/kb/show/UserData/">read more</a>)</p>
+ </div>
+
+ <div class="dash_block first">
+ <form id="frm_launch" action="{% url nova_images_launch project.projectname ami.id %}" method="post">
+ {% csrf_token %}
+ <fieldset>
+ <h3 class="image_id">Launch Image {{ ami.id }}</h3>
+ <div class="even">
+ <label>Location</label>
+ <span class="image_location">{{ ami.location }}</span>
+ </div>
+ {% include "django_nova/images/_launch_form.html" %}
+ <input id="launch_image" type="submit" value="Launch" />
+ </fieldset>
+ </form>
+ </div>
+</div>
+
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html b/django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html
new file mode 100644
index 00000000..f6b9f283
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/instances/_instances_list.html
@@ -0,0 +1,104 @@
+ {% if instances %}
+ <table style="width: 100%">
+ <tr>
+ <th>ID</th>
+ <th>Image</th>
+ <th>Size</th>
+ <th>IP</th>
+ <th colspan="2">State</th>
+ </tr>
+ {% for instance in instances %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ {% if instance.id == selected_instance.id %}
+ <td class="detail_wrapper" colspan="6">
+ <div id="{{selected_instance.id}}" class="instance_detail">
+ <div class="column">
+ <div class="instance_detail_item">
+ <span class="label">Instance ID: </span>
+ <span class="data">{{ selected_instance.id }}</span>
+ </div>
+ <div class="instance_detail_item">
+ <span class="label">Name: </span>
+ <span class="data">{% if selected_instance.displayName != "" %}{{ selected_instance.displayName }}{% else %} None {% endif %}</span>
+ </div>
+ <div class="instance_detail_item">
+ <span class="label">Description: </span>
+ <span class="data" id="desc">{{ selected_instance.displayDescription }}</span>
+ </div>
+ </div>
+
+ <div class="column">
+ <div class="instance_detail_item">
+ <span class="label">Region: </span>
+ <span class="data">{{ selected_instance.region.name }}</span>
+ </div>
+ <div class="instance_detail_item">
+ <span class="label">Size: </span>
+ <span class="data">{{ selected_instance.instance_type }}</span>
+ </div>
+ <div class="instance_detail_item">
+ <span class="label">State: </span>
+ <span class="data">{{ selected_instance.state }}</span>
+ </div>
+ <div class="instance_detail_item">
+ <span class="label">Image ID: </span>
+ <span class="data">{{ selected_instance.image_id }}</span>
+ </div>
+ <div class="instance_detail_item">
+ <span class="label">IP Address: </span>
+ <span class="data">{{ selected_instance.dns_name }}</span>
+ </div>
+
+ </div>
+
+ <div id="last" class="column">
+ {% if instance.state == "running" %}
+ <a href="{% url nova_instances_console project.projectname instance.id %}" id="console_{{instance.id}}" class="console" target="_blank">Show Console</a>{% endif %}
+ <a id="edit_instance_link" href="{% url nova_instance_update project.projectname instance.id %}">Edit Instance</a>
+
+
+ </div>
+
+ <span class="delete">
+ <form id="form_terminate_{{ instance.id }}" class="form-terminate" method="post" action="{% url nova_instances_terminate project.projectname %}" >
+ <input name="instance_id" type="hidden" value="{{ instance.id }}" />
+ <input id="terminate_{{instance.id}}" class="terminate" type="submit" value="Terminate" />
+ {% csrf_token %}
+ </form>
+ </span>
+
+ </div>
+
+ </td>
+ {% else %}
+
+ <td><a href="{% url nova_instances_detail project.projectname instance.id %}">{{ instance.id }} {% if instance.displayName %}({{ instance.displayName }}){% endif %}
+ </a></td>
+ <td class="odd">{{ instance.image_id }}</td>
+ <td>{{ instance.instance_type }}</td>
+ <td class="odd">{{ instance.dns_name }}</td>
+ <td>{{ instance.state }}</td>
+ <td id="actions" class="odd">
+ <form id="form_terminate_{{ instance.id }}" class="form-terminate" method="post" action="{% url nova_instances_terminate project.projectname %}" >
+ <input name="instance_id" type="hidden" value="{{ instance.id }}" />
+ <input id="terminate_{{instance.id}}" class="terminate" type="submit" value="Terminate" />
+ {% csrf_token %}
+ </form>
+ {% if instance.state == "running" %}
+ <a href="{% url nova_instances_console project.projectname instance.id %}" id="console_{{instance.id}}" class="console" target="_blank">Show Console</a>
+ {% endif %}
+ </td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No instances are currently running. You may start a new instance from the <a href="{% url nova_images project.projectname %}">images</a> tab.
+ </p>
+ </div>
+ </div>
+ {% endif %}
diff --git a/django-nova/src/django_nova/templates/django_nova/instances/base.html b/django-nova/src/django_nova/templates/django_nova/instances/base.html
new file mode 100644
index 00000000..b793e937
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/instances/base.html
@@ -0,0 +1,7 @@
+{% extends "django_nova/base.html" %}
+{% load sidebar_tags %}
+
+{% block nav_projects %}
+ {% sidebar_select instances %}
+ {{ block.super }}
+{% endblock %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/django_nova/instances/detail_list.html b/django-nova/src/django_nova/templates/django_nova/instances/detail_list.html
new file mode 100644
index 00000000..a3ba8724
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/instances/detail_list.html
@@ -0,0 +1,33 @@
+{% extends "django_nova/instances/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block content %}
+
+<div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Instance ID: {{ instance.id }}</h2>
+ <p id="page_description">Here you can see up to the minute performance data about your instance.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+
+ <div class="dash_block first">
+ <h3 class="image_id">Edit Instance: {{ instance.id }}</h3>
+
+ <form class="edit_instance" id="rename_{{ instance.id }}" action="{% url nova_instance_update project.projectname instance.id %}" method="post">
+ <fieldset>
+ {% csrf_token %}
+ {% for field in form %}
+
+ {{ field.label_tag }}
+ {% if field.errors %}{{ field.errors }}{% endif %}
+ {{ field }}
+ {% endfor %}
+ <input type="submit" value="Update Instance" />
+ </fieldset>
+ </form>
+ </div>
+
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/instances/edit.html b/django-nova/src/django_nova/templates/django_nova/instances/edit.html
new file mode 100644
index 00000000..17c987e8
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/instances/edit.html
@@ -0,0 +1,34 @@
+{% extends "django_nova/instances/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block headerjs %}
+<script type="text/javascript" src="{{ COMMON_MEDIA_PREFIX }}js/jquery.form.js"></script>
+{% endblock %}
+
+{% block content %}
+<div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Edit Instance</h2>
+ <p id="page_description">From this page you can give your instance an alias, so you don't have to remember its unique id.</p>
+ </div>
+
+ <div class="dash_block first">
+ <h3 class="image_id">Edit Instance: {{ instance.id }}</h3>
+
+ <form class="edit_instance" id="rename_{{ selected_instance.id }}" action="{% url nova_instance_update project.projectname instance.id %}" method="post">
+ <fieldset>
+ {% csrf_token %}
+ {% for field in update_form %}
+
+ {{ field.label_tag }}
+ {% if field.errors %}{{ field.errors }}{% endif %}
+ {{ field }}
+ {% endfor %}
+ <input type="submit" value="Update Instance" />
+ </fieldset>
+ </form>
+ </div>
+</div>
+
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/instances/index.html b/django-nova/src/django_nova/templates/django_nova/instances/index.html
new file mode 100644
index 00000000..4774782a
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/instances/index.html
@@ -0,0 +1,98 @@
+{% extends "django_nova/instances/base.html" %}
+
+{% block title %} - {{ project.projectname|capfirst }} Instances{% endblock %}
+{% block pageclass %}instances{% endblock %}
+
+{% block content %}
+ <div id="page_head">
+ <div id="spinner"></div>
+
+ <h2 id="page_heading">Instances</h2>
+ <p id="page_description">Instances are virtual servers launched from images. You can launch instances from the <a href="{% url nova_images project.projectname %}">images tab</a>.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <div id="instances">
+ {% include "django_nova/instances/_instances_list.html" %}
+ </div>
+
+ <div id="dlg_confirm" title="Confirm Termination" style="display:none;">
+ <p>Are you sure you wish to terminate instance <span id="spn_terminate"></span>?</p>
+ </div>
+
+ <div id="connection_error" style="display:none;" title="Connection Error">
+ <p><span class="ui-icon ui-icon-alert"></span>A connection error has occurred. Please ensure you are still connected to VPN.</p>
+ </div>
+{% endblock %}
+
+{% block footerjs %}
+{{ block.super }}
+<script type="text/javascript">
+
+ $(function() {
+ setInterval(function() {
+ $('#spinner').show();
+ {% if detail %}
+ $('#instances').load('{% url nova_instances_refresh_detail project.projectname selected_instance.id %}', onInstancesUpdated);
+ {% else %}
+ $('#instances').load('{% url nova_instances_refresh project.projectname %}', onInstancesUpdated);
+ {% endif %}
+ }, 15000);
+
+ initInstanceForms();
+
+ $('#dlg_confirm').dialog({
+ buttons: {
+ 'Ok': onConfirmOK,
+ 'Cancel': function() { $(this).dialog('close'); }
+ },
+ autoOpen: false,
+ resizable: false,
+ width: 500,
+ height: 200
+ });
+ });
+
+ var _terminateID = null;
+
+ function initInstanceForms() {
+ $('.form-terminate').submit(function() {
+ _terminateID = $(this).children(':first').val()
+ $('#spn_terminate').text(_terminateID);
+ $('#dlg_confirm').dialog('open');
+ return false;
+ });
+ }
+
+ function onInstancesUpdated(response, status, xhr) {
+ $('#spinner').hide();
+
+ switch(xhr.status) {
+ case 200:
+ initInstanceForms();
+ break;
+
+ case 403:
+ document.location = '{% url auth_login %}';
+ break;
+
+ default:
+ $('#connection_error').dialog({
+ dialogClass: 'alert',
+ modal: true,
+ closeOnEscape: true,
+ buttons:{ "Close": function() { $(this).dialog("close"); } },
+ });
+ $('#connection_error').dialog('open');
+ break;
+ }
+ }
+
+ function onConfirmOK() {
+ $(this).dialog('close');
+ form = document.getElementById('form_terminate_' + _terminateID);
+ if(form) form.submit();
+ }
+</script>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/instances/performance.html b/django-nova/src/django_nova/templates/django_nova/instances/performance.html
new file mode 100644
index 00000000..7c941ab8
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/instances/performance.html
@@ -0,0 +1,58 @@
+{% extends "django_nova/instances/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block content %}
+
+<div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Instance ID: {{ instance.id }} Performance</h2>
+ <p id="page_description">Here you can see up to the minute performance data about your instance.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <p>
+ <h1>CPU Usage</h1>
+ <h3>Today</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "cpu-1d.png" %}" />
+ </p>
+ <p>
+ <h3>This Week</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "cpu-1w.png" %}" />
+ </p>
+ <p>
+ <h3>This Month</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "cpu-1m.png" %}" />
+ </p>
+
+ <p>
+ <h1>Network Activity</h1>
+ <h3>Today</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "net-1d.png" %}" />
+ </p>
+ <p>
+ <h3>This Week</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "net-1w.png" %}" />
+ </p>
+ <p>
+ <h3>This Month</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "net-1m.png" %}" />
+ </p>
+
+ <p>
+ <h1>Disk Activity</h3>
+ <h3>Today</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "disk-1d.png" %}" />
+ </p>
+ <p>
+ <h3>This Week</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "disk-1w.png" %}" />
+ </p>
+ <p>
+ <h3>This Month</h3>
+ <img src="{% url nova_instances_graph project.projectname instance.id "disk-1m.png" %}" />
+ </p>
+</div>
+
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html b/django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html
new file mode 100644
index 00000000..d295c406
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/keypairs/_create_form.html
@@ -0,0 +1,5 @@
+{% for field in create_form %}
+{{ field.label_tag }}
+{% if field.errors %}{{ field.errors }}{% endif %}
+{{ field }}
+{% endfor %}
diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/_list.html b/django-nova/src/django_nova/templates/django_nova/keypairs/_list.html
new file mode 100644
index 00000000..2447a193
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/keypairs/_list.html
@@ -0,0 +1,31 @@
+ {% if keypairs %}
+ <table style="width: 100%">
+ <tr>
+ <th>Key Pair Name</th>
+ <th>Fingerprint</th>
+ <th>&nbsp;</th>
+ </tr>
+ {% for keypair in keypairs %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ <td>{{ keypair.name }}</td>
+ <td class="odd">{{ keypair.fingerprint }}</td>
+ <td>
+ <form id="form_key_delete_{{keypair.name}}" class="form-key-delete" method="post" action="{% url nova_keypairs_delete project.projectname %}">
+ <input name="key_name" type="hidden" value="{{ keypair.name }}" />
+ <input id="keypair_delete_{{keypair.name}}" class="delete" type="submit" value="Delete" />
+ {% csrf_token %}
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No key pairs currently exist.
+ </p>
+ </div>
+ </div>
+ {% endif %}
diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/base.html b/django-nova/src/django_nova/templates/django_nova/keypairs/base.html
new file mode 100644
index 00000000..5fd996ca
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/keypairs/base.html
@@ -0,0 +1,7 @@
+{% extends "django_nova/base.html" %}
+{% load sidebar_tags %}
+
+{% block nav_projects %}
+ {% sidebar_select keys %}
+ {{ block.super }}
+{% endblock %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/django_nova/keypairs/index.html b/django-nova/src/django_nova/templates/django_nova/keypairs/index.html
new file mode 100644
index 00000000..2233f481
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/keypairs/index.html
@@ -0,0 +1,77 @@
+{% extends "django_nova/keypairs/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block headerjs %}
+{{ block.super }}
+<script type="text/javascript" src="/media/dashboard/js/jquery.form.js"></script>
+{% endblock %}
+
+{% block content %}
+ <div id="page_head">
+ <h2 id="page_heading">Keys</h2>
+ <p id="page_description">Key pairs are ssh credentials which are injected into images when they are launched. Creating a new key pair registers the public key and downloads the private key (a pem file). <em>Protect and use the key as a normal private key.</em></p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <div id="instances">
+ {% include "django_nova/keypairs/_list.html" %}
+ </div>
+
+ <div class="dash_block first">
+ <form id="frm_key_create" action="{% url nova_keypairs_add project.projectname %}" method="post">
+ {% csrf_token %}
+ <input id="js" name="js" type="hidden" value="0" />
+ <fieldset>
+ <h3>Create New Keypair</h3>
+ {% include "django_nova/keypairs/_create_form.html" %}
+ <input id="keypair_create" class="create" type="submit" value="Create" />
+ </fieldset>
+ </form>
+ </div>
+
+ <div id="dlg_confirm" title="Confirm Termination">
+ <p>Are you sure you wish to delete key <span id="spn_delete_key_name"></span>?</p>
+ </div>
+{% endblock %}
+
+{% block footerjs %}
+{{ block.super }}
+<script type="text/javascript">
+ $(function() { $('#js').val('1'); });
+
+ {% if download_key %}
+ $(function() { window.location = '{% url nova_keypairs_download project.projectname download_key %}'; });
+ {% endif %}
+
+ $(function() {
+ $('.form-key-delete').submit(function() {
+ _key_name = $(this).children(':first').val()
+ $('#spn_delete_key_name').text(_key_name);
+ $('#dlg_confirm').dialog('open');
+ return false;
+ });
+
+ $('#dlg_confirm').dialog({
+ buttons: {
+ 'Ok': onConfirmOK,
+ 'Cancel': function() { $(this).dialog('close'); }
+ },
+ autoOpen: false,
+ resizable: false,
+ width: 500,
+ height: 200
+ });
+
+ });
+
+ var _terminateID = null;
+
+ function onConfirmOK() {
+ $(this).dialog('close');
+ form = document.getElementById('form_key_delete_' + _key_name);
+ if(form) form.submit();
+ }
+</script>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/projects/edit_user.html b/django-nova/src/django_nova/templates/django_nova/projects/edit_user.html
new file mode 100644
index 00000000..44675ce4
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/projects/edit_user.html
@@ -0,0 +1,72 @@
+{% extends "django_nova/base.html" %}
+{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %}
+{% block pageclass %}overview{% endblock %}
+
+{% block headerjs %}
+{{ block.super }}
+<script type="text/javascript" src="/media/dashboard/js/django-admin.multiselect.js"></script>
+<link rel="stylesheet" type="text/css" href="/media/dashboard/css/django-admin-widgets.css" />
+
+<script type="text/javascript" charset="utf-8">
+ $(function(){
+ $.each($("#user_edit form select"), function () {
+ // "Locations" can be any label you want
+ SelectFilter.init(this.id, "Roles", 0, "/media/admin/");
+ });
+ })
+</script>
+{% endblock %}
+
+
+{% block content %}
+ <div id="page_head">
+ <h2 id="page_heading">Edit User Roles</h2>
+ <p id="page_description">From here you can edit multiple user roles.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+ <div id="user_edit" class="dash_block first">
+ {% if user %}
+
+ <form action="." method="post" enctype="multipart/form-data">
+ {% csrf_token %}
+ <fieldset class="module aligned {{ fieldset.classes }}">
+ <h3 id="edit_{{ user.username }}">Edit Roles for User: {{ user.username }}</h3>
+ <div class="form-row">
+ <label>User</label>
+ <span id="user_name">{{ user.username }}</span>
+ </div>
+ <input type="hidden" name="username" value="{{ user.id }}" id="username" />
+
+ {% for field in form.visible_fields %}
+ <div class="form-row">
+ {{ field.errors }}
+ {{ field.label_tag }}{{ field }}
+ {% if field.field.help_text %}<p class="help">{{ field.field.help_text|safe }}</p>{% endif %}
+ </div>
+ {% endfor %}
+ {% for field in form.hidden_fields %}
+ {{ field }}
+ {% endfor %}
+ </fieldset>
+ <div class="cancel">
+ <a href="{% url nova_project_manage project.projectname %}">Cancel</a>
+ </div>
+ <div class="submit-row">
+ <input type="submit" value="Save" class="default" />
+ {# <a href="#" class="deletelink">Remove User</a> #}
+ </div>
+ </form>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No users are currently associated with this project.
+ </p>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+{% endblock %}
+
diff --git a/django-nova/src/django_nova/templates/django_nova/projects/index.html b/django-nova/src/django_nova/templates/django_nova/projects/index.html
new file mode 100644
index 00000000..bd7b08c1
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/projects/index.html
@@ -0,0 +1,26 @@
+{% extends "django_nova/base.html" %}
+{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %}
+{% block pageclass %}overview{% endblock %}
+
+{% block content %}
+ <div id="page_head">
+ <h2><span>{{ project.projectname|capfirst }}</span> Overview</h2>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <div id="welcome">
+ <p>Welcome to the <span>{{ project.projectname|capfirst }}</span> Overview. From here you can manage your instances, images, keys, and security groups.</p>
+ <p>To get started using the command line management tools, you can <a target="_blank" href="http://open.eucalyptus.com/wiki/Euca2oolsGuide_v1.1">download euca2ools</a> and use them with your x509 credentials.</p>
+ </div>
+
+ <div id="resources" class="dash_block">
+ <h3>Project Resources</h3>
+ <ul>
+ <li><a href="{% url nova_download_credentials project.projectname %}">Generate X509 credentials.</a></li>
+ <li><a href="{% url nova_instances project.projectname %}">View Instances&nbsp;(<strong>{{ instance_count }}</strong> running).</a></li>
+ <li><a href="{% url nova_images project.projectname %}">View Images.</a></li>
+ </ul>
+ </div>
+{% endblock %}
+
diff --git a/django-nova/src/django_nova/templates/django_nova/projects/manage.html b/django-nova/src/django_nova/templates/django_nova/projects/manage.html
new file mode 100644
index 00000000..d7dc4968
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/projects/manage.html
@@ -0,0 +1,45 @@
+{% extends "django_nova/base.html" %}
+{% block title %} - {{ project.projectname|capfirst }} Overview{% endblock %}
+{% block pageclass %}overview{% endblock %}
+
+{% block content %}
+ <div id="page_head">
+ <h2 id="page_heading">Manage Users and Roles</h2>
+ <p id="page_description">From here you can manage users and roles.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <div id="users">
+ {% if members %}
+ <table style="width: 100%">
+ <tr>
+ <th>Username</th>
+ <th>Project Roles</th>
+ <th>Global Roles</th>
+ <th>&nbsp;</th>
+ </tr>
+ {% for member in members %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ <td>{{ member.memberId }} {% if project.projectManagerId == member.memberId %}(<em>project manager</em>){% endif %}</td>
+ <td>{{ member.project_roles }}</td>
+ <td>{{ member.global_roles }}</td>
+ <td class="odd">
+ <a href="{% url nova_project_edit_user project.projectname member.memberId%}">Edit</a>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No users are currently associated with this project.
+ </p>
+ </div>
+ </div>
+ {% endif %}
+ </div>
+{% endblock %}
+
diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html
new file mode 100644
index 00000000..80de4bee
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/_authorize_form.html
@@ -0,0 +1,5 @@
+{% for field in authorize_form %}
+ {{ field.label_tag }}
+ {% if field.errors %}{{ field.errors }}{% endif %}
+ {{ field }}
+{% endfor %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html
new file mode 100644
index 00000000..d295c406
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/_create_form.html
@@ -0,0 +1,5 @@
+{% for field in create_form %}
+{{ field.label_tag }}
+{% if field.errors %}{{ field.errors }}{% endif %}
+{{ field }}
+{% endfor %}
diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html
new file mode 100644
index 00000000..4a884cde
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/_revoke_form.html
@@ -0,0 +1,3 @@
+<input type="hidden" name="protocol" value="{{ rule.ip_protocol }}" />
+<input type="hidden" name="from_port" value="{{ rule.from_port}}" />
+<input type="hidden" name="to_port" value="{{ rule.to_port }}" />
diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/base.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/base.html
new file mode 100644
index 00000000..0e8a232e
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/base.html
@@ -0,0 +1,7 @@
+{% extends "django_nova/base.html" %}
+{% load sidebar_tags %}
+
+{% block nav_projects %}
+ {% sidebar_select securitygroups %}
+ {{ block.super }}
+{% endblock %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html
new file mode 100644
index 00000000..0e4afb9c
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/detail.html
@@ -0,0 +1,62 @@
+{% extends "django_nova/securitygroups/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block content %}
+ <div id="dashboard_tabs">
+ <div id="tabs-1" class="ui-tabs-panel ui-widget-content ui-corner-bottom dash-wrap" style="margin-left:0;min-height:300px;">
+ <ul id="dashboard_nav">
+ <li><a id="lnk_overview" href="{% url dashboard_project project.projectname %}">Overview</a></li>
+ <li><a id="lnk_instances" href="{% url dashboard_instances project.projectname %}">Instances</a></li>
+ <li><a id="lnk_images" href="{% url dashboard_images project.projectname %}">Images</a></li>
+ <li><a id="lnk_keypairs" href="{% url dashboard_keypairs project.projectname %}">Keys</a></li>
+ <li class="active"><a id="lnk_securitygroups" href="{% url dashboard_securitygroups project.projectname %}">Security Groups</a></li>
+ <li><a id="lnk_volumes" href="{% url dashboard_volumes project.projectname %}">Volumes</a></li>
+ </ul>
+ <div id="right_content">
+ <div id="page_head">
+ <h2>Security Group: {{ securitygroup.name }}</h2>
+ <p>Add and remove protocols to the security group by authorizing and revoking port forwarding. For instance<br /> [tcp, 80, 80] will allow access to HTTP from devices outside this security group.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <table>
+ <tr>
+ <th>Protocol</th>
+ <th>From Port</th>
+ <th>To Port</th>
+ <th></th>
+ </tr>
+ {% for rule in securitygroup.rules %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ <td>{{ rule.ip_protocol }}</td>
+ <td class="odd">{{ rule.from_port }}</td>
+ <td>{{ rule.to_port }}</td>
+ <td class="odd">
+ <form id="security_groups" method="post" action="{% url dashboard_securitygroups_revoke project.projectname securitygroup.name %}">
+ {% csrf_token %}
+ {% include "django_nova/securitygroups/_revoke_form.html" %}
+ <input class="ui-state-default ui-corner-all" type="submit" value="Revoke" />
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+
+ <div class="block">
+ <h3>Authorize</h3>
+ <form id="authorize" method="post" action="{% url dashboard_securitygroups_authorize project.projectname securitygroup.name %}">
+ {% csrf_token %}
+ <fieldset>
+ <input type="hidden" name="group" value="{{ securitygroup.name }}" />
+ {% include "django_nova/securitygroups/_authorize_form.html" %}
+ <input class="ui-state-default ui-corner-all" type="submit" value="Authorize">
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ <div class="clr"></div>
+ </div>
+ </div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/securitygroups/index.html b/django-nova/src/django_nova/templates/django_nova/securitygroups/index.html
new file mode 100644
index 00000000..a3c992c6
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/securitygroups/index.html
@@ -0,0 +1,59 @@
+{% extends "django_nova/securitygroups/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block content %}
+ <div id="dashboard_tabs">
+ <div id="tabs-1" class="ui-tabs-panel ui-widget-content ui-corner-bottom dash-wrap" style="margin-left:0px;min-height:300px;">
+ <ul id="dashboard_nav">
+ <li><a id="lnk_overview" href="{% url dashboard_project project.projectname %}">Overview</a></li>
+ <li><a id="lnk_instances" href="{% url dashboard_instances project.projectname %}">Instances</a></li>
+ <li><a id="lnk_images" href="{% url dashboard_images project.projectname %}">Images</a></li>
+ <li><a id="lnk_keypairs" href="{% url dashboard_keypairs project.projectname %}">Keys</a></li>
+ <li class="active"><a id="lnk_securitygroups" href="{% url dashboard_securitygroups project.projectname %}">Security Groups</a></li>
+ <li><a id="lnk_volumes" href="{% url dashboard_volumes project.projectname %}">Volumes</a></li>
+ </ul>
+ <div id="right_content">
+ <div id="page_head">
+ <h2 id="page_heading">Security Groups</h2>
+ <p id="page_description">Security groups are firewall rules which allow access to your instances from other groups as well as the internet. All ports/protocols are denied by default.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ <table style="width:100%;">
+ <tr>
+ <th>Name</th>
+ <th style="min-width:60%;">Description</th>
+ <th>Rules</th>
+ <th>&nbsp;</th>
+ </tr>
+ {% for securitygroup in securitygroups %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ <td id="group_{{ securitygroup.id }}"><a href="{% url dashboard_securitygroups_detail project.projectname securitygroup.name %}">{{ securitygroup.name }}</a></td>
+ <td id="group_{{ securitygroup.id }}_description" class="odd">{{ securitygroup.description }}</td>
+ <td id="group_{{ securitygroup.id }}_rules">{{ securitygroup.rules|length }}</td>
+ <td class="odd">
+ <form id="delete_group_{{ securitygroup.id }}" method="post" action="{% url dashboard_securitygroups_delete project.projectname securitygroup.name %}">
+ {% csrf_token %}
+ <input class="ui-state-default ui-corner-all" type="submit" value="Delete">
+ </form>
+ </td>
+ </tr>
+ {% endfor %}
+ </table>
+ <div class="block">
+ <form id="add_group_form" method="post" action="{% url dashboard_securitygroups_add project.projectname %}">
+ {% csrf_token %}
+ <fieldset>
+ <h3>New Group</h3>
+ {% include "django_nova/securitygroups/_create_form.html" %}
+ <label>&nbsp;</label><input class="ui-state-default ui-corner-all" type="submit" value="Create" />
+ </fieldset>
+ </form>
+ </div>
+ </div>
+ <div class="clr"></div>
+ </div>
+ </div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html b/django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html
new file mode 100644
index 00000000..b473d74e
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/volumes/_attach_form.html
@@ -0,0 +1,5 @@
+{% for field in attach_form %}
+{{ field.label_tag }}
+{% if field.errors %}{{ field.errors }}{% endif %}
+{{ field }}
+{% endfor %}
diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html b/django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html
new file mode 100644
index 00000000..d295c406
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/volumes/_create_form.html
@@ -0,0 +1,5 @@
+{% for field in create_form %}
+{{ field.label_tag }}
+{% if field.errors %}{{ field.errors }}{% endif %}
+{{ field }}
+{% endfor %}
diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/base.html b/django-nova/src/django_nova/templates/django_nova/volumes/base.html
new file mode 100644
index 00000000..88d1bced
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/volumes/base.html
@@ -0,0 +1,7 @@
+{% extends "django_nova/base.html" %}
+{% load sidebar_tags %}
+
+{% block nav_projects %}
+ {% sidebar_select volumes %}
+ {{ block.super }}
+{% endblock %} \ No newline at end of file
diff --git a/django-nova/src/django_nova/templates/django_nova/volumes/index.html b/django-nova/src/django_nova/templates/django_nova/volumes/index.html
new file mode 100644
index 00000000..2f03a066
--- /dev/null
+++ b/django-nova/src/django_nova/templates/django_nova/volumes/index.html
@@ -0,0 +1,84 @@
+{% extends "django_nova/volumes/base.html" %}
+
+{% block title %} - Cloud Computing{% endblock %}
+
+{% block content %}
+ <div id="page_head">
+ <h2 id="page_heading">Volumes</h2>
+ <p id="page_description">Volumes provide persistent block storage. Creating a new volume gives you a raw block device which you may format with your choice of filesystems (ext3 is recommended). A volume may only be attached to a single instance at a time.</p>
+ </div>
+
+ {% include "django_nova/_messages.html" %}
+
+ {% if volumes %}
+ <table style="width: 100%">
+ <tr>
+ <th>ID</th>
+ <th>Size</th>
+ <th colspan="2">Status</th>
+ </tr>
+ {% for volume in volumes %}
+ <tr class="{% cycle 'odd' 'even' %}">
+ <td id="volume_{{ volume.id }}">{{ volume.id }} {{ volume.displayName }}</td>
+ <td id="volume_{{ volume.id }}_size" class="odd">{{ volume.size }}GB</td>
+ <td id="volume_{{ volume.id }})_status">
+ {% if volume.status == "in-use" %}
+ {% if volume.attachment_state == "attached" %}
+ attached: {{ volume.attach_data.instance_id }}
+ {% else %}
+ {{ volume.attachment_state }}
+ {% endif %}
+ {% else %}
+ {{ volume.status }}
+ {% endif %}
+ </td>
+ {% if volume.attachment_state == "attached" %}
+ <td class="odd">
+ <form class="volume" action="{% url nova_volumes_detach project.projectname volume.id %}" method="post">
+ {% csrf_token %}
+ <input id="detach_{{ volume.id }}" class="detach" type="submit" value="Detach">
+ </form>
+ </td>
+ {% else %}
+ <td class="odd">
+ <form class="volume" action="{% url nova_volumes_delete project.projectname volume.id %}" method="post">
+ {% csrf_token %}
+ <input id="delete_{{ volume.id }}" class="delete" type="submit" value="Delete">
+ </form>
+ </td>
+ {% endif %}
+ </tr>
+ {% endfor %}
+ </table>
+ {% else %}
+ <div class="ui-widget">
+ <div class="ui-state-highlight ui-corner-all">
+ <p>
+ <span class="ui-icon ui-icon-info"></span>
+ No volumes currently exist.
+ </p>
+ </div>
+ </div>
+ {% endif %}
+ <div class="dash_block first">
+ <form id="new_volume_form" method="post" action="{% url nova_volumes_add project.projectname %}">
+ {% csrf_token %}
+ <fieldset>
+ <h3>Create New Volume</h3>
+ {% include "django_nova/volumes/_create_form.html" %}
+ <input id="create_volume" class="create" type="submit" value="Create" />
+ </fieldset>
+ </form>
+ </div>
+
+ <div class="dash_block">
+ <form id="new_volume_form" method="post" action="{% url nova_volumes_attach project.projectname %}">
+ {% csrf_token %}
+ <fieldset>
+ <h3>Attach Volume</h3>
+ {% include "django_nova/volumes/_attach_form.html" %}
+ <input id="attach_volume" class="attach" type="submit" value="Attach" />
+ </fieldset>
+ </form>
+ </div>
+{% endblock %}
diff --git a/django-nova/src/django_nova/templatetags/__init__.py b/django-nova/src/django_nova/templatetags/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/__init__.py
diff --git a/django-nova/src/django_nova/templatetags/admin_extras.py b/django-nova/src/django_nova/templatetags/admin_extras.py
new file mode 100644
index 00000000..18e7792e
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/admin_extras.py
@@ -0,0 +1,50 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Template tags for extending the Django admin interface.
+"""
+
+from django.contrib.admin.templatetags.admin_list import items_for_result, result_headers
+from django.core.urlresolvers import reverse
+from django.template import Library
+from django.utils.safestring import mark_safe
+
+
+register = Library()
+
+def project_result_list(cl):
+ headers = list(result_headers(cl))
+ headers.append({'text': mark_safe('&nbsp;')})
+
+ results = list()
+
+ for project in cl.result_list:
+ rl = list(items_for_result(cl,project,None))
+
+ url = reverse('admin_project_sendcredentials', args=[project.projectname])
+ content = mark_safe('<td><a href="%s">Send Credentials</a></td>' % url)
+
+ rl.append(content)
+ results.append(rl)
+
+ return {
+ 'cl': cl,
+ 'result_headers': headers,
+ 'results': results
+ }
+project_result_list = register.inclusion_tag("admin/change_list_results.html")(project_result_list)
diff --git a/django-nova/src/django_nova/templatetags/django_nova_tags.py b/django-nova/src/django_nova/templatetags/django_nova_tags.py
new file mode 100644
index 00000000..f199af29
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/django_nova_tags.py
@@ -0,0 +1,37 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Template tags for working with django_nova.
+"""
+
+from django import template
+from django.conf import settings
+
+
+register = template.Library()
+
+
+class SiteBrandingNode(template.Node):
+ def render(self, context):
+ return settings.SITE_BRANDING
+
+@register.tag
+def site_branding(parser, token):
+ return SiteBrandingNode()
+
diff --git a/django-nova/src/django_nova/templatetags/project_tags.py b/django-nova/src/django_nova/templatetags/project_tags.py
new file mode 100644
index 00000000..9cdf2a67
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/project_tags.py
@@ -0,0 +1,39 @@
+ # vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Template tags for gathering contextual region data.
+"""
+
+from django import template
+from django_nova.shortcuts import get_projects
+
+
+register = template.Library()
+
+
+class ProjectsNode(template.Node):
+ def render(self, context):
+ # Store project list in template context.
+ context['projects'] = get_projects(context['request'].user)
+ return ''
+
+
+@register.tag
+def load_projects(parser, token):
+ return ProjectsNode()
diff --git a/django-nova/src/django_nova/templatetags/region_tags.py b/django-nova/src/django_nova/templatetags/region_tags.py
new file mode 100644
index 00000000..9552229d
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/region_tags.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Template tags for gathering contextual region data.
+"""
+
+from django import template
+from django_nova.shortcuts import get_current_region, get_all_regions
+
+
+register = template.Library()
+
+
+class RegionsNode(template.Node):
+ def render(self, context):
+ # Store region info in template context.
+ context['current_region'] = get_current_region(context['request'])
+ context['regions'] = get_all_regions()
+ return ''
+
+
+@register.tag
+def load_regions(parser, token):
+ return RegionsNode()
+
diff --git a/django-nova/src/django_nova/templatetags/sidebar_tags.py b/django-nova/src/django_nova/templatetags/sidebar_tags.py
new file mode 100644
index 00000000..e2175725
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/sidebar_tags.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Template tags for rendering the sidebar.
+"""
+
+from django import template
+
+
+register = template.Library()
+
+
+class SidebarSelectNode(template.Node):
+ def __init__(self, selected):
+ self.selected = selected
+
+ def render(self, context):
+ # Store page type in template context.
+ context['sidebar_selected'] = self.selected
+ return ''
+
+
+@register.tag
+def sidebar_select(parser, token):
+ try:
+ tag_name, selected = token.split_contents()
+ except ValueError:
+ raise template.TemplateSyntaxError, "%r tag requires exactly one argument" % token.contents.split()[0]
+ return SidebarSelectNode(str(selected))
+
diff --git a/django-nova/src/django_nova/templatetags/truncate_filter.py b/django-nova/src/django_nova/templatetags/truncate_filter.py
new file mode 100644
index 00000000..14098781
--- /dev/null
+++ b/django-nova/src/django_nova/templatetags/truncate_filter.py
@@ -0,0 +1,31 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+Template tags for truncating strings.
+"""
+
+from django import template
+
+register = template.Library()
+
+@register.filter("truncate")
+def truncate(value, size):
+ if len(value) > size and size > 3:
+ return value[0:(size-3)] + '...'
+ else:
+ return value[0:size]
diff --git a/django-nova/src/django_nova/tests/__init__.py b/django-nova/src/django_nova/tests/__init__.py
new file mode 100644
index 00000000..7470671d
--- /dev/null
+++ b/django-nova/src/django_nova/tests/__init__.py
@@ -0,0 +1 @@
+from view_tests import * \ No newline at end of file
diff --git a/django-nova/src/django_nova/tests/templates/base-sidebar.html b/django-nova/src/django_nova/tests/templates/base-sidebar.html
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/django-nova/src/django_nova/tests/templates/base-sidebar.html
diff --git a/django-nova/src/django_nova/tests/urls.py b/django-nova/src/django_nova/tests/urls.py
new file mode 100644
index 00000000..d85381c2
--- /dev/null
+++ b/django-nova/src/django_nova/tests/urls.py
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+URL patterns for testing django-nova views.
+"""
+
+from django.conf.urls.defaults import *
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('',
+ url(r'^projects/', include('django_nova.urls.project')),
+ url(r'^region/', include('django_nova.urls.region')),
+ url(r'^admin/projects/', include('django_nova.urls.admin_project')),
+ url(r'^admin/roles/', include('django_nova.urls.admin_roles')),
+ url(r'^credentials/download/(?P<auth_token>\w+)/$',
+ 'django_nova.views.credentials.authorize_credentials',
+ name='nova_credentials_authorize'),
+)
+
diff --git a/django-nova/src/django_nova/tests/view_tests/__init__.py b/django-nova/src/django_nova/tests/view_tests/__init__.py
new file mode 100644
index 00000000..51d01c59
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/__init__.py
@@ -0,0 +1,7 @@
+from credential_tests import *
+from image_tests import *
+from instance_tests import *
+from keypair_tests import *
+from region_tests import *
+from volume_tests import *
+
diff --git a/django-nova/src/django_nova/tests/view_tests/base.py b/django-nova/src/django_nova/tests/view_tests/base.py
new file mode 100644
index 00000000..8b394d0e
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/base.py
@@ -0,0 +1,90 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Base classes for view based unit tests.
+"""
+
+import mox
+
+from django import test
+from django.conf import settings
+from django.contrib.auth import models as auth_models
+from django_nova import adminclient
+from django_nova import manager
+from django_nova import shortcuts
+
+
+TEST_PROJECT = 'test'
+TEST_USER = 'test'
+TEST_REGION = 'test'
+
+
+class BaseViewTests(test.TestCase):
+ def setUp(self):
+ self.mox = mox.Mox()
+
+ def tearDown(self):
+ self.mox.UnsetStubs()
+
+ def assertRedirectsNoFollow(self, response, expected_url):
+ self.assertEqual(response._headers['location'],
+ ('Location', settings.TESTSERVER + expected_url))
+ self.assertEqual(response.status_code, 302)
+
+ def authenticateTestUser(self):
+ user = auth_models.User.objects.create_user(TEST_USER,
+ 'test@test.com',
+ password='test')
+ login = self.client.login(username=TEST_USER, password='test')
+ self.failUnless(login, 'Unable to login')
+ return user
+
+
+class BaseProjectViewTests(BaseViewTests):
+ def setUp(self):
+ super(BaseProjectViewTests, self).setUp()
+
+ project = adminclient.ProjectInfo()
+ project.projectname = TEST_PROJECT
+ project.projectManagerId = TEST_USER
+
+ self.user = self.authenticateTestUser()
+ self.region = adminclient.RegionInfo(name=TEST_REGION,
+ endpoint='http://test:8773/')
+ self.project = manager.ProjectManager(self.user.username,
+ project,
+ self.region)
+ self.mox.StubOutWithMock(shortcuts, 'get_project_or_404')
+ shortcuts.get_project_or_404(mox.IgnoreArg(),
+ 'test').AndReturn(self.project)
+
+ def create_key_pair_choices(self, key_names):
+ return [(k, k) for k in key_names]
+
+ def create_instance_type_choices(self):
+ return [('m1.medium', 'm1.medium'),
+ ('m1.large', 'm1.large')]
+
+ def create_instance_choices(self, instance_ids):
+ return [(id, id) for id in instance_ids]
+
+ def create_available_volume_choices(self, volumes):
+ return [(v.id, '%s %s - %dGB' % (v.id, v.displayName, v.size)) \
+ for v in volumes]
+
diff --git a/django-nova/src/django_nova/tests/view_tests/credential_tests.py b/django-nova/src/django_nova/tests/view_tests/credential_tests.py
new file mode 100644
index 00000000..5838ba91
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/credential_tests.py
@@ -0,0 +1,70 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Unit tests for credential views.
+"""
+
+import mox
+from django.conf import settings
+from django.core.urlresolvers import reverse
+from django_nova import models
+from django_nova.tests.view_tests.base import BaseViewTests
+
+
+class CredentialViewTests(BaseViewTests):
+ def test_download_expired_credentials(self):
+ auth_token = 'expired'
+ self.mox.StubOutWithMock(models.CredentialsAuthorization,
+ 'get_by_token')
+ models.CredentialsAuthorization.get_by_token(auth_token) \
+ .AndReturn(None)
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_credentials_authorize',
+ args=[auth_token]))
+ self.assertTemplateUsed(res, 'django_nova/credentials/expired.html')
+
+ self.mox.VerifyAll()
+
+ def test_download_good_credentials(self):
+ auth_token = 'good'
+
+ creds = models.CredentialsAuthorization()
+ creds.username = 'test'
+ creds.project = 'test'
+ creds.auth_token = auth_token
+
+ self.mox.StubOutWithMock(models.CredentialsAuthorization,
+ 'get_by_token')
+ self.mox.StubOutWithMock(creds, 'get_zip')
+ models.CredentialsAuthorization.get_by_token(auth_token) \
+ .AndReturn(creds)
+ creds.get_zip().AndReturn('zip')
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_credentials_authorize',
+ args=[auth_token]))
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res['Content-Disposition'],
+ 'attachment; filename=%s-test-test-x509.zip' %
+ settings.SITE_NAME)
+ self.assertContains(res, 'zip')
+
+ self.mox.VerifyAll()
diff --git a/django-nova/src/django_nova/tests/view_tests/image_tests.py b/django-nova/src/django_nova/tests/view_tests/image_tests.py
new file mode 100644
index 00000000..7e1ee838
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/image_tests.py
@@ -0,0 +1,232 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Unit tests for image views.
+"""
+
+import boto.ec2.image
+import boto.ec2.instance
+import mox
+
+from django.core.urlresolvers import reverse
+from django_nova import forms
+from django_nova import shortcuts
+from django_nova.tests.view_tests.base import BaseProjectViewTests, TEST_PROJECT
+
+
+TEST_IMAGE_ID = 'ami_test'
+TEST_INSTANCE_ID = 'i-abcdefg'
+TEST_KEY = 'foo'
+
+
+class ImageViewTests(BaseProjectViewTests):
+ def setUp(self):
+ self.ami = boto.ec2.image.Image()
+ self.ami.id = TEST_IMAGE_ID
+ setattr(self.ami, 'displayName', TEST_IMAGE_ID)
+ setattr(self.ami, 'description', TEST_IMAGE_ID)
+ super(ImageViewTests, self).setUp()
+
+ def test_index(self):
+ self.mox.StubOutWithMock(self.project, 'get_images')
+ self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
+ self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
+
+ self.project.get_images().AndReturn([])
+ forms.get_key_pair_choices(self.project).AndReturn([])
+ forms.get_instance_type_choices().AndReturn([])
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_images', args=[TEST_PROJECT]))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/images/index.html')
+ self.assertEqual(len(res.context['image_lists']), 3)
+
+ self.mox.VerifyAll()
+
+ def test_launch_form(self):
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
+ self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
+
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ forms.get_key_pair_choices(self.project).AndReturn([])
+ forms.get_instance_type_choices().AndReturn([])
+
+ self.mox.ReplayAll()
+
+ args = [TEST_PROJECT, TEST_IMAGE_ID]
+ res = self.client.get(reverse('nova_images_launch', args=args))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/images/launch.html')
+ self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
+
+ self.mox.VerifyAll()
+
+ def test_launch(self):
+ instance = boto.ec2.instance.Instance()
+ instance.id = TEST_INSTANCE_ID
+ instance.image_id = TEST_IMAGE_ID
+ reservation = boto.ec2.instance.Reservation()
+ reservation.instances = [instance]
+
+ self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
+ self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
+ self.mox.StubOutWithMock(self.project, 'run_instances')
+
+ forms.get_key_pair_choices(self.project).AndReturn(
+ self.create_key_pair_choices([TEST_KEY]))
+ forms.get_instance_type_choices().AndReturn(
+ self.create_instance_type_choices())
+ self.project.run_instances(TEST_IMAGE_ID,
+ addressing_type=mox.IgnoreArg(),
+ key_name=TEST_KEY,
+ user_data='',
+ instance_type='m1.medium',
+ min_count='1',
+ max_count='1').AndReturn(reservation)
+ self.mox.ReplayAll()
+
+ url = reverse('nova_images_launch', args=[TEST_PROJECT, TEST_IMAGE_ID])
+ data = {'key_name': TEST_KEY,
+ 'count': '1',
+ 'size': 'm1.medium',
+ 'user_data': ''}
+ res = self.client.post(url, data)
+ self.assertRedirectsNoFollow(res, reverse('nova_instances',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_detail(self):
+ self.mox.StubOutWithMock(self.project, 'get_images')
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.mox.StubOutWithMock(shortcuts, 'get_user_image_permissions')
+ self.mox.StubOutWithMock(forms, 'get_key_pair_choices')
+ self.mox.StubOutWithMock(forms, 'get_instance_type_choices')
+
+ self.project.get_images().AndReturn([self.ami])
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ shortcuts.get_user_image_permissions(mox.IgnoreArg(),
+ TEST_PROJECT).AndReturn(True)
+ forms.get_key_pair_choices(self.project).AndReturn(
+ self.create_key_pair_choices([TEST_KEY]))
+ forms.get_instance_type_choices().AndReturn(
+ self.create_instance_type_choices())
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_images_detail',
+ args=[TEST_PROJECT, TEST_IMAGE_ID]))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/images/index.html')
+ self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
+
+ self.mox.VerifyAll()
+
+ def test_remove_form(self):
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_images_remove',
+ args=[TEST_PROJECT, TEST_IMAGE_ID]))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/images/detail_list.html')
+ self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
+
+ self.mox.VerifyAll()
+
+ def test_remove(self):
+ self.mox.StubOutWithMock(self.project, 'deregister_image')
+ self.project.deregister_image(TEST_IMAGE_ID).AndReturn(True)
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse('nova_images_remove',
+ args=[TEST_PROJECT, TEST_IMAGE_ID]))
+ self.assertRedirectsNoFollow(res, reverse('nova_images',
+ args=[TEST_PROJECT]))
+
+ self.mox.VerifyAll()
+
+ def test_make_public(self):
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.mox.StubOutWithMock(self.project, 'modify_image_attribute')
+
+ self.ami.is_public = False
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ self.project.modify_image_attribute(TEST_IMAGE_ID,
+ attribute='launchPermission',
+ operation='add').AndReturn(True)
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse('nova_images_privacy',
+ args=[TEST_PROJECT, TEST_IMAGE_ID]))
+ self.assertRedirectsNoFollow(res, reverse('nova_images_detail',
+ args=[TEST_PROJECT, TEST_IMAGE_ID]))
+ self.mox.VerifyAll()
+
+ def test_make_private(self):
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.mox.StubOutWithMock(self.project, 'modify_image_attribute')
+
+ self.ami.is_public = True
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ self.project.modify_image_attribute(TEST_IMAGE_ID,
+ attribute='launchPermission',
+ operation='remove').AndReturn(True)
+ self.mox.ReplayAll()
+
+ args = [TEST_PROJECT, TEST_IMAGE_ID]
+ res = self.client.post(reverse('nova_images_privacy', args=args))
+ self.assertRedirectsNoFollow(res, reverse('nova_images_detail',
+ args=args))
+ self.mox.VerifyAll()
+
+ def test_update_form(self):
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ self.mox.ReplayAll()
+
+ args = [TEST_PROJECT, TEST_IMAGE_ID]
+ res = self.client.get(reverse('nova_images_update', args=args))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/images/edit.html')
+ self.assertEqual(res.context['ami'].id, TEST_IMAGE_ID)
+
+ self.mox.VerifyAll()
+
+ def test_update(self):
+ self.mox.StubOutWithMock(self.project, 'get_image')
+ self.mox.StubOutWithMock(self.project, 'update_image')
+
+ self.project.get_image(TEST_IMAGE_ID).AndReturn(self.ami)
+ self.project.update_image(TEST_IMAGE_ID, 'test', 'test').AndReturn(True)
+
+ self.mox.ReplayAll()
+
+ args = [TEST_PROJECT, TEST_IMAGE_ID]
+ data = {'nickname': 'test',
+ 'description': 'test'}
+ url = reverse('nova_images_update', args=args)
+ res = self.client.post(url, data)
+ expected_url = reverse('nova_images_detail', args=args)
+ self.assertRedirectsNoFollow(res, expected_url)
+
+ self.mox.VerifyAll()
diff --git a/django-nova/src/django_nova/tests/view_tests/instance_tests.py b/django-nova/src/django_nova/tests/view_tests/instance_tests.py
new file mode 100644
index 00000000..890b0640
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/instance_tests.py
@@ -0,0 +1,67 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Unit tests for instance views.
+"""
+
+import boto.ec2.instance
+import mox
+
+from django.core.urlresolvers import reverse
+from django_nova.tests.view_tests.base import BaseProjectViewTests, TEST_PROJECT
+
+
+TEST_INSTANCE_ID = 'i-abcdefgh'
+
+
+class InstanceViewTests(BaseProjectViewTests):
+ def test_index(self):
+ self.mox.StubOutWithMock(self.project, 'get_instances')
+ self.project.get_instances().AndReturn([])
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_instances', args=[TEST_PROJECT]))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/instances/index.html')
+ self.assertEqual(len(res.context['instances']), 0)
+
+ self.mox.VerifyAll()
+
+ def test_detail(self):
+ instance = boto.ec2.instance.Instance()
+ instance.id = TEST_INSTANCE_ID
+ instance.displayName = instance.id
+ instance.displayDescription = instance.id
+
+ self.mox.StubOutWithMock(self.project, 'get_instance')
+ self.project.get_instance(instance.id).AndReturn(instance)
+ self.mox.StubOutWithMock(self.project, 'get_instances')
+ self.project.get_instances().AndReturn([instance])
+
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_instances_detail',
+ args=[TEST_PROJECT, TEST_INSTANCE_ID]))
+ self.assertEqual(res.status_code, 200)
+ self.assertTemplateUsed(res, 'django_nova/instances/index.html')
+ self.assertEqual(res.context['selected_instance'].id, instance.id)
+
+ self.mox.VerifyAll()
+
diff --git a/django-nova/src/django_nova/tests/view_tests/keypair_tests.py b/django-nova/src/django_nova/tests/view_tests/keypair_tests.py
new file mode 100644
index 00000000..95085ce3
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/keypair_tests.py
@@ -0,0 +1,93 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Unit tests for key pair views.
+"""
+
+import boto.ec2.keypair
+import mox
+
+from django.core.urlresolvers import reverse
+from django_nova.tests.view_tests.base import (BaseProjectViewTests,
+ TEST_PROJECT)
+
+
+TEST_KEY = 'test_key'
+
+
+class KeyPairViewTests(BaseProjectViewTests):
+ def test_index(self):
+ self.mox.StubOutWithMock(self.project, 'get_key_pairs')
+ self.project.get_key_pairs().AndReturn([])
+
+ self.mox.ReplayAll()
+
+ response = self.client.get(reverse('nova_keypairs',
+ args=[TEST_PROJECT]))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'django_nova/keypairs/index.html')
+ self.assertEqual(len(response.context['keypairs']), 0)
+
+ self.mox.VerifyAll()
+
+ def test_add_keypair(self):
+ key = boto.ec2.keypair.KeyPair()
+ key.name = TEST_KEY
+
+ self.mox.StubOutWithMock(self.project, 'create_key_pair')
+ self.project.create_key_pair(key.name).AndReturn(key)
+ self.mox.StubOutWithMock(self.project, 'has_key_pair')
+ self.project.has_key_pair(key.name).AndReturn(False)
+
+ self.mox.ReplayAll()
+
+ url = reverse('nova_keypairs_add', args=[TEST_PROJECT])
+ data = {'js': '0', 'name': key.name}
+ res = self.client.post(url, data)
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res['Content-Type'], 'application/binary')
+
+ self.mox.VerifyAll()
+
+ def test_delete_keypair(self):
+ self.mox.StubOutWithMock(self.project, 'delete_key_pair')
+ self.project.delete_key_pair(TEST_KEY).AndReturn(None)
+
+ self.mox.ReplayAll()
+
+ data = {'key_name': TEST_KEY}
+ url = reverse('nova_keypairs_delete', args=[TEST_PROJECT])
+ res = self.client.post(url, data)
+ self.assertRedirectsNoFollow(res, reverse('nova_keypairs',
+ args=[TEST_PROJECT]))
+
+ self.mox.VerifyAll()
+
+ def test_download_keypair(self):
+ material = 'abcdefgh'
+ session = self.client.session
+ session['key.%s' % TEST_KEY] = material
+ session.save()
+
+ res = self.client.get(reverse('nova_keypairs_download',
+ args=['test', TEST_KEY]))
+ self.assertEqual(res.status_code, 200)
+ self.assertEqual(res['Content-Type'], 'application/binary')
+ self.assertContains(res, material)
+
diff --git a/django-nova/src/django_nova/tests/view_tests/region_tests.py b/django-nova/src/django_nova/tests/view_tests/region_tests.py
new file mode 100644
index 00000000..6e4171ff
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/region_tests.py
@@ -0,0 +1,43 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Unit tests for region views.
+"""
+
+from django.core.urlresolvers import reverse
+from django_nova.tests.view_tests.base import BaseViewTests
+from django_nova import shortcuts
+
+
+TEST_REGION = 'one'
+
+
+class RegionViewTests(BaseViewTests):
+ def test_change(self):
+ self.authenticateTestUser()
+ session = self.client.session
+ session['region'] = 'two'
+ session.save()
+
+ data = {'redirect_url': '/',
+ 'region': TEST_REGION}
+ res = self.client.post(reverse('region_change'), data)
+ self.assertEqual(self.client.session['region'], TEST_REGION)
+ self.assertRedirectsNoFollow(res, '/')
+
diff --git a/django-nova/src/django_nova/tests/view_tests/volume_tests.py b/django-nova/src/django_nova/tests/view_tests/volume_tests.py
new file mode 100644
index 00000000..cd917406
--- /dev/null
+++ b/django-nova/src/django_nova/tests/view_tests/volume_tests.py
@@ -0,0 +1,170 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Unit tests for volume views.
+"""
+
+import boto.ec2.volume
+import mox
+
+from django.core.urlresolvers import reverse
+from django_nova import forms
+from django_nova.tests.view_tests.base import (BaseProjectViewTests,
+ TEST_PROJECT)
+
+
+TEST_VOLUME = 'vol-0000001'
+
+
+class VolumeTests(BaseProjectViewTests):
+ def test_index(self):
+ instance_id = 'i-abcdefgh'
+
+ volume = boto.ec2.volume.Volume()
+ volume.id = TEST_VOLUME
+ volume.displayName = TEST_VOLUME
+ volume.size = 1
+
+ self.mox.StubOutWithMock(self.project, 'get_volumes')
+ self.mox.StubOutWithMock(forms, 'get_available_volume_choices')
+ self.mox.StubOutWithMock(forms, 'get_instance_choices')
+ self.project.get_volumes().AndReturn([])
+ forms.get_available_volume_choices(mox.IgnoreArg()).AndReturn(
+ self.create_available_volume_choices([volume]))
+ forms.get_instance_choices(mox.IgnoreArg()).AndReturn(
+ self.create_instance_choices([instance_id]))
+
+ self.mox.ReplayAll()
+
+ response = self.client.get(reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.assertEqual(response.status_code, 200)
+ self.assertTemplateUsed(response, 'django_nova/volumes/index.html')
+ self.assertEqual(len(response.context['volumes']), 0)
+
+ self.mox.VerifyAll()
+
+ def test_add_get(self):
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_volumes_add', args=[TEST_PROJECT]))
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_add_post(self):
+ vol = boto.ec2.volume.Volume()
+ vol.name = TEST_VOLUME
+ vol.displayName = TEST_VOLUME
+ vol.size = 1
+
+ self.mox.StubOutWithMock(self.project, 'create_volume')
+ self.project.create_volume(vol.size, vol.name, vol.name).AndReturn(vol)
+
+ self.mox.ReplayAll()
+
+ url = reverse('nova_volumes_add', args=[TEST_PROJECT])
+ data = {'size': '1',
+ 'nickname': TEST_VOLUME,
+ 'description': TEST_VOLUME}
+ res = self.client.post(url, data)
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_delete_get(self):
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_volumes_delete',
+ args=[TEST_PROJECT, TEST_VOLUME]))
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_delete_post(self):
+ self.mox.StubOutWithMock(self.project, 'delete_volume')
+ self.project.delete_volume(TEST_VOLUME).AndReturn(True)
+
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse('nova_volumes_delete',
+ args=[TEST_PROJECT, TEST_VOLUME]))
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_attach_get(self):
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_volumes_attach',
+ args=[TEST_PROJECT]))
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_attach_post(self):
+ volume = boto.ec2.volume.Volume()
+ volume.id = TEST_VOLUME
+ volume.displayName = TEST_VOLUME
+ volume.size = 1
+
+ instance_id = 'i-abcdefgh'
+ device = '/dev/vdb'
+
+ self.mox.StubOutWithMock(self.project, 'attach_volume')
+ self.mox.StubOutWithMock(forms, 'get_available_volume_choices')
+ self.mox.StubOutWithMock(forms, 'get_instance_choices')
+ self.project.attach_volume(TEST_VOLUME, instance_id, device) \
+ .AndReturn(True)
+ forms.get_available_volume_choices(mox.IgnoreArg()).AndReturn(
+ self.create_available_volume_choices([volume]))
+ forms.get_instance_choices(mox.IgnoreArg()).AndReturn(
+ self.create_instance_choices([instance_id]))
+
+ self.mox.ReplayAll()
+
+ url = reverse('nova_volumes_attach', args=[TEST_PROJECT])
+ data = {'volume': TEST_VOLUME,
+ 'instance': instance_id,
+ 'device': device}
+ res = self.client.post(url, data)
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_detach_get(self):
+ self.mox.ReplayAll()
+
+ res = self.client.get(reverse('nova_volumes_detach',
+ args=[TEST_PROJECT, TEST_VOLUME]))
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
+
+ def test_detach_post(self):
+ self.mox.StubOutWithMock(self.project, 'detach_volume')
+ self.project.detach_volume(TEST_VOLUME).AndReturn(True)
+
+ self.mox.ReplayAll()
+
+ res = self.client.post(reverse('nova_volumes_detach',
+ args=[TEST_PROJECT, TEST_VOLUME]))
+ self.assertRedirectsNoFollow(res, reverse('nova_volumes',
+ args=[TEST_PROJECT]))
+ self.mox.VerifyAll()
diff --git a/django-nova/src/django_nova/testsettings.py b/django-nova/src/django_nova/testsettings.py
new file mode 100644
index 00000000..80eb224f
--- /dev/null
+++ b/django-nova/src/django_nova/testsettings.py
@@ -0,0 +1,21 @@
+import os
+
+ROOT_PATH = os.path.dirname(os.path.abspath(__file__))
+DEBUG = True
+TESTSERVER = 'http://testserver'
+DATABASE_ENGINE = 'sqlite3'
+DATABASE_NAME = '/tmp/django-nova.db'
+INSTALLED_APPS = ['django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django_nova']
+ROOT_URLCONF = 'django_nova.tests.urls'
+TEMPLATE_DIRS = (
+ os.path.join(ROOT_PATH, 'tests', 'templates')
+)
+SITE_BRANDING = 'OpenStack'
+SITE_NAME = 'openstack'
+NOVA_DEFAULT_ENDPOINT = 'none'
+NOVA_DEFAULT_REGION = 'test'
+NOVA_ACCESS_KEY = 'test'
+NOVA_SECRET_KEY = 'test'
diff --git a/django-nova/src/django_nova/urls/__init__.py b/django-nova/src/django_nova/urls/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/django-nova/src/django_nova/urls/__init__.py
diff --git a/django-nova/src/django_nova/urls/admin_project.py b/django-nova/src/django_nova/urls/admin_project.py
new file mode 100644
index 00000000..c32e2adb
--- /dev/null
+++ b/django-nova/src/django_nova/urls/admin_project.py
@@ -0,0 +1,55 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+URL patterns for managing Nova projects through the Django admin interface.
+"""
+
+from django.conf.urls.defaults import *
+
+
+#TODO(devcamcar): Standardize url names admin_project_*.
+
+urlpatterns = patterns('',
+ url(r'^$',
+ 'django_nova.views.admin.projects_list',
+ name='admin_projects'),
+ url(r'^add/$',
+ 'django_nova.views.admin.add_project',
+ name='add_project'),
+ url(r'^(?P<project_name>[^/]+)/$',
+ 'django_nova.views.admin.project_view',
+ name='admin_project'),
+ url(r'^(?P<project_name>[^/]+)/user/(?P<project_user>[^/]+)/delete/',
+ 'django_nova.views.admin.delete_project_user',
+ name='admin_project_delete_user'),
+ url(r'^(?P<project_name>[^/]+)/delete/$',
+ 'django_nova.views.admin.delete_project',
+ name='delete_project'),
+ url(r'^(?P<project_name>[^/]+)/user/add/$',
+ 'django_nova.views.admin.add_project_user',
+ name='add_project_user'),
+ url(r'^(?P<project_name>[^/]+)/user/(?P<project_user>[^/]+)/$',
+ 'django_nova.views.admin.project_user',
+ name='project_user'),
+ url(r'^(?P<project_id>[^/]+)/sendcredentials/$',
+ 'django_nova.views.admin.project_sendcredentials',
+ name='admin_project_sendcredentials'),
+ url(r'^(?P<project_id>[^/]+)/start_vpn/$',
+ 'django_nova.views.admin.project_start_vpn',
+ name='admin_project_start_vpn'),
+)
diff --git a/django-nova/src/django_nova/urls/admin_roles.py b/django-nova/src/django_nova/urls/admin_roles.py
new file mode 100644
index 00000000..d23380bc
--- /dev/null
+++ b/django-nova/src/django_nova/urls/admin_roles.py
@@ -0,0 +1,32 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+URL patterns for managing Nova user roles through the Django admin interface.
+"""
+
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('',
+ url(r'^(?P<user_id>[^/]+)/$',
+ 'django_nova.views.admin.user_roles',
+ name='admin_user_roles'),
+ url(r'^$',
+ 'django_nova.views.admin.users_list',
+ name='admin_users_list'),
+)
diff --git a/django-nova/src/django_nova/urls/project.py b/django-nova/src/django_nova/urls/project.py
new file mode 100644
index 00000000..a5cc6056
--- /dev/null
+++ b/django-nova/src/django_nova/urls/project.py
@@ -0,0 +1,129 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+URL patterns for managing Nova projects.
+"""
+
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('',
+ url(r'^(?P<project_id>[^/]+)/$',
+ 'django_nova.views.projects.detail',
+ name='nova_project'),
+ url(r'^(?P<project_id>[^/]+)/manage/(?P<username>[^/]+)/',
+ 'django_nova.views.projects.edit_user',
+ name='nova_project_edit_user'),
+ url(r'^(?P<project_id>[^/]+)/manage$',
+ 'django_nova.views.projects.manage',
+ name='nova_project_manage'),
+ url(r'^(?P<project_id>[^/]+)/download/credentials$',
+ 'django_nova.views.projects.download_credentials',
+ name='nova_download_credentials'),
+ url(r'^(?P<project_id>[^/]+)/images$',
+ 'django_nova.views.images.index',
+ name='nova_images'),
+ url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/launch$',
+ 'django_nova.views.images.launch',
+ name='nova_images_launch'),
+ url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/remove$',
+ 'django_nova.views.images.remove',
+ name='nova_images_remove'),
+ url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/update$',
+ 'django_nova.views.images.update',
+ name='nova_images_update'),
+ url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)/detail$',
+ 'django_nova.views.images.detail',
+ name='nova_images_detail'),
+ url(r'^(?P<project_id>[^/]+)/images/(?P<image_id>[^/]+)$',
+ 'django_nova.views.images.privacy',
+ name='nova_images_privacy'),
+ url(r'^(?P<project_id>[^/]+)/instances$',
+ 'django_nova.views.instances.index',
+ name='nova_instances'),
+ url(r'^(?P<project_id>[^/]+)/instances/refresh$',
+ 'django_nova.views.instances.refresh',
+ name='nova_instances_refresh'),
+ url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/refresh$',
+ 'django_nova.views.instances.refresh_detail',
+ name='nova_instances_refresh_detail'),
+ url(r'^(?P<project_id>[^/]+)/instances/terminate$',
+ 'django_nova.views.instances.terminate',
+ name='nova_instances_terminate'),
+ url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)$',
+ 'django_nova.views.instances.detail',
+ name='nova_instances_detail'),
+ url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/performance$',
+ 'django_nova.views.instances.performance',
+ name='nova_instances_performance'),
+ url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/console$',
+ 'django_nova.views.instances.console',
+ name='nova_instances_console'),
+ url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>.*)/update$',
+ 'django_nova.views.instances.update',
+ name='nova_instance_update'),
+ url(r'^(?P<project_id>[^/]+)/instances/(?P<instance_id>[^/]+)/graph/(?P<graph_name>[^/]+)$',
+ 'django_nova.views.instances.graph',
+ name='nova_instances_graph'),
+ url(r'^(?P<project_id>[^/]+)/keys$',
+ 'django_nova.views.keypairs.index',
+ name='nova_keypairs'),
+ url(r'^(?P<project_id>[^/]+)/keys/add$',
+ 'django_nova.views.keypairs.add',
+ name='nova_keypairs_add'),
+ url(r'^(?P<project_id>[^/]+)/keys/delete$',
+ 'django_nova.views.keypairs.delete',
+ name='nova_keypairs_delete'),
+ url(r'^(?P<project_id>[^/]+)/keys/(?P<key_name>.*)/download$',
+ 'django_nova.views.keypairs.download',
+ name='nova_keypairs_download'),
+ #url(r'^(?P<project_id>[^/]+)/securitygroups/$',
+ # 'django_nova.views.securitygroups.index',
+ # name='nova_securitygroups'),
+ #url(r'^(?P<project_id>[^/]+)/securitygroups/add$',
+ # 'django_nova.views.securitygroups.add',
+ # name='nova_securitygroups_add'),
+ #url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>[^/]+)$',
+ # 'django_nova.views.securitygroups.detail',
+ # name='nova_securitygroups_detail'),
+ #url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>[^/]+)/authorize/$',
+ # 'django_nova.views.securitygroups.authorize',
+ # name='nova_securitygroups_authorize'),
+ #url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>[^/]+)/delete/$',
+ # 'django_nova.views.securitygroups.delete',
+ # name='nova_securitygroups_delete'),
+ #url(r'^(?P<project_id>[^/]+)/securitygroups/(?P<group_name>.*)/revoke/$',
+ # 'django_nova.views.securitygroups.revoke',
+ # name='nova_securitygroups_revoke'),
+ url(r'^(?P<project_id>[^/]+)/volumes/$',
+ 'django_nova.views.volumes.index',
+ name='nova_volumes'),
+ url(r'^(?P<project_id>[^/]+)/volumes/add$',
+ 'django_nova.views.volumes.add',
+ name='nova_volumes_add'),
+ url(r'^(?P<project_id>[^/]+)/volumes/attach$',
+ 'django_nova.views.volumes.attach',
+ name='nova_volumes_attach'),
+ url(r'^(?P<project_id>[^/]+)/volumes/(?P<volume_id>[^/]+)/detach$',
+ 'django_nova.views.volumes.detach',
+ name='nova_volumes_detach'),
+ url(r'^(?P<project_id>[^/]+)/volumes/(?P<volume_id>[^/]+)/delete$',
+ 'django_nova.views.volumes.delete',
+ name='nova_volumes_delete'),
+)
diff --git a/django-nova/src/django_nova/urls/region.py b/django-nova/src/django_nova/urls/region.py
new file mode 100644
index 00000000..dde1e01b
--- /dev/null
+++ b/django-nova/src/django_nova/urls/region.py
@@ -0,0 +1,29 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+"""
+URL patterns for managing Nova regions.
+"""
+
+from django.conf.urls.defaults import *
+
+
+urlpatterns = patterns('',
+ url(r'^change/$',
+ 'django_nova.views.regions.change',
+ name='region_change'),
+)
diff --git a/django-nova/src/django_nova/views/__init__.py b/django-nova/src/django_nova/views/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/django-nova/src/django_nova/views/__init__.py
diff --git a/django-nova/src/django_nova/views/admin.py b/django-nova/src/django_nova/views/admin.py
new file mode 100644
index 00000000..ec1b4390
--- /dev/null
+++ b/django-nova/src/django_nova/views/admin.py
@@ -0,0 +1,326 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova through the Django admin interface.
+"""
+
+import boto.exception
+from django import http
+from django import template
+from django.contrib import messages
+from django.contrib.admin.views.decorators import staff_member_required
+from django.contrib.auth import models as auth_models
+from django.shortcuts import redirect, render_to_response
+from django_nova import forms
+from django_nova import models
+from django_nova.connection import get_nova_admin_connection
+
+
+@staff_member_required
+def project_sendcredentials(request, project_id):
+ nova = get_nova_admin_connection()
+ project = nova.get_project(project_id)
+
+ users = [user.memberId for user in nova.get_project_members(project_id)]
+ form = forms.SendCredentialsForm(query_list=users)
+
+ if project == None:
+ raise http.Http404()
+
+ if request.method == 'POST':
+ if len(request.POST.getlist('users')) < 1:
+ msg = "Please select a user to send credentials to."
+
+ return render_to_response('admin/django_nova/project/send_credentials.html', {
+ 'project' : project,
+ 'form' : form,
+ 'users' : users,
+ 'error': msg,
+ }, context_instance = template.RequestContext(request))
+ else:
+ for username in request.POST.getlist('users'):
+ models.CredentialsAuthorization.authorize(username, project_id)
+ msg = "Credentials were successfully sent."
+ return render_to_response('admin/django_nova/project/send_credentials.html', {
+ 'project' : project,
+ 'form' : form,
+ 'users' : users,
+ 'success': msg,
+ }, context_instance = template.RequestContext(request))
+
+ return render_to_response('admin/django_nova/project/send_credentials.html', {
+ 'project' : project,
+ 'form' : form,
+ 'users' : users,
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def project_start_vpn(request, project_id):
+ nova = get_nova_admin_connection()
+ project = nova.get_project(project_id)
+
+ if project == None:
+ raise http.Http404()
+
+ try:
+ nova.start_vpn(project_id)
+ messages.success(request,
+ 'Successfully started VPN for project %s.' %
+ project_id)
+ except boto.exception.EC2ResponseError, e:
+ messages.error(request,
+ 'Unable to start VPN for the project %s: %s - %s' %
+ (project_id, e.code, e.error_message))
+
+ return redirect('admin_projects')
+
+
+@staff_member_required
+def projects_list(request):
+ nova = get_nova_admin_connection()
+ projects = nova.get_projects()
+
+ return render_to_response('admin/django_nova/project/project_list.html', {
+ 'projects' : projects
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def project_view(request, project_name):
+ nova = get_nova_admin_connection()
+ project = nova.get_project(project_name)
+ users = nova.get_project_members(project_name)
+ try:
+ manager = auth_models.User.objects.get(username=project.projectManagerId)
+ except auth_models.User.DoesNotExist:
+ manager = None
+
+ for user in users:
+ project_role = [str(role.role) for role in nova.get_user_roles(user.memberId, project_name)]
+ global_role = [str(role.role) for role in nova.get_user_roles(user.memberId, project=False)]
+
+ user.project_roles = ", ".join(project_role)
+ user.global_roles = ", ".join(global_role)
+
+ return render_to_response('admin/django_nova/project/edit_project.html', {
+ 'project' : project,
+ 'users' : users,
+ 'projectname': project.projectname,
+ 'manager': manager,
+ 'description': project.description,
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def add_project(request):
+ nova = get_nova_admin_connection()
+
+ if request.method == 'POST':
+ form = forms.ProjectForm(request.POST)
+ if form.is_valid():
+ manager = form.cleaned_data["manager"]
+ nova.create_project(form.cleaned_data["projectname"],
+ manager.username,
+ form.cleaned_data["description"])
+ return redirect('admin_project', request.POST["projectname"])
+ else:
+ form = forms.ProjectForm()
+
+ return render_to_response('admin/django_nova/project/add_project.html', {
+ 'form' : form,
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def delete_project(request, project_name):
+ nova = get_nova_admin_connection()
+
+ if request.method == 'POST':
+ nova.delete_project(project_name)
+ return redirect('admin_projects')
+
+ project = nova.get_project(project_name)
+
+ return render_to_response('admin/django_nova/project/delete_project.html', {
+ 'project' : project,
+ }, context_instance = template.RequestContext(request))
+
+def remove_project_roles(username, project):
+ nova = get_nova_admin_connection()
+ userroles = nova.get_user_roles(username, project)
+ roles = [str(role.role) for role in userroles]
+
+ for role in roles:
+ if role == "developer":
+ nova.remove_user_role(username, "developer", project)
+ if role == "sysadmin":
+ nova.remove_user_role(username, "sysadmin", project)
+ if role == "netadmin":
+ nova.remove_user_role(username, "netadmin", project)
+
+def remove_global_roles(username):
+ nova = get_nova_admin_connection()
+ userroles = nova.get_user_roles(username)
+ roles = [str(role.role) for role in userroles]
+
+ for role in roles:
+ if role == "developer":
+ nova.remove_user_role(username, "developer")
+ if role == "sysadmin":
+ nova.remove_user_role(username, "sysadmin")
+ if role == "netadmin":
+ nova.remove_user_role(username, "netadmin")
+ if role == "cloudadmin":
+ nova.remove_user_role(username, "cloudadmin")
+ if role == "itsec":
+ nova.remove_user_role(username, "itsec")
+
+
+@staff_member_required
+def project_user(request, project_name, project_user):
+ nova = get_nova_admin_connection()
+ userroles = nova.get_user_roles(project_user, project_name)
+ try:
+ modeluser = auth_models.User.objects.get(username = project_user)
+ except auth_models.User.DoesNotExist:
+ modeluser = None
+
+ if request.method == 'POST':
+ form = forms.ProjectUserForm(request.POST)
+ if form.is_valid():
+ username = project_user
+
+ # hacky work around to interface correctly with multiple select form
+ remove_project_roles(username, project_name)
+
+ roleform = request.POST.getlist("role")
+ for role in roleform:
+ nova.add_user_role(username, str(role), project_name)
+
+ return redirect('admin_project', project_name)
+ else:
+ roles = [str(role.role) for role in userroles]
+ form = forms.ProjectUserForm({
+ 'role': roles,
+ 'user': modeluser,
+ })
+
+ project = nova.get_project(project_name)
+
+ return render_to_response('admin/django_nova/project/project_user.html', {
+ 'form' : form,
+ 'project' : project,
+ 'user': modeluser,
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def add_project_user(request, project_name):
+ nova = get_nova_admin_connection()
+
+ if request.method == 'POST':
+ form = forms.AddProjectUserForm(request.POST, project=project_name)
+ if form.is_valid():
+ username = form.cleaned_data["username"].username
+ nova.add_project_member(username, project_name,)
+
+ roleform = request.POST.getlist("role")
+ for role in roleform:
+ nova.add_user_role(username, str(role), project_name)
+
+ return redirect('admin_project', project_name)
+ else:
+ form = forms.AddProjectUserForm(project=project_name)
+
+ project = nova.get_project(project_name)
+
+ return render_to_response('admin/django_nova/project/add_project_user.html', {
+ 'form' : form,
+ 'project' : project,
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def delete_project_user(request, project_name, project_user):
+ nova = get_nova_admin_connection()
+
+ if request.method == 'POST':
+ nova.remove_project_member(project_user, project_name)
+ return redirect('admin_project', project_name)
+
+ project = nova.get_project(project_name)
+ user = nova.get_user(project_user)
+
+ return render_to_response('admin/django_nova/project/delete_project_user.html', {
+ 'user' : user,
+ 'project' : project,
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def users_list(request):
+ nova = get_nova_admin_connection()
+ users = nova.get_users()
+
+ for user in users:
+ # NOTE(devcamcar): Temporarily disabled for performance reasons.
+ #roles = [str(role.role) for role in nova.get_user_roles(user.username)]
+ roles = []
+ user.roles = ", ".join(roles)
+
+ return render_to_response('admin/django_nova/project/user_list.html', {
+ 'users' : users
+ }, context_instance = template.RequestContext(request))
+
+
+@staff_member_required
+def user_roles(request, user_id):
+ nova = get_nova_admin_connection()
+ userroles = nova.get_user_roles(user_id)
+ try:
+ modeluser = auth_models.User.objects.get(username=user_id)
+ except auth_models.User.DoesNotExist:
+ modeluser = None
+
+ if request.method == 'POST':
+ form = forms.GlobalRolesForm(request.POST)
+ if form.is_valid():
+ username = user_id
+
+ # hacky work around to interface correctly with multiple select form
+ remove_global_roles(username)
+
+ roleform = request.POST.getlist("role")
+ for role in roleform:
+ nova.add_user_role(username, str(role))
+
+ return redirect('admin_user_roles', user_id)
+ else:
+ roles = [str(role.role) for role in userroles]
+ form = forms.GlobalRolesForm({
+ 'username': modeluser and modeluser.id or None,
+ 'role': roles,
+ })
+
+ return render_to_response('admin/django_nova/project/global_edit_user.html', {
+ 'form' : form,
+ 'user' : modeluser,
+ }, context_instance = template.RequestContext(request))
+
diff --git a/django-nova/src/django_nova/views/credentials.py b/django-nova/src/django_nova/views/credentials.py
new file mode 100644
index 00000000..3626e027
--- /dev/null
+++ b/django-nova/src/django_nova/views/credentials.py
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for downloading X509 credentials. Useful when using an invitation
+style system for configuring first time users.
+"""
+
+from django import http
+from django.conf import settings
+from django.shortcuts import render_to_response
+from django_nova import models
+
+
+def authorize_credentials(request, auth_token):
+ """Sends X509 credentials to user if their auth token is valid."""
+ auth_token = auth_token.lower()
+ credentials = models.CredentialsAuthorization.get_by_token(auth_token)
+
+ # NOTE(devcamcar): If nothing returned, then token was bad or has expired.
+ if not credentials:
+ return render_to_response('django_nova/credentials/expired.html')
+
+ response = http.HttpResponse(mimetype='application/zip')
+ response['Content-Disposition'] = \
+ 'attachment; filename=%s-%s-%s-x509.zip' % \
+ (settings.SITE_NAME, credentials.project, credentials.username)
+ response.write(credentials.get_zip())
+
+ return response
+
diff --git a/django-nova/src/django_nova/views/images.py b/django-nova/src/django_nova/views/images.py
new file mode 100644
index 00000000..1d09d2df
--- /dev/null
+++ b/django-nova/src/django_nova/views/images.py
@@ -0,0 +1,229 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova images.
+"""
+
+import boto.exception
+import re
+
+from django import http
+from django import template
+from django.conf import settings
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render_to_response
+from django_nova import exceptions
+from django_nova import forms
+from django_nova import shortcuts
+from django_nova.exceptions import handle_nova_error
+
+
+def _image_lists(images, project_id):
+ def image_is_project(i):
+ return i.ownerId == project_id
+
+ def image_is_admin(i):
+ return i.ownerId in ['admin']
+
+ def image_is_community(i):
+ return (not image_is_admin(i)) and (not image_is_project(i))
+
+ return {'Project Images': filter(image_is_project, images),
+ '%s Images' % settings.SITE_BRANDING: filter(image_is_admin,
+ images),
+ 'Community Images': filter(image_is_community, images)}
+
+
+@login_required
+@handle_nova_error
+def index(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ images = project.get_images()
+
+ return render_to_response('django_nova/images/index.html', {
+ 'form': forms.LaunchInstanceForm(project),
+ 'region': project.region,
+ 'project': project,
+ 'image_lists': _image_lists(images, project_id),
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def launch(request, project_id, image_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ form = forms.LaunchInstanceForm(project, request.POST)
+ if form.is_valid():
+ try:
+ reservation = project.run_instances(
+ image_id,
+ addressing_type='private',
+ key_name=form.cleaned_data['key_name'],
+ #security_groups=[form.cleaned_data['security_group']],
+ user_data=re.sub('\r\n', '\n',
+ form.cleaned_data['user_data']),
+ instance_type=form.cleaned_data['size'],
+ min_count=form.cleaned_data['count'],
+ max_count=form.cleaned_data['count']
+ )
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to launch: %s' % e.message)
+ else:
+ for instance in reservation.instances:
+ messages.success(request,
+ 'Instance %s launched.' % instance.id)
+ return redirect('nova_instances', project_id)
+ else:
+ form = forms.LaunchInstanceForm(project)
+
+ ami = project.get_image(image_id)
+
+ return render_to_response('django_nova/images/launch.html', {
+ 'form': form,
+ 'region': project.region,
+ 'project': project,
+ 'ami': ami,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def detail(request, project_id, image_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ images = project.get_images()
+
+ ami = project.get_image(image_id)
+ can_modify = shortcuts.get_user_image_permissions(request.user.username,
+ project_id)
+
+ if not ami:
+ raise http.Http404()
+ return render_to_response('django_nova/images/index.html', {
+ 'form': forms.LaunchInstanceForm(project),
+ 'region': project.region,
+ 'project': project,
+ 'images': images,
+ 'image_lists': _image_lists(images, project_id),
+ 'ami': ami,
+ 'can_modify': can_modify,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def remove(request, project_id, image_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ try:
+ project.deregister_image(image_id)
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to deregister image: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Image %s has been successfully deregistered.' %
+ image_id)
+
+ return redirect('nova_images', project_id)
+ else:
+ ami = project.get_image(image_id)
+
+ #FIXME - is the code below used? if we reach here should we
+ #just redirect? (anthony)
+ return render_to_response('django_nova/images/detail_list.html', {
+ 'region': project.region,
+ 'project': project,
+ 'ami': ami,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def privacy(request, project_id, image_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ ami = project.get_image(image_id)
+
+ if ami.is_public:
+ try:
+ project.modify_image_attribute(image_id,
+ attribute='launchPermission',
+ operation='remove')
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to make image private: %s' % e.message)
+ else:
+ try:
+ project.modify_image_attribute(image_id,
+ attribute='launchPermission',
+ operation='add')
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to make image public: %s' % e.message)
+
+ return redirect('nova_images_detail', project_id, image_id)
+
+
+@login_required
+@handle_nova_error
+def update(request, project_id, image_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ ami = project.get_image(image_id)
+
+ if request.method == 'POST':
+ form = forms.UpdateImageForm(ami, request.POST)
+ if form.is_valid():
+ try:
+ project.update_image(image_id,
+ form.cleaned_data['nickname'],
+ form.cleaned_data['description'])
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to update image: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Image %s has been updated.' % image_id)
+
+ return redirect('nova_images_detail', project_id, image_id)
+
+ # TODO(devcamcar): This needs to be cleaned up. Can make
+ # one of the render_to_response blocks go away.
+ else:
+ form = forms.UpdateImageForm(ami)
+ return render_to_response('django_nova/images/edit.html', {
+ 'form': form,
+ 'region': project.region,
+ 'project': project,
+ 'ami': ami,
+ }, context_instance = template.RequestContext(request))
+ else:
+ form = forms.UpdateImageForm(ami)
+ return render_to_response('django_nova/images/edit.html', {
+ 'form': form,
+ 'region': project.region,
+ 'project': project,
+ 'ami': ami,
+ }, context_instance = template.RequestContext(request))
+
diff --git a/django-nova/src/django_nova/views/instances.py b/django-nova/src/django_nova/views/instances.py
new file mode 100644
index 00000000..6fe2e0d0
--- /dev/null
+++ b/django-nova/src/django_nova/views/instances.py
@@ -0,0 +1,203 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova instances.
+"""
+
+from django import http
+from django import template
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render_to_response
+from django_nova import exceptions
+from django_nova import forms as nova_forms
+from django_nova import shortcuts
+from django_nova.exceptions import handle_nova_error
+
+
+@login_required
+@handle_nova_error
+def index(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
+
+ return render_to_response('django_nova/instances/index.html', {
+ 'region': project.region,
+ 'project': project,
+ 'instances': instances,
+ 'detail' : False,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def detail(request, project_id, instance_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ instance = project.get_instance(instance_id)
+ instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
+
+ if not instance:
+ raise http.Http404()
+
+ return render_to_response('django_nova/instances/index.html', {
+ 'region': project.region,
+ 'project': project,
+ 'selected_instance': instance,
+ 'instances': instances,
+ 'update_form': nova_forms.UpdateInstanceForm(instance),
+ 'detail' : True,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def performance(request, project_id, instance_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ instance = project.get_instance(instance_id)
+
+ if not instance:
+ raise http.Http404()
+
+ return render_to_response('django_nova/instances/performance.html', {
+ 'region': project.region,
+ 'project': project,
+ 'instance': instance,
+ 'update_form': nova_forms.UpdateInstanceForm(instance),
+ }, context_instance = template.RequestContext(request))
+
+
+# TODO(devcamcar): Wrap this in an @ajax decorator.
+def refresh(request, project_id):
+ # TODO(devcamcar): This logic belongs in decorator.
+ if not request.user.is_authenticated():
+ return http.HttpResponseForbidden()
+
+ project = shortcuts.get_project_or_404(request, project_id)
+ instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
+
+ return render_to_response('django_nova/instances/_instances_list.html', {
+ 'project': project,
+ 'instances': instances,
+ }, context_instance = template.RequestContext(request))
+
+
+@handle_nova_error
+def refresh_detail(request, project_id, instance_id):
+ # TODO(devcamcar): This logic belongs in decorator.
+ if not request.user.is_authenticated():
+ return http.HttpResponseForbidden()
+
+ project = shortcuts.get_project_or_404(request, project_id)
+ instance = project.get_instance(instance_id)
+ instances = sorted(project.get_instances(), key=lambda k: k.public_dns_name)
+
+ return render_to_response('django_nova/instances/_instances_list.html', {
+ 'project': project,
+ 'selected_instance': instance,
+ 'instances': instances,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def terminate(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ instance_id = request.POST['instance_id']
+
+ try:
+ project.terminate_instance(instance_id)
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to terminate %s: %s' %
+ (instance_id, e.message,))
+ else:
+ messages.success(request,
+ 'Instance %s has been terminated.' % instance_id)
+
+ return redirect('nova_instances', project_id)
+
+
+@login_required
+@handle_nova_error
+def console(request, project_id, instance_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ conn = project.get_nova_connection()
+ console = conn.get_console_output(instance_id)
+ response = http.HttpResponse(mimetype='text/plain')
+ response.write(console.output)
+ response.flush()
+
+ return response
+
+
+@login_required
+@handle_nova_error
+def graph(request, project_id, instance_id, graph_name):
+ project = shortcuts.get_project_or_404(request, project_id)
+ graph = project.get_instance_graph(instance_id, graph_name)
+
+ if graph is None:
+ raise http.Http404()
+
+ response = http.HttpResponse(mimetype='image/png')
+ response.write(graph)
+
+ return response
+
+
+@login_required
+@handle_nova_error
+def update(request, project_id, instance_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ instance = project.get_instance(instance_id)
+
+ if not instance:
+ raise http.Http404()
+
+ if request.method == 'POST':
+ form = nova_forms.UpdateInstanceForm(instance, request.POST)
+ if form.is_valid():
+ try:
+ project.update_instance(instance_id, form.cleaned_data)
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to update instance %s: %s' %
+ (instance_id, e.message,))
+ else:
+ messages.success(request,
+ 'Instance %s has been updated.' % instance_id)
+ return redirect('nova_instances', project_id)
+ else:
+ return render_to_response('django_nova/instances/edit.html', {
+ 'region': project.region,
+ 'project': project,
+ 'instance': instance,
+ 'update_form': form,
+ }, context_instance = template.RequestContext(request))
+
+ else:
+ return render_to_response('django_nova/instances/edit.html', {
+ 'region': project.region,
+ 'project': project,
+ 'instance': instance,
+ 'update_form': nova_forms.UpdateInstanceForm(instance),
+ }, context_instance = template.RequestContext(request))
+
diff --git a/django-nova/src/django_nova/views/keypairs.py b/django-nova/src/django_nova/views/keypairs.py
new file mode 100644
index 00000000..90db0d2e
--- /dev/null
+++ b/django-nova/src/django_nova/views/keypairs.py
@@ -0,0 +1,122 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova keypairs.
+"""
+
+from django import http
+from django import template
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render_to_response
+from django_nova import exceptions
+from django_nova import forms
+from django_nova import shortcuts
+from django_nova.exceptions import handle_nova_error
+
+
+@login_required
+@handle_nova_error
+def index(request, project_id, download_key=None):
+ project = shortcuts.get_project_or_404(request, project_id)
+ keypairs = project.get_key_pairs()
+
+ return render_to_response('django_nova/keypairs/index.html', {
+ 'create_form': forms.CreateKeyPairForm(project),
+ 'region': project.region,
+ 'project': project,
+ 'keypairs': keypairs,
+ 'download_key': download_key
+ }, context_instance = template.RequestContext(request))
+
+@login_required
+@handle_nova_error
+def add(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ form = forms.CreateKeyPairForm(project, request.POST)
+
+ if form.is_valid():
+ try:
+ keypair = project.create_key_pair(form.cleaned_data['name'])
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to create key: %s' % e.message)
+ else:
+ if request.POST['js'] == '1':
+ request.session['key.%s' % keypair.name] = keypair.material
+ return index(request,
+ project_id,
+ download_key=keypair.name)
+ else:
+ response = http.HttpResponse(mimetype='application/binary')
+ response['Content-Disposition'] = \
+ 'attachment; filename=%s.pem' % \
+ form.cleaned_data['name']
+ response.write(keypair.material)
+ return response
+ else:
+ keypairs = project.get_key_pairs()
+
+ return render_to_response('django_nova/keypairs/index.html', {
+ 'create_form': form,
+ 'region': project.region,
+ 'project': project,
+ 'keypairs': keypairs,
+ }, context_instance = template.RequestContext(request))
+
+ return redirect('nova_keypairs', project_id)
+
+@login_required
+@handle_nova_error
+def delete(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ key_name = request.POST['key_name']
+
+ try:
+ project.delete_key_pair(key_name)
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to delete key: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Key %s has been successfully deleted.' % \
+ key_name)
+
+ return redirect('nova_keypairs', project_id)
+
+@login_required
+@handle_nova_error
+def download(request, project_id, key_name):
+ # Ensure the project exists.
+ shortcuts.get_project_or_404(request, project_id)
+
+ try:
+ material = request.session.pop('key.%s' % key_name)
+ except KeyError:
+ return redirect('nova_keypairs', project_id)
+
+ response = http.HttpResponse(mimetype='application/binary')
+ response['Content-Disposition'] = 'attachment; filename=%s.pem' % key_name
+ response.write(material)
+
+ return response
diff --git a/django-nova/src/django_nova/views/projects.py b/django-nova/src/django_nova/views/projects.py
new file mode 100644
index 00000000..b2a217c7
--- /dev/null
+++ b/django-nova/src/django_nova/views/projects.py
@@ -0,0 +1,107 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova projects.
+"""
+
+from django import http
+from django import template
+from django.conf import settings
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render_to_response
+from django_nova import forms as nova_forms
+from django_nova.connection import get_nova_admin_connection
+from django_nova.exceptions import handle_nova_error
+from django_nova.shortcuts import get_project_or_404
+
+
+@login_required
+@handle_nova_error
+def detail(request, project_id):
+ project = get_project_or_404(request, project_id)
+
+ return render_to_response('django_nova/projects/index.html', {
+ 'project': project,
+ 'instance_count': project.get_instance_count(),
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def manage(request, project_id):
+ project = get_project_or_404(request, project_id)
+
+ if project.projectManagerId != request.user.username:
+ return redirect('login')
+
+ nova = get_nova_admin_connection()
+ members = nova.get_project_members(project_id)
+
+ for member in members:
+ project_role = [str(role.role) for role in nova.get_user_roles(member.memberId, project_id)]
+ global_role = [str(role.role) for role in nova.get_user_roles(member.memberId, project=False)]
+
+ member.project_roles = ", ".join(project_role)
+ member.global_roles = ", ".join(global_role)
+
+
+ return render_to_response('django_nova/projects/manage.html', {
+ 'project': project,
+ 'members': members,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def edit_user(request, project_id, username):
+ nova = get_nova_admin_connection()
+ project = get_project_or_404(request, project_id)
+ user = nova.get_user(username)
+
+ if project.projectManagerId != request.user.username:
+ return redirect('login')
+
+ if request.method == 'POST':
+ form = nova_forms.ProjectUserForm(project, user, request.POST)
+ if form.is_valid():
+ form.save()
+
+ return redirect('nova_project_manage', project_id)
+ else:
+ form = nova_forms.ProjectUserForm(project, user)
+
+ return render_to_response('django_nova/projects/edit_user.html', {
+ 'form' : form,
+ 'project': project,
+ 'user' : user,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def download_credentials(request, project_id):
+ project = get_project_or_404(request, project_id)
+
+ response = http.HttpResponse(mimetype='application/zip')
+ response['Content-Disposition'] = \
+ 'attachment; filename=%s-%s-%s-x509.zip' % \
+ (settings.SITE_NAME, project.projectname, request.user)
+ response.write(project.get_zip())
+
+ return response
diff --git a/django-nova/src/django_nova/views/regions.py b/django-nova/src/django_nova/views/regions.py
new file mode 100644
index 00000000..18914276
--- /dev/null
+++ b/django-nova/src/django_nova/views/regions.py
@@ -0,0 +1,36 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova regions.
+"""
+
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect
+from django_nova.shortcuts import set_current_region
+
+
+@login_required
+def change(request):
+ region = request.POST['region']
+ redirect_url = request.POST['redirect_url']
+ set_current_region(request, region)
+ messages.success(request, 'You are now using the region "%s".' % region)
+
+ return redirect(redirect_url)
diff --git a/django-nova/src/django_nova/views/securitygroups.py b/django-nova/src/django_nova/views/securitygroups.py
new file mode 100644
index 00000000..6f85b4af
--- /dev/null
+++ b/django-nova/src/django_nova/views/securitygroups.py
@@ -0,0 +1,180 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova security groups.
+"""
+
+from django import http
+from django import template
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render_to_response
+from django_nova import exceptions
+from django_nova import forms
+from django_nova.exceptions import handle_nova_error
+from django_nova.shortcuts import get_project_or_404
+
+
+@login_required
+@handle_nova_error
+def index(request, project_id):
+ project = get_project_or_404(request, project_id)
+ securitygroups = project.get_security_groups()
+
+ return render_to_response('django_nova/securitygroups/index.html', {
+ 'create_form': forms.CreateSecurityGroupForm(project),
+ 'project': project,
+ 'securitygroups': securitygroups,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def detail(request, project_id, group_name):
+ project = get_project_or_404(request, project_id)
+ securitygroup = project.get_security_group(group_name)
+
+ if not securitygroup:
+ raise http.Http404
+
+ return render_to_response('django_nova/securitygroups/detail.html', {
+ 'authorize_form': forms.AuthorizeSecurityGroupRuleForm(),
+ 'project': project,
+ 'securitygroup': securitygroup,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def add(request, project_id):
+ project = get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ form = forms.CreateSecurityGroupForm(project, request.POST)
+ if form.is_valid():
+ try:
+ project.create_security_group(
+ form.cleaned_data['name'],
+ form.cleaned_data['description'])
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to create security group: %s' % e.message)
+ else:
+ messages.success(
+ request,
+ 'Security Group %s has been succesfully created.' % \
+ form.cleaned_data['name'])
+ else:
+ securitygroups = project.get_security_groups()
+
+ return render_to_response('django_nova/securitygroups/index.html', {
+ 'create_form': form,
+ 'project': project,
+ 'securitygroups': securitygroups,
+ }, context_instance = template.RequestContext(request))
+
+ return redirect('nova_securitygroups', project_id)
+
+
+@login_required
+@handle_nova_error
+def authorize(request, project_id, group_name):
+ project = get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ form = forms.AuthorizeSecurityGroupRuleForm(request.POST)
+ if form.is_valid():
+ try:
+ project.authorize_security_group(
+ group_name = group_name,
+ ip_protocol = form.cleaned_data['protocol'],
+ from_port = form.cleaned_data['from_port'],
+ to_port = form.cleaned_data['to_port'])
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to authorize: %s' % e.message)
+ else:
+ messages.success(
+ request,
+ 'Security Group %s: Access to %s ports %d - %d'
+ ' has been authorized.' %
+ (group_name,
+ form.cleaned_data['protocol'],
+ form.cleaned_data['from_port'],
+ form.cleaned_data['to_port']))
+ else:
+ securitygroup = project.get_security_group(group_name)
+
+ if not securitygroup:
+ raise http.Http404
+
+ return render_to_response('django_nova/securitygroups/detail.html', {
+ 'authorize_form': form,
+ 'project': project,
+ 'securitygroup': securitygroup,
+ }, context_instance = template.RequestContext(request))
+
+ return redirect('nova_securitygroups_detail', project_id, group_name)
+
+
+@login_required
+@handle_nova_error
+def revoke(request, project_id, group_name):
+ project = get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ try:
+ project.revoke_security_group(
+ group_name = group_name,
+ ip_protocol = request.POST['protocol'],
+ from_port = request.POST['from_port'],
+ to_port = request.POST['to_port'])
+ except exceptions.NovaApiError, e:
+ messages.error(request, 'Unable to revoke: %s' % e.message)
+ else:
+ messages.success(
+ request,
+ 'Security Group %s: Access to %s ports %s - %s '
+ 'has been revoked.' %
+ (group_name,
+ request.POST['protocol'],
+ request.POST['from_port'],
+ request.POST['to_port']))
+
+ return redirect('nova_securitygroups_detail', project_id, group_name)
+
+
+@login_required
+@handle_nova_error
+def delete(request, project_id, group_name):
+ project = get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ try:
+ project.delete_security_group(name=group_name)
+ except exceptions.NovaApiError, e:
+ messages.error(
+ request,
+ 'Unable to delete security group: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Security Group %s was successfully deleted.' %
+ group_name)
+
+ return redirect('nova_securitygroups', project_id)
diff --git a/django-nova/src/django_nova/views/volumes.py b/django-nova/src/django_nova/views/volumes.py
new file mode 100644
index 00000000..9d623ed8
--- /dev/null
+++ b/django-nova/src/django_nova/views/volumes.py
@@ -0,0 +1,151 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2010 United States Government as represented by the
+# Administrator of the National Aeronautics and Space Administration.
+# 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.
+
+"""
+Views for managing Nova volumes.
+"""
+
+from django import template
+from django.contrib import messages
+from django.contrib.auth.decorators import login_required
+from django.shortcuts import redirect, render_to_response
+from django_nova import exceptions
+from django_nova import forms
+from django_nova import shortcuts
+from django_nova.exceptions import handle_nova_error
+
+
+@login_required
+@handle_nova_error
+def index(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+ volumes = project.get_volumes()
+
+ return render_to_response('django_nova/volumes/index.html', {
+ 'create_form': forms.CreateVolumeForm(),
+ 'attach_form': forms.AttachVolumeForm(project),
+ 'region': project.region,
+ 'project': project,
+ 'volumes': volumes,
+ }, context_instance = template.RequestContext(request))
+
+
+@login_required
+@handle_nova_error
+def add(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ form = forms.CreateVolumeForm(request.POST)
+ if form.is_valid():
+ try:
+ volume = project.create_volume(form.cleaned_data['size'],
+ form.cleaned_data['nickname'],
+ form.cleaned_data['description'])
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to create volume: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Volume %s %s has been successfully created.' %
+ (volume.id, volume.displayName))
+ else:
+ volumes = project.get_volumes()
+
+ return render_to_response('django_nova/volumes/index.html', {
+ 'create_form': form,
+ 'attach_form': forms.AttachVolumeForm(project),
+ 'region': project.region,
+ 'project': project,
+ 'volumes': volumes,
+ }, context_instance = template.RequestContext(request))
+
+ return redirect('nova_volumes', project_id)
+
+
+@login_required
+@handle_nova_error
+def delete(request, project_id, volume_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ try:
+ project.delete_volume(volume_id)
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to delete volume: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Volume %s has been successfully deleted.'
+ % volume_id)
+
+ return redirect('nova_volumes', project_id)
+
+
+@login_required
+@handle_nova_error
+def attach(request, project_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ form = forms.AttachVolumeForm(project, request.POST)
+ if form.is_valid():
+ try:
+ project.attach_volume(
+ form.cleaned_data['volume'],
+ form.cleaned_data['instance'],
+ form.cleaned_data['device']
+ )
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to attach volume: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Volume %s has been successfully attached.' %
+ form.cleaned_data['volume'])
+ else:
+ volumes = project.get_volumes()
+
+ return render_to_response('django_nova/volumes/index.html', {
+ 'create_form': forms.CreateVolumeForm(),
+ 'attach_form': form,
+ 'region': project.region,
+ 'project': project,
+ 'volumes': volumes,
+ }, context_instance = template.RequestContext(request))
+
+ return redirect('nova_volumes', project_id)
+
+
+@login_required
+@handle_nova_error
+def detach(request, project_id, volume_id):
+ project = shortcuts.get_project_or_404(request, project_id)
+
+ if request.method == 'POST':
+ try:
+ project.detach_volume(volume_id)
+ except exceptions.NovaApiError, e:
+ messages.error(request,
+ 'Unable to detach volume: %s' % e.message)
+ else:
+ messages.success(request,
+ 'Volume %s has been successfully detached.' %
+ volume_id)
+
+ return redirect('nova_volumes', project_id)
diff --git a/openstack-dashboard/README b/openstack-dashboard/README
new file mode 100644
index 00000000..3856aca7
--- /dev/null
+++ b/openstack-dashboard/README
@@ -0,0 +1,49 @@
+OpenStack Dashboard
+-------------------
+
+The OpenStack Dashboard is a reference implementation of a Django site that
+uses the Django-Nova project to provide web based interactions with the
+OpenStack Nova cloud controller.
+
+For more information about the Django-Nova project, please visit:
+
+ http://launchpad.net/django-nova
+
+
+Getting Started
+---------------
+
+The first step is to obtain a local copy of the django-nova project:
+
+ $ mkdir django-nova
+ $ cd django-nova
+ $ bzr init-repo .
+ $ bzr branch lp:django-nova/trunk
+
+
+Next we will create the virtualenv for local development. A tool is included to
+create one for you:
+
+ $ python tools/install_venv.py
+
+
+Now that the virtualenv is created, you need to configure your local
+environment. To do this, create a local_settings.py file in the local/
+directory. There is a local_settings.py.example file there that may be used
+as a template.
+
+Finally, issue the django syncdb command:
+
+ $ tools/with_venv.sh dashboard/manage.py syncdb
+
+If after you have specified the admin user the script appears to hang, it
+probably means the installation of Nova being referred to in local_settings.py
+is unavailable.
+
+
+If all is well you should now able to run the server locally:
+
+ $ tools/with_venv.sh dashboard/manage.py runserver
+
+
+
diff --git a/openstack-dashboard/dashboard/__init__.py b/openstack-dashboard/dashboard/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/openstack-dashboard/dashboard/__init__.py
diff --git a/dashboard/manage.py b/openstack-dashboard/dashboard/manage.py
index 5e78ea97..5e78ea97 100755
--- a/dashboard/manage.py
+++ b/openstack-dashboard/dashboard/manage.py
diff --git a/dashboard/settings.py b/openstack-dashboard/dashboard/settings.py
index 2a374212..2a374212 100644
--- a/dashboard/settings.py
+++ b/openstack-dashboard/dashboard/settings.py
diff --git a/dashboard/templates/403.html b/openstack-dashboard/dashboard/templates/403.html
index 14e0a875..14e0a875 100644
--- a/dashboard/templates/403.html
+++ b/openstack-dashboard/dashboard/templates/403.html
diff --git a/dashboard/templates/404.html b/openstack-dashboard/dashboard/templates/404.html
index 53aabfa1..53aabfa1 100644
--- a/dashboard/templates/404.html
+++ b/openstack-dashboard/dashboard/templates/404.html
diff --git a/dashboard/templates/500.html b/openstack-dashboard/dashboard/templates/500.html
index 228be539..228be539 100644
--- a/dashboard/templates/500.html
+++ b/openstack-dashboard/dashboard/templates/500.html
diff --git a/dashboard/templates/admin/index.html b/openstack-dashboard/dashboard/templates/admin/index.html
index ddb79a8c..ddb79a8c 100644
--- a/dashboard/templates/admin/index.html
+++ b/openstack-dashboard/dashboard/templates/admin/index.html
diff --git a/dashboard/templates/base-root.html b/openstack-dashboard/dashboard/templates/base-root.html
index 7454a342..7454a342 100644
--- a/dashboard/templates/base-root.html
+++ b/openstack-dashboard/dashboard/templates/base-root.html
diff --git a/dashboard/templates/base-sidebar.html b/openstack-dashboard/dashboard/templates/base-sidebar.html
index d7d0021d..d7d0021d 100644
--- a/dashboard/templates/base-sidebar.html
+++ b/openstack-dashboard/dashboard/templates/base-sidebar.html
diff --git a/dashboard/templates/base.html b/openstack-dashboard/dashboard/templates/base.html
index cf37c0e4..cf37c0e4 100644
--- a/dashboard/templates/base.html
+++ b/openstack-dashboard/dashboard/templates/base.html
diff --git a/dashboard/templates/index.html b/openstack-dashboard/dashboard/templates/index.html
index ba99a5d1..14820687 100644
--- a/dashboard/templates/index.html
+++ b/openstack-dashboard/dashboard/templates/index.html
@@ -36,8 +36,8 @@
<h3>OpenStack Resources</h3>
<ul>
<li><a href="http://openstack.org" target="_blank">OpenStack.org</a></li>
+ <li><a href="http://wiki.openstack.org/OpenStackDashboard" target="_blank">OpenStack Dashboard Wiki</a></li>
<li><a href="https://launchpad.net/openstack-dashboard" target="_blank">OpenStack Dashboard Launchpad Repository</a></li>
- <li><a href="https://launchpad.net/django-nova" target="_blank">Django-Nova Launchpad Repository</a></li>
</ul>
</div>
@@ -69,8 +69,8 @@
<h3>OpenStack Resources</h3>
<ul>
<li><a href="http://openstack.org" target="_blank">OpenStack.org</a></li>
+ <li><a href="http://wiki.openstack.org/OpenStackDashboard" target="_blank">OpenStack Dashboard Wiki</a></li>
<li><a href="https://launchpad.net/openstack-dashboard" target="_blank">OpenStack Dashboard Launchpad Repository</a></li>
- <li><a href="https://launchpad.net/django-nova" target="_blank">Django-Nova Launchpad Repository</a></li>
</ul>
</div>
{% endif %}
diff --git a/dashboard/templates/permission_denied.html b/openstack-dashboard/dashboard/templates/permission_denied.html
index 96b02dcc..96b02dcc 100644
--- a/dashboard/templates/permission_denied.html
+++ b/openstack-dashboard/dashboard/templates/permission_denied.html
diff --git a/dashboard/templates/registration/activate.html b/openstack-dashboard/dashboard/templates/registration/activate.html
index e6ffdc9e..e6ffdc9e 100644
--- a/dashboard/templates/registration/activate.html
+++ b/openstack-dashboard/dashboard/templates/registration/activate.html
diff --git a/dashboard/templates/registration/activation_email.txt b/openstack-dashboard/dashboard/templates/registration/activation_email.txt
index c9059ec0..c9059ec0 100644
--- a/dashboard/templates/registration/activation_email.txt
+++ b/openstack-dashboard/dashboard/templates/registration/activation_email.txt
diff --git a/dashboard/templates/registration/activation_email_subject.txt b/openstack-dashboard/dashboard/templates/registration/activation_email_subject.txt
index b0472aea..b0472aea 100644
--- a/dashboard/templates/registration/activation_email_subject.txt
+++ b/openstack-dashboard/dashboard/templates/registration/activation_email_subject.txt
diff --git a/dashboard/templates/registration/login.html b/openstack-dashboard/dashboard/templates/registration/login.html
index 2e85c7ed..2e85c7ed 100644
--- a/dashboard/templates/registration/login.html
+++ b/openstack-dashboard/dashboard/templates/registration/login.html
diff --git a/dashboard/templates/registration/logout.html b/openstack-dashboard/dashboard/templates/registration/logout.html
index a97e3859..a97e3859 100644
--- a/dashboard/templates/registration/logout.html
+++ b/openstack-dashboard/dashboard/templates/registration/logout.html
diff --git a/dashboard/templates/registration/password_change_done.html b/openstack-dashboard/dashboard/templates/registration/password_change_done.html
index 852ea526..852ea526 100644
--- a/dashboard/templates/registration/password_change_done.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_change_done.html
diff --git a/dashboard/templates/registration/password_change_form.html b/openstack-dashboard/dashboard/templates/registration/password_change_form.html
index 70bf79c2..70bf79c2 100644
--- a/dashboard/templates/registration/password_change_form.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_change_form.html
diff --git a/dashboard/templates/registration/password_reset_complete.html b/openstack-dashboard/dashboard/templates/registration/password_reset_complete.html
index dc972451..dc972451 100644
--- a/dashboard/templates/registration/password_reset_complete.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_reset_complete.html
diff --git a/dashboard/templates/registration/password_reset_confirm.html b/openstack-dashboard/dashboard/templates/registration/password_reset_confirm.html
index acd9af6e..acd9af6e 100644
--- a/dashboard/templates/registration/password_reset_confirm.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_reset_confirm.html
diff --git a/dashboard/templates/registration/password_reset_done.html b/openstack-dashboard/dashboard/templates/registration/password_reset_done.html
index 9aed573a..9aed573a 100644
--- a/dashboard/templates/registration/password_reset_done.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_reset_done.html
diff --git a/dashboard/templates/registration/password_reset_email.html b/openstack-dashboard/dashboard/templates/registration/password_reset_email.html
index cd75e305..cd75e305 100644
--- a/dashboard/templates/registration/password_reset_email.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_reset_email.html
diff --git a/dashboard/templates/registration/password_reset_form.html b/openstack-dashboard/dashboard/templates/registration/password_reset_form.html
index 06461f1f..06461f1f 100644
--- a/dashboard/templates/registration/password_reset_form.html
+++ b/openstack-dashboard/dashboard/templates/registration/password_reset_form.html
diff --git a/dashboard/templates/registration/registration_complete.html b/openstack-dashboard/dashboard/templates/registration/registration_complete.html
index c5c0e97f..c5c0e97f 100644
--- a/dashboard/templates/registration/registration_complete.html
+++ b/openstack-dashboard/dashboard/templates/registration/registration_complete.html
diff --git a/dashboard/templates/registration/registration_form.html b/openstack-dashboard/dashboard/templates/registration/registration_form.html
index 747d8684..747d8684 100644
--- a/dashboard/templates/registration/registration_form.html
+++ b/openstack-dashboard/dashboard/templates/registration/registration_form.html
diff --git a/dashboard/templates/unavailable.html b/openstack-dashboard/dashboard/templates/unavailable.html
index 5568bc4e..5568bc4e 100644
--- a/dashboard/templates/unavailable.html
+++ b/openstack-dashboard/dashboard/templates/unavailable.html
diff --git a/dashboard/urls.py b/openstack-dashboard/dashboard/urls.py
index 485fe031..485fe031 100644
--- a/dashboard/urls.py
+++ b/openstack-dashboard/dashboard/urls.py
diff --git a/dashboard/views.py b/openstack-dashboard/dashboard/views.py
index 28bcefc9..28bcefc9 100644
--- a/dashboard/views.py
+++ b/openstack-dashboard/dashboard/views.py
diff --git a/dashboard/wsgi/django.wsgi b/openstack-dashboard/dashboard/wsgi/django.wsgi
index 9c821ecb..9c821ecb 100644
--- a/dashboard/wsgi/django.wsgi
+++ b/openstack-dashboard/dashboard/wsgi/django.wsgi
diff --git a/openstack-dashboard/local/__init__.py b/openstack-dashboard/local/__init__.py
new file mode 100644
index 00000000..e69de29b
--- /dev/null
+++ b/openstack-dashboard/local/__init__.py
diff --git a/local/local_settings.py.example b/openstack-dashboard/local/local_settings.py.example
index 95631bc2..95631bc2 100644
--- a/local/local_settings.py.example
+++ b/openstack-dashboard/local/local_settings.py.example
diff --git a/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png
index 6348115e..6348115e 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_diagonals-thick_90_eeeeee_40x40.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png
index 7680b543..7680b543 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_flat_15_cd0a0a_40x100.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png
index 5ae77ce6..5ae77ce6 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_100_e4f1fb_1x400.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png
index baabca6b..baabca6b 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_50_3baae3_1x400.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png
index d9387e95..d9387e95 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_glass_80_d7ebf9_1x400.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png
index 28b566c2..28b566c2 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_100_f2f5f7_1x100.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png
index d5882978..d5882978 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-hard_70_000000_1x100.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png
index 2289d3c7..2289d3c7 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_100_deedf7_1x100.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png
index 0de3275b..0de3275b 100644
--- a/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-bg_highlight-soft_25_ffef8f_1x100.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png
index bdc74718..bdc74718 100644
--- a/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2694e8_256x240.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png
index 45e8928e..45e8928e 100644
--- a/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_2e83ff_256x240.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png
index 76a020d9..76a020d9 100644
--- a/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_3d80b3_256x240.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png
index 0e8f4d9d..0e8f4d9d 100644
--- a/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_72a7cf_256x240.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png
index bef5178a..bef5178a 100644
--- a/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png
+++ b/openstack-dashboard/media/dashboard/css/cupertino/images/ui-icons_ffffff_256x240.png
Binary files differ
diff --git a/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css b/openstack-dashboard/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css
index 6b3ee7a8..6b3ee7a8 100644
--- a/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css
+++ b/openstack-dashboard/media/dashboard/css/cupertino/jquery-ui-1.7.2.custom.css
diff --git a/media/dashboard/css/django-admin-widgets.css b/openstack-dashboard/media/dashboard/css/django-admin-widgets.css
index 43521be0..43521be0 100644
--- a/media/dashboard/css/django-admin-widgets.css
+++ b/openstack-dashboard/media/dashboard/css/django-admin-widgets.css
diff --git a/media/dashboard/css/ie7.css b/openstack-dashboard/media/dashboard/css/ie7.css
index e29f06fb..e29f06fb 100755
--- a/media/dashboard/css/ie7.css
+++ b/openstack-dashboard/media/dashboard/css/ie7.css
diff --git a/media/dashboard/css/openstack.css b/openstack-dashboard/media/dashboard/css/openstack.css
index dfbe3797..dfbe3797 100644
--- a/media/dashboard/css/openstack.css
+++ b/openstack-dashboard/media/dashboard/css/openstack.css
diff --git a/media/dashboard/css/reset.css b/openstack-dashboard/media/dashboard/css/reset.css
index 26cd169c..26cd169c 100644
--- a/media/dashboard/css/reset.css
+++ b/openstack-dashboard/media/dashboard/css/reset.css
diff --git a/media/dashboard/img/body_bg.gif b/openstack-dashboard/media/dashboard/img/body_bg.gif
index e45adc52..e45adc52 100644
--- a/media/dashboard/img/body_bg.gif
+++ b/openstack-dashboard/media/dashboard/img/body_bg.gif
Binary files differ
diff --git a/media/dashboard/img/body_bg.png b/openstack-dashboard/media/dashboard/img/body_bg.png
index e4625ec0..e4625ec0 100755
--- a/media/dashboard/img/body_bg.png
+++ b/openstack-dashboard/media/dashboard/img/body_bg.png
Binary files differ
diff --git a/media/dashboard/img/book_icon.png b/openstack-dashboard/media/dashboard/img/book_icon.png
index bd41e7de..bd41e7de 100644
--- a/media/dashboard/img/book_icon.png
+++ b/openstack-dashboard/media/dashboard/img/book_icon.png
Binary files differ
diff --git a/media/dashboard/img/bread_crumb.gif b/openstack-dashboard/media/dashboard/img/bread_crumb.gif
index 49d92810..49d92810 100644
--- a/media/dashboard/img/bread_crumb.gif
+++ b/openstack-dashboard/media/dashboard/img/bread_crumb.gif
Binary files differ
diff --git a/media/dashboard/img/btn_bg.png b/openstack-dashboard/media/dashboard/img/btn_bg.png
index 00227dba..00227dba 100644
--- a/media/dashboard/img/btn_bg.png
+++ b/openstack-dashboard/media/dashboard/img/btn_bg.png
Binary files differ
diff --git a/media/dashboard/img/chat_icon.png b/openstack-dashboard/media/dashboard/img/chat_icon.png
index a905633f..a905633f 100644
--- a/media/dashboard/img/chat_icon.png
+++ b/openstack-dashboard/media/dashboard/img/chat_icon.png
Binary files differ
diff --git a/media/dashboard/img/content_bg.gif b/openstack-dashboard/media/dashboard/img/content_bg.gif
index e77a90c5..e77a90c5 100644
--- a/media/dashboard/img/content_bg.gif
+++ b/openstack-dashboard/media/dashboard/img/content_bg.gif
Binary files differ
diff --git a/media/dashboard/img/content_shadow.png b/openstack-dashboard/media/dashboard/img/content_shadow.png
index 658a725f..658a725f 100644
--- a/media/dashboard/img/content_shadow.png
+++ b/openstack-dashboard/media/dashboard/img/content_shadow.png
Binary files differ
diff --git a/media/dashboard/img/dashboard_nav_bg.png b/openstack-dashboard/media/dashboard/img/dashboard_nav_bg.png
index f0e21bd3..f0e21bd3 100644
--- a/media/dashboard/img/dashboard_nav_bg.png
+++ b/openstack-dashboard/media/dashboard/img/dashboard_nav_bg.png
Binary files differ
diff --git a/media/dashboard/img/foot_back.png b/openstack-dashboard/media/dashboard/img/foot_back.png
index 13f81049..13f81049 100644
--- a/media/dashboard/img/foot_back.png
+++ b/openstack-dashboard/media/dashboard/img/foot_back.png
Binary files differ
diff --git a/media/dashboard/img/gears.png b/openstack-dashboard/media/dashboard/img/gears.png
index a25cadbe..a25cadbe 100644
--- a/media/dashboard/img/gears.png
+++ b/openstack-dashboard/media/dashboard/img/gears.png
Binary files differ
diff --git a/media/dashboard/img/header_bg.png b/openstack-dashboard/media/dashboard/img/header_bg.png
index b1b926be..b1b926be 100755
--- a/media/dashboard/img/header_bg.png
+++ b/openstack-dashboard/media/dashboard/img/header_bg.png
Binary files differ
diff --git a/media/dashboard/img/home_head_back.png b/openstack-dashboard/media/dashboard/img/home_head_back.png
index 6ebaca18..6ebaca18 100644
--- a/media/dashboard/img/home_head_back.png
+++ b/openstack-dashboard/media/dashboard/img/home_head_back.png
Binary files differ
diff --git a/media/dashboard/img/image_detail.png b/openstack-dashboard/media/dashboard/img/image_detail.png
index 054eeff7..054eeff7 100644
--- a/media/dashboard/img/image_detail.png
+++ b/openstack-dashboard/media/dashboard/img/image_detail.png
Binary files differ
diff --git a/media/dashboard/img/logged_in_box_bg.gif b/openstack-dashboard/media/dashboard/img/logged_in_box_bg.gif
index ddd950d5..ddd950d5 100644
--- a/media/dashboard/img/logged_in_box_bg.gif
+++ b/openstack-dashboard/media/dashboard/img/logged_in_box_bg.gif
Binary files differ
diff --git a/media/dashboard/img/login_bg.png b/openstack-dashboard/media/dashboard/img/login_bg.png
index 3928fb42..3928fb42 100644
--- a/media/dashboard/img/login_bg.png
+++ b/openstack-dashboard/media/dashboard/img/login_bg.png
Binary files differ
diff --git a/media/dashboard/img/login_btn.png b/openstack-dashboard/media/dashboard/img/login_btn.png
index fe5a1ac0..fe5a1ac0 100644
--- a/media/dashboard/img/login_btn.png
+++ b/openstack-dashboard/media/dashboard/img/login_btn.png
Binary files differ
diff --git a/media/dashboard/img/logo.gif b/openstack-dashboard/media/dashboard/img/logo.gif
index 8da127c3..8da127c3 100644
--- a/media/dashboard/img/logo.gif
+++ b/openstack-dashboard/media/dashboard/img/logo.gif
Binary files differ
diff --git a/media/dashboard/img/nav_arrow.png b/openstack-dashboard/media/dashboard/img/nav_arrow.png
index 46c113cd..46c113cd 100644
--- a/media/dashboard/img/nav_arrow.png
+++ b/openstack-dashboard/media/dashboard/img/nav_arrow.png
Binary files differ
diff --git a/media/dashboard/img/nav_bg.png b/openstack-dashboard/media/dashboard/img/nav_bg.png
index 90ae6dc0..90ae6dc0 100644
--- a/media/dashboard/img/nav_bg.png
+++ b/openstack-dashboard/media/dashboard/img/nav_bg.png
Binary files differ
diff --git a/media/dashboard/img/nav_highlight.png b/openstack-dashboard/media/dashboard/img/nav_highlight.png
index b562b1c3..b562b1c3 100755
--- a/media/dashboard/img/nav_highlight.png
+++ b/openstack-dashboard/media/dashboard/img/nav_highlight.png
Binary files differ
diff --git a/media/dashboard/img/page_header.png b/openstack-dashboard/media/dashboard/img/page_header.png
index dac79491..dac79491 100644
--- a/media/dashboard/img/page_header.png
+++ b/openstack-dashboard/media/dashboard/img/page_header.png
Binary files differ
diff --git a/media/dashboard/img/projects_bg.png b/openstack-dashboard/media/dashboard/img/projects_bg.png
index cd52bf15..cd52bf15 100644
--- a/media/dashboard/img/projects_bg.png
+++ b/openstack-dashboard/media/dashboard/img/projects_bg.png
Binary files differ
diff --git a/media/dashboard/img/server_icon.png b/openstack-dashboard/media/dashboard/img/server_icon.png
index 855e57ce..855e57ce 100644
--- a/media/dashboard/img/server_icon.png
+++ b/openstack-dashboard/media/dashboard/img/server_icon.png
Binary files differ
diff --git a/media/dashboard/img/spinner.gif b/openstack-dashboard/media/dashboard/img/spinner.gif
index 42832024..42832024 100644
--- a/media/dashboard/img/spinner.gif
+++ b/openstack-dashboard/media/dashboard/img/spinner.gif
Binary files differ
diff --git a/media/dashboard/img/sub-head-back.png b/openstack-dashboard/media/dashboard/img/sub-head-back.png
index b4aacbd5..b4aacbd5 100644
--- a/media/dashboard/img/sub-head-back.png
+++ b/openstack-dashboard/media/dashboard/img/sub-head-back.png
Binary files differ
diff --git a/media/dashboard/img/sub_head_back.png b/openstack-dashboard/media/dashboard/img/sub_head_back.png
index 3903dcec..3903dcec 100644
--- a/media/dashboard/img/sub_head_back.png
+++ b/openstack-dashboard/media/dashboard/img/sub_head_back.png
Binary files differ
diff --git a/media/dashboard/img/table_header_bg.png b/openstack-dashboard/media/dashboard/img/table_header_bg.png
index 06eb3dee..06eb3dee 100644
--- a/media/dashboard/img/table_header_bg.png
+++ b/openstack-dashboard/media/dashboard/img/table_header_bg.png
Binary files differ
diff --git a/media/dashboard/img/table_heading_bg.png b/openstack-dashboard/media/dashboard/img/table_heading_bg.png
index 6fcba877..6fcba877 100644
--- a/media/dashboard/img/table_heading_bg.png
+++ b/openstack-dashboard/media/dashboard/img/table_heading_bg.png
Binary files differ
diff --git a/media/dashboard/img/title-blank-short-foot.png b/openstack-dashboard/media/dashboard/img/title-blank-short-foot.png
index 548ce450..548ce450 100644
--- a/media/dashboard/img/title-blank-short-foot.png
+++ b/openstack-dashboard/media/dashboard/img/title-blank-short-foot.png
Binary files differ
diff --git a/media/dashboard/js/dashboard.js b/openstack-dashboard/media/dashboard/js/dashboard.js
index 4559d6c3..4559d6c3 100644
--- a/media/dashboard/js/dashboard.js
+++ b/openstack-dashboard/media/dashboard/js/dashboard.js
diff --git a/media/dashboard/js/django-admin.multiselect.js b/openstack-dashboard/media/dashboard/js/django-admin.multiselect.js
index f91cdb53..f91cdb53 100644
--- a/media/dashboard/js/django-admin.multiselect.js
+++ b/openstack-dashboard/media/dashboard/js/django-admin.multiselect.js
diff --git a/media/dashboard/js/jquery-ui.min.js b/openstack-dashboard/media/dashboard/js/jquery-ui.min.js
index 3e168d10..3e168d10 100755
--- a/media/dashboard/js/jquery-ui.min.js
+++ b/openstack-dashboard/media/dashboard/js/jquery-ui.min.js
diff --git a/media/dashboard/js/jquery.form.js b/openstack-dashboard/media/dashboard/js/jquery.form.js
index be8c0b6b..be8c0b6b 100644
--- a/media/dashboard/js/jquery.form.js
+++ b/openstack-dashboard/media/dashboard/js/jquery.form.js
diff --git a/media/dashboard/js/jquery.min.js b/openstack-dashboard/media/dashboard/js/jquery.min.js
index 7c243080..7c243080 100644
--- a/media/dashboard/js/jquery.min.js
+++ b/openstack-dashboard/media/dashboard/js/jquery.min.js
diff --git a/run_tests.sh b/openstack-dashboard/run_tests.sh
index 38499f09..38499f09 100755
--- a/run_tests.sh
+++ b/openstack-dashboard/run_tests.sh
diff --git a/tools/install_venv.py b/openstack-dashboard/tools/install_venv.py
index 987e8b88..8e90e60c 100644
--- a/tools/install_venv.py
+++ b/openstack-dashboard/tools/install_venv.py
@@ -32,6 +32,7 @@ VENV = os.path.join(ROOT, '.dashboard-venv')
WITH_VENV = os.path.join(ROOT, 'tools', 'with_venv.sh')
PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
+
def die(message, *args):
print >>sys.stderr, message % args
sys.exit(1)
@@ -97,21 +98,11 @@ def install_dependencies(venv=VENV):
f.write("%s\n" % ROOT)
-def install_django_nova(path):
+def install_django_nova():
print 'Installing django_nova in development mode...'
+ path = os.path.join(ROOT, '..', 'django-nova')
run_command([WITH_VENV, 'python', 'setup.py', 'develop'], cwd=path)
-def print_usage():
- usage = """
- OpenStack Dashboard development uses virtualenv to track and manage Python
- dependencies while in development and testing.
-
- It uses the OpenStack django_nova module. For more information on how to
- obtian the django_nova module, please refer to the README file.
-
- usage: python install_venv.py <path to django_nova checkout>
- """
- print usage
def print_summary():
summary = """
@@ -125,15 +116,12 @@ def print_summary():
print summary
-def main(argv):
- if len(argv) != 2:
- print_usage()
- sys.exit(1)
+def main():
check_dependencies()
create_virtualenv()
install_dependencies()
- install_django_nova(argv[1])
+ install_django_nova()
print_summary()
if __name__ == '__main__':
- main(sys.argv)
+ main()
diff --git a/tools/pip-requires b/openstack-dashboard/tools/pip-requires
index 608447d1..608447d1 100644
--- a/tools/pip-requires
+++ b/openstack-dashboard/tools/pip-requires
diff --git a/tools/with_venv.sh b/openstack-dashboard/tools/with_venv.sh
index 91299647..91299647 100755
--- a/tools/with_venv.sh
+++ b/openstack-dashboard/tools/with_venv.sh