Merge remote branch '4p/master' into floating-ips-mergeready

Conflicts:
	openstack-dashboard/dashboard/static/dashboard/css/style.css
	openstack-dashboard/dashboard/templates/_dash_sidebar.html
	openstack-dashboard/tools/pip-requires
This commit is contained in:
Anthony Young 2011-09-12 16:15:29 -07:00
commit 3aff55d436
52 changed files with 2264 additions and 172 deletions

View File

@ -200,7 +200,8 @@ class ServerAttributes(APIDictWrapper):
_attrs = ['description', 'disk_gb', 'host', 'image_ref', 'kernel_id',
'key_name', 'launched_at', 'mac_address', 'memory_mb', 'name',
'os_type', 'tenant_id', 'ramdisk_id', 'scheduled_at',
'terminated_at', 'user_data', 'user_id', 'vcpus', 'hostname']
'terminated_at', 'user_data', 'user_id', 'vcpus', 'hostname',
'security_groups']
class Services(APIResourceWrapper):
@ -235,6 +236,22 @@ class User(APIResourceWrapper):
_attrs = ['email', 'enabled', 'id', 'tenantId']
class SecurityGroup(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.security_groups.SecurityGroup"""
_attrs = ['id', 'name', 'description', 'tenant_id', 'rules']
class SecurityGroupRule(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.security_groups.SecurityGroupRule"""
_attrs = ['id', 'parent_group_id', 'group_id', 'ip_protocol',
'from_port', 'to_port', 'groups', 'ip_ranges']
class SecurityGroupRule(APIResourceWrapper):
"""Simple wrapper around openstackx.extras.users.User"""
_attrs = ['id', 'name', 'description', 'tenant_id', 'security_group_rules']
class SwiftAuthentication(object):
"""Auth container to pass CloudFiles storage URL and token from
session.
@ -340,6 +357,16 @@ def extras_api(request):
return openstackx.extras.Extras(auth_token=request.user.token,
management_url=url_for(request, 'nova'))
def novaclient(request):
LOG.debug('novaclient connection created using token "%s"'
' and url "%s"' % (request.user.token, url_for(request, 'nova')))
c = client.Client(username=request.user.username,
api_key=request.user.token,
project_id=request.user.tenant,
auth_url=url_for(request, 'nova'))
c.client.auth_token = request.user.token
c.client.management_url=url_for(request, 'nova')
return c
def novaclient(request):
LOG.debug('novaclient connection created using token "%s"'
@ -477,10 +504,11 @@ def keypair_list(request):
return [KeyPair(key) for key in extras_api(request).keypairs.list()]
def server_create(request, name, image, flavor, key_name, user_data):
def server_create(request, name, image, flavor,
key_name, user_data, security_groups):
return Server(extras_api(request).servers.create(
name, image, flavor, key_name=key_name, user_data=user_data),
request)
name, image, flavor, key_name=key_name, user_data=user_data,
security_groups=security_groups), request)
def server_delete(request, instance):
@ -637,6 +665,39 @@ def user_get(request, user_id):
return User(account_api(request).users.get(user_id))
def security_group_list(request):
return [SecurityGroup(g) for g in novaclient(request).\
security_groups.list()]
def security_group_get(request, security_group_id):
return SecurityGroup(novaclient(request).\
security_groups.get(security_group_id))
def security_group_create(request, name, description):
return SecurityGroup(novaclient(request).\
security_groups.create(name, description))
def security_group_delete(request, security_group_id):
novaclient(request).security_groups.delete(security_group_id)
def security_group_rule_create(request, parent_group_id, ip_protocol=None,
from_port=None, to_port=None, cidr=None,
group_id=None):
return SecurityGroup(novaclient(request).\
security_group_rules.create(parent_group_id,
ip_protocol,
from_port,
to_port,
cidr,
group_id))
def security_group_rule_delete(request, security_group_rule_id):
novaclient(request).security_group_rules.delete(security_group_rule_id)
@check_openstackx
def user_list(request):
return [User(u) for u in account_api(request).users.list()]
@ -846,28 +907,38 @@ class GlobalSummary(object):
for service in self.service_list:
if service.type == 'nova-compute':
self.summary['total_vcpus'] += min(service.stats['max_vcpus'], service.stats.get('vcpus', 0))
self.summary['total_disk_size'] += min(service.stats['max_gigabytes'], service.stats.get('local_gb', 0))
self.summary['total_ram_size'] += min(service.stats['max_ram'], service.stats['memory_mb']) if 'max_ram' in service.stats else service.stats.get('memory_mb', 0)
self.summary['total_vcpus'] += min(service.stats['max_vcpus'],
service.stats.get('vcpus', 0))
self.summary['total_disk_size'] += min(
service.stats['max_gigabytes'],
service.stats.get('local_gb', 0))
self.summary['total_ram_size'] += min(
service.stats['max_ram'],
service.stats['memory_mb']) if 'max_ram' \
in service.stats \
else service.stats.get('memory_mb', 0)
def usage(self, datetime_start, datetime_end):
try:
self.usage_list = usage_list(self.request, datetime_start, datetime_end)
self.usage_list = usage_list(self.request, datetime_start,
datetime_end)
except api_exceptions.ApiException, e:
self.usage_list = []
LOG.error('ApiException fetching usage list in instance usage'
' on date range "%s to %s"' % (datetime_start,
datetime_end),
exc_info=True)
messages.error(self.request, 'Unable to get usage info: %s' % e.message)
messages.error(self.request,
'Unable to get usage info: %s' % e.message)
return
for usage in self.usage_list:
# FIXME: api needs a simpler dict interface (with iteration) - anthony
# NOTE(mgius): Changed this on the api end. Not too much neater, but
# at least its not going into private member data of an external
# class anymore
#usage = usage._info
# FIXME: api needs a simpler dict interface (with iteration)
# - anthony
# NOTE(mgius): Changed this on the api end. Not too much
# neater, but at least its not going into private member
# data of an external class anymore
# usage = usage._info
for k in usage._attrs:
v = usage.__getattr__(k)
if type(v) in [float, int]:
@ -884,8 +955,11 @@ class GlobalSummary(object):
mult = 1.0
for kind in GlobalSummary.node_resource_info:
self.summary['total_' + kind + rsrc + '_hr'] = self.summary['total_' + kind + rsrc] / mult
self.summary['total_' + kind + rsrc + '_hr'] = \
self.summary['total_' + kind + rsrc] / mult
def avail(self):
for rsrc in GlobalSummary.node_resources:
self.summary['total_avail_' + rsrc] = self.summary['total_' + rsrc] - self.summary['total_active_' + rsrc]
self.summary['total_avail_' + rsrc] = \
self.summary['total_' + rsrc] - \
self.summary['total_active_' + rsrc]

View File

@ -25,5 +25,6 @@ from django.conf import settings
urlpatterns = patterns('django_openstack.auth.views',
url(r'login/$', 'login', name='auth_login'),
url(r'logout/$', 'logout', name='auth_logout'),
url(r'switch/(?P<tenant_id>[^/]+)/$', 'switch_tenants', name='auth_switch'),
url(r'switch/(?P<tenant_id>[^/]+)/$', 'switch_tenants',
name='auth_switch'),
)

View File

@ -66,7 +66,7 @@ class Login(forms.SelfHandlingForm):
class LoginWithTenant(Login):
username = forms.CharField(max_length="20",
widget=forms.TextInput(attrs={'readonly':'readonly'}))
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
tenant = forms.CharField(widget=forms.HiddenInput())

View File

@ -20,6 +20,7 @@
from django.conf.urls.defaults import *
SECURITY_GROUPS = r'^(?P<tenant_id>[^/]+)/security_groups/(?P<security_group_id>[^/]+)/%s$'
INSTANCES = r'^(?P<tenant_id>[^/]+)/instances/(?P<instance_id>[^/]+)/%s$'
IMAGES = r'^(?P<tenant_id>[^/]+)/images/(?P<image_id>[^/]+)/%s$'
KEYPAIRS = r'^(?P<tenant_id>[^/]+)/keypairs/%s$'
@ -33,12 +34,19 @@ PORTS = r'^(?P<tenant_id>[^/]+)/networks/(?P<network_id>[^/]+)/ports/%s$'
urlpatterns = patterns('django_openstack.dash.views.instances',
url(r'^(?P<tenant_id>[^/]+)/$', 'usage', name='dash_usage'),
url(r'^(?P<tenant_id>[^/]+)/instances/$', 'index', name='dash_instances'),
url(r'^(?P<tenant_id>[^/]+)/instances/refresh$', 'refresh', name='dash_instances_refresh'),
url(r'^(?P<tenant_id>[^/]+)/instances/refresh$', 'refresh',
name='dash_instances_refresh'),
url(INSTANCES % 'console', 'console', name='dash_instances_console'),
url(INSTANCES % 'vnc', 'vnc', name='dash_instances_vnc'),
url(INSTANCES % 'update', 'update', name='dash_instances_update'),
)
urlpatterns += patterns('django_openstack.dash.views.security_groups',
url(r'^(?P<tenant_id>[^/]+)/security_groups/$', 'index', name='dash_security_groups'),
url(r'^(?P<tenant_id>[^/]+)/security_groups/create$', 'create', name='dash_security_groups_create'),
url(SECURITY_GROUPS % 'edit_rules', 'edit_rules', name='dash_security_groups_edit_rules'),
)
urlpatterns += patterns('django_openstack.dash.views.images',
url(r'^(?P<tenant_id>[^/]+)/images/$', 'index', name='dash_images'),
url(IMAGES % 'launch', 'launch', name='dash_images_launch'),
@ -78,11 +86,14 @@ urlpatterns += patterns('django_openstack.dash.views.objects',
urlpatterns += patterns('django_openstack.dash.views.networks',
url(r'^(?P<tenant_id>[^/]+)/networks/$', 'index', name='dash_networks'),
url(NETWORKS % 'create', 'create', name='dash_network_create'),
url(NETWORKS % '(?P<network_id>[^/]+)/detail', 'detail', name='dash_networks_detail'),
url(NETWORKS % '(?P<network_id>[^/]+)/rename', 'rename', name='dash_network_rename'),
url(NETWORKS % '(?P<network_id>[^/]+)/detail', 'detail',
name='dash_networks_detail'),
url(NETWORKS % '(?P<network_id>[^/]+)/rename', 'rename',
name='dash_network_rename'),
)
urlpatterns += patterns('django_openstack.dash.views.ports',
url(PORTS % 'create', 'create', name='dash_ports_create'),
url(PORTS % '(?P<port_id>[^/]+)/attach', 'attach', name='dash_ports_attach'),
url(PORTS % '(?P<port_id>[^/]+)/attach', 'attach',
name='dash_ports_attach'),
)

View File

@ -39,6 +39,7 @@ from django_openstack import api
from django_openstack import forms
from openstackx.api import exceptions as api_exceptions
from glance.common import exception as glance_exception
from novaclient import exceptions as novaclient_exceptions
LOG = logging.getLogger('django_openstack.dash.views.images')
@ -68,6 +69,14 @@ class LaunchForm(forms.SelfHandlingForm):
required=False,
help_text="Which keypair to use for authentication")
securitygrouplist = kwargs.get('initial', {}).get('securitygrouplist', [])
self.fields['security_groups'] = forms.MultipleChoiceField(choices=securitygrouplist,
label='Security Groups',
required=True,
initial=['default'],
widget=forms.SelectMultiple(attrs={'class': 'chzn-select',
'style': "min-width: 200px"}),
help_text="Launch instance in these Security Groups")
# setting self.fields.keyOrder seems to break validation,
# so ordering fields manually
field_list = (
@ -78,7 +87,6 @@ class LaunchForm(forms.SelfHandlingForm):
for field in field_list[::-1]:
self.fields.insert(0, field, self.fields.pop(field))
def handle(self, request, data):
image_id = data['image_id']
tenant_id = data['tenant_id']
@ -90,7 +98,8 @@ class LaunchForm(forms.SelfHandlingForm):
image,
flavor,
data.get('key_name'),
data.get('user_data'))
data.get('user_data'),
data.get('security_groups'))
msg = 'Instance was successfully launched'
LOG.info(msg)
@ -163,13 +172,22 @@ def launch(request, tenant_id, image_id):
LOG.error('Unable to retrieve list of keypairs', exc_info=True)
return []
def securitygrouplist():
try:
fl = api.security_group_list(request)
sel = [(f.name, f.name) for f in fl]
return sel
except novaclient_exceptions.ClientException, e:
LOG.error('Unable to retrieve list of security groups', exc_info=True)
return []
# TODO(mgius): Any reason why these can't be after the launchform logic?
# If The form is valid, we've just wasted these two api calls
image = api.image_get(request, image_id)
tenant = api.token_get_tenant(request, request.user.tenant)
quotas = api.tenant_quota_get(request, request.user.tenant)
try:
quotas.ram = int(quotas.ram)/100
quotas.ram = int(quotas.ram) / 100
except Exception, e:
messages.error(request, 'Error parsing quota for %s: %s' %
(image_id, e.message))
@ -178,6 +196,7 @@ def launch(request, tenant_id, image_id):
form, handled = LaunchForm.maybe_handle(
request, initial={'flavorlist': flavorlist(),
'keynamelist': keynamelist(),
'securitygrouplist': securitygrouplist(),
'image_id': image_id,
'tenant_id': tenant_id})
if handled:

View File

@ -90,7 +90,7 @@ class RebootInstance(forms.SelfHandlingForm):
class UpdateInstance(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput())
instance = forms.CharField(widget=forms.TextInput(
attrs={'readonly':'readonly'}))
attrs={'readonly': 'readonly'}))
name = forms.CharField(required=True)
description = forms.CharField(required=False)
@ -98,7 +98,10 @@ class UpdateInstance(forms.SelfHandlingForm):
tenant_id = data['tenant_id']
description = data.get('description', '')
try:
api.server_update(request, data['instance'], data['name'], description)
api.server_update(request,
data['instance'],
data['name'],
description)
messages.success(request, "Instance '%s' updated" % data['name'])
except api_exceptions.ApiException, e:
messages.error(request,
@ -131,6 +134,7 @@ def index(request, tenant_id):
'reboot_form': reboot_form,
}, context_instance=template.RequestContext(request))
@login_required
def refresh(request, tenant_id):
instances = []
@ -150,6 +154,7 @@ def refresh(request, tenant_id):
'reboot_form': reboot_form,
}, context_instance=template.RequestContext(request))
@login_required
def usage(request, tenant_id=None):
today = utils.today()
@ -212,7 +217,7 @@ def usage(request, tenant_id=None):
'datetime_start': datetime_start,
'datetime_end': datetime_end,
'instances': instances
}, context_instance = template.RequestContext(request), mimetype=mimetype)
}, context_instance=template.RequestContext(request), mimetype=mimetype)
@login_required
@ -238,7 +243,8 @@ def vnc(request, tenant_id, instance_id):
try:
console = api.console_create(request, instance_id, 'vnc')
instance = api.server_get(request, instance_id)
return shortcuts.redirect(console.output + ("&title=%s(%s)" % (instance.name, instance_id)))
return shortcuts.redirect(console.output +
("&title=%s(%s)" % (instance.name, instance_id)))
except api_exceptions.ApiException, e:
LOG.error('ApiException while fetching instance vnc connection',
exc_info=True)

View File

@ -0,0 +1,200 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2011 Fourth Paradigm Development, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
Views for managing Nova instances.
"""
import logging
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.core import validators
from django import shortcuts
from django.shortcuts import redirect, render_to_response
from django.utils.translation import ugettext as _
from django_openstack import api
from django_openstack import forms
from novaclient import exceptions as novaclient_exceptions
LOG = logging.getLogger('django_openstack.dash.views.security_groups')
class CreateGroup(forms.SelfHandlingForm):
name = forms.CharField(validators=[validators.validate_slug])
description = forms.CharField()
tenant_id = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
LOG.info('Add security_group: "%s"' % data)
security_group = api.security_group_create(request,
data['name'],
data['description'])
messages.info(request, 'Successfully created security_group: %s' \
% data['name'])
return shortcuts.redirect('dash_security_groups',
data['tenant_id'])
except novaclient_exceptions.ClientException, e:
LOG.error("ClientException in CreateGroup", exc_info=True)
messages.error(request, 'Error creating security group: %s' %
e.message)
class DeleteGroup(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput())
security_group_id = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
try:
LOG.info('Delete security_group: "%s"' % data)
security_group = api.security_group_delete(request,
data['security_group_id'])
messages.info(request, 'Successfully deleted security_group: %s' \
% data['security_group_id'])
except novaclient_exceptions.ClientException, e:
LOG.error("ClientException in DeleteGroup", exc_info=True)
messages.error(request, 'Error deleting security group: %s'
% e.message)
return shortcuts.redirect('dash_security_groups', data['tenant_id'])
class AddRule(forms.SelfHandlingForm):
ip_protocol = forms.ChoiceField(choices=[('tcp', 'tcp'),
('udp', 'udp'),
('icmp', 'icmp')])
from_port = forms.CharField()
to_port = forms.CharField()
cidr = forms.CharField()
# TODO (anthony) source group support
# group_id = forms.CharField()
security_group_id = forms.CharField(widget=forms.HiddenInput())
tenant_id = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
tenant_id = data['tenant_id']
try:
LOG.info('Add security_group_rule: "%s"' % data)
rule = api.security_group_rule_create(request,
data['security_group_id'],
data['ip_protocol'],
data['from_port'],
data['to_port'],
data['cidr'])
messages.info(request, 'Successfully added rule: %s' \
% rule.id)
except novaclient_exceptions.ClientException, e:
LOG.error("ClientException in AddRule", exc_info=True)
messages.error(request, 'Error adding rule security group: %s'
% e.message)
return shortcuts.redirect(request.build_absolute_uri())
class DeleteRule(forms.SelfHandlingForm):
security_group_rule_id = forms.CharField(widget=forms.HiddenInput())
tenant_id = forms.CharField(widget=forms.HiddenInput())
def handle(self, request, data):
security_group_rule_id = data['security_group_rule_id']
tenant_id = data['tenant_id']
try:
LOG.info('Delete security_group_rule: "%s"' % data)
security_group = api.security_group_rule_delete(
request,
security_group_rule_id)
messages.info(request, 'Successfully deleted rule: %s' \
% security_group_rule_id)
except novaclient_exceptions.ClientException, e:
LOG.error("ClientException in DeleteRule", exc_info=True)
messages.error(request, 'Error authorizing security group: %s'
% e.message)
return shortcuts.redirect(request.build_absolute_uri())
@login_required
def index(request, tenant_id):
delete_form, handled = DeleteGroup.maybe_handle(request,
initial={'tenant_id': tenant_id})
if handled:
return handled
try:
security_groups = api.security_group_list(request)
except novaclient_exceptions.ClientException, e:
security_groups = []
LOG.error("ClientException in security_groups index", exc_info=True)
messages.error(request, 'Error fetching security_groups: %s'
% e.message)
return shortcuts.render_to_response('dash_security_groups.html', {
'security_groups': security_groups,
'delete_form': delete_form,
}, context_instance=template.RequestContext(request))
@login_required
def edit_rules(request, tenant_id, security_group_id):
add_form, handled = AddRule.maybe_handle(request,
initial={'tenant_id': tenant_id,
'security_group_id': security_group_id})
if handled:
return handled
delete_form, handled = DeleteRule.maybe_handle(request,
initial={'tenant_id': tenant_id,
'security_group_id': security_group_id})
if handled:
return handled
try:
security_group = api.security_group_get(request, security_group_id)
except novaclient_exceptions.ClientException, e:
LOG.error("ClientException in security_groups rules edit", exc_info=True)
messages.error(request, 'Error getting security_group: %s' % e.message)
return shortcuts.redirect('dash_security_groups', tenant_id)
return shortcuts.render_to_response(
'dash_security_groups_edit_rules.html', {
'security_group': security_group,
'delete_form': delete_form,
'form': add_form,
}, context_instance=template.RequestContext(request))
@login_required
def create(request, tenant_id):
form, handled = CreateGroup.maybe_handle(request,
initial={'tenant_id': tenant_id})
if handled:
return handled
return shortcuts.render_to_response('dash_security_groups_create.html', {
'form': form,
}, context_instance=template.RequestContext(request))

View File

@ -46,13 +46,16 @@ LOG = logging.getLogger('django_openstack.dash.views.snapshots')
class CreateSnapshot(forms.SelfHandlingForm):
tenant_id = forms.CharField(widget=forms.HiddenInput())
instance_id = forms.CharField(widget=forms.TextInput(attrs={'readonly':'readonly'}))
instance_id = forms.CharField(widget=forms.TextInput(
attrs={'readonly': 'readonly'}))
name = forms.CharField(max_length="20", label="Snapshot Name")
def handle(self, request, data):
try:
LOG.info('Creating snapshot "%s"' % data['name'])
snapshot = api.snapshot_create(request, data['instance_id'], data['name'])
snapshot = api.snapshot_create(request,
data['instance_id'],
data['name'])
instance = api.server_get(request, data['instance_id'])
messages.info(request, 'Snapshot "%s" created for instance "%s"' %\

View File

@ -18,7 +18,7 @@
# License for the specific language governing permissions and limitations
# under the License.
"""
"""
Simple decorator container for general purpose
"""
@ -30,13 +30,13 @@ LOG = logging.getLogger('django_openstack.syspanel')
def enforce_admin_access(fn):
""" Preserve unauthorized bypass typing directly the URL and redirects to
""" Preserve unauthorized bypass typing directly the URL and redirects to
the overview dash page """
def dec(*args,**kwargs):
def dec(*args, **kwargs):
if args[0].user.is_admin():
return fn(*args,**kwargs)
return fn(*args, **kwargs)
else:
LOG.warn('Redirecting user "%s" from syspanel to dash ( %s )' %
( args[0].user.username, fn.__name__) , exc_info=True)
(args[0].user.username, fn.__name__), exc_info=True)
return redirect('dash_overview')
return dec

View File

@ -48,8 +48,10 @@ class SelectDateWidget(widgets.Widget):
day_field = '%s_day'
year_field = '%s_year'
def __init__(self, attrs=None, years=None, required=True, skip_day_field=False):
# years is an optional list/tuple of years to use in the "year" select box.
def __init__(self, attrs=None, years=None, required=True,
skip_day_field=False):
# years is an optional list/tuple of years to use in
# the "year" select box.
self.attrs = attrs or {}
self.required = required
self.skip_day_field = skip_day_field
@ -57,7 +59,7 @@ class SelectDateWidget(widgets.Widget):
self.years = years
else:
this_year = datetime.date.today().year
self.years = range(this_year, this_year+10)
self.years = range(this_year, this_year + 10)
def render(self, name, value, attrs=None, skip_day_field=True):
print "Render %s %s" % (name, value)
@ -68,25 +70,32 @@ class SelectDateWidget(widgets.Widget):
if isinstance(value, basestring):
if settings.USE_L10N:
try:
input_format = formats.get_format('DATE_INPUT_FORMATS')[0]
input_format = formats.get_format(
'DATE_INPUT_FORMATS')[0]
# Python 2.4 compatibility:
# v = datetime.datetime.strptime(value, input_format)
# v = datetime.datetime.strptime(value,
# input_format)
# would be clearer, but datetime.strptime was added in
# Python 2.5
v = datetime.datetime(*(time.strptime(value, input_format)[0:6]))
v = datetime.datetime(*(time.strptime(value,
input_format)[0:6]))
year_val, month_val, day_val = v.year, v.month, v.day
except ValueError:
pass
else:
match = RE_DATE.match(value)
if match:
year_val, month_val, day_val = [int(v) for v in match.groups()]
year_val, month_val, day_val = \
[int(v) for v in match.groups()]
choices = [(i, i) for i in self.years]
year_html = self.create_select(name, self.year_field, value, year_val, choices)
year_html = self.create_select(name,
self.year_field, value, year_val, choices)
choices = dates.MONTHS.items()
month_html = self.create_select(name, self.month_field, value, month_val, choices)
month_html = self.create_select(name,
self.month_field, value, month_val, choices)
choices = [(i, i) for i in range(1, 32)]
day_html = self.create_select(name, self.day_field, value, day_val, choices)
day_html = self.create_select(name,
self.day_field, value, day_val, choices)
format = formats.get_format('DATE_FORMAT')
escaped = False
@ -174,4 +183,6 @@ class SelfHandlingForm(Form):
class DateForm(Form):
date = DateField(widget=SelectDateWidget(years=range(datetime.date.today().year, 2009, -1), skip_day_field=True))
date = DateField(widget=SelectDateWidget(
years=range(datetime.date.today().year, 2009, -1),
skip_day_field=True))

View File

@ -42,7 +42,7 @@ class User(object):
def get_user_from_request(request):
if 'user' not in request.session:
return User(None,None,None,None,None)
return User(None, None, None, None, None)
return User(request.session['token'],
request.session['user'],
request.session['tenant'],

View File

@ -9,14 +9,14 @@ def dash_modules_detect():
"""
Sends a pinging signal to the app, all listening modules will reply with
items for the sidebar.
The response is a tuple of the Signal object instance, and a dictionary.
The values within the dictionary containing links and a title which should
be added to the sidebar navigation.
Example: (<dash_apps_ping>,
{'title': 'Nixon',
'links': [{'url':'/syspanel/nixon/google',
{'title': 'Nixon',
'links': [{'url':'/syspanel/nixon/google',
'text':'Google', 'active_text': 'google'}],
'type': syspanel})
"""
@ -28,5 +28,3 @@ def dash_app_setup_urls():
Adds urls from modules
"""
return dash_modules_urls.send(sender=dash_modules_urls)

View File

@ -75,6 +75,7 @@ class DeleteFlavor(forms.SelfHandlingForm):
e.message)
return redirect(request.build_absolute_uri())
@login_required
@enforce_admin_access
def index(request):
@ -93,10 +94,10 @@ def index(request):
messages.error(request, 'Unable to get usage info: %s' % e.message)
flavors.sort(key=lambda x: x.id, reverse=True)
return render_to_response('syspanel_flavors.html',{
return render_to_response('syspanel_flavors.html', {
'delete_form': delete_form,
'flavors': flavors,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -112,7 +113,7 @@ def create(request):
global_summary.human_readable('disk_size')
global_summary.human_readable('ram_size')
return render_to_response('syspanel_create_flavor.html',{
return render_to_response('syspanel_create_flavor.html', {
'global_summary': global_summary.summary,
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))

View File

@ -59,7 +59,8 @@ class ToggleImage(forms.SelfHandlingForm):
def handle(self, request, data):
image_id = data['image_id']
try:
api.image_update(request, image_id, image_meta={'is_public': False})
api.image_update(request, image_id,
image_meta={'is_public': False})
except glance_exception.ClientConnectionError, e:
LOG.error("Error connecting to glance", exc_info=True)
messages.error(request,
@ -70,16 +71,21 @@ class ToggleImage(forms.SelfHandlingForm):
messages.error(request, "Error updating image: %s" % e.message)
return redirect(request.build_absolute_uri())
class UpdateImageForm(forms.Form):
name = forms.CharField(max_length="25", label="Name")
kernel = forms.CharField(max_length="25", label="Kernel ID", required=False)
ramdisk = forms.CharField(max_length="25", label="Ramdisk ID", required=False)
kernel = forms.CharField(max_length="25", label="Kernel ID",
required=False)
ramdisk = forms.CharField(max_length="25", label="Ramdisk ID",
required=False)
architecture = forms.CharField(label="Architecture", required=False)
#project_id = forms.CharField(label="Project ID")
container_format = forms.CharField(label="Container Format", required=False)
container_format = forms.CharField(label="Container Format",
required=False)
disk_format = forms.CharField(label="Disk Format")
#is_public = forms.BooleanField(label="Publicly Available", required=False)
@login_required
@enforce_admin_access
def index(request):
@ -109,7 +115,7 @@ def index(request):
'delete_form': delete_form,
'toggle_form': toggle_form,
'images': images,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -142,9 +148,11 @@ def update(request, image_id):
if image_form['kernel']:
metadata['properties']['kernel_id'] = image_form['kernel']
if image_form['ramdisk']:
metadata['properties']['ramdisk_id'] = image_form['ramdisk']
metadata['properties']['ramdisk_id'] = \
image_form['ramdisk']
if image_form['architecture']:
metadata['properties']['architecture'] = image_form['architecture']
metadata['properties']['architecture'] = \
image_form['architecture']
api.image_update(request, image_id, metadata)
messages.success(request, "Image was successfully updated.")
except glance_exception.ClientConnectionError, e:
@ -167,10 +175,10 @@ def update(request, image_id):
messages.error(request,
"Image could not be uploaded, please try agian.")
form = UpdateImageForm(request.POST)
return render_to_response('syspanel_image_update.html',{
return render_to_response('syspanel_image_update.html', {
'image': image,
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
else:
form = UpdateImageForm(initial={
'name': image.get('name', ''),
@ -185,10 +193,10 @@ def update(request, image_id):
'disk_format': image.get('disk_format', ''),
})
return render_to_response('syspanel_image_update.html',{
return render_to_response('syspanel_image_update.html', {
'image': image,
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -206,7 +214,8 @@ def upload(request):
messages.success(request, "Image was successfully uploaded.")
except:
# TODO add better error management
messages.error(request, "Image could not be uploaded, please try again.")
messages.error(request, "Image could not be uploaded, "
"please try again.")
try:
api.image_create(request, metadata, image['image_file'])
@ -225,13 +234,15 @@ def upload(request):
messages.error(request,
"Image could not be uploaded, please try agian.")
form = UploadImageForm(request.POST)
return render_to_response('django_nova_syspanel/images/image_upload.html',{
return render_to_response('django_nova_syspanel/images/'
'image_upload.html', {
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
return redirect('syspanel_images')
else:
form = UploadImageForm()
return render_to_response('django_nova_syspanel/images/image_upload.html',{
return render_to_response('django_nova_syspanel/images/'
'image_upload.html', {
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))

View File

@ -45,8 +45,8 @@ LOG = logging.getLogger('django_openstack.syspanel.views.instances')
def _next_month(date_start):
y = date_start.year + (date_start.month + 1)/13
m = ((date_start.month + 1)%13)
y = date_start.year + (date_start.month + 1) / 13
m = ((date_start.month + 1) % 13)
if m == 0:
m = 1
return datetime.date(y, m, 1)
@ -54,15 +54,18 @@ def _next_month(date_start):
def _current_month():
today = datetime.date.today()
return datetime.date(today.year, today.month,1)
return datetime.date(today.year, today.month, 1)
def _get_start_and_end_date(request):
try:
date_start = datetime.date(int(request.GET['date_year']), int(request.GET['date_month']), 1)
date_start = datetime.date(
int(request.GET['date_year']),
int(request.GET['date_month']),
1)
except:
today = datetime.date.today()
date_start = datetime.date(today.year, today.month,1)
date_start = datetime.date(today.year, today.month, 1)
date_end = _next_month(date_start)
datetime_start = datetime.datetime.combine(date_start, datetime.time())
@ -74,13 +77,15 @@ def _get_start_and_end_date(request):
def _csv_usage_link(date_start):
return "?date_month=%s&date_year=%s&format=csv" % (date_start.month, date_start.year)
return "?date_month=%s&date_year=%s&format=csv" % (date_start.month,
date_start.year)
@login_required
@enforce_admin_access
def usage(request):
(date_start, date_end, datetime_start, datetime_end) = _get_start_and_end_date(request)
(date_start, date_end, datetime_start, datetime_end) = \
_get_start_and_end_date(request)
global_summary = api.GlobalSummary(request)
if date_start > _current_month():
@ -104,7 +109,7 @@ def usage(request):
else:
template_name = 'syspanel_usage.html'
mimetype = "text/html"
return render_to_response(
template_name, {
'dateform': dateform,
@ -114,13 +119,14 @@ def usage(request):
'csv_link': _csv_usage_link(date_start),
'global_summary': global_summary.summary,
'external_links': settings.EXTERNAL_MONITORING,
}, context_instance = template.RequestContext(request), mimetype=mimetype)
}, context_instance=template.RequestContext(request), mimetype=mimetype)
@login_required
@enforce_admin_access
def tenant_usage(request, tenant_id):
(date_start, date_end, datetime_start, datetime_end) = _get_start_and_end_date(request)
(date_start, date_end, datetime_start, datetime_end) = \
_get_start_and_end_date(request)
if date_start > _current_month():
messages.error(request, 'No data for the selected period')
date_end = date_start
@ -167,7 +173,7 @@ def tenant_usage(request, tenant_id):
'csv_link': _csv_usage_link(date_start),
'instances': running_instances + terminated_instances,
'tenant_id': tenant_id,
}, context_instance = template.RequestContext(request), mimetype=mimetype)
}, context_instance=template.RequestContext(request), mimetype=mimetype)
@login_required
@ -196,6 +202,7 @@ def index(request):
'reboot_form': reboot_form,
}, context_instance=template.RequestContext(request))
@login_required
@enforce_admin_access
def refresh(request):

View File

@ -16,6 +16,7 @@ from django_openstack import api
from django_openstack import forms
from django_openstack.decorators import enforce_admin_access
@login_required
@enforce_admin_access
def index(request):
@ -23,7 +24,6 @@ def index(request):
quotas['ram'] = int(quotas['ram']) / 100
quotas.pop('id')
return render_to_response('syspanel_quotas.html',{
return render_to_response('syspanel_quotas.html', {
'quotas': quotas,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))

View File

@ -100,7 +100,7 @@ def index(request):
up = False
hostname = urlparse.urlparse(v['internalURL']).hostname
row = {'type': k, 'internalURL': v['internalURL'], 'host': hostname,
'region': v['region'], 'up': up }
'region': v['region'], 'up': up}
other_services.append(row)
services = sorted(services, key=lambda svc: (svc.type +
@ -112,4 +112,4 @@ def index(request):
'services': services,
'service_toggle_enabled_form': ToggleService,
'other_services': other_services,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))

View File

@ -48,8 +48,10 @@ class AddUser(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.account_api(request).role_refs.add_for_tenant_user(data['tenant'],
data['user'], settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE)
api.account_api(request).role_refs.add_for_tenant_user(
data['tenant'],
data['user'],
settings.OPENSTACK_KEYSTONE_DEFAULT_ROLE)
messages.success(request,
'%s was successfully added to %s.'
% (data['user'], data['tenant']))
@ -65,8 +67,10 @@ class RemoveUser(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.account_api(request).role_refs.delete_for_tenant_user(data['tenant'],
data['user'], 'Member')
api.account_api(request).role_refs.delete_for_tenant_user(
data['tenant'],
data['user'],
'Member')
messages.success(request,
'%s was successfully removed from %s.'
% (data['user'], data['tenant']))
@ -78,8 +82,10 @@ class RemoveUser(forms.SelfHandlingForm):
class CreateTenant(forms.SelfHandlingForm):
id = forms.CharField(label="ID (name)")
description = forms.CharField(widget=forms.widgets.Textarea(), label="Description")
enabled = forms.BooleanField(label="Enabled", required=False, initial=True)
description = forms.CharField(widget=forms.widgets.Textarea(),
label="Description")
enabled = forms.BooleanField(label="Enabled", required=False,
initial=True)
def handle(self, request, data):
try:
@ -102,8 +108,10 @@ class CreateTenant(forms.SelfHandlingForm):
class UpdateTenant(forms.SelfHandlingForm):
id = forms.CharField(label="ID (name)", widget=forms.TextInput(attrs={'readonly':'readonly'}))
description = forms.CharField(widget=forms.widgets.Textarea(), label="Description")
id = forms.CharField(label="ID (name)",
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
description = forms.CharField(widget=forms.widgets.Textarea(),
label="Description")
enabled = forms.BooleanField(label="Enabled", required=False)
def handle(self, request, data):
@ -126,10 +134,12 @@ class UpdateTenant(forms.SelfHandlingForm):
class UpdateQuotas(forms.SelfHandlingForm):
tenant_id = forms.CharField(label="ID (name)", widget=forms.TextInput(attrs={'readonly':'readonly'}))
tenant_id = forms.CharField(label="ID (name)",
widget=forms.TextInput(attrs={'readonly': 'readonly'}))
metadata_items = forms.CharField(label="Metadata Items")
injected_files = forms.CharField(label="Injected Files")
injected_file_content_bytes = forms.CharField(label="Injected File Content Bytes")
injected_file_content_bytes = forms.CharField(label="Injected File "
"Content Bytes")
cores = forms.CharField(label="VCPUs")
instances = forms.CharField(label="Instances")
volumes = forms.CharField(label="Volumes")
@ -140,16 +150,15 @@ class UpdateQuotas(forms.SelfHandlingForm):
def handle(self, request, data):
try:
api.admin_api(request).quota_sets.update(data['tenant_id'],
metadata_items=data['metadata_items'],
injected_file_content_bytes=
data['injected_file_content_bytes'],
volumes=data['volumes'],
gigabytes=data['gigabytes'],
ram=int(data['ram']) * 100,
floating_ips=data['floating_ips'],
instances=data['instances'],
injected_files=data['injected_files'],
cores=data['cores'],
metadata_items=data['metadata_items'],
injected_file_content_bytes=data['injected_file_content_bytes'],
volumes=data['volumes'],
gigabytes=data['gigabytes'],
ram=int(data['ram']) * 100,
floating_ips=data['floating_ips'],
instances=data['instances'],
injected_files=data['injected_files'],
cores=data['cores'],
)
messages.success(request,
'Quotas for %s were successfully updated.'
@ -169,9 +178,9 @@ def index(request):
LOG.error('ApiException while getting tenant list', exc_info=True)
messages.error(request, 'Unable to get tenant info: %s' % e.message)
tenants.sort(key=lambda x: x.id, reverse=True)
return render_to_response('syspanel_tenants.html',{
return render_to_response('syspanel_tenants.html', {
'tenants': tenants,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -182,9 +191,9 @@ def create(request):
return handled
return render_to_response(
'syspanel_tenant_create.html',{
'syspanel_tenant_create.html', {
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -207,9 +216,9 @@ def update(request, tenant_id):
return redirect('syspanel_tenants')
return render_to_response(
'syspanel_tenant_update.html',{
'syspanel_tenant_update.html', {
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -237,13 +246,13 @@ def users(request, tenant_id):
if i in new_user_ids:
new_user_ids.remove(i)
return render_to_response(
'syspanel_tenant_users.html',{
'syspanel_tenant_users.html', {
'add_user_form': add_user_form,
'remove_user_form': remove_user_form,
'tenant_id': tenant_id,
'users': users,
'new_users': new_user_ids,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -270,8 +279,8 @@ def quotas(request, tenant_id):
form = UpdateQuotas(initial=quota_set)
return render_to_response(
'syspanel_tenant_quotas.html',{
'syspanel_tenant_quotas.html', {
'form': form,
'tenant_id': tenant_id,
'quotas': quotas,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))

View File

@ -47,11 +47,14 @@ class UserForm(forms.Form):
def __init__(self, *args, **kwargs):
tenant_list = kwargs.pop('tenant_list', None)
super(UserForm, self).__init__(*args, **kwargs)
self.fields['tenant_id'].choices = [[tenant.id,tenant.id] for tenant in tenant_list]
self.fields['tenant_id'].choices = [[tenant.id, tenant.id]
for tenant in tenant_list]
id = forms.CharField(label="ID (username)")
email = forms.CharField(label="Email")
password = forms.CharField(label="Password", widget=forms.PasswordInput(render_value=False), required=False)
password = forms.CharField(label="Password",
widget=forms.PasswordInput(render_value=False),
required=False)
tenant_id = forms.ChoiceField(label="Primary Tenant")
@ -64,7 +67,6 @@ class UserDeleteForm(forms.SelfHandlingForm):
api.user_delete(request, user_id)
messages.info(request, '%s was successfully deleted.'
% user_id)
return redirect(request.build_absolute_uri())
@ -108,7 +110,7 @@ def index(request):
user_delete_form = UserDeleteForm()
user_enable_disable_form = UserEnableDisableForm()
return shortcuts.render_to_response('syspanel_users.html', {
'users': users,
'user_delete_form': user_delete_form,
@ -144,10 +146,10 @@ def update(request, user_id):
please try again.')
return render_to_response(
'syspanel_user_update.html',{
'syspanel_user_update.html', {
'form': form,
'user_id': user_id,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
else:
u = api.user_get(request, user_id)
@ -167,10 +169,10 @@ def update(request, user_id):
'email': email},
tenant_list=tenants)
return render_to_response(
'syspanel_user_update.html',{
'syspanel_user_update.html', {
'form': form,
'user_id': user_id,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
@login_required
@ -216,13 +218,13 @@ def create(request):
return redirect('syspanel_users')
else:
return render_to_response(
'syspanel_user_create.html',{
'syspanel_user_create.html', {
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))
else:
form = UserForm(tenant_list=tenants)
return render_to_response(
'syspanel_user_create.html',{
'syspanel_user_create.html', {
'form': form,
}, context_instance = template.RequestContext(request))
}, context_instance=template.RequestContext(request))

View File

@ -38,6 +38,7 @@ class SiteBrandingNode(template.Node):
def site_branding(parser, token):
return SiteBrandingNode()
@register.tag
def site_title(parser, token):
return settings.SITE_BRANDING

View File

@ -26,8 +26,10 @@ import datetime
from django import template
from dateutil import tz
register = template.Library()
def _parse_datetime(dtstr):
fmts = ["%Y-%m-%dT%H:%M:%S.%f", "%Y-%m-%d %H:%M:%S.%f",
"%Y-%m-%dT%H:%M:%S", "%Y-%m-%d %H:%M:%S"]

View File

@ -10,16 +10,17 @@ def dash_sidebar_modules(request):
if signals_call:
if signals_call[0][1]['type'] == "dash":
return {'modules': [module[1] for module in signals_call],
'request': request }
'request': request}
else:
return {}
@register.inclusion_tag('_sidebar_module.html')
def syspanel_sidebar_modules(request):
signals_call = signals.dash_modules_detect()
if signals_call:
if signals_call[0][1]['type'] == "syspanel":
return {'modules': [module[1] for module in signals_call],
'request': request }
'request': request}
else:
return {}

View File

@ -10,6 +10,7 @@ from django.utils import formats
register = template.Library()
def int_format(value):
return int(value)
@ -21,20 +22,27 @@ def float_format(value):
def filesizeformat(bytes, filesize_number_format):
try:
bytes = float(bytes)
except (TypeError,ValueError,UnicodeDecodeError):
return translation.ungettext("%(size)d byte", "%(size)d bytes", 0) % {'size': 0}
except (TypeError, ValueError, UnicodeDecodeError):
return translation.ungettext("%(size)d byte",
"%(size)d bytes", 0) % {'size': 0}
if bytes < 1024:
return translation.ungettext("%(size)d", "%(size)d", bytes) % {'size': bytes}
return translation.ungettext("%(size)d",
"%(size)d", bytes) % {'size': bytes}
if bytes < 1024 * 1024:
return translation.ugettext("%s KB") % filesize_number_format(bytes / 1024)
return translation.ugettext("%s KB") % \
filesize_number_format(bytes / 1024)
if bytes < 1024 * 1024 * 1024:
return translation.ugettext("%s MB") % filesize_number_format(bytes / (1024 * 1024))
return translation.ugettext("%s MB") % \
filesize_number_format(bytes / (1024 * 1024))
if bytes < 1024 * 1024 * 1024 * 1024:
return translation.ugettext("%s GB") % filesize_number_format(bytes / (1024 * 1024 * 1024))
return translation.ugettext("%s GB") % \
filesize_number_format(bytes / (1024 * 1024 * 1024))
if bytes < 1024 * 1024 * 1024 * 1024 * 1024:
return translation.ugettext("%s TB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024))
return translation.ugettext("%s PB") % filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024 * 1024))
return translation.ugettext("%s TB") % \
filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024))
return translation.ugettext("%s PB") % \
filesize_number_format(bytes / (1024 * 1024 * 1024 * 1024 * 1024))
@register.filter(name='mbformat')
@ -44,4 +52,5 @@ def mbformat(mb):
@register.filter(name='diskgbformat')
def diskgbformat(gb):
return filesizeformat(gb * 1024 * 1024 * 1024, float_format).replace(' ', '')
return filesizeformat(gb * 1024 * 1024 * 1024,
float_format).replace(' ', '')

View File

@ -58,7 +58,7 @@ class Server(object):
""" More or less fakes what the api is looking for """
def __init__(self, id, image, attrs=None):
self.id = id
self.image = image
if attrs is not None:
self.attrs = attrs
@ -230,7 +230,7 @@ class ServerWrapperTests(test.TestCase):
HOST = 'hostname'
ID = '1'
IMAGE_NAME = 'imageName'
IMAGE_OBJ = { 'id': '3', 'links': [{'href': '3', u'rel': u'bookmark'}] }
IMAGE_OBJ = {'id': '3', 'links': [{'href': '3', u'rel': u'bookmark'}]}
def setUp(self):
super(ServerWrapperTests, self).setUp()
@ -1051,16 +1051,19 @@ class ExtrasApiTests(test.TestCase):
FLAVOR = 'cherry'
USER_DATA = {'nuts': 'berries'}
KEY = 'user'
SECGROUP = self.mox.CreateMock(api.SecurityGroup)
extras_api = self.stub_extras_api()
extras_api.servers = self.mox.CreateMockAnything()
extras_api.servers.create(NAME, IMAGE, FLAVOR, user_data=USER_DATA,
key_name=KEY).AndReturn(TEST_RETURN)
key_name=KEY,
security_groups=[SECGROUP])\
.AndReturn(TEST_RETURN)
self.mox.ReplayAll()
ret_val = api.server_create(self.request, NAME, IMAGE, FLAVOR,
KEY, USER_DATA)
KEY, USER_DATA, [SECGROUP])
self.assertIsInstance(ret_val, api.Server)
self.assertEqual(ret_val._apiresource, TEST_RETURN)

View File

@ -63,7 +63,7 @@ OPENSTACK_ADMIN_TOKEN = 'test'
QUANTUM_URL = '127.0.0.1'
QUANTUM_PORT = '9696'
QUANTUM_TENANT = '1234'
QUANTUM_CLIENT_VERSION='0.1'
QUANTUM_CLIENT_VERSION = '0.1'
CREDENTIAL_AUTHORIZATION_DAYS = 2
CREDENTIAL_DOWNLOAD_URL = TESTSERVER + '/credentials/'

View File

@ -29,8 +29,10 @@ from django_openstack import urls as django_openstack_urls
urlpatterns = patterns('',
url(r'^$', 'django_openstack.tests.views.fakeView', name='splash'),
url(r'^dash/$', 'django_openstack.dash.views.instances.usage', name='dash_overview'),
url(r'^syspanel/$', 'django_openstack.syspanel.views.instances.usage', name='syspanel_overview')
url(r'^dash/$', 'django_openstack.dash.views.instances.usage',
name='dash_overview'),
url(r'^syspanel/$', 'django_openstack.syspanel.views.instances.usage',
name='syspanel_overview')
)

View File

@ -38,6 +38,10 @@ class ImageViewTests(base.BaseViewTests):
keypair.key_name = 'keyName'
self.keypairs = (keypair,)
security_group = self.mox.CreateMock(api.SecurityGroup)
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(self):
self.mox.StubOutWithMock(api, 'token_get_tenant')
api.token_get_tenant(IsA(http.HttpRequest), self.TEST_TENANT)
@ -135,6 +139,10 @@ class ImageViewTests(base.BaseViewTests):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndReturn(
self.security_groups)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images_launch',
@ -172,6 +180,7 @@ class ImageViewTests(base.BaseViewTests):
'name': SERVER_NAME,
'user_data': USER_DATA,
'tenant_id': self.TEST_TENANT,
'security_groups': 'default',
}
self.mox.StubOutWithMock(api, 'image_get')
@ -192,6 +201,10 @@ class ImageViewTests(base.BaseViewTests):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndReturn(
self.security_groups)
# called again by the form
api.image_get(IsA(http.HttpRequest),
IMAGE_ID).AndReturn(self.visibleImage)
@ -204,7 +217,7 @@ class ImageViewTests(base.BaseViewTests):
api.server_create(IsA(http.HttpRequest), SERVER_NAME,
self.visibleImage, self.flavors[0],
KEY_NAME, USER_DATA)
KEY_NAME, USER_DATA, [self.security_groups[0].name])
self.mox.StubOutWithMock(messages, 'success')
messages.success(IsA(http.HttpRequest), IsA(str))
@ -242,6 +255,10 @@ class ImageViewTests(base.BaseViewTests):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndReturn(
self.security_groups)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images_launch',
@ -278,6 +295,10 @@ class ImageViewTests(base.BaseViewTests):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndReturn(
self.security_groups)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_images_launch',
@ -306,6 +327,7 @@ class ImageViewTests(base.BaseViewTests):
'name': SERVER_NAME,
'tenant_id': self.TEST_TENANT,
'user_data': USER_DATA,
'security_groups': 'default',
}
self.mox.StubOutWithMock(api, 'image_get')
@ -326,6 +348,10 @@ class ImageViewTests(base.BaseViewTests):
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IgnoreArg()).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndReturn(
self.security_groups)
# called again by the form
api.image_get(IgnoreArg(),
IMAGE_ID).AndReturn(self.visibleImage)
@ -339,8 +365,8 @@ class ImageViewTests(base.BaseViewTests):
exception = api_exceptions.ApiException('apiException')
api.server_create(IsA(http.HttpRequest), SERVER_NAME,
self.visibleImage, self.flavors[0],
KEY_NAME,
USER_DATA).AndRaise(exception)
KEY_NAME, USER_DATA,
self.security_groups).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))

View File

@ -321,7 +321,8 @@ class InstanceViewTests(base.BaseViewTests):
res = self.client.get(reverse('dash_instances_vnc',
args=[self.TEST_TENANT, INSTANCE_ID]))
self.assertRedirectsNoFollow(res, CONSOLE_OUTPUT + '&title=serverName(1)')
self.assertRedirectsNoFollow(res,
CONSOLE_OUTPUT + '&title=serverName(1)')
self.mox.VerifyAll()

View File

@ -7,6 +7,7 @@ from mox import IsA
import openstackx.api.exceptions as api_exceptions
class KeyPairViewTests(base.BaseViewTests):
def setUp(self):
super(KeyPairViewTests, self).setUp()
@ -20,7 +21,8 @@ class KeyPairViewTests(base.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('dash_keypairs', args=[self.TEST_TENANT]))
res = self.client.get(reverse('dash_keypairs',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_keypairs.html')
self.assertItemsEqual(res.context['keypairs'], self.keypairs)
@ -38,7 +40,8 @@ class KeyPairViewTests(base.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('dash_keypairs', args=[self.TEST_TENANT]))
res = self.client.get(reverse('dash_keypairs',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_keypairs.html')
self.assertEqual(len(res.context['keypairs']), 0)
@ -91,7 +94,7 @@ class KeyPairViewTests(base.BaseViewTests):
def test_create_keypair_get(self):
res = self.client.get(reverse('dash_keypairs_create',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_keypairs_create.html')
def test_create_keypair_post(self):

View File

@ -6,6 +6,7 @@ from django_openstack import api
from django_openstack.tests.view_tests import base
from mox import IsA
class ObjectViewTests(base.BaseViewTests):
CONTAINER_NAME = 'containerName'

View File

@ -0,0 +1,347 @@
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from django_openstack import api
from django_openstack.tests.view_tests import base
from glance.common import exception as glance_exception
from openstackx.api import exceptions as api_exceptions
from novaclient import exceptions as novaclient_exceptions
from mox import IgnoreArg, IsA
class SecurityGroupsViewTests(base.BaseViewTests):
def setUp(self):
super(SecurityGroupsViewTests, self).setUp()
security_group = self.mox.CreateMock(api.SecurityGroup)
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(self):
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_security_groups',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_security_groups.html')
self.assertItemsEqual(res.context['security_groups'],
self.security_groups)
self.mox.VerifyAll()
def test_index_exception(self):
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.get(reverse('dash_security_groups',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_security_groups.html')
self.assertEqual(len(res.context['security_groups']), 0)
self.mox.VerifyAll()
def test_create_security_groups_get(self):
res = self.client.get(reverse('dash_security_groups_create',
args=[self.TEST_TENANT]))
self.assertTemplateUsed(res, 'dash_security_groups_create.html')
def test_create_security_groups_post(self):
SECGROUP_NAME = 'fakegroup'
SECGROUP_DESC = 'fakegroup_desc'
new_group = self.mox.CreateMock(api.SecurityGroup)
new_group.name = SECGROUP_NAME
formData = {'method': 'CreateGroup',
'tenant_id': self.TEST_TENANT,
'name': SECGROUP_NAME,
'description': SECGROUP_DESC,
}
self.mox.StubOutWithMock(api, 'security_group_create')
api.security_group_create(IsA(http.HttpRequest),
SECGROUP_NAME, SECGROUP_DESC).AndReturn(new_group)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups_create',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_security_groups',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_create_security_groups_post_exception(self):
SECGROUP_NAME = 'fakegroup'
SECGROUP_DESC = 'fakegroup_desc'
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
formData = {'method': 'CreateGroup',
'tenant_id': self.TEST_TENANT,
'name': SECGROUP_NAME,
'description': SECGROUP_DESC,
}
self.mox.StubOutWithMock(api, 'security_group_create')
api.security_group_create(IsA(http.HttpRequest),
SECGROUP_NAME, SECGROUP_DESC).AndRaise(exception)
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups_create',
args=[self.TEST_TENANT]),
formData)
self.assertTemplateUsed(res, 'dash_security_groups_create.html')
self.mox.VerifyAll()
def test_edit_rules_get(self):
SECGROUP_ID = '1'
self.mox.StubOutWithMock(api, 'security_group_get')
api.security_group_get(IsA(http.HttpRequest), SECGROUP_ID).AndReturn(
self.security_groups[0])
self.mox.ReplayAll()
res = self.client.get(reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]))
self.assertTemplateUsed(res, 'dash_security_groups_edit_rules.html')
self.assertItemsEqual(res.context['security_group'].name,
self.security_groups[0].name)
self.mox.VerifyAll()
def test_edit_rules_get_exception(self):
SECGROUP_ID = '1'
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
self.mox.StubOutWithMock(api, 'security_group_get')
api.security_group_get(IsA(http.HttpRequest), SECGROUP_ID).AndRaise(
exception)
self.mox.ReplayAll()
res = self.client.get(reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]))
self.assertRedirectsNoFollow(res, reverse('dash_security_groups',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_edit_rules_add_rule(self):
SECGROUP_ID = '1'
RULE_ID = '1'
FROM_PORT = '-1'
TO_PORT = '-1'
IP_PROTOCOL = 'icmp'
CIDR = '0.0.0.0/0'
new_rule = self.mox.CreateMock(api.SecurityGroup)
new_rule.from_port = FROM_PORT
new_rule.to_port = TO_PORT
new_rule.ip_protocol = IP_PROTOCOL
new_rule.cidr = CIDR
new_rule.security_group_id = SECGROUP_ID
new_rule.id = RULE_ID
formData = {'method': 'AddRule',
'tenant_id': self.TEST_TENANT,
'security_group_id': SECGROUP_ID,
'from_port': FROM_PORT,
'to_port': TO_PORT,
'ip_protocol': IP_PROTOCOL,
'cidr': CIDR}
self.mox.StubOutWithMock(api, 'security_group_rule_create')
api.security_group_rule_create(IsA(http.HttpRequest),
SECGROUP_ID, IP_PROTOCOL, FROM_PORT, TO_PORT, CIDR)\
.AndReturn(new_rule)
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]),
formData)
self.assertRedirectsNoFollow(res,
reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]))
self.mox.VerifyAll()
def test_edit_rules_add_rule_exception(self):
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
SECGROUP_ID = '1'
RULE_ID = '1'
FROM_PORT = '-1'
TO_PORT = '-1'
IP_PROTOCOL = 'icmp'
CIDR = '0.0.0.0/0'
formData = {'method': 'AddRule',
'tenant_id': self.TEST_TENANT,
'security_group_id': SECGROUP_ID,
'from_port': FROM_PORT,
'to_port': TO_PORT,
'ip_protocol': IP_PROTOCOL,
'cidr': CIDR}
self.mox.StubOutWithMock(api, 'security_group_rule_create')
api.security_group_rule_create(IsA(http.HttpRequest),
SECGROUP_ID, IP_PROTOCOL, FROM_PORT,
TO_PORT, CIDR).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]),
formData)
self.assertRedirectsNoFollow(res,
reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]))
self.mox.VerifyAll()
def test_edit_rules_delete_rule(self):
SECGROUP_ID = '1'
RULE_ID = '1'
formData = {'method': 'DeleteRule',
'tenant_id': self.TEST_TENANT,
'security_group_rule_id': RULE_ID,
}
self.mox.StubOutWithMock(api, 'security_group_rule_delete')
api.security_group_rule_delete(IsA(http.HttpRequest), RULE_ID)
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(unicode))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]),
formData)
self.assertRedirectsNoFollow(res,
reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]))
self.mox.VerifyAll()
def test_edit_rules_delete_rule_exception(self):
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
SECGROUP_ID = '1'
RULE_ID = '1'
formData = {'method': 'DeleteRule',
'tenant_id': self.TEST_TENANT,
'security_group_rule_id': RULE_ID,
}
self.mox.StubOutWithMock(api, 'security_group_rule_delete')
api.security_group_rule_delete(IsA(http.HttpRequest), RULE_ID).\
AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]),
formData)
self.assertRedirectsNoFollow(res,
reverse('dash_security_groups_edit_rules',
args=[self.TEST_TENANT, SECGROUP_ID]))
self.mox.VerifyAll()
def test_delete_group(self):
SECGROUP_ID = '1'
formData = {'method': 'DeleteGroup',
'tenant_id': self.TEST_TENANT,
'security_group_id': SECGROUP_ID,
}
self.mox.StubOutWithMock(api, 'security_group_delete')
api.security_group_delete(IsA(http.HttpRequest), SECGROUP_ID)
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(unicode))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_security_groups',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()
def test_delete_group_exception(self):
exception = novaclient_exceptions.ClientException('ClientException',
message='ClientException')
SECGROUP_ID = '1'
formData = {'method': 'DeleteGroup',
'tenant_id': self.TEST_TENANT,
'security_group_id': SECGROUP_ID,
}
self.mox.StubOutWithMock(api, 'security_group_delete')
api.security_group_delete(IsA(http.HttpRequest), SECGROUP_ID).\
AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(str))
self.mox.ReplayAll()
res = self.client.post(reverse('dash_security_groups',
args=[self.TEST_TENANT]),
formData)
self.assertRedirectsNoFollow(res, reverse('dash_security_groups',
args=[self.TEST_TENANT]))
self.mox.VerifyAll()

View File

@ -4,6 +4,7 @@ from django_openstack.tests.view_tests import base
from mox import IgnoreArg
from openstackx.api import exceptions as api_exceptions
class UsersViewTests(base.BaseViewTests):
def setUp(self):
super(UsersViewTests, self).setUp()
@ -71,8 +72,10 @@ class UsersViewTests(base.BaseViewTests):
'enabled': 'enable'}
self.mox.StubOutWithMock(api, 'user_update_enabled')
api_exception = api_exceptions.ApiException('apiException', message='apiException')
api.user_update_enabled(IgnoreArg(), OTHER_USER, True).AndRaise(api_exception)
api_exception = api_exceptions.ApiException('apiException',
message='apiException')
api.user_update_enabled(IgnoreArg(),
OTHER_USER, True).AndRaise(api_exception)
self.mox.ReplayAll()

View File

@ -21,10 +21,14 @@
from django.core.management import execute_manager
try:
import settings # Assumed to be in the same directory.
import settings # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n(If the file settings.py does indeed exist, it's causing an ImportError somehow.)\n" % __file__)
sys.stderr.write("Error: Can't find the file 'settings.py' in the "
"directory containing %r. It appears you've customized things.\nYou'll "
"have to run django-admin.py, passing it your settings module.\n(If "
"the file settings.py does indeed exist, it's causing an ImportError "
"somehow.)\n" % __file__)
sys.exit(1)
if __name__ == "__main__":

View File

@ -131,8 +131,9 @@ if DEBUG:
import debug_toolbar
INSTALLED_APPS += ('debug_toolbar',)
MIDDLEWARE_CLASSES += ('debug_toolbar.middleware.DebugToolbarMiddleware',)
MIDDLEWARE_CLASSES += (
'debug_toolbar.middleware.DebugToolbarMiddleware',)
except ImportError:
logging.info('Running in debug mode without debug_toolbar.')
OPENSTACK_KEYSTONE_DEFAULT_ROLE='Member'
OPENSTACK_KEYSTONE_DEFAULT_ROLE = 'Member'

View File

@ -948,8 +948,34 @@ td ul span {
padding-left: 15px;
}
.dash_block .left h3 {
font-size: 24px;
margin: 0 0 25px 0;
}
/* Security Groups */
.security_group_rule_form_inner {
margin: 30px 30px;
}
.dash_block .security_group_rule_form_wrapper {
border: 1px solid #eee;
width: 46%;
}
.dash_block .security_group_rule_form_wrapper input[type="submit"]{
background: #93bc8d;
color: #fff;
margin: 0 17px 0 0;
border: 1px solid #8eb08a;
-webkit-transition-property: background;
-webkit-transition-duration: 0.2s;
text-shadow: #62935c 0 -1px 0;
}
/* Network details page */
td.ACTIVE {
color:#6EAF6E;
}

View File

@ -45,6 +45,9 @@ $(function(){
});
return true;
});
// Fancy multi-selects
$(".chzn-select").chosen()
$(".detach").click(function(e){
var response = confirm('Are you sure you want to detach the '+$(this).attr('title')+" ?");

View File

@ -9,6 +9,7 @@
<li><a {% if current_sidebar == "snapshots" %} class="active" {% endif %} href="{% url dash_snapshots request.user.tenant %}">Snapshots</a></li>
<li><a {% if current_sidebar == "keypairs" %} class="active" {% endif %} href="{% url dash_keypairs request.user.tenant %}">Keypairs</a></li>
<li><a {% if current_sidebar == "floatingips" %} class="active" {% endif %} href="{% url dash_floating_ips request.user.tenant %}">Floating IPs</a></li>
<li><a {% if current_sidebar == "security_groups" %} class="active" {% endif %} href="{% url dash_security_groups request.user.tenant %}">Security Groups</a></li>
{% if quantum_configured %}
<li><a {% if current_sidebar == "networks" %} class="active" {% endif %} href="{% url dash_networks request.user.tenant %}">Networks</a></li>
{% endif %}

View File

@ -0,0 +1,8 @@
<form id="form_delete_{{security_group.id}}" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="security_group_id" type="hidden" value="{{security_group.id}}" />
<input id="delete_{{security_group.id}}" class="delete" title="Security Group: {{security_group.id}}" type="submit" value="Delete" />
</form>

View File

@ -0,0 +1,8 @@
<form id="form_delete_{{rule.id}}" class="form-delete" method="post">
{% csrf_token %}
{% for hidden in form.hidden_fields %}
{{hidden}}
{% endfor %}
<input name="security_group_rule_id" type="hidden" value="{{rule.id}}" />
<input id="delete_{{rule.id}}" class="delete" title="Security Group: {{rule.id}}" type="submit" value="Delete" />
</form>

View File

@ -4,6 +4,7 @@
<tr id='headings'>
<th>ID</th>
<th>Name</th>
<th>Groups</th>
<th>Image</th>
<th>Size</th>
<th>IPs</th>
@ -20,6 +21,13 @@
<small> ({{instance.attrs.key_name}}) </small>
{% endif %}
</td>
<td>
<ul>
{% for group in instance.attrs.security_groups %}
<li>{{group}}</li>
{% endfor %}
<ul>
</td>
<td>{{instance.image_name}}</td>
<td>
<ul>

View File

@ -0,0 +1,12 @@
<form id="security_group_form" method="post">
<fieldset>
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input type="submit" value="Create Security Group" class="large-rounded" />
</fieldset>
</form>

View File

@ -0,0 +1,23 @@
<table id="security_groups" class="wide">
<tr>
<th>Id</th>
<th>Name</th>
<th>Description</th>
<th>Actions</th>
</tr>
{% for security_group in security_groups %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ security_group.id }}</td>
<td>{{ security_group.name }}</td>
<td>{{ security_group.description }}</td>
<td id="actions">
<ul>
<li><a href="{% url dash_security_groups_edit_rules request.user.tenant security_group.id %}">Edit Rules</a></li>
{% if security_group.name != 'default' %}
<li class="form">{% include "_delete_security_group.html" with form=delete_form %}</li>
{% endif %}
</ul>
</td>
</tr>
{% endfor %}
</table>

View File

@ -44,8 +44,8 @@
<ul>
<li class="form">{% include "_terminate.html" with form=terminate_form %}</li>
<li class="form">{% include "_reboot.html" with form=reboot_form %}</li>
<li><a target="_blank" href="{% url dash_instances_console instance.attrs.tenant_id instance.id %}">Console Log</a></li>
<li><a target="_blank" href="{% url dash_instances_vnc instance.attrs.tenant_id instance.id %}">VNC Console</a></li>
<li><a target="_blank" href="{% url dash_instances_console request.user.tenant instance.id %}">Console Log</a></li>
<li><a target="_blank" href="{% url dash_instances_vnc request.user.tenant instance.id %}">VNC Console</a></li>
</ul>
</td>
</tr>

View File

@ -11,6 +11,7 @@
<script charset='utf-8' src='{{ STATIC_URL }}dashboard/js/form_examples.js' type='text/javascript'></script>
<script charset='utf-8' src='{{ STATIC_URL }}dashboard/js/application.js' type='text/javascript'></script>
<link href='{{ STATIC_URL }}dashboard/css/style.css' media='screen' rel='stylesheet' />
<link href='/media/dashboard/css/chosen.css' media='screen' rel='stylesheet' />
{% block headerjs %}{% endblock %}
{% block headercss %}{% endblock %}
</head>

View File

@ -0,0 +1,25 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="security_groups" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{% url dash_security_groups request.user.tenant as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "_page_header.html" with title="Security Groups" refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block dash_main %}
{% if security_groups %}
{% include '_security_group_list.html' %}
<a id="security_groups_create_link" class="action_link large-rounded" href="{% url dash_security_groups_create request.user.tenant %}">Create Security Group</a>
{% else %}
<div class="message_box info">
<h2>Info</h2>
<p>There are currently no security groups. <a href='{% url dash_security_groups_create request.user.tenant %}'>Create A Security Group &gt;&gt;</a></p>
</div>
{% endif %}
{% endblock %}

View File

@ -0,0 +1,25 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="security_groups" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{% include "_page_header.html" with title="Create Security Group" %}
{% endblock page_header %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
{% include '_security_group_form.html' %}
</div>
<div class="right">
<h3>Description:</h3>
<p>From here you can create a new security group</p>
</div>
<div class="clear">&nbsp;</div>
</div>
{% endblock %}

View File

@ -0,0 +1,68 @@
{% extends 'dash_base.html' %}
{% block sidebar %}
{% with current_sidebar="security_groups" %}
{{block.super}}
{% endwith %}
{% endblock %}
{% block page_header %}
{% include "_page_header.html" with title="Edit Security Group Rules" %}
{% endblock page_header %}
{% block dash_main %}
<div class="dash_block">
<div class="left">
<h3> Rules for Security Group '{{security_group.name}}'</h3>
<table id="security_groups" class="wide">
<tr>
<th>ID</th>
<th>IP Protocol</th>
<th>From Port</th>
<th>To Port</th>
<th>CIDR</th>
<th>Actions</th>
</tr>
{% for rule in security_group.rules %}
<tr class="{% cycle 'odd' 'even' %}">
<td>{{ rule.id }}</td>
<td>{{ rule.ip_protocol }}</td>
<td>{{ rule.from_port }}</td>
<td>{{ rule.to_port }}</td>
<td>{{rule.ip_range.cidr}}</td>
<td id="actions">
<ul>
<li class="form">{% include "_delete_security_group_rule.html" with form=delete_form %}</li>
</ul>
</td>
</tr>
{% empty %}
<tr>
<td colspan="100%">
No rules for this security group
</td>
</tr>
{% endfor %}
</table>
</div>
<div class="right security_group_rule_form_wrapper">
<div class="security_group_rule_form_inner">
<h3>Add a rule</h3>
<form id="security_group_rule_form" method="post">
<fieldset>
{% csrf_token %}
{% for hidden in form.hidden_fields %}{{ hidden }}{% endfor %}
{% for field in form.visible_fields %}
{{ field.label_tag }}
{{ field.errors }}
{{ field }}
{% endfor %}
<input name="security_group_id" type="hidden" value="{{security_group.id}}" />
<input type="submit" value="Add Rule" class="large-rounded action_link" />
</fieldset>
</form>
</div>
</div>
<div style="clear: both;"></div>
</div>
{% endblock %}

View File

@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
''' Test for django mailer.
This test is pretty much worthless, and should be removed once real testing of
This test is pretty much worthless, and should be removed once real testing of
views that send emails is implemented
'''

View File

@ -33,8 +33,10 @@ from django_openstack import urls as django_openstack_urls
urlpatterns = patterns('',
url(r'^$', 'dashboard.views.splash', name='splash'),
url(r'^dash/$', 'django_openstack.dash.views.instances.usage', name='dash_overview'),
url(r'^syspanel/$', 'django_openstack.syspanel.views.instances.usage', name='syspanel_overview'),
url(r'^dash/$', 'django_openstack.dash.views.instances.usage',
name='dash_overview'),
url(r'^syspanel/$', 'django_openstack.syspanel.views.instances.usage',
name='syspanel_overview'),
)
# Development static app and project media serving using the staticfiles app.

View File

@ -0,0 +1,340 @@
/* @group Base */
select.chzn-select {
visibility: hidden;
height: 28px !important;
min-height: 28px !important;
}
.chzn-container {
font-size: 13px;
position: relative;
display: inline-block;
zoom: 1;
*display: inline;
}
.chzn-container .chzn-drop {
background: #fff;
border: 1px solid #aaa;
border-top: 0;
position: absolute;
top: 29px;
left: 0;
-webkit-box-shadow: 0 4px 5px rgba(0,0,0,.15);
-moz-box-shadow : 0 4px 5px rgba(0,0,0,.15);
-o-box-shadow : 0 4px 5px rgba(0,0,0,.15);
box-shadow : 0 4px 5px rgba(0,0,0,.15);
z-index: 999;
}
/* @end */
/* @group Single Chosen */
.chzn-container-single .chzn-single {
background-color: #fff;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #eeeeee), color-stop(0.5, white));
background-image: -webkit-linear-gradient(center bottom, #eeeeee 0%, white 50%);
background-image: -moz-linear-gradient(center bottom, #eeeeee 0%, white 50%);
background-image: -o-linear-gradient(top, #eeeeee 0%,#ffffff 50%);
background-image: -ms-linear-gradient(top, #eeeeee 0%,#ffffff 50%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#eeeeee', endColorstr='#ffffff',GradientType=0 );
background-image: linear-gradient(top, #eeeeee 0%,#ffffff 50%);
-webkit-border-radius: 4px;
-moz-border-radius : 4px;
border-radius : 4px;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
border: 1px solid #aaa;
display: block;
overflow: hidden;
white-space: nowrap;
position: relative;
height: 26px;
line-height: 26px;
padding: 0 0 0 8px;
color: #444;
text-decoration: none;
}
.chzn-container-single .chzn-single span {
margin-right: 26px;
display: block;
overflow: hidden;
white-space: nowrap;
-o-text-overflow: ellipsis;
-ms-text-overflow: ellipsis;
-moz-binding: url('/xml/ellipsis.xml#ellipsis');
text-overflow: ellipsis;
}
.chzn-container-single .chzn-single div {
-webkit-border-radius: 0 4px 4px 0;
-moz-border-radius : 0 4px 4px 0;
border-radius : 0 4px 4px 0;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
background: #ccc;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #ccc), color-stop(0.6, #eee));
background-image: -webkit-linear-gradient(center bottom, #ccc 0%, #eee 60%);
background-image: -moz-linear-gradient(center bottom, #ccc 0%, #eee 60%);
background-image: -o-linear-gradient(bottom, #ccc 0%, #eee 60%);
background-image: -ms-linear-gradient(top, #cccccc 0%,#eeeeee 60%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#cccccc', endColorstr='#eeeeee',GradientType=0 );
background-image: linear-gradient(top, #cccccc 0%,#eeeeee 60%);
border-left: 1px solid #aaa;
position: absolute;
right: 0;
top: 0;
display: block;
height: 100%;
width: 18px;
}
.chzn-container-single .chzn-single div b {
background: url('chosen-sprite.png') no-repeat 0 1px;
display: block;
width: 100%;
height: 100%;
}
.chzn-container-single .chzn-search {
padding: 3px 4px;
margin: 0;
white-space: nowrap;
}
.chzn-container-single .chzn-search input {
background: #fff url('chosen-sprite.png') no-repeat 100% -20px;
background: url('chosen-sprite.png') no-repeat 100% -20px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
background: url('chosen-sprite.png') no-repeat 100% -20px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('chosen-sprite.png') no-repeat 100% -20px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('chosen-sprite.png') no-repeat 100% -20px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
background: url('chosen-sprite.png') no-repeat 100% -20px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%);
background: url('chosen-sprite.png') no-repeat 100% -20px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%);
background: url('chosen-sprite.png') no-repeat 100% -20px, linear-gradient(top, #ffffff 85%,#eeeeee 99%);
margin: 1px 0;
padding: 4px 20px 4px 5px;
outline: 0;
border: 1px solid #aaa;
font-family: sans-serif;
font-size: 1em;
}
.chzn-container-single .chzn-drop {
-webkit-border-radius: 0 0 4px 4px;
-moz-border-radius : 0 0 4px 4px;
border-radius : 0 0 4px 4px;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
}
/* @end */
/* @group Multi Chosen */
.chzn-container-multi .chzn-choices {
background-color: #fff;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
background-image: -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background-image: -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background-image: -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
background-image: -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
background-image: linear-gradient(top, #ffffff 85%,#eeeeee 99%);
border: 1px solid #aaa;
margin: 0;
padding: 0;
cursor: text;
overflow: hidden;
height: auto !important;
height: 1%;
position: relative;
}
.chzn-container-multi .chzn-choices li {
float: left;
list-style: none;
}
.chzn-container-multi .chzn-choices .search-field {
white-space: nowrap;
margin: 0;
padding: 0;
}
.chzn-container-multi .chzn-choices .search-field input {
color: #666;
background: transparent !important;
border: 0 !important;
padding: 5px;
margin: 1px 0;
outline: 0;
-webkit-box-shadow: none;
-moz-box-shadow : none;
-o-box-shadow : none;
box-shadow : none;
}
.chzn-container-multi .chzn-choices .search-field .default {
color: #999;
}
.chzn-container-multi .chzn-choices .search-choice {
-webkit-border-radius: 3px;
-moz-border-radius : 3px;
border-radius : 3px;
-moz-background-clip : padding;
-webkit-background-clip: padding-box;
background-clip : padding-box;
background-color: #e4e4e4;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, #e4e4e4), color-stop(0.7, #eeeeee));
background-image: -webkit-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%);
background-image: -moz-linear-gradient(center bottom, #e4e4e4 0%, #eeeeee 70%);
background-image: -o-linear-gradient(bottom, #e4e4e4 0%, #eeeeee 70%);
background-image: -ms-linear-gradient(top, #e4e4e4 0%,#eeeeee 70%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#e4e4e4', endColorstr='#eeeeee',GradientType=0 );
background-image: linear-gradient(top, #e4e4e4 0%,#eeeeee 70%);
color: #333;
border: 1px solid #b4b4b4;
line-height: 13px;
padding: 3px 19px 3px 6px;
margin: 3px 0 3px 5px;
position: relative;
}
.chzn-container-multi .chzn-choices .search-choice span {
cursor: default;
}
.chzn-container-multi .chzn-choices .search-choice-focus {
background: #d4d4d4;
}
.chzn-container-multi .chzn-choices .search-choice .search-choice-close {
display: block;
position: absolute;
right: 5px;
top: 6px;
width: 8px;
height: 9px;
font-size: 1px;
background: url(chosen-sprite.png) right top no-repeat;
}
.chzn-container-multi .chzn-choices .search-choice .search-choice-close:hover {
background-position: right -9px;
}
.chzn-container-multi .chzn-choices .search-choice-focus .search-choice-close {
background-position: right -9px;
}
/* @end */
/* @group Results */
.chzn-container .chzn-results {
margin: 0 4px 4px 0;
max-height: 190px;
padding: 0 0 0 4px;
position: relative;
overflow-x: hidden;
overflow-y: auto;
}
.chzn-container-multi .chzn-results {
margin: -1px 0 0;
padding: 0;
}
.chzn-container .chzn-results li {
line-height: 80%;
padding: 7px 7px 8px;
margin: 0;
list-style: none;
}
.chzn-container .chzn-results .active-result {
cursor: pointer;
}
.chzn-container .chzn-results .highlighted {
background: #3875d7;
color: #fff;
}
.chzn-container .chzn-results li em {
background: #feffde;
font-style: normal;
}
.chzn-container .chzn-results .highlighted em {
background: transparent;
}
.chzn-container .chzn-results .no-results {
background: #f4f4f4;
}
.chzn-container .chzn-results .group-result {
cursor: default;
color: #999;
font-weight: bold;
}
.chzn-container .chzn-results .group-option {
padding-left: 20px;
}
.chzn-container-multi .chzn-drop .result-selected {
display: none;
}
/* @end */
/* @group Active */
.chzn-container-active .chzn-single {
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
-moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
-o-box-shadow : 0 0 5px rgba(0,0,0,.3);
box-shadow : 0 0 5px rgba(0,0,0,.3);
border: 1px solid #5897fb;
}
.chzn-container-active .chzn-single-with-drop {
border: 1px solid #aaa;
-webkit-box-shadow: 0 1px 0 #fff inset;
-moz-box-shadow : 0 1px 0 #fff inset;
-o-box-shadow : 0 1px 0 #fff inset;
box-shadow : 0 1px 0 #fff inset;
background-color: #eee;
background-image: -webkit-gradient(linear, left bottom, left top, color-stop(0, white), color-stop(0.5, #eeeeee));
background-image: -webkit-linear-gradient(center bottom, white 0%, #eeeeee 50%);
background-image: -moz-linear-gradient(center bottom, white 0%, #eeeeee 50%);
background-image: -o-linear-gradient(bottom, white 0%, #eeeeee 50%);
background-image: -ms-linear-gradient(top, #ffffff 0%,#eeeeee 50%);
filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#ffffff', endColorstr='#eeeeee',GradientType=0 );
background-image: linear-gradient(top, #ffffff 0%,#eeeeee 50%);
-webkit-border-bottom-left-radius : 0;
-webkit-border-bottom-right-radius: 0;
-moz-border-radius-bottomleft : 0;
-moz-border-radius-bottomright: 0;
border-bottom-left-radius : 0;
border-bottom-right-radius: 0;
}
.chzn-container-active .chzn-single-with-drop div {
background: transparent;
border-left: none;
}
.chzn-container-active .chzn-single-with-drop div b {
background-position: -18px 1px;
}
.chzn-container-active .chzn-choices {
-webkit-box-shadow: 0 0 5px rgba(0,0,0,.3);
-moz-box-shadow : 0 0 5px rgba(0,0,0,.3);
-o-box-shadow : 0 0 5px rgba(0,0,0,.3);
box-shadow : 0 0 5px rgba(0,0,0,.3);
border: 1px solid #5897fb;
}
.chzn-container-active .chzn-choices .search-field input {
color: #111 !important;
}
/* @end */
/* @group Right to Left */
.chzn-rtl { direction:rtl;text-align: right; }
.chzn-rtl .chzn-single { padding-left: 0; padding-right: 8px; }
.chzn-rtl .chzn-single span { margin-left: 26px; margin-right: 0; }
.chzn-rtl .chzn-single div {
left: 0; right: auto;
border-left: none; border-right: 1px solid #aaaaaa;
-webkit-border-radius: 4px 0 0 4px;
-moz-border-radius : 4px 0 0 4px;
border-radius : 4px 0 0 4px;
}
.chzn-rtl .chzn-choices li { float: right; }
.chzn-rtl .chzn-choices .search-choice { padding: 3px 6px 3px 19px; margin: 3px 5px 3px 0; }
.chzn-rtl .chzn-choices .search-choice .search-choice-close { left: 5px; right: auto; background-position: right top;}
.chzn-rtl.chzn-container-single .chzn-results { margin-left: 4px; margin-right: 0; padding-left: 0; padding-right: 4px; }
.chzn-rtl .chzn-results .group-option { padding-left: 0; padding-right: 20px; }
.chzn-rtl.chzn-container-active .chzn-single-with-drop div { border-right: none; }
.chzn-rtl .chzn-search input {
background: url('chosen-sprite.png') no-repeat -38px -20px, #ffffff;
background: url('chosen-sprite.png') no-repeat -38px -20px, -webkit-gradient(linear, left bottom, left top, color-stop(0.85, white), color-stop(0.99, #eeeeee));
background: url('chosen-sprite.png') no-repeat -38px -20px, -webkit-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('chosen-sprite.png') no-repeat -38px -20px, -moz-linear-gradient(center bottom, white 85%, #eeeeee 99%);
background: url('chosen-sprite.png') no-repeat -38px -20px, -o-linear-gradient(bottom, white 85%, #eeeeee 99%);
background: url('chosen-sprite.png') no-repeat -38px -20px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%);
background: url('chosen-sprite.png') no-repeat -38px -20px, -ms-linear-gradient(top, #ffffff 85%,#eeeeee 99%);
background: url('chosen-sprite.png') no-repeat -38px -20px, linear-gradient(top, #ffffff 85%,#eeeeee 99%);
padding: 4px 5px 4px 20px;
}
/* @end */

View File

@ -0,0 +1,786 @@
// Chosen, a Select Box Enhancer for jQuery and Protoype
// by Patrick Filler for Harvest, http://getharvest.com
//
// Version 0.9
// Full source at https://github.com/harvesthq/chosen
// Copyright (c) 2011 Harvest http://getharvest.com
// MIT License, https://github.com/harvesthq/chosen/blob/master/LICENSE.md
// This file is generated by `cake build`, do not edit it by hand.
(function() {
/*
Chosen source: generate output using 'cake build'
Copyright (c) 2011 by Harvest
*/ var $, Chosen, get_side_border_padding, root;
var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
root = this;
$ = jQuery;
$.fn.extend({
chosen: function(data, options) {
return $(this).each(function(input_field) {
if (!($(this)).hasClass("chzn-done")) {
return new Chosen(this, data, options);
}
});
}
});
Chosen = (function() {
function Chosen(elmn) {
this.set_default_values();
this.form_field = elmn;
this.form_field_jq = $(this.form_field);
this.is_multiple = this.form_field.multiple;
this.is_rtl = this.form_field_jq.hasClass("chzn-rtl");
this.default_text_default = this.form_field.multiple ? "Select Some Options" : "Select an Option";
this.set_up_html();
this.register_observers();
this.form_field_jq.addClass("chzn-done");
}
Chosen.prototype.set_default_values = function() {
this.click_test_action = __bind(function(evt) {
return this.test_active_click(evt);
}, this);
this.active_field = false;
this.mouse_on_container = false;
this.results_showing = false;
this.result_highlighted = null;
this.result_single_selected = null;
return this.choices = 0;
};
Chosen.prototype.set_up_html = function() {
var container_div, dd_top, dd_width, sf_width;
this.container_id = this.form_field.id.length ? this.form_field.id.replace(/(:|\.)/g, '_') : this.generate_field_id();
this.container_id += "_chzn";
this.f_width = this.form_field_jq.width();
this.default_text = this.form_field_jq.data('placeholder') ? this.form_field_jq.data('placeholder') : this.default_text_default;
container_div = $("<div />", {
id: this.container_id,
"class": "chzn-container " + (this.is_rtl ? ' chzn-rtl' : void 0),
style: 'width: ' + this.f_width + 'px;'
});
if (this.is_multiple) {
container_div.html('<ul class="chzn-choices"><li class="search-field"><input type="text" value="' + this.default_text + '" class="default" style="width:25px;" /></li></ul><div class="chzn-drop" style="left:-9000px;"><ul class="chzn-results"></ul></div>');
} else {
container_div.html('<a href="javascript:void(0)" class="chzn-single"><span>' + this.default_text + '</span><div><b></b></div></a><div class="chzn-drop" style="left:-9000px;"><div class="chzn-search"><input type="text" /></div><ul class="chzn-results"></ul></div>');
}
this.form_field_jq.hide().after(container_div);
this.container = $('#' + this.container_id);
this.container.addClass("chzn-container-" + (this.is_multiple ? "multi" : "single"));
this.dropdown = this.container.find('div.chzn-drop').first();
dd_top = this.container.height();
dd_width = this.f_width - get_side_border_padding(this.dropdown);
this.dropdown.css({
"width": dd_width + "px",
"top": dd_top + "px"
});
this.search_field = this.container.find('input').first();
this.search_results = this.container.find('ul.chzn-results').first();
this.search_field_scale();
this.search_no_results = this.container.find('li.no-results').first();
if (this.is_multiple) {
this.search_choices = this.container.find('ul.chzn-choices').first();
this.search_container = this.container.find('li.search-field').first();
} else {
this.search_container = this.container.find('div.chzn-search').first();
this.selected_item = this.container.find('.chzn-single').first();
sf_width = dd_width - get_side_border_padding(this.search_container) - get_side_border_padding(this.search_field);
this.search_field.css({
"width": sf_width + "px"
});
}
this.results_build();
return this.set_tab_index();
};
Chosen.prototype.register_observers = function() {
this.container.click(__bind(function(evt) {
return this.container_click(evt);
}, this));
this.container.mouseenter(__bind(function(evt) {
return this.mouse_enter(evt);
}, this));
this.container.mouseleave(__bind(function(evt) {
return this.mouse_leave(evt);
}, this));
this.search_results.click(__bind(function(evt) {
return this.search_results_click(evt);
}, this));
this.search_results.mouseover(__bind(function(evt) {
return this.search_results_mouseover(evt);
}, this));
this.search_results.mouseout(__bind(function(evt) {
return this.search_results_mouseout(evt);
}, this));
this.form_field_jq.bind("liszt:updated", __bind(function(evt) {
return this.results_update_field(evt);
}, this));
this.search_field.blur(__bind(function(evt) {
return this.input_blur(evt);
}, this));
this.search_field.keyup(__bind(function(evt) {
return this.keyup_checker(evt);
}, this));
this.search_field.keydown(__bind(function(evt) {
return this.keydown_checker(evt);
}, this));
if (this.is_multiple) {
this.search_choices.click(__bind(function(evt) {
return this.choices_click(evt);
}, this));
return this.search_field.focus(__bind(function(evt) {
return this.input_focus(evt);
}, this));
} else {
return this.selected_item.focus(__bind(function(evt) {
return this.activate_field(evt);
}, this));
}
};
Chosen.prototype.container_click = function(evt) {
if (evt && evt.type === "click") {
evt.stopPropagation();
}
if (!this.pending_destroy_click) {
if (!this.active_field) {
if (this.is_multiple) {
this.search_field.val("");
}
$(document).click(this.click_test_action);
this.results_show();
} else if (!this.is_multiple && evt && ($(evt.target) === this.selected_item || $(evt.target).parents("a.chzn-single").length)) {
evt.preventDefault();
this.results_toggle();
}
return this.activate_field();
} else {
return this.pending_destroy_click = false;
}
};
Chosen.prototype.mouse_enter = function() {
return this.mouse_on_container = true;
};
Chosen.prototype.mouse_leave = function() {
return this.mouse_on_container = false;
};
Chosen.prototype.input_focus = function(evt) {
if (!this.active_field) {
return setTimeout((__bind(function() {
return this.container_click();
}, this)), 50);
}
};
Chosen.prototype.input_blur = function(evt) {
if (!this.mouse_on_container) {
this.active_field = false;
return setTimeout((__bind(function() {
return this.blur_test();
}, this)), 100);
}
};
Chosen.prototype.blur_test = function(evt) {
if (!this.active_field && this.container.hasClass("chzn-container-active")) {
return this.close_field();
}
};
Chosen.prototype.close_field = function() {
$(document).unbind("click", this.click_test_action);
if (!this.is_multiple) {
this.selected_item.attr("tabindex", this.search_field.attr("tabindex"));
this.search_field.attr("tabindex", -1);
}
this.active_field = false;
this.results_hide();
this.container.removeClass("chzn-container-active");
this.winnow_results_clear();
this.clear_backstroke();
this.show_search_field_default();
return this.search_field_scale();
};
Chosen.prototype.activate_field = function() {
if (!this.is_multiple && !this.active_field) {
this.search_field.attr("tabindex", this.selected_item.attr("tabindex"));
this.selected_item.attr("tabindex", -1);
}
this.container.addClass("chzn-container-active");
this.active_field = true;
this.search_field.val(this.search_field.val());
return this.search_field.focus();
};
Chosen.prototype.test_active_click = function(evt) {
if ($(evt.target).parents('#' + this.container_id).length) {
return this.active_field = true;
} else {
return this.close_field();
}
};
Chosen.prototype.results_build = function() {
var content, data, startTime, _i, _len, _ref;
startTime = new Date();
this.parsing = true;
this.results_data = root.SelectParser.select_to_array(this.form_field);
if (this.is_multiple && this.choices > 0) {
this.search_choices.find("li.search-choice").remove();
this.choices = 0;
} else if (!this.is_multiple) {
this.selected_item.find("span").text(this.default_text);
}
content = '';
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
data = _ref[_i];
if (data.group) {
content += this.result_add_group(data);
} else if (!data.empty) {
content += this.result_add_option(data);
if (data.selected && this.is_multiple) {
this.choice_build(data);
} else if (data.selected && !this.is_multiple) {
this.selected_item.find("span").text(data.text);
}
}
}
this.show_search_field_default();
this.search_field_scale();
this.search_results.html(content);
return this.parsing = false;
};
Chosen.prototype.result_add_group = function(group) {
if (!group.disabled) {
group.dom_id = this.container_id + "_g_" + group.array_index;
return '<li id="' + group.dom_id + '" class="group-result">' + $("<div />").text(group.label).html() + '</li>';
} else {
return "";
}
};
Chosen.prototype.result_add_option = function(option) {
var classes;
if (!option.disabled) {
option.dom_id = this.container_id + "_o_" + option.array_index;
classes = option.selected && this.is_multiple ? [] : ["active-result"];
if (option.selected) {
classes.push("result-selected");
}
if (option.group_array_index != null) {
classes.push("group-option");
}
return '<li id="' + option.dom_id + '" class="' + classes.join(' ') + '">' + option.html + '</li>';
} else {
return "";
}
};
Chosen.prototype.results_update_field = function() {
this.result_clear_highlight();
this.result_single_selected = null;
return this.results_build();
};
Chosen.prototype.result_do_highlight = function(el) {
var high_bottom, high_top, maxHeight, visible_bottom, visible_top;
if (el.length) {
this.result_clear_highlight();
this.result_highlight = el;
this.result_highlight.addClass("highlighted");
maxHeight = parseInt(this.search_results.css("maxHeight"), 10);
visible_top = this.search_results.scrollTop();
visible_bottom = maxHeight + visible_top;
high_top = this.result_highlight.position().top + this.search_results.scrollTop();
high_bottom = high_top + this.result_highlight.outerHeight();
if (high_bottom >= visible_bottom) {
return this.search_results.scrollTop((high_bottom - maxHeight) > 0 ? high_bottom - maxHeight : 0);
} else if (high_top < visible_top) {
return this.search_results.scrollTop(high_top);
}
}
};
Chosen.prototype.result_clear_highlight = function() {
if (this.result_highlight) {
this.result_highlight.removeClass("highlighted");
}
return this.result_highlight = null;
};
Chosen.prototype.results_toggle = function() {
if (this.results_showing) {
return this.results_hide();
} else {
return this.results_show();
}
};
Chosen.prototype.results_show = function() {
var dd_top;
if (!this.is_multiple) {
this.selected_item.addClass("chzn-single-with-drop");
if (this.result_single_selected) {
this.result_do_highlight(this.result_single_selected);
}
}
dd_top = this.is_multiple ? this.container.height() : this.container.height() - 1;
this.dropdown.css({
"top": dd_top + "px",
"left": 0
});
this.results_showing = true;
this.search_field.focus();
this.search_field.val(this.search_field.val());
return this.winnow_results();
};
Chosen.prototype.results_hide = function() {
if (!this.is_multiple) {
this.selected_item.removeClass("chzn-single-with-drop");
}
this.result_clear_highlight();
this.dropdown.css({
"left": "-9000px"
});
return this.results_showing = false;
};
Chosen.prototype.set_tab_index = function(el) {
var ti;
if (this.form_field_jq.attr("tabindex")) {
ti = this.form_field_jq.attr("tabindex");
this.form_field_jq.attr("tabindex", -1);
if (this.is_multiple) {
return this.search_field.attr("tabindex", ti);
} else {
this.selected_item.attr("tabindex", ti);
return this.search_field.attr("tabindex", -1);
}
}
};
Chosen.prototype.show_search_field_default = function() {
if (this.is_multiple && this.choices < 1 && !this.active_field) {
this.search_field.val(this.default_text);
return this.search_field.addClass("default");
} else {
this.search_field.val("");
return this.search_field.removeClass("default");
}
};
Chosen.prototype.search_results_click = function(evt) {
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target.length) {
this.result_highlight = target;
return this.result_select();
}
};
Chosen.prototype.search_results_mouseover = function(evt) {
var target;
target = $(evt.target).hasClass("active-result") ? $(evt.target) : $(evt.target).parents(".active-result").first();
if (target) {
return this.result_do_highlight(target);
}
};
Chosen.prototype.search_results_mouseout = function(evt) {
if ($(evt.target).hasClass("active-result" || $(evt.target).parents('.active-result').first())) {
return this.result_clear_highlight();
}
};
Chosen.prototype.choices_click = function(evt) {
evt.preventDefault();
if (this.active_field && !($(evt.target).hasClass("search-choice" || $(evt.target).parents('.search-choice').first)) && !this.results_showing) {
return this.results_show();
}
};
Chosen.prototype.choice_build = function(item) {
var choice_id, link;
choice_id = this.container_id + "_c_" + item.array_index;
this.choices += 1;
this.search_container.before('<li class="search-choice" id="' + choice_id + '"><span>' + item.html + '</span><a href="javascript:void(0)" class="search-choice-close" rel="' + item.array_index + '"></a></li>');
link = $('#' + choice_id).find("a").first();
return link.click(__bind(function(evt) {
return this.choice_destroy_link_click(evt);
}, this));
};
Chosen.prototype.choice_destroy_link_click = function(evt) {
evt.preventDefault();
this.pending_destroy_click = true;
return this.choice_destroy($(evt.target));
};
Chosen.prototype.choice_destroy = function(link) {
this.choices -= 1;
this.show_search_field_default();
if (this.is_multiple && this.choices > 0 && this.search_field.val().length < 1) {
this.results_hide();
}
this.result_deselect(link.attr("rel"));
return link.parents('li').first().remove();
};
Chosen.prototype.result_select = function() {
var high, high_id, item, position;
if (this.result_highlight) {
high = this.result_highlight;
high_id = high.attr("id");
this.result_clear_highlight();
high.addClass("result-selected");
if (this.is_multiple) {
this.result_deactivate(high);
} else {
this.result_single_selected = high;
}
position = high_id.substr(high_id.lastIndexOf("_") + 1);
item = this.results_data[position];
item.selected = true;
this.form_field.options[item.options_index].selected = true;
if (this.is_multiple) {
this.choice_build(item);
} else {
this.selected_item.find("span").first().text(item.text);
}
this.results_hide();
this.search_field.val("");
this.form_field_jq.trigger("change");
return this.search_field_scale();
}
};
Chosen.prototype.result_activate = function(el) {
return el.addClass("active-result").show();
};
Chosen.prototype.result_deactivate = function(el) {
return el.removeClass("active-result").hide();
};
Chosen.prototype.result_deselect = function(pos) {
var result, result_data;
result_data = this.results_data[pos];
result_data.selected = false;
this.form_field.options[result_data.options_index].selected = false;
result = $("#" + this.container_id + "_o_" + pos);
result.removeClass("result-selected").addClass("active-result").show();
this.result_clear_highlight();
this.winnow_results();
this.form_field_jq.trigger("change");
return this.search_field_scale();
};
Chosen.prototype.results_search = function(evt) {
if (this.results_showing) {
return this.winnow_results();
} else {
return this.results_show();
}
};
Chosen.prototype.winnow_results = function() {
var found, option, part, parts, regex, result_id, results, searchText, startTime, startpos, text, zregex, _i, _j, _len, _len2, _ref;
startTime = new Date();
this.no_results_clear();
results = 0;
searchText = this.search_field.val() === this.default_text ? "" : $('<div/>').text($.trim(this.search_field.val())).html();
regex = new RegExp('^' + searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');
zregex = new RegExp(searchText.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&"), 'i');
_ref = this.results_data;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
if (!option.disabled && !option.empty) {
if (option.group) {
$('#' + option.dom_id).hide();
} else if (!(this.is_multiple && option.selected)) {
found = false;
result_id = option.dom_id;
if (regex.test(option.html)) {
found = true;
results += 1;
} else if (option.html.indexOf(" ") >= 0 || option.html.indexOf("[") === 0) {
parts = option.html.replace(/\[|\]/g, "").split(" ");
if (parts.length) {
for (_j = 0, _len2 = parts.length; _j < _len2; _j++) {
part = parts[_j];
if (regex.test(part)) {
found = true;
results += 1;
}
}
}
}
if (found) {
if (searchText.length) {
startpos = option.html.search(zregex);
text = option.html.substr(0, startpos + searchText.length) + '</em>' + option.html.substr(startpos + searchText.length);
text = text.substr(0, startpos) + '<em>' + text.substr(startpos);
} else {
text = option.html;
}
if ($("#" + result_id).html !== text) {
$("#" + result_id).html(text);
}
this.result_activate($("#" + result_id));
if (option.group_array_index != null) {
$("#" + this.results_data[option.group_array_index].dom_id).show();
}
} else {
if (this.result_highlight && result_id === this.result_highlight.attr('id')) {
this.result_clear_highlight();
}
this.result_deactivate($("#" + result_id));
}
}
}
}
if (results < 1 && searchText.length) {
return this.no_results(searchText);
} else {
return this.winnow_results_set_highlight();
}
};
Chosen.prototype.winnow_results_clear = function() {
var li, lis, _i, _len, _results;
this.search_field.val("");
lis = this.search_results.find("li");
_results = [];
for (_i = 0, _len = lis.length; _i < _len; _i++) {
li = lis[_i];
li = $(li);
_results.push(li.hasClass("group-result") ? li.show() : !this.is_multiple || !li.hasClass("result-selected") ? this.result_activate(li) : void 0);
}
return _results;
};
Chosen.prototype.winnow_results_set_highlight = function() {
var do_high;
if (!this.result_highlight) {
do_high = this.search_results.find(".active-result").first();
if (do_high) {
return this.result_do_highlight(do_high);
}
}
};
Chosen.prototype.no_results = function(terms) {
var no_results_html;
no_results_html = $('<li class="no-results">No results match "<span></span>"</li>');
no_results_html.find("span").first().html(terms);
return this.search_results.append(no_results_html);
};
Chosen.prototype.no_results_clear = function() {
return this.search_results.find(".no-results").remove();
};
Chosen.prototype.keydown_arrow = function() {
var first_active, next_sib;
if (!this.result_highlight) {
first_active = this.search_results.find("li.active-result").first();
if (first_active) {
this.result_do_highlight($(first_active));
}
} else if (this.results_showing) {
next_sib = this.result_highlight.nextAll("li.active-result").first();
if (next_sib) {
this.result_do_highlight(next_sib);
}
}
if (!this.results_showing) {
return this.results_show();
}
};
Chosen.prototype.keyup_arrow = function() {
var prev_sibs;
if (!this.results_showing && !this.is_multiple) {
return this.results_show();
} else if (this.result_highlight) {
prev_sibs = this.result_highlight.prevAll("li.active-result");
if (prev_sibs.length) {
return this.result_do_highlight(prev_sibs.first());
} else {
if (this.choices > 0) {
this.results_hide();
}
return this.result_clear_highlight();
}
}
};
Chosen.prototype.keydown_backstroke = function() {
if (this.pending_backstroke) {
this.choice_destroy(this.pending_backstroke.find("a").first());
return this.clear_backstroke();
} else {
this.pending_backstroke = this.search_container.siblings("li.search-choice").last();
return this.pending_backstroke.addClass("search-choice-focus");
}
};
Chosen.prototype.clear_backstroke = function() {
if (this.pending_backstroke) {
this.pending_backstroke.removeClass("search-choice-focus");
}
return this.pending_backstroke = null;
};
Chosen.prototype.keyup_checker = function(evt) {
var stroke, _ref;
stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
this.search_field_scale();
switch (stroke) {
case 8:
if (this.is_multiple && this.backstroke_length < 1 && this.choices > 0) {
return this.keydown_backstroke();
} else if (!this.pending_backstroke) {
this.result_clear_highlight();
return this.results_search();
}
break;
case 13:
evt.preventDefault();
if (this.results_showing) {
return this.result_select();
}
break;
case 27:
if (this.results_showing) {
return this.results_hide();
}
break;
case 9:
case 38:
case 40:
case 16:
break;
default:
return this.results_search();
}
};
Chosen.prototype.keydown_checker = function(evt) {
var stroke, _ref;
stroke = (_ref = evt.which) != null ? _ref : evt.keyCode;
this.search_field_scale();
if (stroke !== 8 && this.pending_backstroke) {
this.clear_backstroke();
}
switch (stroke) {
case 8:
this.backstroke_length = this.search_field.val().length;
break;
case 9:
this.mouse_on_container = false;
break;
case 13:
evt.preventDefault();
break;
case 38:
evt.preventDefault();
this.keyup_arrow();
break;
case 40:
this.keydown_arrow();
break;
}
};
Chosen.prototype.search_field_scale = function() {
var dd_top, div, h, style, style_block, styles, w, _i, _len;
if (this.is_multiple) {
h = 0;
w = 0;
style_block = "position:absolute; left: -1000px; top: -1000px; display:none;";
styles = ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 'text-transform', 'letter-spacing'];
for (_i = 0, _len = styles.length; _i < _len; _i++) {
style = styles[_i];
style_block += style + ":" + this.search_field.css(style) + ";";
}
div = $('<div />', {
'style': style_block
});
div.text(this.search_field.val());
$('body').append(div);
w = div.width() + 25;
div.remove();
if (w > this.f_width - 10) {
w = this.f_width - 10;
}
this.search_field.css({
'width': w + 'px'
});
dd_top = this.container.height();
return this.dropdown.css({
"top": dd_top + "px"
});
}
};
Chosen.prototype.generate_field_id = function() {
var new_id;
new_id = this.generate_random_id();
this.form_field.id = new_id;
return new_id;
};
Chosen.prototype.generate_random_id = function() {
var string;
string = "sel" + this.generate_random_char() + this.generate_random_char() + this.generate_random_char();
while ($("#" + string).length > 0) {
string += this.generate_random_char();
}
return string;
};
Chosen.prototype.generate_random_char = function() {
var chars, newchar, rand;
chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXTZ";
rand = Math.floor(Math.random() * chars.length);
return newchar = chars.substring(rand, rand + 1);
};
return Chosen;
})();
get_side_border_padding = function(elmt) {
var side_border_padding;
return side_border_padding = elmt.outerWidth() - elmt.width();
};
root.get_side_border_padding = get_side_border_padding;
}).call(this);
(function() {
var SelectParser;
SelectParser = (function() {
function SelectParser() {
this.options_index = 0;
this.parsed = [];
}
SelectParser.prototype.add_node = function(child) {
if (child.nodeName === "OPTGROUP") {
return this.add_group(child);
} else {
return this.add_option(child);
}
};
SelectParser.prototype.add_group = function(group) {
var group_position, option, _i, _len, _ref, _results;
group_position = this.parsed.length;
this.parsed.push({
array_index: group_position,
group: true,
label: group.label,
children: 0,
disabled: group.disabled
});
_ref = group.childNodes;
_results = [];
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
option = _ref[_i];
_results.push(this.add_option(option, group_position, group.disabled));
}
return _results;
};
SelectParser.prototype.add_option = function(option, group_position, group_disabled) {
if (option.nodeName === "OPTION") {
if (option.text !== "") {
if (group_position != null) {
this.parsed[group_position].children += 1;
}
this.parsed.push({
array_index: this.parsed.length,
options_index: this.options_index,
value: option.value,
text: option.text,
html: option.innerHTML,
selected: option.selected,
disabled: group_disabled === true ? group_disabled : option.disabled,
group_array_index: group_position
});
} else {
this.parsed.push({
array_index: this.parsed.length,
options_index: this.options_index,
empty: true
});
}
return this.options_index += 1;
}
};
return SelectParser;
})();
SelectParser.select_to_array = function(select) {
var child, parser, _i, _len, _ref;
parser = new SelectParser();
_ref = select.childNodes;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
child = _ref[_i];
parser.add_node(child);
}
return parser.parsed;
};
this.SelectParser = SelectParser;
}).call(this);

View File

@ -4,7 +4,6 @@ django-nose==0.1.2
django-mailer
django-registration==0.7
kombu
nova-adminclient
python-cloudfiles
python-dateutil
routes
@ -22,6 +21,6 @@ coverage
bzr+https://launchpad.net/glance#egg=glance
bzr+https://launchpad.net/quantum#egg=quantum
-e git://github.com/jacobian/openstack.compute.git#egg=openstack
-e git://github.com/cloudbuilders/openstackx.git#egg=openstackx
-e git+https://github.com/jacobian/openstack.compute.git#egg=openstack
-e git+https://github.com/cloudbuilders/openstackx.git#egg=openstackx
-e git://github.com/rackspace/python-novaclient.git#egg=python-novaclient