mirror of
https://github.com/netbox-community/netbox.git
synced 2025-12-22 13:22:24 -06:00
WIP
This commit is contained in:
@@ -5,21 +5,42 @@ from netbox.ui.components import ObjectPanel
|
||||
|
||||
|
||||
class DevicePanel(ObjectPanel):
|
||||
region = attrs.NestedObjectAttr('site.region', linkify=True)
|
||||
site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
|
||||
location = attrs.NestedObjectAttr('location', linkify=True)
|
||||
rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
|
||||
virtual_chassis = attrs.NestedObjectAttr('virtual_chassis', linkify=True)
|
||||
region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True)
|
||||
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group')
|
||||
location = attrs.NestedObjectAttr('location', label=_('Location'), linkify=True)
|
||||
rack = attrs.TemplatedAttr('rack', label=_('Rack'), template_name='dcim/device/attrs/rack.html')
|
||||
virtual_chassis = attrs.NestedObjectAttr('virtual_chassis', label=_('Virtual chassis'), linkify=True)
|
||||
parent_device = attrs.TemplatedAttr(
|
||||
'parent_bay',
|
||||
label=_('Parent device'),
|
||||
template_name='dcim/device/attrs/parent_device.html',
|
||||
label=_('Parent Device'),
|
||||
)
|
||||
gps_coordinates = attrs.GPSCoordinatesAttr()
|
||||
tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
|
||||
device_type = attrs.ObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
|
||||
description = attrs.TextAttr('description')
|
||||
airflow = attrs.TextAttr('get_airflow_display')
|
||||
serial = attrs.TextAttr('serial', style='font-monospace')
|
||||
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace')
|
||||
config_template = attrs.ObjectAttr('config_template', linkify=True)
|
||||
tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group')
|
||||
device_type = attrs.ObjectAttr('device_type', label=_('Device type'), linkify=True, grouped_by='manufacturer')
|
||||
description = attrs.TextAttr('description', label=_('Description'))
|
||||
airflow = attrs.ChoiceAttr('airflow', label=_('Airflow'))
|
||||
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
|
||||
asset_tag = attrs.TextAttr('asset_tag', label=_('Asset tag'), style='font-monospace', copy_button=True)
|
||||
config_template = attrs.ObjectAttr('config_template', label=_('Config template'), linkify=True)
|
||||
|
||||
|
||||
class DeviceManagementPanel(ObjectPanel):
|
||||
status = attrs.ChoiceAttr('status', label=_('Status'))
|
||||
role = attrs.NestedObjectAttr('role', label=_('Role'), linkify=True, max_depth=3)
|
||||
platform = attrs.NestedObjectAttr('platform', label=_('Platform'), linkify=True, max_depth=3)
|
||||
primary_ip4 = attrs.TemplatedAttr(
|
||||
'primary_ip4',
|
||||
label=_('Primary IPv4'),
|
||||
template_name='dcim/device/attrs/ipaddress.html',
|
||||
)
|
||||
primary_ip6 = attrs.TemplatedAttr(
|
||||
'primary_ip6',
|
||||
label=_('Primary IPv6'),
|
||||
template_name='dcim/device/attrs/ipaddress.html',
|
||||
)
|
||||
oob_ip = attrs.TemplatedAttr(
|
||||
'oob_ip',
|
||||
label=_('Out-of-band IP'),
|
||||
template_name='dcim/device/attrs/ipaddress.html',
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ from django.utils.translation import gettext_lazy as _
|
||||
from django.views.generic import View
|
||||
|
||||
from circuits.models import Circuit, CircuitTermination
|
||||
from dcim.ui.panels import DevicePanel
|
||||
from dcim.ui import panels
|
||||
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
|
||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||
@@ -2227,7 +2227,8 @@ class DeviceView(generic.ObjectView):
|
||||
return {
|
||||
'vc_members': vc_members,
|
||||
'svg_extra': f'highlight=id:{instance.pk}',
|
||||
'device_panel': DevicePanel(instance, _('Device')),
|
||||
'device_panel': panels.DevicePanel(instance, _('Device')),
|
||||
'management_panel': panels.DeviceManagementPanel(instance, _('Management')),
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
from django.template.loader import render_to_string
|
||||
from django.utils.html import escape
|
||||
from django.utils.safestring import mark_safe
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
@@ -10,7 +11,7 @@ from netbox.config import get_config
|
||||
# Attributes
|
||||
#
|
||||
|
||||
class Attr:
|
||||
class Attr(ABC):
|
||||
template_name = None
|
||||
placeholder = mark_safe('<span class="text-muted">—</span>')
|
||||
|
||||
@@ -19,6 +20,10 @@ class Attr:
|
||||
self.label = label
|
||||
self.template_name = template_name or self.template_name
|
||||
|
||||
@abstractmethod
|
||||
def render(self, obj, context=None):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _resolve_attr(obj, path):
|
||||
cur = obj
|
||||
@@ -30,35 +35,65 @@ class Attr:
|
||||
|
||||
|
||||
class TextAttr(Attr):
|
||||
template_name = 'components/attrs/text.html'
|
||||
|
||||
def __init__(self, *args, style=None, **kwargs):
|
||||
def __init__(self, *args, style=None, copy_button=False, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.style = style
|
||||
self.copy_button = copy_button
|
||||
|
||||
def render(self, obj):
|
||||
def render(self, obj, context=None):
|
||||
context = context or {}
|
||||
value = self._resolve_attr(obj, self.accessor)
|
||||
if value in (None, ''):
|
||||
return self.placeholder
|
||||
if self.style:
|
||||
return mark_safe(f'<span class="{self.style}">{escape(value)}</span>')
|
||||
return value
|
||||
return render_to_string(self.template_name, {
|
||||
**context,
|
||||
'value': value,
|
||||
'style': self.style,
|
||||
'copy_button': self.copy_button,
|
||||
})
|
||||
|
||||
|
||||
class ChoiceAttr(Attr):
|
||||
template_name = 'components/attrs/choice.html'
|
||||
|
||||
def render(self, obj, context=None):
|
||||
context = context or {}
|
||||
try:
|
||||
value = getattr(obj, f'get_{self.accessor}_display')()
|
||||
except AttributeError:
|
||||
value = self._resolve_attr(obj, self.accessor)
|
||||
if value in (None, ''):
|
||||
return self.placeholder
|
||||
try:
|
||||
bg_color = getattr(obj, f'get_{self.accessor}_color')()
|
||||
except AttributeError:
|
||||
bg_color = None
|
||||
return render_to_string(self.template_name, {
|
||||
**context,
|
||||
'value': value,
|
||||
'bg_color': bg_color,
|
||||
})
|
||||
|
||||
|
||||
class ObjectAttr(Attr):
|
||||
template_name = 'components/object.html'
|
||||
template_name = 'components/attrs/object.html'
|
||||
|
||||
def __init__(self, *args, linkify=None, grouped_by=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.linkify = linkify
|
||||
self.grouped_by = grouped_by
|
||||
|
||||
def render(self, obj):
|
||||
def render(self, obj, context=None):
|
||||
context = context or {}
|
||||
value = self._resolve_attr(obj, self.accessor)
|
||||
if value is None:
|
||||
return self.placeholder
|
||||
group = getattr(value, self.grouped_by, None) if self.grouped_by else None
|
||||
|
||||
return render_to_string(self.template_name, {
|
||||
**context,
|
||||
'object': value,
|
||||
'group': group,
|
||||
'linkify': self.linkify,
|
||||
@@ -66,24 +101,30 @@ class ObjectAttr(Attr):
|
||||
|
||||
|
||||
class NestedObjectAttr(Attr):
|
||||
template_name = 'components/nested_object.html'
|
||||
template_name = 'components/attrs/nested_object.html'
|
||||
|
||||
def __init__(self, *args, linkify=None, **kwargs):
|
||||
def __init__(self, *args, linkify=None, max_depth=None, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.linkify = linkify
|
||||
self.max_depth = max_depth
|
||||
|
||||
def render(self, obj):
|
||||
def render(self, obj, context=None):
|
||||
context = context or {}
|
||||
value = self._resolve_attr(obj, self.accessor)
|
||||
if value is None:
|
||||
return self.placeholder
|
||||
nodes = value.get_ancestors(include_self=True)
|
||||
if self.max_depth:
|
||||
nodes = list(nodes)[-self.max_depth:]
|
||||
return render_to_string(self.template_name, {
|
||||
'nodes': value.get_ancestors(include_self=True),
|
||||
**context,
|
||||
'nodes': nodes,
|
||||
'linkify': self.linkify,
|
||||
})
|
||||
|
||||
|
||||
class GPSCoordinatesAttr(Attr):
|
||||
template_name = 'components/gps_coordinates.html'
|
||||
template_name = 'components/attrs/gps_coordinates.html'
|
||||
|
||||
def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
|
||||
kwargs.setdefault('label', _('GPS Coordinates'))
|
||||
@@ -97,12 +138,14 @@ class GPSCoordinatesAttr(Attr):
|
||||
else:
|
||||
self.map_url = None
|
||||
|
||||
def render(self, obj):
|
||||
def render(self, obj, context=None):
|
||||
context = context or {}
|
||||
latitude = self._resolve_attr(obj, self.latitude_attr)
|
||||
longitude = self._resolve_attr(obj, self.longitude_attr)
|
||||
if latitude is None or longitude is None:
|
||||
return self.placeholder
|
||||
return render_to_string(self.template_name, {
|
||||
**context,
|
||||
'latitude': latitude,
|
||||
'longitude': longitude,
|
||||
'map_url': self.map_url,
|
||||
@@ -115,12 +158,17 @@ class TemplatedAttr(Attr):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.context = context or {}
|
||||
|
||||
def render(self, obj):
|
||||
def render(self, obj, context=None):
|
||||
context = context or {}
|
||||
value = self._resolve_attr(obj, self.accessor)
|
||||
if value is None:
|
||||
return self.placeholder
|
||||
return render_to_string(
|
||||
self.template_name,
|
||||
{
|
||||
**context,
|
||||
**self.context,
|
||||
'object': obj,
|
||||
'value': self._resolve_attr(obj, self.accessor),
|
||||
'value': value,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -40,7 +40,7 @@ class ObjectPanel(Component, metaclass=ObjectDetailsPanelMeta):
|
||||
return [
|
||||
{
|
||||
'label': attr.label or title(name),
|
||||
'value': attr.render(self.object),
|
||||
'value': attr.render(self.object, {'name': name}),
|
||||
} for name, attr in self._attrs.items()
|
||||
]
|
||||
|
||||
|
||||
5
netbox/templates/components/attrs/choice.html
Normal file
5
netbox/templates/components/attrs/choice.html
Normal file
@@ -0,0 +1,5 @@
|
||||
{% if bg_color %}
|
||||
{% badge value bg_color=bg_color %}
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
7
netbox/templates/components/attrs/text.html
Normal file
7
netbox/templates/components/attrs/text.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% load i18n %}
|
||||
<span{% if name %} id="attr_{{ name }}"{% endif %}{% if style %} class="{{ style }}"{% endif %}>{{ value }}</span>
|
||||
{% if copy_button %}
|
||||
<a class="btn btn-sm btn-primary copy-content" data-clipboard-target="#attr_{{ name }}" title="{% trans "Copy to clipboard" %}">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
@@ -11,115 +11,7 @@
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col col-12 col-xl-6">
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Device" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Region" %}</th>
|
||||
<td>{% nested_tree object.site.region %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Site" %}</th>
|
||||
<td>{{ object.site|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Location" %}</th>
|
||||
<td>{% nested_tree object.location %}</td>
|
||||
</tr>
|
||||
{% if object.virtual_chassis %}
|
||||
<tr>
|
||||
<th scope="row">{% trans "Virtual Chassis" %}</th>
|
||||
<td>{{ object.virtual_chassis|linkify }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th scope="row">{% trans "Rack" %}</th>
|
||||
<td class="d-flex justify-content-between align-items-start">
|
||||
{% if object.rack %}
|
||||
{{ object.rack|linkify }}
|
||||
<a href="{{ object.rack.get_absolute_url }}?device={% firstof object.parent_bay.device.pk object.pk %}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
|
||||
<i class="mdi mdi-view-day-outline"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Position" %}</th>
|
||||
<td>
|
||||
{% if object.parent_bay %}
|
||||
{% with object.parent_bay.device as parent %}
|
||||
{{ parent|linkify }} / {{ object.parent_bay }}
|
||||
{% if parent.position %}
|
||||
(U{{ parent.position|floatformat }} / {{ parent.get_face_display }})
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
{% elif object.rack and object.position %}
|
||||
<span>U{{ object.position|floatformat }} / {{ object.get_face_display }}</span>
|
||||
{% elif object.rack and object.device_type.u_height %}
|
||||
<span class="badge text-bg-warning">{% trans "Not racked" %}</span>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "GPS Coordinates" %}</th>
|
||||
<td class="position-relative">
|
||||
{% if object.latitude and object.longitude %}
|
||||
{% if config.MAPS_URL %}
|
||||
<div class="position-absolute top-50 end-0 me-2 translate-middle-y d-print-none">
|
||||
<a href="{{ config.MAPS_URL }}{{ object.latitude|unlocalize }},{{ object.longitude|unlocalize }}" target="_blank" class="btn btn-primary btn-sm">
|
||||
<i class="mdi mdi-map-marker"></i> {% trans "Map" %}
|
||||
</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
<span>{{ object.latitude }}, {{ object.longitude }}</span>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Tenant" %}</th>
|
||||
<td>
|
||||
{% if object.tenant.group %}
|
||||
{{ object.tenant.group|linkify }} /
|
||||
{% endif %}
|
||||
{{ object.tenant|linkify|placeholder }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Device Type" %}</th>
|
||||
<td>
|
||||
{{ object.device_type|linkify:"full_name" }} ({{ object.device_type.u_height|floatformat }}U)
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Description" %}</th>
|
||||
<td>{{ object.description|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Airflow" %}</th>
|
||||
<td>
|
||||
{{ object.get_airflow_display|placeholder }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Serial Number" %}</th>
|
||||
<td class="font-monospace">{{ object.serial|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Asset Tag" %}</th>
|
||||
<td class="font-monospace">{{ object.asset_tag|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Config Template" %}</th>
|
||||
<td>{{ object.config_template|linkify|placeholder }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
{{ device_panel }}
|
||||
{% if vc_members %}
|
||||
<div class="card">
|
||||
<h2 class="card-header">
|
||||
@@ -177,83 +69,7 @@
|
||||
{% plugin_left_page object %}
|
||||
</div>
|
||||
<div class="col col-12 col-xl-6">
|
||||
{{ device_panel }}
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Management" %}</h2>
|
||||
<table class="table table-hover attr-table">
|
||||
<tr>
|
||||
<th scope="row">{% trans "Status" %}</th>
|
||||
<td>{% badge object.get_status_display bg_color=object.get_status_color %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Role" %}</th>
|
||||
<td>{{ object.role|linkify }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Platform" %}</th>
|
||||
<td>{{ object.platform|linkify|placeholder }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Primary IPv4" %}</th>
|
||||
<td>
|
||||
{% if object.primary_ip4 %}
|
||||
<a href="{{ object.primary_ip4.get_absolute_url }}" id="primary_ip4">{{ object.primary_ip4.address.ip }}</a>
|
||||
{% if object.primary_ip4.nat_inside %}
|
||||
({% trans "NAT for" %} <a href="{{ object.primary_ip4.nat_inside.get_absolute_url }}">{{ object.primary_ip4.nat_inside.address.ip }}</a>)
|
||||
{% elif object.primary_ip4.nat_outside.exists %}
|
||||
({% trans "NAT" %}: {% for nat in object.primary_ip4.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
||||
{% endif %}
|
||||
{% copy_content "primary_ip4" %}
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans "Primary IPv6" %}</th>
|
||||
<td>
|
||||
{% if object.primary_ip6 %}
|
||||
<a href="{{ object.primary_ip6.get_absolute_url }}" id="primary_ip6">{{ object.primary_ip6.address.ip }}</a>
|
||||
{% if object.primary_ip6.nat_inside %}
|
||||
({% trans "NAT for" %} <a href="{{ object.primary_ip6.nat_inside.get_absolute_url }}">{{ object.primary_ip6.nat_inside.address.ip }}</a>)
|
||||
{% elif object.primary_ip6.nat_outside.exists %}
|
||||
({% trans "NAT" %}: {% for nat in object.primary_ip6.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
||||
{% endif %}
|
||||
{% copy_content "primary_ip6" %}
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">Out-of-band IP</th>
|
||||
<td>
|
||||
{% if object.oob_ip %}
|
||||
<a href="{{ object.oob_ip.get_absolute_url }}" id="oob_ip">{{ object.oob_ip.address.ip }}</a>
|
||||
{% if object.oob_ip.nat_inside %}
|
||||
({% trans "NAT for" %} <a href="{{ object.oob_ip.nat_inside.get_absolute_url }}">{{ object.oob_ip.nat_inside.address.ip }}</a>)
|
||||
{% elif object.oob_ip.nat_outside.exists %}
|
||||
({% trans "NAT" %}: {% for nat in object.oob_ip.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
||||
{% endif %}
|
||||
{% copy_content "oob_ip" %}
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if object.cluster %}
|
||||
<tr>
|
||||
<th>{% trans "Cluster" %}</th>
|
||||
<td>
|
||||
{% if object.cluster.group %}
|
||||
{{ object.cluster.group|linkify }} /
|
||||
{% endif %}
|
||||
{{ object.cluster|linkify }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
</table>
|
||||
</div>
|
||||
{{ management_panel }}
|
||||
{% if object.powerports.exists and object.poweroutlets.exists %}
|
||||
<div class="card">
|
||||
<h2 class="card-header">{% trans "Power Utilization" %}</h2>
|
||||
|
||||
11
netbox/templates/dcim/device/attrs/ipaddress.html
Normal file
11
netbox/templates/dcim/device/attrs/ipaddress.html
Normal file
@@ -0,0 +1,11 @@
|
||||
{# TODO: Add copy-to-clipboard button #}
|
||||
{% load i18n %}
|
||||
<a href="{{ value.get_absolute_url }}"{% if name %} id="attr_{{ name }}"{% endif %}>{{ value.address.ip }}</a>
|
||||
{% if value.nat_inside %}
|
||||
({% trans "NAT for" %} <a href="{{ value.nat_inside.get_absolute_url }}">{{ value.nat_inside.address.ip }}</a>)
|
||||
{% elif value.nat_outside.exists %}
|
||||
({% trans "NAT" %}: {% for nat in value.nat_outside.all %}<a href="{{ nat.get_absolute_url }}">{{ nat.address.ip }}</a>{% if not forloop.last %}, {% endif %}{% endfor %})
|
||||
{% endif %}
|
||||
<a class="btn btn-sm btn-primary copy-content" data-clipboard-target="#attr_{{ name }}" title="{% trans "Copy to clipboard" %}">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
</a>
|
||||
@@ -1,8 +1,10 @@
|
||||
{% if value %}
|
||||
<ol class="breadcrumb" aria-label="breadcrumbs">
|
||||
<li class="breadcrumb-item">{{ device.parent_bay.device|linkify }}</li>
|
||||
<li class="breadcrumb-item">{{ device.parent_bay }}</li>
|
||||
</ol>
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% load i18n %}
|
||||
<ol class="breadcrumb" aria-label="breadcrumbs">
|
||||
<li class="breadcrumb-item">{{ value.device|linkify }}</li>
|
||||
<li class="breadcrumb-item">{{ value }}</li>
|
||||
</ol>
|
||||
{% if value.device.position %}
|
||||
<a href="{{ value.device.rack.get_absolute_url }}?device={{ value.device.pk }}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
|
||||
<i class="mdi mdi-view-day-outline"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,18 +1,14 @@
|
||||
{% load i18n %}
|
||||
{% if value %}
|
||||
<span>
|
||||
<span>
|
||||
{{ value|linkify }}
|
||||
{% if value and object.position %}
|
||||
(U{{ object.position|floatformat }} / {{ object.get_face_display }})
|
||||
{% elif value and object.device_type.u_height %}
|
||||
<span class="badge text-bg-warning">{% trans "Not racked" %}</span>
|
||||
{% endif %}
|
||||
</span>
|
||||
{% if value and object.position %}
|
||||
</span>
|
||||
{% if object.position %}
|
||||
<a href="{{ value.get_absolute_url }}?device={{ object.pk }}" class="btn btn-primary btn-sm d-print-none" title="{% trans "Highlight device in rack" %}">
|
||||
<i class="mdi mdi-view-day-outline"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ ''|placeholder }}
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user