Use breadcrumb nav across Horizon
We've added breadcrumbs to the Details pages, but its a pretty inconsistent experience. This patch adds breadcrumbs to the base template, making the breadcrumbs consistent across all pages; this will be useful when the side nav is hidden for responsive design. This patch also makes the breadcrumbs on the detail pages behave better with theming. - Made the detail header and actions avoid using floats - Moved breadcrumb truncating to the front end, so that it can be customised easily via CSS - Manually added breadcrumb for ngcontainers page - Removed overly specific HTML check from Key Pair Details test - Fixed <dt> alignment on Key Pair Details page Closes-Bug: 1554812 Partially-Implements: blueprint navigation-improvements Change-Id: Ibcd4c369b5d8ad62f7c839c0deeaefc750677b40
This commit is contained in:
parent
a55663a49a
commit
d5b24d7b97
|
@ -0,0 +1,15 @@
|
|||
{% spaceless %}
|
||||
<ol class="breadcrumb">
|
||||
{% for name, target in breadcrumb %}
|
||||
{% if forloop.last %}
|
||||
<li class="breadcrumb-item-truncate active">{{ name }}</li>
|
||||
{% elif target %}
|
||||
<li class="breadcrumb-item-truncate">
|
||||
<a href="{{ target }}">{{ name }}</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li class="breadcrumb-item-truncate">{{ name }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ol>
|
||||
{% endspaceless %}
|
|
@ -1,26 +0,0 @@
|
|||
{% load truncate_filter %}
|
||||
|
||||
<ol class="breadcrumb">
|
||||
{% if breadcrumb %}
|
||||
{% for name, target in breadcrumb %}
|
||||
{% if target %}
|
||||
<li><a href="{{ target }}">{{ name|truncate:20 }}</a></li>
|
||||
{% else %}
|
||||
<li class="active">{{ name|truncate:20 }}</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% if panel %}
|
||||
<li><a href="{{ panel.get_absolute_url }}">{{ panel.name }}</a></li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<li class="active">{{ page_title }}</li>
|
||||
|
||||
{% if actions %}
|
||||
<form class='actions_column pull-right' action='{{ url }}' method="POST">
|
||||
{% csrf_token %}
|
||||
{{ actions }}
|
||||
</form>
|
||||
{% endif %}
|
||||
</ol>
|
|
@ -1,15 +1,12 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load breadcrumb_nav %}
|
||||
|
||||
{% block title %}
|
||||
{{ page_title }}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include 'horizon/common/_detail_header.html' %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<div class='page-header'>
|
||||
<div class="row">
|
||||
<div class="col-xs-12 col-sm-9 text-left">
|
||||
<span class="h1">{{ page_title }}</span>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 col-sm-3 text-right">
|
||||
{% if actions %}
|
||||
<div class='actions_column' action='{{ url }}' method="POST">
|
||||
{% csrf_token %}
|
||||
{{ actions }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright 2015 Cisco Systems, Inc.
|
||||
# Copyright 2016 Cisco Systems, 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
|
||||
|
@ -17,11 +17,44 @@ from django import template
|
|||
register = template.Library()
|
||||
|
||||
|
||||
@register.inclusion_tag('horizon/common/_breadcrumb_nav.html',
|
||||
@register.inclusion_tag('bootstrap/breadcrumb.html',
|
||||
takes_context=True)
|
||||
def breadcrumb_nav(context):
|
||||
return {'actions': context.get('actions'),
|
||||
'breadcrumb': context.get('custom_breadcrumb'),
|
||||
'url': context.get('url'),
|
||||
'page_title': context['page_title'],
|
||||
'panel': context.request.horizon['panel'], }
|
||||
"""A logic heavy function for automagically creating a breadcrumb.
|
||||
It uses the dashboard, panel group(if it exists), then current panel.
|
||||
Can also use a "custom_breadcrumb" context item to add extra items.
|
||||
"""
|
||||
breadcrumb = []
|
||||
dashboard = context.request.horizon['dashboard']
|
||||
try:
|
||||
panel_groups = dashboard.get_panel_groups()
|
||||
except KeyError:
|
||||
panel_groups = None
|
||||
panel_group = None
|
||||
panel = context.request.horizon['panel']
|
||||
|
||||
# Add panel group, if there is one
|
||||
if panel_groups:
|
||||
for group in panel_groups.values():
|
||||
if panel.slug in group.panels and group.slug != 'default':
|
||||
panel_group = group
|
||||
break
|
||||
|
||||
# Remove panel reference if that is the current page
|
||||
if panel.get_absolute_url() == context.request.path:
|
||||
panel = None
|
||||
|
||||
# Get custom breadcrumb, if there is one.
|
||||
custom_breadcrumb = context.get('custom_breadcrumb')
|
||||
|
||||
# Build list of tuples (name, optional url)
|
||||
breadcrumb.append((dashboard.name,))
|
||||
if panel_group:
|
||||
breadcrumb.append((panel_group.name,))
|
||||
if panel:
|
||||
breadcrumb.append((panel.name, panel.get_absolute_url()))
|
||||
if custom_breadcrumb:
|
||||
breadcrumb.extend(custom_breadcrumb)
|
||||
breadcrumb.append((context.get('page_title'),))
|
||||
|
||||
return {'breadcrumb': breadcrumb}
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load breadcrumb_nav %}
|
||||
{% block title %}{% trans "Hypervisor Servers" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from horizon import exceptions
|
||||
|
@ -69,7 +68,6 @@ class AdminDetailView(tables.DataTableView):
|
|||
context = super(AdminDetailView, self).get_context_data(**kwargs)
|
||||
hypervisor_name = self.kwargs['hypervisor'].split('_', 1)[1]
|
||||
breadcrumb = [
|
||||
(_("Hypervisors"), reverse('horizon:admin:hypervisors:index')),
|
||||
(hypervisor_name,), ]
|
||||
context['custom_breadcrumb'] = breadcrumb
|
||||
return context
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Volume Type Encryption Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load breadcrumb_nav %}
|
||||
|
||||
{% block title %}{% trans "Project Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% load breadcrumb_nav %}
|
||||
|
||||
{% block title %}{% trans "User Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -116,10 +116,6 @@ class KeyPairViewTests(test.TestCase):
|
|||
url = reverse('horizon:project:access_and_security:keypairs:detail',
|
||||
kwargs={'keypair_name': keypair.name})
|
||||
res = self.client.get(url, context)
|
||||
|
||||
# Note(Itxaka): With breadcrumbs, the title is in a list as active
|
||||
self.assertContains(res, '<li class="active">Key Pair Details</li>',
|
||||
1, 200)
|
||||
self.assertContains(res, "<dd>%s</dd>" % keypair.name, 1, 200)
|
||||
|
||||
@test.create_stubs({api.nova: ("keypair_create", "keypair_delete")})
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n sizeformat breadcrumb_nav %}
|
||||
{% load i18n sizeformat %}
|
||||
|
||||
{% block title %}{% trans "Key Pair Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
<div class="detail">
|
||||
<dl class="dl-horizontal">
|
||||
<dt>{% trans "Name" %}</dt>
|
||||
<dd>{{ keypair.name|default:_("None") }}</dd>
|
||||
|
@ -28,4 +27,5 @@
|
|||
<div class="key-text word-wrap">{{ keypair.public_key|default:_("None") }}</div>
|
||||
</dd>
|
||||
</dl>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Manage Security Group Rules" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -2,6 +2,14 @@
|
|||
{% load i18n %}
|
||||
{% block title %}{% trans "Containers" %}{% endblock %}
|
||||
|
||||
{% block breadcrumb_nav %}
|
||||
<ol class="breadcrumb">
|
||||
<li>{% trans "Project" %}</li>
|
||||
<li>{% trans "Object Store" %}</li>
|
||||
<li class="active">{% trans "Containers" %}</li>
|
||||
</ol>
|
||||
{% endblock %}
|
||||
|
||||
{% block ng_route_base %}
|
||||
<base href="{{ WEBROOT }}">
|
||||
{% endblock %}
|
||||
|
|
|
@ -90,8 +90,6 @@ class RuleDetailsView(tabs.TabView):
|
|||
rule = self.get_data()
|
||||
table = fw_tabs.RulesTable(self.request)
|
||||
breadcrumb = [
|
||||
(_("Firewalls"),
|
||||
reverse_lazy('horizon:project:firewalls:firewalls')),
|
||||
(_("Rules"), reverse_lazy('horizon:project:firewalls:rules'))]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
context["rule"] = rule
|
||||
|
@ -126,8 +124,6 @@ class PolicyDetailsView(tabs.TabView):
|
|||
policy = self.get_data()
|
||||
table = fw_tabs.PoliciesTable(self.request)
|
||||
breadcrumb = [
|
||||
(_("Firewalls"),
|
||||
reverse_lazy('horizon:project:firewalls:firewalls')),
|
||||
(_("Policies"),
|
||||
reverse_lazy('horizon:project:firewalls:policies'))]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
|
|
@ -136,7 +136,6 @@ class VipDetailsView(tabs.TabView):
|
|||
context['vip'] = vip
|
||||
vip_nav = vip.pool.name_or_id
|
||||
breadcrumb = [
|
||||
(_("Load Balancers"), self.get_redirect_url()),
|
||||
(vip_nav,
|
||||
reverse('horizon:project:loadbalancers:vipdetails',
|
||||
args=(vip.id,))),
|
||||
|
@ -173,7 +172,6 @@ class MemberDetailsView(tabs.TabView):
|
|||
context['member'] = member
|
||||
member_nav = member.pool.name_or_id
|
||||
breadcrumb = [
|
||||
(_("Load Balancers"), self.get_redirect_url()),
|
||||
(member_nav,
|
||||
reverse('horizon:project:loadbalancers:pooldetails',
|
||||
args=(member.pool.id,))),
|
||||
|
@ -213,7 +211,6 @@ class MonitorDetailsView(tabs.TabView):
|
|||
monitor = self.get_data()
|
||||
context['monitor'] = monitor
|
||||
breadcrumb = [
|
||||
(_("Load Balancers"), self.get_redirect_url()),
|
||||
(_("Monitors"), reverse('horizon:project:loadbalancers:monitors')),
|
||||
]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
|
|
@ -89,7 +89,6 @@ class DetailView(tabs.TabView):
|
|||
network_id=port.network_id)
|
||||
# TODO(robcresswell) Add URL for "Ports" crumb after bug/1416838
|
||||
breadcrumb = [
|
||||
(_("Networks"), self.get_redirect_url()),
|
||||
((port.network_name or port.network_id), port.network_url),
|
||||
(_("Ports"),), ]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
|
|
@ -158,7 +158,6 @@ class DetailView(tabs.TabView):
|
|||
network_id=subnet.network_id)
|
||||
# TODO(robcresswell) Add URL for "Subnets" crumb after bug/1416838
|
||||
breadcrumb = [
|
||||
(_("Networks"), self.get_redirect_url()),
|
||||
(network_nav, subnet.network_url),
|
||||
(_("Subnets"),), ]
|
||||
context["custom_breadcrumb"] = breadcrumb
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Network Details"%}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n breadcrumb_nav %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Volume Encryption Details" %}{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
<div class='page-header'>
|
||||
{% breadcrumb_nav %}
|
||||
</div>
|
||||
{% include "horizon/common/_detail_header.html" %}
|
||||
{% endblock %}
|
||||
|
||||
{% block main %}
|
||||
|
|
|
@ -1273,7 +1273,7 @@ class VolumeViewTests(test.TestCase):
|
|||
|
||||
self.assertContains(res,
|
||||
"Volume Encryption Details: %s" % volume.name,
|
||||
1, 200)
|
||||
2, 200)
|
||||
self.assertContains(res, "<dd>%s</dd>" % volume.volume_type, 1, 200)
|
||||
self.assertContains(res, "<dd>%s</dd>" % enc_meta.provider, 1, 200)
|
||||
self.assertContains(res, "<dd>%s</dd>" % enc_meta.control_location, 1,
|
||||
|
@ -1301,7 +1301,7 @@ class VolumeViewTests(test.TestCase):
|
|||
|
||||
self.assertContains(res,
|
||||
"Volume Encryption Details: %s" % volume.name,
|
||||
1, 200)
|
||||
2, 200)
|
||||
self.assertContains(res, "<h3>Volume is Unencrypted</h3>", 1, 200)
|
||||
|
||||
self.assertNoMessages()
|
||||
|
|
|
@ -76,3 +76,6 @@ $members-list-roles-width: 125px !default;
|
|||
// https://github.com/twbs/bootstrap/issues/13443
|
||||
$dropdown-item-padding-vertical: 3px;
|
||||
$dropdown-item-padding-horizontal: 20px;
|
||||
|
||||
// This defines the max-width for a breadcrumb item before it will be truncated
|
||||
$breadcrumb-item-width: 15em !default;
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
.breadcrumb {
|
||||
margin-top: $line-height-computed;
|
||||
|
||||
.breadcrumb-item-truncate {
|
||||
@include text-overflow();
|
||||
vertical-align: middle;
|
||||
max-width: $breadcrumb-item-width;
|
||||
}
|
||||
}
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
// Dashboard Components
|
||||
@import "components/bar_charts";
|
||||
@import "components/breadcrumbs";
|
||||
@import "components/charts";
|
||||
@import "components/checkboxes";
|
||||
@import "components/datepicker";
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
{% load branding i18n %}
|
||||
{% load context_selection %}
|
||||
{% load breadcrumb_nav %}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
|
@ -36,6 +38,10 @@
|
|||
<div class='container-fluid'>
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
{% block breadcrumb_nav %}
|
||||
{% breadcrumb_nav %}
|
||||
{% endblock %}
|
||||
|
||||
{% block page_header %}
|
||||
{% include "horizon/common/_page_header.html" with title=page_title %}
|
||||
{% endblock %}
|
||||
|
|
|
@ -772,9 +772,9 @@ $badge-border-radius: 10px !default;
|
|||
//##
|
||||
|
||||
$breadcrumb-padding-vertical: 8px !default;
|
||||
$breadcrumb-padding-horizontal: 10px !default;
|
||||
$breadcrumb-padding-horizontal: 15px !default;
|
||||
//** Breadcrumb background color
|
||||
$breadcrumb-bg: $body-bg !default;
|
||||
$breadcrumb-bg: #f5f5f5 !default;
|
||||
//** Breadcrumb text color
|
||||
$breadcrumb-color: $gray !default;
|
||||
//** Text color of current page in the breadcrumb
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
.page-header {
|
||||
border-bottom: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
@import "/bootstrap/scss/bootstrap/mixins/_vendor-prefixes.scss";
|
||||
|
||||
@import "components/breadcrumb_header";
|
||||
@import "components/context_selection";
|
||||
@import "components/login";
|
||||
@import "components/messages";
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
/* Breadcrumb used as a header in the details pages */
|
||||
.page-header > .breadcrumb {
|
||||
font-size: $font-size-h3;
|
||||
margin-bottom: 0;
|
||||
padding: 8px 0px;
|
||||
|
||||
.actions_column {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
features:
|
||||
- >
|
||||
[`blueprint navigation-improvements <https://blueprints.launchpad.net/horizon/+spec/navigation-improvements>`_] Breadcrumb navigation has been added across Horizon.
|
||||
|
||||
upgrade:
|
||||
- The breadcrumb navigation inside the details pages now applies across
|
||||
Horizon. A small change in the logic means that ``custom_breadcrumb``
|
||||
items in the context no longer need to specify the panel name and link.
|
||||
See [`blueprint navigation-improvements <https://blueprints.launchpad.net/horizon/+spec/navigation-improvements>`_]
|
Loading…
Reference in New Issue