Fixes inconsistent Router Details page

This patch addresses the stylistic inconsistencies in the Router details
pages, by changing it into a tabbed detail page

- Fixed bug on the Interfaces table causing the Name field to be empty
- Overview page has also been restyled to more clearly show information
  and link to relevant network
- Increased inheritance where possible, to reduce duplicate code
- Deleted unused files/functions. These can always be added as required
  later, but there is no need for unnecessary code bloat

Co-Authored-By: Sam Betts <sam@code-smash.net>

Change-Id: Ifbdfbf46127e9445395207c547c2b81ea9459dac
Closes-Bug: 1378895
This commit is contained in:
Rob 2015-01-20 16:41:46 -08:00 committed by Rob Cresswell
parent 4fdc42cf63
commit 14b2e700e8
13 changed files with 88 additions and 122 deletions

View File

@ -1,29 +0,0 @@
# Copyright 2013, Big Switch Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from django.utils.translation import ugettext_lazy as _
from horizon import tables
class RouterRulesTable(tables.DataTable):
source = tables.Column("source", verbose_name=_("Source CIDR"))
destination = tables.Column("destination",
verbose_name=_("Destination CIDR"))
action = tables.Column("action", verbose_name=_("Action"))
nexthops = tables.Column("nexthops", verbose_name=_("Next Hops"))
class Meta(object):
name = "routerrules"
verbose_name = _("Router Rules")

View File

@ -27,3 +27,6 @@ class PortsTable(routers_tables.PortsTable):
class Meta(object):
name = "interfaces"
verbose_name = _("Interfaces")
table_actions = (routers_tables.AddInterface,
routers_tables.RemoveInterface)
row_actions = (routers_tables.RemoveInterface,)

View File

@ -12,16 +12,14 @@
# License for the specific language governing permissions and limitations
# under the License.
from openstack_dashboard.dashboards.admin.\
routers.extensions.routerrules import tables as rrtbl
from openstack_dashboard.dashboards.admin.routers.ports import tables as ptbl
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
import tabs as rr_tabs
from openstack_dashboard.dashboards.project.routers import tabs as r_tabs
class RouterRulesTab(rr_tabs.RouterRulesTab):
table_classes = (rrtbl.RouterRulesTable,)
class OverviewTab(r_tabs.OverviewTab):
template_name = "project/routers/_detail_overview.html"
class InterfacesTab(r_tabs.InterfacesTab):
@ -29,6 +27,6 @@ class InterfacesTab(r_tabs.InterfacesTab):
class RouterDetailTabs(r_tabs.RouterDetailTabs):
slug = "router_details"
tabs = (InterfacesTab, rr_tabs.RouterRulesTab)
tabs = (OverviewTab, InterfacesTab, rr_tabs.RulesGridTab,
rr_tabs.RouterRulesTab)
sticky = True

View File

@ -1,32 +0,0 @@
{% load i18n sizeformat parse_date %}
<h3>{% blocktrans with router_name=router.name %}Router Overview: {{ router_name }}{% endblocktrans %}</h3>
<div class="info detail">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ router.name|default:_("None") }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ router.id }}</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ router.tenant_id }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ router.status|capfirst }}</dd>
<dt>{% trans "Admin State" %}</dt>
<dd>{{ router.admin_state|default:_("Unknown") }}</dd>
{% if dvr_supported %}
<dt>{% trans "Distributed" %}</dt>
<dd>{{ router.distributed|yesno|capfirst }}</dd>
{% endif %}
{% if ha_supported %}
<dt>{% trans "High Availability Mode" %}</dt>
<dd>{{ router.ha|yesno|capfirst }}</dd>
{% endif %}
{% if router.external_gateway_info %}
<dt>{% trans "External Gateway Information" %}</dt>
<dd>
{% blocktrans with router_gw_info_network=router.external_gateway_info.network %}Connected External Network: {{ router_gw_info_network }}{% endblocktrans %}
</dd>
{% endif %}
</dl>
</div>

View File

@ -3,10 +3,9 @@
{% block title %}{% trans "Router Details" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{% include "admin/routers/_detail_overview.html" %}
{{ tab_group.render }}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
</div>
{% endblock %}

View File

@ -35,14 +35,15 @@ class RouterRulesTab(tabs.TableTab):
def allowed(self, request):
try:
getattr(self.tab_group.router, 'router_rules')
getattr(self.tab_group.kwargs['router'], 'router_rules')
return True
except Exception:
return False
def get_routerrules_data(self):
try:
routerrules = getattr(self.tab_group.router, 'router_rules')
routerrules = getattr(self.tab_group.kwargs['router'],
'router_rules')
except Exception:
routerrules = []
return [rulemanager.RuleObject(r) for r in routerrules]
@ -51,8 +52,8 @@ class RouterRulesTab(tabs.TableTab):
if request.POST['action'] == 'routerrules__resetrules':
kwargs['reset_rules'] = True
rulemanager.remove_rules(request, [], **kwargs)
self.tab_group.router = api.neutron.router_get(request,
kwargs['router_id'])
self.tab_group.kwargs['router'] = \
api.neutron.router_get(request, kwargs['router_id'])
class RulesGridTab(tabs.Tab):
@ -62,7 +63,7 @@ class RulesGridTab(tabs.Tab):
def allowed(self, request):
try:
getattr(self.tab_group.router, 'router_rules')
getattr(self.tab_group.kwargs['router'], 'router_rules')
return True
except Exception:
return False
@ -82,7 +83,7 @@ class RulesGridTab(tabs.Tab):
return data
def get_routerrulesgrid_data(self, rules):
ports = self.tab_group.ports
ports = self.tab_group.kwargs['ports']
networks = api.neutron.network_list_for_tenant(
self.request, self.request.user.tenant_id)
netnamemap = {}
@ -214,7 +215,8 @@ class RulesGridTab(tabs.Tab):
def get_routerrules_data(self, checksupport=False):
try:
routerrules = getattr(self.tab_group.router, 'router_rules')
routerrules = getattr(self.tab_group.kwargs['router'],
'router_rules')
supported = True
except Exception:
routerrules = []

View File

@ -14,14 +14,22 @@
from django.utils.translation import ugettext_lazy as _
from horizon import exceptions
from horizon import tabs
from openstack_dashboard import api
from openstack_dashboard.dashboards.project.routers.extensions.routerrules\
import tabs as rr_tabs
from openstack_dashboard.dashboards.project.routers.ports import tables as ptbl
class OverviewTab(tabs.Tab):
name = _("Overview")
slug = "overview"
template_name = "project/routers/_detail_overview.html"
def get_context_data(self, request):
return {"router": self.tab_group.kwargs['router']}
class InterfacesTab(tabs.TableTab):
table_classes = (ptbl.PortsTable,)
name = _("Interfaces")
@ -29,26 +37,11 @@ class InterfacesTab(tabs.TableTab):
template_name = "horizon/common/_detail_table.html"
def get_interfaces_data(self):
ports = self.tab_group.ports
return ports
return self.tab_group.kwargs['ports']
class RouterDetailTabs(tabs.TabGroup):
slug = "router_details"
tabs = (InterfacesTab, rr_tabs.RulesGridTab, rr_tabs.RouterRulesTab)
tabs = (OverviewTab, InterfacesTab, rr_tabs.RulesGridTab,
rr_tabs.RouterRulesTab)
sticky = True
def __init__(self, request, **kwargs):
rid = kwargs['router_id']
self.router = {}
if 'router' in kwargs:
self.router = kwargs['router']
else:
self.router = api.neutron.router_get(request, rid)
try:
self.ports = api.neutron.port_list(request, device_id=rid)
except Exception:
self.ports = []
msg = _('Unable to retrieve router details.')
exceptions.handle(request, msg)
super(RouterDetailTabs, self).__init__(request, **kwargs)

View File

@ -1,17 +1,15 @@
{% load i18n sizeformat parse_date %}
<h3>
{% blocktrans with router_name=router.name|default:_("None") %}Router Overview: {{ router_name }}{% endblocktrans %}
</h3>
<div class="info detail">
<div class="detail">
<dl class="dl-horizontal">
<dt>{% trans "Name" %}</dt>
<dd>{{ router.name|default:_("None") }}</dd>
<dt>{% trans "ID" %}</dt>
<dd>{{ router.id|default:_("None") }}</dd>
<dd>{{ router.id }}</dd>
<dt>{% trans "Project ID" %}</dt>
<dd>{{ router.tenant_id }}</dd>
<dt>{% trans "Status" %}</dt>
<dd>{{ router.status|default:_("Unknown") }}</dd>
<dd>{{ router.status|capfirst }}</dd>
<dt>{% trans "Admin State" %}</dt>
<dd>{{ router.admin_state|default:_("Unknown") }}</dd>
{% if dvr_supported %}
@ -22,11 +20,38 @@
<dt>{% trans "High Availability Mode" %}</dt>
<dd>{{ router.ha|yesno|capfirst }}</dd>
{% endif %}
{% if router.external_gateway_info %}
<dt>{% trans "External Gateway" %}</dt>
<dd>{% trans "Connected External Network:" %}
{{ router.external_gateway_info.network }}</dd>
</dl>
<h4>{% trans "External Gateway" %}</h4>
<hr class="header_rule">
<dl class="dl-horizontal">
<dt>{% trans "Network Name" %}</dt>
<dd>{{ router.external_gateway_info.network|default:_("None") }}</dd>
{% url 'horizon:admin:networks:detail' router.external_gateway_info.network_id as network_url %}
<dt>{% trans "Network ID" %}</dt>
<dd><a href="{{ network_url }}">{{ router.external_gateway_info.network_id|default:_("Unknown") }}</a></dd>
<dt>{% trans "External Fixed IPs" %}</dt>
<dd>
<ul>
{% for ip in router.external_gateway_info.external_fixed_ips %}
{% url 'horizon:project:networks:subnets:detail' ip.subnet_id as subnet_url %}
<li><strong>{% trans "Subnet ID" %}</strong> <a href="{{ subnet_url }}">{{ ip.subnet_id|default:_("Unknown") }}</a></li>
<li><strong>{% trans "IP Address" %}</strong> {{ ip.ip_address }}</li>
{% empty %}
{% trans "None" %}
{% endfor %}
</ul>
</dd>
<dt>{% trans "SNAT" %}</dt>
{% if router.external_gateway_info.enable_snat %}
<dd>{% trans "Enabled" %}</dd>
{% else %}
<dd>{% trans "Disabled" %}</dd>
{% endif %}
{% else %}
<dt>{% trans "External Gateway"%}</dt>
<dd>{% trans "None" %}</dd>
{% endif %}
</dl>
</div>

View File

@ -3,10 +3,9 @@
{% block title %}{% trans "Router Details" %}{% endblock %}
{% block main %}
<div class="row">
<div class="col-sm-12">
{% include "project/routers/_detail_overview.html" %}
{{ tab_group.render }}
<div class="row">
<div class="col-sm-12">
{{ tab_group.render }}
</div>
</div>
</div>
{% endblock %}

View File

@ -760,8 +760,6 @@ class RouterRuleTests(RouterMixin, test.TestCase):
params = {}
params['router_rules'] = rulemanager.format_for_api(
post_router['router_rules'])
api.neutron.router_get(IsA(http.HttpRequest),
pre_router.id).AndReturn(pre_router)
router_update = api.neutron.router_update(IsA(http.HttpRequest),
pre_router.id, **params)
router_update.AndReturn({'router': post_router})
@ -797,8 +795,6 @@ class RouterRuleTests(RouterMixin, test.TestCase):
router_update = api.neutron.router_update(IsA(http.HttpRequest),
pre_router.id, **params)
router_update.AndReturn({'router': post_router})
api.neutron.router_get(IsA(http.HttpRequest),
pre_router.id).AndReturn(post_router)
api.neutron.port_list(IsA(http.HttpRequest),
device_id=pre_router.id)\
.AndReturn([self.ports.first()])

View File

@ -125,6 +125,17 @@ class DetailView(tabs.TabbedTableView):
router.external_gateway_info['network'] = ext_net_id
return router
@memoized.memoized_method
def _get_ports(self):
try:
ports = api.neutron.port_list(self.request,
device_id=self.kwargs['router_id'])
except Exception:
ports = []
msg = _('Unable to retrieve port details.')
exceptions.handle(self.request, msg)
return ports
def get_context_data(self, **kwargs):
context = super(DetailView, self).get_context_data(**kwargs)
router = self._get_data()
@ -140,10 +151,11 @@ class DetailView(tabs.TabbedTableView):
return context
def get(self, request, *args, **kwargs):
def get_tabs(self, request, *args, **kwargs):
router = self._get_data()
self.kwargs['router'] = router
return super(DetailView, self).get(request, *args, **kwargs)
ports = self._get_ports()
return self.tab_group_class(request, router=router,
ports=ports, **kwargs)
class CreateView(forms.ModalFormView):