From dfdcc259f4d4dc9abe898abc4e94c8004506b5b0 Mon Sep 17 00:00:00 2001 From: Tres Henry Date: Mon, 27 Jun 2011 16:43:08 -0700 Subject: [PATCH] Support for VERY basic Swift container and object management. --- django-openstack/django_openstack/api.py | 48 ++++++++++- .../django_openstack/context_processors.py | 6 +- .../django_openstack/dash/urls.py | 16 +++- .../django_openstack/dash/views/containers.py | 73 ++++++++++++++++ .../django_openstack/dash/views/keypairs.py | 2 +- .../django_openstack/dash/views/objects.py | 85 +++++++++++++++++++ django-openstack/django_openstack/forms.py | 5 +- openstack-dashboard/dashboard/settings.py | 1 + .../dashboard/templates/_container_form.html | 10 +++ .../dashboard/templates/_container_list.html | 17 ++++ .../dashboard/templates/_dash_sidebar.html | 6 ++ .../templates/_delete_container.html | 8 ++ .../dashboard/templates/_delete_object.html | 9 ++ .../dashboard/templates/_object_form.html | 11 +++ .../dashboard/templates/_object_list.html | 17 ++++ .../dashboard/templates/dash_containers.html | 37 ++++++++ .../templates/dash_containers_create.html | 33 +++++++ .../dashboard/templates/dash_objects.html | 38 +++++++++ .../templates/dash_objects_upload.html | 36 ++++++++ .../local/local_settings.py.example | 7 ++ .../media/dashboard/css/openstack.css | 17 ++-- .../media/dashboard/css/style.css | 44 ++++++---- openstack-dashboard/tools/pip-requires | 1 + 23 files changed, 492 insertions(+), 35 deletions(-) create mode 100644 django-openstack/django_openstack/dash/views/containers.py create mode 100644 django-openstack/django_openstack/dash/views/objects.py create mode 100644 openstack-dashboard/dashboard/templates/_container_form.html create mode 100644 openstack-dashboard/dashboard/templates/_container_list.html create mode 100644 openstack-dashboard/dashboard/templates/_delete_container.html create mode 100644 openstack-dashboard/dashboard/templates/_delete_object.html create mode 100644 openstack-dashboard/dashboard/templates/_object_form.html create mode 100644 openstack-dashboard/dashboard/templates/_object_list.html create mode 100644 openstack-dashboard/dashboard/templates/dash_containers.html create mode 100644 openstack-dashboard/dashboard/templates/dash_containers_create.html create mode 100644 openstack-dashboard/dashboard/templates/dash_objects.html create mode 100644 openstack-dashboard/dashboard/templates/dash_objects_upload.html diff --git a/django-openstack/django_openstack/api.py b/django-openstack/django_openstack/api.py index 34457103c..45c2c72d0 100644 --- a/django-openstack/django_openstack/api.py +++ b/django-openstack/django_openstack/api.py @@ -2,17 +2,16 @@ from django.conf import settings -import logging - +import cloudfiles import glance.client import httplib import json +import logging import openstack.compute import openstackx.admin import openstackx.extras import openstackx.auth from urlparse import urlparse -import json LOG = logging.getLogger('django_openstack.api') @@ -76,7 +75,15 @@ def extras_api(request): def auth_api(): LOG.debug('auth_api connection created using url "%s"' % settings.OPENSTACK_KEYSTONE_URL) - return openstackx.auth.Auth(management_url=settings.OPENSTACK_KEYSTONE_URL) + return openstackx.auth.Auth( + management_url=settings.OPENSTACK_KEYSTONE_URL) + + +def swift_api(): + return cloudfiles.get_connection( + settings.SWIFT_ACCOUNT + ":" + settings.SWIFT_USER, + settings.SWIFT_PASS, + authurl=settings.SWIFT_AUTHURL) def console_create(request, instance_id, kind=None): @@ -270,3 +277,36 @@ def user_update_password(request, user_id, password): def user_update_tenant(request, user_id, tenant_id): return account_api(request).users.update_tenant(user_id, tenant_id) + + +def swift_get_containers(): + return [{"name":c.name} for c in swift_api().get_all_containers()] + + +def swift_create_container(name): + return swift_api().create_container(name) + + +def swift_delete_container(name): + return swift_api().delete_container(name) + + +def swift_get_objects(container_name): + container = swift_api().get_container(container_name) + return [{"name":obj.name} for obj in container.get_objects()] + + +def swift_upload_object(container_name, object_name, object_data): + container = swift_api().get_container(container_name) + obj = container.create_object(object_name) + return obj.write(object_data) + + +def swift_delete_object(container_name, object_name): + container = swift_api().get_container(container_name) + return container.delete_object(object_name) + + +def swift_get_object_data(container_name, object_name): + container = swift_api().get_container(container_name) + return container.get_object(object_name).stream() diff --git a/django-openstack/django_openstack/context_processors.py b/django-openstack/django_openstack/context_processors.py index 579a068b3..569d1d4ac 100644 --- a/django-openstack/django_openstack/context_processors.py +++ b/django-openstack/django_openstack/context_processors.py @@ -1,4 +1,4 @@ - +from django.conf import settings from django_openstack import api @@ -6,3 +6,7 @@ def tenants(request): if not request.user or not request.user.is_authenticated(): return {} return {'tenants': api.token_list_tenants(request, request.user.token)} + + +def swift(request): + return {'swift_configured': hasattr(settings, "SWIFT_AUTHURL")} diff --git a/django-openstack/django_openstack/dash/urls.py b/django-openstack/django_openstack/dash/urls.py index e9bc62cfd..622aa7920 100644 --- a/django-openstack/django_openstack/dash/urls.py +++ b/django-openstack/django_openstack/dash/urls.py @@ -1,11 +1,12 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 from django.conf.urls.defaults import * -from django.conf import settings INSTANCES = r'^(?P[^/]+)/instances/(?P[^/]+)/%s$' IMAGES = r'^(?P[^/]+)/images/(?P[^/]+)/%s$' KEYPAIRS = r'^(?P[^/]+)/keypairs/%s$' +CONTAINERS = r'^(?P[^/]+)/containers/%s$' +OBJECTS = r'^(?P[^/]+)/containers/(?P[^/]+)/%s$' urlpatterns = patterns('django_openstack.dash.views.instances', url(r'^(?P[^/]+)/$', 'usage', name='dash_usage'), @@ -23,3 +24,16 @@ urlpatterns += patterns('django_openstack.dash.views.keypairs', url(r'^(?P[^/]+)/keypairs/$', 'index', name='dash_keypairs'), url(KEYPAIRS % 'create', 'create', name='dash_keypairs_create'), ) + +# Swift containers and objects. +urlpatterns += patterns('django_openstack.dash.views.containers', + url(CONTAINERS % '', 'index', name='dash_containers'), + url(CONTAINERS % 'create', 'create', name='dash_containers_create'), +) + +urlpatterns += patterns('django_openstack.dash.views.objects', + url(OBJECTS % '', 'index', name='dash_objects'), + url(OBJECTS % 'upload', 'upload', name='dash_objects_upload'), + url(OBJECTS % '(?P[^/]+)/download', + 'download', name='dash_objects_download'), +) diff --git a/django-openstack/django_openstack/dash/views/containers.py b/django-openstack/django_openstack/dash/views/containers.py new file mode 100644 index 000000000..f9582d364 --- /dev/null +++ b/django-openstack/django_openstack/dash/views/containers.py @@ -0,0 +1,73 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +Views for managing Swift containers. +""" +import logging + +from django import template +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django import shortcuts +from django.shortcuts import render_to_response + +from django_openstack import api +from django_openstack import forms + +from cloudfiles.errors import ContainerNotEmpty + + +LOG = logging.getLogger('django_openstack.dash') + + +class DeleteContainer(forms.SelfHandlingForm): + container_name = forms.CharField(widget=forms.HiddenInput()) + + def handle(self, request, data): + try: + api.swift_delete_container(data['container_name']) + except ContainerNotEmpty, e: + messages.error(request, + 'Unable to delete non-empty container: %s' % \ + data['container_name']) + LOG.error('Unable to delete container "%s". Exception: "%s"' % + (data['container_name'], str(e))) + else: + messages.info(request, + 'Successfully deleted container: %s' % \ + data['container_name']) + return shortcuts.redirect(request.build_absolute_uri()) + + +class CreateContainer(forms.SelfHandlingForm): + name = forms.CharField(max_length="255", label="Container Name") + + def handle(self, request, data): + api.swift_create_container(data['name']) + messages.success(request, "Container was successfully created.") + return shortcuts.redirect(request.build_absolute_uri()) + + +@login_required +def index(request, tenant_id): + delete_form, handled = DeleteContainer.maybe_handle(request) + if handled: + return handled + + containers = api.swift_get_containers() + + return render_to_response('dash_containers.html', { + 'containers': containers, + 'delete_form': delete_form, + }, context_instance=template.RequestContext(request)) + + +@login_required +def create(request, tenant_id): + form, handled = CreateContainer.maybe_handle(request) + if handled: + return handled + + return render_to_response('dash_containers_create.html', { + 'create_form': form, + }, context_instance=template.RequestContext(request)) diff --git a/django-openstack/django_openstack/dash/views/keypairs.py b/django-openstack/django_openstack/dash/views/keypairs.py index ba27b4bc3..be77c25df 100644 --- a/django-openstack/django_openstack/dash/views/keypairs.py +++ b/django-openstack/django_openstack/dash/views/keypairs.py @@ -85,7 +85,7 @@ def index(request, tenant_id): except api_exceptions.ApiException, e: keypairs = [] LOG.error("ApiException in keypair index", exc_info=True) - messages.error(request, 'Error featching keypairs: %s' % e.message) + messages.error(request, 'Error fetching keypairs: %s' % e.message) return render_to_response('dash_keypairs.html', { 'keypairs': keypairs, diff --git a/django-openstack/django_openstack/dash/views/objects.py b/django-openstack/django_openstack/dash/views/objects.py new file mode 100644 index 000000000..4ae66c843 --- /dev/null +++ b/django-openstack/django_openstack/dash/views/objects.py @@ -0,0 +1,85 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +""" +Views for managing Swift containers. +""" +import logging + +from django import http +from django import template +from django.contrib import messages +from django.contrib.auth.decorators import login_required +from django import shortcuts +from django.shortcuts import render_to_response + +from django_openstack import api +from django_openstack import forms + + +LOG = logging.getLogger('django_openstack.dash') + + +class DeleteObject(forms.SelfHandlingForm): + object_name = forms.CharField(widget=forms.HiddenInput()) + + def handle(self, request, data): + api.swift_delete_object( + request.POST['container_name'], + data['object_name']) + messages.info(request, + 'Successfully deleted object: %s' % \ + data['object_name']) + return shortcuts.redirect(request.build_absolute_uri()) + + +class UploadObject(forms.SelfHandlingForm): + name = forms.CharField(max_length="255", label="Object Name") + object_file = forms.FileField(label="File") + + def handle(self, request, data): + api.swift_upload_object( + request.POST['container_name'], + data['name'], + request.FILES['object_file'].read()) + messages.success(request, "Object was successfully uploaded.") + return shortcuts.redirect(request.build_absolute_uri()) + + +@login_required +def index(request, tenant_id, container_name): + delete_form, handled = DeleteObject.maybe_handle(request) + if handled: + return handled + + objects = api.swift_get_objects(container_name) + + return render_to_response('dash_objects.html', { + 'container_name': container_name, + 'objects': objects, + 'delete_form': delete_form, + }, context_instance=template.RequestContext(request)) + + +@login_required +def upload(request, tenant_id, container_name): + form, handled = UploadObject.maybe_handle(request) + if handled: + return handled + + return render_to_response('dash_objects_upload.html', { + 'container_name': container_name, + 'upload_form': form, + }, context_instance=template.RequestContext(request)) + + +@login_required +def download(request, tenant_id, container_name, object_name): + object_data = api.swift_get_object_data( + container_name, object_name) + + response = http.HttpResponse() + response['Content-Disposition'] = 'attachment; filename=%s' % \ + object_name + for data in object_data: + response.write(data) + return response diff --git a/django-openstack/django_openstack/forms.py b/django-openstack/django_openstack/forms.py index a365df143..350fd9938 100644 --- a/django-openstack/django_openstack/forms.py +++ b/django-openstack/django_openstack/forms.py @@ -138,7 +138,10 @@ class SelfHandlingForm(Form): return cls(*args, **kwargs), None try: - form = cls(request.POST, *args, **kwargs) + if request.FILES: + form = cls(request.POST, request.FILES, *args, **kwargs) + else: + form = cls(request.POST, *args, **kwargs) if not form.is_valid(): return form, None diff --git a/openstack-dashboard/dashboard/settings.py b/openstack-dashboard/dashboard/settings.py index a78929de6..44e64bb87 100644 --- a/openstack-dashboard/dashboard/settings.py +++ b/openstack-dashboard/dashboard/settings.py @@ -44,6 +44,7 @@ TEMPLATE_CONTEXT_PROCESSORS = ( 'django.core.context_processors.media', 'django.contrib.messages.context_processors.messages', 'django_openstack.context_processors.tenants', + 'django_openstack.context_processors.swift', ) TEMPLATE_LOADERS = ( diff --git a/openstack-dashboard/dashboard/templates/_container_form.html b/openstack-dashboard/dashboard/templates/_container_form.html new file mode 100644 index 000000000..c0a201b74 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/_container_form.html @@ -0,0 +1,10 @@ +
+ {% csrf_token %} + {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} + {% for field in form.visible_fields %} + {{ field.label_tag }} + {{ field.errors }} + {{ field }} + {% endfor %} + +
diff --git a/openstack-dashboard/dashboard/templates/_container_list.html b/openstack-dashboard/dashboard/templates/_container_list.html new file mode 100644 index 000000000..f0742bc99 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/_container_list.html @@ -0,0 +1,17 @@ + + + + + + {% for container in containers %} + + + + + {% endfor %} +
NameActions
{{ container.name }} +
    +
  • List Objects
  • +
  • {% include "_delete_container.html" with form=delete_form %}
  • +
+
diff --git a/openstack-dashboard/dashboard/templates/_dash_sidebar.html b/openstack-dashboard/dashboard/templates/_dash_sidebar.html index f4b6e4681..027fcf5bb 100644 --- a/openstack-dashboard/dashboard/templates/_dash_sidebar.html +++ b/openstack-dashboard/dashboard/templates/_dash_sidebar.html @@ -6,4 +6,10 @@
  • Images
  • Keypairs
  • + {% if swift_configured %} +

    Manage Object Store

    + + {% endif %} diff --git a/openstack-dashboard/dashboard/templates/_delete_container.html b/openstack-dashboard/dashboard/templates/_delete_container.html new file mode 100644 index 000000000..dca872274 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/_delete_container.html @@ -0,0 +1,8 @@ +
    + {% csrf_token %} + {% for hidden in form.hidden_fields %} + {{hidden}} + {% endfor %} + + +
    diff --git a/openstack-dashboard/dashboard/templates/_delete_object.html b/openstack-dashboard/dashboard/templates/_delete_object.html new file mode 100644 index 000000000..f776bae6e --- /dev/null +++ b/openstack-dashboard/dashboard/templates/_delete_object.html @@ -0,0 +1,9 @@ +
    + {% csrf_token %} + {% for hidden in form.hidden_fields %} + {{hidden}} + {% endfor %} + + + +
    diff --git a/openstack-dashboard/dashboard/templates/_object_form.html b/openstack-dashboard/dashboard/templates/_object_form.html new file mode 100644 index 000000000..ce3643ed7 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/_object_form.html @@ -0,0 +1,11 @@ +
    + {% csrf_token %} + + {% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %} + {% for field in form.visible_fields %} + {{ field.label_tag }} + {{ field.errors }} + {{ field }} + {% endfor %} + +
    diff --git a/openstack-dashboard/dashboard/templates/_object_list.html b/openstack-dashboard/dashboard/templates/_object_list.html new file mode 100644 index 000000000..36ad9a0cd --- /dev/null +++ b/openstack-dashboard/dashboard/templates/_object_list.html @@ -0,0 +1,17 @@ + + + + + + {% for object in objects %} + + + + + {% endfor %} +
    NameActions
    {{ object.name }} +
      +
    • {% include "_delete_object.html" with form=delete_form %}
    • +
    • Download +
    +
    diff --git a/openstack-dashboard/dashboard/templates/dash_containers.html b/openstack-dashboard/dashboard/templates/dash_containers.html new file mode 100644 index 000000000..1a07ce5b4 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/dash_containers.html @@ -0,0 +1,37 @@ +{% extends 'dash_base.html' %} +{# list of user's containers #} + +{% block sidebar %} + {% with current_sidebar="containers" %} + {{block.super}} + {% endwith %} +{% endblock %} + +{% block main %} + + {% include "_messages.html" %} + +
    + {% if containers %} +
    +

    Containers

    + +
    + + {% include '_container_list.html' %} + + {% endif %} + Create New Container >> +
    +{% endblock %} + diff --git a/openstack-dashboard/dashboard/templates/dash_containers_create.html b/openstack-dashboard/dashboard/templates/dash_containers_create.html new file mode 100644 index 000000000..56ba9c500 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/dash_containers_create.html @@ -0,0 +1,33 @@ +{% extends 'dash_base.html' %} + +{% block sidebar %} + {% with current_sidebar="containers" %} + {{block.super}} + {% endwith %} +{% endblock %} + +{% block main %} + + + {% include "_messages.html" %} + +
    +
    +
    +

    Create Container

    +
    +
    + {% include '_container_form.html' with form=create_form %} +

    << Return to containers list

    +
    + +
    +

    Description:

    +

    A container is a storage compartment for your data and provides a way for you to organize your data. You can think of a container as a folder in Windows® or a directory in UNIX®. The primary difference between a container and these other file system concepts is that containers cannot be nested. You can, however, create an unlimited number of containers within your account. Data must be stored in a container so you must have at least one container defined in your account prior to uploading data.

    +
    +
    +
    +{% endblock %} diff --git a/openstack-dashboard/dashboard/templates/dash_objects.html b/openstack-dashboard/dashboard/templates/dash_objects.html new file mode 100644 index 000000000..42db15b55 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/dash_objects.html @@ -0,0 +1,38 @@ +{% extends 'dash_base.html' %} + +{% block sidebar %} + {% with current_sidebar="containers" %} + {{block.super}} + {% endwith %} +{% endblock %} + +{% block main %} + + {% include "_messages.html" %} +
    Container:
    +
    {{ container_name }}
    +
    +
    + {% if objects %} +
    +

    Objects

    + +
    + + {% include '_object_list.html' %} + + {% endif %} + Upload New Object >> +
    +{% endblock %} + diff --git a/openstack-dashboard/dashboard/templates/dash_objects_upload.html b/openstack-dashboard/dashboard/templates/dash_objects_upload.html new file mode 100644 index 000000000..0f8023e15 --- /dev/null +++ b/openstack-dashboard/dashboard/templates/dash_objects_upload.html @@ -0,0 +1,36 @@ +{% extends 'dash_base.html' %} + +{% block sidebar %} + {% with current_sidebar="containers" %} + {{block.super}} + {% endwith %} +{% endblock %} + +{% block main %} + + + {% include "_messages.html" %} + +
    Container:
    +
    {{ container_name }}
    +
    +
    +
    +
    +

    Upload Object

    +
    +
    + {% include '_object_form.html' with form=upload_form %} +

    << Return to objects list

    +
    + +
    +

    Description:

    +

    An object is the basic storage entity and any optional metadata that represents the files you store in the OpenStack Object Storage system. When you upload data to OpenStack Object Storage, the data is stored as-is (no compression or encryption) and consists of a location (container), the object's name, and any metadata consisting of key/value pairs.

    +
    +
    +
    +{% endblock %} diff --git a/openstack-dashboard/local/local_settings.py.example b/openstack-dashboard/local/local_settings.py.example index 7a215169c..6a2759874 100644 --- a/openstack-dashboard/local/local_settings.py.example +++ b/openstack-dashboard/local/local_settings.py.example @@ -34,6 +34,13 @@ MAILER_EMAIL_BACKEND = EMAIL_BACKEND OPENSTACK_ADMIN_TOKEN = "999888777666" OPENSTACK_KEYSTONE_URL = "http://localhost:5000/v2.0/" +# NOTE(tres): Replace all of this nonsense once Swift+Keystone +# is stable. +# SWIFT_AUTHURL = 'http://localhost:8080/auth/v1.0' +# SWIFT_ACCOUNT = 'test' +# SWIFT_USER = 'tester' +# SWIFT_PASS = 'testing' + # If you have external monitoring links EXTERNAL_MONITORING = [ ['Nagios','http://foo.com'], diff --git a/openstack-dashboard/media/dashboard/css/openstack.css b/openstack-dashboard/media/dashboard/css/openstack.css index 70021e1a5..3abdddaa2 100644 --- a/openstack-dashboard/media/dashboard/css/openstack.css +++ b/openstack-dashboard/media/dashboard/css/openstack.css @@ -1,7 +1,7 @@ @import url("reset.css"); /*================== - === Misc/General + === Misc/General ==================*/ body { @@ -60,7 +60,7 @@ p .ui-icon { /*================== - === Header + === Header ==================*/ #header { @@ -96,7 +96,7 @@ p .ui-icon { /*================== - === Content + === Content ==================*/ #content_wrap { @@ -483,7 +483,7 @@ input.delete, input.detach { } #right_content td.image_location { - text-align: left; + text-align: left; } h3.image_list_heading { @@ -505,7 +505,7 @@ h3.image_list_heading { td.detail_wrapper { padding: 0 !important; text-align: left !important; - + } div.image_detail, div.instance_detail { background: url(/media/dashboard/img/image_detail.png) top left no-repeat; @@ -727,20 +727,19 @@ div.image_detail, div.instance_detail { border-bottom: none; border-top: none; } -#projects .project a.manage_link:hover { +#projects .project a.manage_link:hover { background-color: #f1f1f1 !important; border-color: #b7b7b7; } #projects .project.active a.manage_link { background-color: #5393c9 !important; border-color: #4c83af;} -#projects .project.active a.manage_link:hover { +#projects .project.active a.manage_link:hover { background-color: #80afd6 !important; border-color: #b7b7b7; } - /* Footer */ -#footer { +#footer { clear: both; margin-bottom: 50px; } diff --git a/openstack-dashboard/media/dashboard/css/style.css b/openstack-dashboard/media/dashboard/css/style.css index c25be10a4..4f9be6962 100644 --- a/openstack-dashboard/media/dashboard/css/style.css +++ b/openstack-dashboard/media/dashboard/css/style.css @@ -46,7 +46,7 @@ ul, li { h1 { float: left; - + } h1 a{ @@ -91,7 +91,7 @@ border: 1px solid #cbe0e8; z-index: 500; color: #8eacb7; text-transform: uppercase; - font-size: 12px; + font-size: 12px; position: relative; display: block; float: left; @@ -126,7 +126,7 @@ border: 1px solid #cbe0e8; } #nav li, #nav li a{ - float: left; + float: left; } #nav li a.active { @@ -166,7 +166,7 @@ border: 1px solid #cbe0e8; float: right; height: 400px; margin-bottom: -400px; - + } #login_btn { @@ -213,7 +213,7 @@ border: 1px solid #cbe0e8; #standalone #login { float:left; margin: 0 !important; - -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.2); + -webkit-box-shadow: 0 0 10px rgba(0,0,0,0.2); } .clear { @@ -880,7 +880,7 @@ ol { ol li { display: block; - float: left; + float: left; position: relative; margin-right: -28px; } @@ -1134,23 +1134,23 @@ ol li.first a { margin-bottom: 25px; } -.status_box.info { +.status_box.info { background-color: #e8f8ff; border-color: #9ac7dc; color: #7ab6c5; } -.status_box.success { +.status_box.success { background-color: #e9ffe8; border-color: #9edd9b; color: #7ec67b; } -.status_box.warning { +.status_box.warning { background-color: #ffffe8; border-color: #ffe37b; color: #d1b12d; } -.status_box.error { +.status_box.error { background-color: #ffdbdc; border-color: #ff9a99; color: #ff8080; @@ -1161,7 +1161,7 @@ ol li.first a { float: left; padding: 20px; min-width: 120px; - + } .status_box.info h2 { color: #2a7380; border-color: #9ac7dc; } @@ -1308,18 +1308,18 @@ a.refresh:hover { -.stat_box.good { +.stat_box.good { background-color: #e9ffe8; border-color: #9edd9b; color: #7ec67b; } -.stat_box.medium { +.stat_box.medium { background-color: #ffffe8; border-color: #ffe37b; color: #eada83; } -.stat_box.bad { +.stat_box.bad { background-color: #ffdbdc; border-color: #ff9a99; color: #ff8080; @@ -1376,7 +1376,7 @@ a.refresh:hover { } -.stat_box p.avail span, .stat_box p.used span { +.stat_box p.avail span, .stat_box p.used span { font-size: 11px; text-transform: uppercase; } @@ -1609,8 +1609,16 @@ li.title h4{ margin-left: 25px; } +.container-label { + font-weight: bold; + font-size: 1.22em; + margin-left: 10px; + margin-bottom: 10px; + float: left; +} - - - +.container-name { + float: left; + margin-left: 5px; +} diff --git a/openstack-dashboard/tools/pip-requires b/openstack-dashboard/tools/pip-requires index aa36636b2..8d37d02cb 100644 --- a/openstack-dashboard/tools/pip-requires +++ b/openstack-dashboard/tools/pip-requires @@ -4,6 +4,7 @@ django-nose==0.1.2 django-mailer django-registration==0.7 nova-adminclient +python-cloudfiles bzr+https://launchpad.net/glance#egg=glance -e git://github.com/jacobian/openstack.compute.git#egg=openstack