Merging Images and Snapshots into a single panel.

Change-Id: I897c6930a45b546dc3a742a4baf735fae4c7fbfd
This commit is contained in:
jakedahn
2011-12-12 17:18:41 -08:00
parent 97dbcb2ba8
commit e950745df6
36 changed files with 468 additions and 199 deletions

View File

@@ -22,9 +22,10 @@ import horizon
class Nova(horizon.Dashboard):
name = "Dashboard"
slug = "nova"
panels = {_("Manage Compute"): ('overview', 'instances_and_volumes',
'images', 'snapshots',
'access_and_security',),
panels = {_("Manage Compute"): ('overview',
'instances_and_volumes',
'access_and_security',
'images_and_snapshots'),
_("Network"): ('networks',),
_("Object Store"): ('containers',)}
default_panel = 'overview'

View File

@@ -29,7 +29,7 @@ from horizon import api
from horizon import test
IMAGES_INDEX_URL = reverse('horizon:nova:images:index')
IMAGES_INDEX_URL = reverse('horizon:nova:images_and_snapshots:images:index')
class FakeQuota:
@@ -67,77 +67,6 @@ class ImageViewTests(test.BaseViewTests):
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(self):
self.mox.StubOutWithMock(api, 'image_list_detailed')
api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(self.images)
self.mox.StubOutWithMock(api, 'tenant_quota_get')
api.tenant_quota_get(IsA(http.HttpRequest), self.TEST_TENANT) \
.AndReturn({})
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
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(IMAGES_INDEX_URL)
self.assertTemplateUsed(res, 'nova/images/index.html')
self.assertIn('images', res.context)
images = res.context['images']
self.assertEqual(len(images), 1)
self.assertEqual(images[0].name, 'visibleImage')
def test_index_no_images(self):
self.mox.StubOutWithMock(api, 'image_list_detailed')
api.image_list_detailed(IsA(http.HttpRequest)).AndReturn([])
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(IMAGES_INDEX_URL)
self.assertTemplateUsed(res, 'nova/images/index.html')
def test_index_client_conn_error(self):
self.mox.StubOutWithMock(api, 'image_list_detailed')
exception = glance_exception.ClientConnectionError('clientConnError')
api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(IMAGES_INDEX_URL)
self.assertTemplateUsed(res,
'nova/images/index.html')
def test_index_glance_error(self):
self.mox.StubOutWithMock(api, 'image_list_detailed')
exception = glance_exception.GlanceException('glanceError')
api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(IMAGES_INDEX_URL)
self.assertTemplateUsed(res, 'nova/images/index.html')
def test_launch_get(self):
IMAGE_ID = '1'
@@ -161,10 +90,12 @@ class ImageViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:images:launch',
args=[IMAGE_ID]))
res = self.client.get(
reverse('horizon:nova:images_and_snapshots:images:launch',
args=[IMAGE_ID]))
self.assertTemplateUsed(res, 'nova/images/launch.html')
self.assertTemplateUsed(res,
'nova/images_and_snapshots/images/launch.html')
image = res.context['image']
self.assertEqual(image.name, self.visibleImage.name)
@@ -232,9 +163,10 @@ class ImageViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:images:launch',
args=[IMAGE_ID]),
form_data)
res = self.client.post(
reverse('horizon:nova:images_and_snapshots:images:launch',
args=[IMAGE_ID]),
form_data)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:instances_and_volumes:instances:index'))
@@ -263,10 +195,12 @@ class ImageViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:images:launch',
args=[IMAGE_ID]))
res = self.client.get(
reverse('horizon:nova:images_and_snapshots:images:launch',
args=[IMAGE_ID]))
self.assertTemplateUsed(res, 'nova/images/launch.html')
self.assertTemplateUsed(res,
'nova/images_and_snapshots/images/launch.html')
form = res.context['form']
@@ -297,10 +231,12 @@ class ImageViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:images:launch',
args=[IMAGE_ID]))
res = self.client.get(
reverse('horizon:nova:images_and_snapshots:images:launch',
args=[IMAGE_ID]))
self.assertTemplateUsed(res, 'nova/images/launch.html')
self.assertTemplateUsed(res,
'nova/images_and_snapshots/images/launch.html')
form = res.context['form']
@@ -362,7 +298,9 @@ class ImageViewTests(test.BaseViewTests):
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
url = reverse('horizon:nova:images:launch', args=[IMAGE_ID])
url = reverse('horizon:nova:images_and_snapshots:images:launch',
args=[IMAGE_ID])
res = self.client.post(url, form_data)
self.assertTemplateUsed(res, 'nova/images/launch.html')
self.assertTemplateUsed(res,
'nova/images_and_snapshots/images/launch.html')

View File

@@ -18,10 +18,10 @@
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls.defaults import *
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('horizon.dashboards.nova.images.views',
urlpatterns = patterns('horizon.dashboards.nova.images_and_snapshots.images.views',
url(r'^$', 'index', name='index'),
url(r'^(?P<image_id>[^/]+)/launch/$', 'launch', name='launch'),
url(r'^(?P<image_id>[^/]+)/update/$', 'update', name='update'))

View File

@@ -33,8 +33,8 @@ from novaclient import exceptions as novaclient_exceptions
from openstackx.api import exceptions as api_exceptions
from horizon import api
from horizon.dashboards.nova.images.forms import (UpdateImageForm,
LaunchForm, DeleteImage)
from horizon.dashboards.nova.images_and_snapshots.images.forms import \
(UpdateImageForm, LaunchForm, DeleteImage)
LOG = logging.getLogger(__name__)
@@ -67,11 +67,11 @@ def index(request):
context = {'delete_form': DeleteImage(), 'images': images}
if images:
quotas = api.tenant_quota_get(request, request.user.tenant_id)
context['quotas'] = quotas
return shortcuts.render(request, 'nova/images/index.html', context)
return shortcuts.render(request,
'nova/images_and_snapshots/images/index.html', {
'delete_form': DeleteImage(),
'quotas': quotas,
'images': images})
@login_required
@@ -131,7 +131,7 @@ def launch(request, image_id):
return handled
return shortcuts.render(request,
'nova/images/launch.html', {
'nova/images_and_snapshots/images/launch.html', {
'image': image,
'form': form,
'quotas': quotas})
@@ -164,4 +164,4 @@ def update(request, image_id):
context = {'form': form, "image": image}
return shortcuts.render(request, 'nova/images/update.html', context)
return shortcuts.render(request, 'nova/images_and_snapshots/images/update.html', context)

View File

@@ -0,0 +1,27 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nebula, Inc.
# Copyright 2011 OpenStack LLC
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import horizon
from horizon.dashboards.nova import dashboard
class ImagesAndSnapshots(horizon.Panel):
name = "Images & Snapshots"
slug = 'images_and_snapshots'
dashboard.Nova.register(ImagesAndSnapshots)

View File

@@ -49,7 +49,7 @@ class CreateSnapshot(forms.SelfHandlingForm):
messages.info(request,
_('Snapshot "%(name)s" created for instance "%(inst)s"') %
{"name": data['name'], "inst": instance.name})
return shortcuts.redirect('horizon:nova:snapshots:index')
return shortcuts.redirect('horizon:nova:images_and_snapshots:snapshots:index')
except api_exceptions.ApiException, e:
msg = _('Error Creating Snapshot: %s') % e.message
LOG.exception(msg)

View File

@@ -65,59 +65,6 @@ class SnapshotsViewTests(test.BaseViewTests):
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(self):
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
api.snapshot_list_detailed(IsA(http.HttpRequest)).\
AndReturn(self.images)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
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('horizon:nova:snapshots:index'))
self.assertTemplateUsed(res, 'nova/snapshots/index.html')
self.assertIn('images', res.context)
images = res.context['images']
self.assertEqual(len(images), 1)
def test_index_client_conn_error(self):
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
exception = glance_exception.ClientConnectionError('clientConnError')
api.snapshot_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:snapshots:index'))
self.assertTemplateUsed(res, 'nova/snapshots/index.html')
def test_index_glance_error(self):
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
exception = glance_exception.GlanceException('glanceError')
api.snapshot_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:snapshots:index'))
self.assertTemplateUsed(res, 'nova/snapshots/index.html')
def test_create_snapshot_get(self):
self.mox.StubOutWithMock(api, 'server_get')
api.server_get(IsA(http.HttpRequest),
@@ -125,10 +72,12 @@ class SnapshotsViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:snapshots:create',
args=[self.good_server.id]))
res = self.client.get(
reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[self.good_server.id]))
self.assertTemplateUsed(res, 'nova/snapshots/create.html')
self.assertTemplateUsed(res,
'nova/images_and_snapshots/snapshots/create.html')
def test_create_snapshot_get_with_invalid_status(self):
self.mox.StubOutWithMock(api, 'server_get')
@@ -137,8 +86,9 @@ class SnapshotsViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:snapshots:create',
args=[self.bad_server.id]))
res = self.client.get(
reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[self.bad_server.id]))
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:instances_and_volumes:instances:index'))
@@ -151,8 +101,9 @@ class SnapshotsViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.get(reverse('horizon:nova:snapshots:create',
args=[self.good_server.id]))
res = self.client.get(
reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[self.good_server.id]))
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:instances_and_volumes:instances:index'))
@@ -179,12 +130,13 @@ class SnapshotsViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:snapshots:create',
args=[self.good_server.id]),
formData)
res = self.client.post(
reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[self.good_server.id]),
formData)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:snapshots:index'))
reverse('horizon:nova:images_and_snapshots:snapshots:index'))
def test_create_snapshot_post_exception(self):
SNAPSHOT_NAME = 'snappy'
@@ -206,10 +158,11 @@ class SnapshotsViewTests(test.BaseViewTests):
self.mox.ReplayAll()
res = self.client.post(reverse('horizon:nova:snapshots:create',
args=[self.good_server.id]),
formData)
res = self.client.post(
reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[self.good_server.id]),
formData)
self.assertRedirectsNoFollow(res,
reverse('horizon:nova:snapshots:create',
args=[self.good_server.id]))
reverse('horizon:nova:images_and_snapshots:snapshots:create',
args=[self.good_server.id]))

View File

@@ -21,6 +21,6 @@
from django.conf.urls.defaults import patterns, url
urlpatterns = patterns('horizon.dashboards.nova.snapshots.views',
urlpatterns = patterns('horizon.dashboards.nova.images_and_snapshots.snapshots.views',
url(r'^$', 'index', name='index'),
url(r'^(?P<instance_id>[^/]+)/create', 'create', name='create'))

View File

@@ -36,7 +36,8 @@ from openstackx.api import exceptions as api_exceptions
from horizon import api
from horizon import forms
from horizon.dashboards.nova.snapshots.forms import CreateSnapshot
from horizon.dashboards.nova.images_and_snapshots.snapshots.forms import \
CreateSnapshot
LOG = logging.getLogger(__name__)
@@ -58,7 +59,7 @@ def index(request):
messages.error(request, msg)
return shortcuts.render(request,
'nova/snapshots/index.html',
'nova/images_and_snapshots/snapshots/index.html',
{'images': images})
@@ -89,6 +90,6 @@ def create(request, instance_id):
'horizon:nova:instances_and_volumes:instances:index')
return shortcuts.render(request,
'nova/snapshots/create.html',
'nova/images_and_snapshots/snapshots/create.html',
{'instance': instance,
'create_form': form})

View File

@@ -0,0 +1,157 @@
# 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 Nebula, Inc.
# Copyright 2011 OpenStack LLC
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django import http
from django.contrib import messages
from django.core.urlresolvers import reverse
from glance.common import exception as glance_exception
from mox import IsA
from horizon import api
from horizon import test
INDEX_URL = reverse('horizon:nova:images_and_snapshots:index')
class ImagesAndSnapshotsTests(test.BaseViewTests):
def setUp(self):
super(ImagesAndSnapshotsTests, self).setUp()
snapshot_dict = {'name': 'snapshot',
'container_format': 'ami',
'id': 3}
snapshot = api.Image(snapshot_dict)
self.snapshots = [snapshot, ]
image_dict = {'name': 'visibleImage',
'container_format': 'novaImage'}
self.visibleImage = api.Image(image_dict)
self.visibleImage.id = '1'
image_dict = {'name': 'invisibleImage',
'container_format': 'aki'}
self.invisibleImage = api.Image(image_dict)
self.invisibleImage.id = '2'
flavor = api.Flavor(None)
flavor.id = 1
flavor.name = 'm1.massive'
flavor.vcpus = 1000
flavor.disk = 1024
flavor.ram = 10000
self.flavors = (flavor,)
self.images = (self.visibleImage, self.invisibleImage)
keypair = api.KeyPair(None)
keypair.name = 'keyName'
self.keypairs = (keypair,)
security_group = api.SecurityGroup(None)
security_group.name = 'default'
self.security_groups = (security_group,)
def test_index(self):
self.mox.StubOutWithMock(api, 'image_list_detailed')
api.image_list_detailed(IsA(http.HttpRequest)).AndReturn(self.images)
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
api.snapshot_list_detailed(IsA(http.HttpRequest)).AndReturn(
self.snapshots)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'tenant_quota_get')
api.tenant_quota_get(IsA(http.HttpRequest),
self.TEST_TENANT).AndReturn({})
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res,
'nova/images_and_snapshots/index.html')
self.assertIn('images', res.context)
images = res.context['images']
self.assertEqual(len(images), 1)
self.assertEqual(images[0].name, 'visibleImage')
def test_index_no_images(self):
self.mox.StubOutWithMock(api, 'snapshot_list_detailed')
api.snapshot_list_detailed(IsA(http.HttpRequest)).\
AndReturn(self.snapshots)
self.mox.StubOutWithMock(api, 'flavor_list')
api.flavor_list(IsA(http.HttpRequest)).AndReturn(self.flavors)
self.mox.StubOutWithMock(api, 'security_group_list')
api.security_group_list(IsA(http.HttpRequest)).\
AndReturn(self.security_groups)
self.mox.StubOutWithMock(api, 'keypair_list')
api.keypair_list(IsA(http.HttpRequest)).AndReturn(self.keypairs)
self.mox.StubOutWithMock(api, 'image_list_detailed')
api.image_list_detailed(IsA(http.HttpRequest)).AndReturn([])
self.mox.StubOutWithMock(api, 'tenant_quota_get')
api.tenant_quota_get(IsA(http.HttpRequest), self.TEST_TENANT) \
.AndReturn({})
self.mox.StubOutWithMock(messages, 'info')
messages.info(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html')
def test_index_client_conn_error(self):
self.mox.StubOutWithMock(api, 'image_list_detailed')
exception = glance_exception.ClientConnectionError('clientConnError')
api.image_list_detailed(IsA(http.HttpRequest)).AndRaise(exception)
self.mox.StubOutWithMock(api, 'tenant_quota_get')
api.tenant_quota_get(IsA(http.HttpRequest), self.TEST_TENANT) \
.AndReturn({})
self.mox.StubOutWithMock(messages, 'error')
messages.error(IsA(http.HttpRequest), IsA(basestring))
self.mox.ReplayAll()
res = self.client.get(INDEX_URL)
self.assertTemplateUsed(res, 'nova/images_and_snapshots/index.html')

View File

@@ -0,0 +1,34 @@
# 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 Nebula, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.conf.urls.defaults import *
import horizon
from horizon.dashboards.nova.images_and_snapshots.images import urls\
as image_urls
from horizon.dashboards.nova.images_and_snapshots.snapshots import urls\
as snapshot_urls
urlpatterns = patterns('horizon.dashboards.nova.images_and_snapshots',
url(r'^$', 'views.index', name='index'),
url(r'', include(image_urls, namespace='images')),
url(r'', include(snapshot_urls, namespace='snapshots')),
)

View File

@@ -0,0 +1,77 @@
# 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 Nebula, Inc.
# Copyright 2011 Openstack LLC
#
# 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 Images and Snapshots.
"""
import logging
from django import shortcuts
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.utils.translation import ugettext as _
from glance.common import exception as glance_exception
from novaclient import exceptions as novaclient_exceptions
from openstackx.api import exceptions as api_exceptions
from horizon import api
from horizon.dashboards.nova.images_and_snapshots.images.forms import \
(UpdateImageForm, LaunchForm, DeleteImage)
LOG = logging.getLogger(__name__)
@login_required
def index(request):
for f in (DeleteImage, ):
unused, handled = f.maybe_handle(request)
if handled:
return handled
all_images = []
snapshots = []
try:
all_images = api.image_list_detailed(request)
snapshots = api.snapshot_list_detailed(request)
if not all_images:
messages.info(request, _("There are currently no images."))
except glance_exception.ClientConnectionError, e:
LOG.exception("Error connecting to glance")
messages.error(request, _("Error connecting to glance: %s") % str(e))
except glance_exception.Error, e:
LOG.exception("Error retrieving image list")
messages.error(request, _("Unable to fetch images: %s") % str(e))
except api_exceptions.ApiException, e:
msg = _("Unable to retrieve image info from glance: %s") % str(e)
LOG.exception(msg)
messages.error(request, msg)
images = [im for im in all_images
if im['container_format'] not in ['aki', 'ari']]
quotas = api.tenant_quota_get(request, request.user.tenant_id)
return shortcuts.render(request,
'nova/images_and_snapshots/index.html', {
'delete_form': DeleteImage(),
'quotas': quotas,
'images': images,
'snapshots': snapshots})

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% block page_header %}
{% url horizon:nova:images:index as refresh_link %}
{% url horizon:nova:images_and_snapshots:images:index as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "horizon/common/_page_header.html" with title=_("Containers") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}{% endblock %}
{% block form_action %}{% url horizon:nova:images:launch image_id %}{% endblock %}
{% block form_action %}{% url horizon:nova:images_and_snapshots:images:launch image_id %}{% endblock %}
{% block modal_id %}launch_image_{{ image_id }}{% endblock %}
@@ -48,5 +48,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{% trans "Launch Instance" %}" />
<a href="{% url horizon:nova:images:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:images_and_snapshots:images:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@@ -2,7 +2,7 @@
<div class="table_title">
<h3>Images and Snapshots</h3>
<h3>Images</h3>
<div class="table_actions">
<div class="images table_search">
<form action="#">
@@ -32,14 +32,14 @@
<td>{{image.properties.image_type|default:"Image"}}</td>
<td>{{image.status|capfirst}}</td>
<td>
<a class="btn small primary" data-controls-modal="launch_image_{{image.id}}" data-backdrop="static" href="{% url horizon:nova:images:launch image.id %}">{% trans "Launch" %}</a>
<a class="btn small primary" data-controls-modal="launch_image_{{image.id}}" data-backdrop="static" href="{% url horizon:nova:images_and_snapshots:images:launch image.id %}">{% trans "Launch" %}</a>
</td>
<td id="name_{{image.name}}" class="actions">
{% if image.owner == request.user.tenant_id %}
<a class="more-actions" href="#">More</a>
<ul>
<li><a class='btn small' href="{% url horizon:nova:images:update image.id %}">{% trans "Edit" %}</a></li>
<li class="form">{% include "nova/images/_delete.html" with form=delete_form %}</li>
<li><a class='btn small' href="{% url horizon:nova:images_and_snapshots:images:update image.id %}">{% trans "Edit" %}</a></li>
<li class="form">{% include "nova/images_and_snapshots/images/_delete.html" with form=delete_form %}</li>
</ul>
{% endif %}
</td>
@@ -49,5 +49,5 @@
{% for image in images %}
{% launch_form request request.user.tenant_id image.id as launch_form %}
{% include 'nova/images/_launch.html' with form=launch_form image_id=image.id hide=True %}
{% include 'nova/images_and_snapshots/images/_launch.html' with form=launch_form image_id=image.id hide=True %}
{% endfor %}

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}update_image_form{% endblock %}
{% block form_action %}{% url horizon:nova:images:update image.id %}{% endblock %}
{% block form_action %}{% url horizon:nova:images_and_snapshots:images:update image.id %}{% endblock %}
{% block modal-header %}Update Image{% endblock %}
@@ -20,5 +20,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{%trans "Update Image"%}" />
<a href="{% url horizon:nova:images:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:images_and_snapshots:images:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@@ -2,14 +2,14 @@
{% load i18n %}
{% block page_header %}
{% url horizon:nova:images:index as refresh_link %}
{% url horizon:nova:images_and_snapshots:images:index as refresh_link %}
{# to make searchable false, just remove it from the include statement #}
{% include "horizon/common/_page_header.html" with title=_("Images") refresh_link=refresh_link searchable="true" %}
{% endblock page_header %}
{% block dash_main %}
{% if images %}
{% include 'nova/images/_list.html' %}
{% include 'nova/images_and_snapshots/images/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no images."%}</p>

View File

@@ -13,6 +13,6 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/images/_launch.html' with image_id=image.id %}
{% include 'nova/images_and_snapshots/images/_launch.html' with image_id=image.id %}
{% endblock %}

View File

@@ -6,5 +6,5 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/images/_update.html' %}
{% include 'nova/images_and_snapshots/images/_update.html' %}
{% endblock %}

View File

@@ -0,0 +1,27 @@
{% extends 'nova/base.html' %}
{% load i18n %}
{% block page_header %}
{% include "horizon/common/_page_header.html" with title=_("Images &amp; Snapshots") %}
{% endblock page_header %}
{% block dash_main %}
{% if images %}
{% include 'nova/images_and_snapshots/images/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no images."%}</p>
</div>
{% endif %}
{% if snapshots %}
{% include 'nova/images_and_snapshots/snapshots/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no snapshots. You can create snapshots from running instances."%}</p>
<div class="alert-actions">
<a class="btn small primary" href='{% url horizon:nova:instances_and_volumes:instances:index %}'>{% trans "View Running Instances" %}</a>
</div>
</div>
{% endif %}
{% endblock %}

View File

@@ -2,7 +2,7 @@
{% load i18n %}
{% block form_id %}create_snapshot_form{% endblock %}
{% block form_action %}{% url horizon:nova:snapshots:create instance.id %}{% endblock %}
{% block form_action %}{% url horizon:nova:images_and_snapshots:snapshots:create instance.id %}{% endblock %}
{% block modal_id %}create_snapshot_modal{% endblock %}
{% block modal-header %}{%trans "Create Snapshot"%}{% endblock %}
@@ -21,5 +21,5 @@
{% block modal-footer %}
<input class="btn primary pull-right" type="submit" value="{%trans "Create Snapshot"%}" />
<a href="{% url horizon:nova:snapshots:index %}" class="btn secondary cancel close">Cancel</a>
<a href="{% url horizon:nova:images_and_snapshots:snapshots:index %}" class="btn secondary cancel close">Cancel</a>
{% endblock %}

View File

@@ -0,0 +1,53 @@
{% load i18n parse_date launch_form %}
<div class="table_title">
<h3>Snapshots</h3>
<div class="table_actions">
<div class="images table_search">
<form action="#">
<input class="span3" type="text">
</form>
</div>
<a class="inspect" href="#">{% trans "inspect" %}</a>
</div>
</div>
<table id="images" class="zebra-striped sortable">
<thead>
<tr>
<th></th>
<th>{% trans "Name" %}</th>
<th>{% trans "Type" %}</th>
<th>{% trans "Status" %}</th>
<th colspan="2">{% trans "Actions" %}</th>
</tr>
</thead>
{% for image in snapshots %}
<tr class="{% cycle 'odd' 'even' %}">
<td class="select">
<input type="checkbox" name="image_{{image.id}}" value="image_{{image.id}}" id="image_select_{{image.id}}" />
</td>
<td>{{image.name}}</td>
<td>{{image.properties.image_type|default:"Image"}}</td>
<td>{{image.status|capfirst}}</td>
<td>
<a class="btn small primary" data-controls-modal="launch_image_{{image.id}}" data-backdrop="static" href="{% url horizon:nova:images_and_snapshots:images:launch image.id %}">{% trans "Launch" %}</a>
</td>
<td id="name_{{image.name}}" class="actions">
{% if image.owner == request.user.tenant_id %}
<a class="more-actions" href="#">View</a>
<ul>
<li><a class='btn small' href="{% url horizon:nova:images_and_snapshots:images:update image.id %}">{% trans "Edit" %}</a></li>
<li class="form">{% include "nova/images_and_snapshots/images/_delete.html" with form=delete_form %}</li>
</ul>
{% endif %}
</td>
</tr>
{% endfor %}
</table>
{% for image in snapshots %}
{% launch_form request request.user.tenant_id image.id as launch_form %}
{% include 'nova/images_and_snapshots/images/_launch.html' with form=launch_form image_id=image.id hide=True %}
{% endfor %}

View File

@@ -6,7 +6,7 @@
{% endblock page_header %}
{% block dash_main %}
{% include 'nova/snapshots/_create.html' with form=create_form %}
{% include 'nova/images_and_snapshots/snapshots/_create.html' with form=create_form %}
{% endblock %}

View File

@@ -9,7 +9,7 @@
{% block dash_main %}
{% if images %}
{% include 'nova/images/_list.html' %}
{% include 'nova/images_and_snapshots/images/_list.html' %}
{% else %}
<div class="alert-message block-message info">
<p><strong>{% trans "Info: "%}</strong>{% trans "There are currently no snapshots. You can create snapshots from running instances."%}</p>

View File

@@ -5,7 +5,7 @@
<div class="table_title">
<h3>{% trans "My Instances" %}</h3>
<div class="table_actions">
<a class="btn small primary" href='{% url horizon:nova:images:index %}'>{% trans "Launch Instance" %}</a>
<a class="btn small primary" href='{% url horizon:nova:images_and_snapshots:images:index %}'>{% trans "Launch Instance" %}</a>
<div class="instances table_search">
<form action="#">
<input class="span3" type="text">
@@ -44,7 +44,7 @@
<li><a class="btn small" target='_blank' href='{% url horizon:nova:instances_and_volumes:instances:vnc instance.id %}'>{% trans 'VNC Console'%}</a></li>
<li><a class='btn small' target='_blank' href='{% url horizon:nova:instances_and_volumes:instances:console instance.id %}'>{% trans 'Log'%}</a></li>
<li><a class='btn small' href='{% url horizon:nova:instances_and_volumes:instances:update instance.id %}'>{% trans 'Edit'%}</a></li>
<li><a class='btn small' href='{% url horizon:nova:snapshots:create instance.id %}'>{% trans 'Snapshot'%}</a></li>
<li><a class='btn small' href='{% url horizon:nova:images_and_snapshots:snapshots:create instance.id %}'>{% trans 'Snapshot'%}</a></li>
<li>{% include 'nova/instances_and_volumes/instances/_reboot.html' with form=reboot_form %}</li>
<li>{% include 'nova/instances_and_volumes/instances/_terminate.html' with form=terminate_form %}</li>
</ul>

View File

@@ -3,6 +3,6 @@
<div class="alert-message block-message info">
<p><strong>{% trans "Info: " %}</strong>{% trans "There are no instances running. Launch an instance from the Images Page." %}</p>
<div class="alert-actions">
<a class="btn small primary" href='{% url horizon:nova:images:index %}'>{% trans "Images Page" %}</a>
<a class="btn small primary" href='{% url horizon:nova:images_and_snapshots:images:index %}'>{% trans "Images Page" %}</a>
</div>
</div>

View File

@@ -1,7 +1,7 @@
{% extends 'nova/instances_and_volumes/instances/_form.html' %}
{% load i18n %}
{% block form_action %}{% url horizon:nova:snapshots:create instance.id %}{% endblock %}
{% block form_action %}{% url horizon:nova:images_and_snapshots:snapshots:create instance.id %}{% endblock %}
{% block modal-footer %}
<div class="modal-footer">

View File

@@ -12,7 +12,7 @@
{% include 'syspanel/instances/_list.html' %}
{% else %}
<div class="alert-message block-message info">
{% url horizon:nova:images:index as dash_image_url%}
{% url horizon:nova:images_and_snapshots:images:index as dash_image_url%}
<p><strong>{% trans "Info: "%}</strong>{% blocktrans %}There are currently no instances. You can launch an instance from the <a class="btn small" href='{{dash_image_url}}'>Images Page.</a>{% endblocktrans %}</p>
</div>
{% endif %}

View File

@@ -28,9 +28,10 @@ from django import template
from openstackx.api import exceptions as api_exceptions
from novaclient import exceptions as novaclient_exceptions
from horizon import api
from horizon.dashboards.nova.images.forms import LaunchForm
from horizon.utils import assignment_tag
from horizon.dashboards.nova.images_and_snapshots.images.forms import \
LaunchForm
from horizon.utils import assignment_tag
LOG = logging.getLogger(__name__)
register = template.Library()