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):
|
class DevicePanel(ObjectPanel):
|
||||||
region = attrs.NestedObjectAttr('site.region', linkify=True)
|
region = attrs.NestedObjectAttr('site.region', label=_('Region'), linkify=True)
|
||||||
site = attrs.ObjectAttr('site', linkify=True, grouped_by='group')
|
site = attrs.ObjectAttr('site', label=_('Site'), linkify=True, grouped_by='group')
|
||||||
location = attrs.NestedObjectAttr('location', linkify=True)
|
location = attrs.NestedObjectAttr('location', label=_('Location'), linkify=True)
|
||||||
rack = attrs.TemplatedAttr('rack', template_name='dcim/device/attrs/rack.html')
|
rack = attrs.TemplatedAttr('rack', label=_('Rack'), template_name='dcim/device/attrs/rack.html')
|
||||||
virtual_chassis = attrs.NestedObjectAttr('virtual_chassis', linkify=True)
|
virtual_chassis = attrs.NestedObjectAttr('virtual_chassis', label=_('Virtual chassis'), linkify=True)
|
||||||
parent_device = attrs.TemplatedAttr(
|
parent_device = attrs.TemplatedAttr(
|
||||||
'parent_bay',
|
'parent_bay',
|
||||||
|
label=_('Parent device'),
|
||||||
template_name='dcim/device/attrs/parent_device.html',
|
template_name='dcim/device/attrs/parent_device.html',
|
||||||
label=_('Parent Device'),
|
|
||||||
)
|
)
|
||||||
gps_coordinates = attrs.GPSCoordinatesAttr()
|
gps_coordinates = attrs.GPSCoordinatesAttr()
|
||||||
tenant = attrs.ObjectAttr('tenant', linkify=True, grouped_by='group')
|
tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group')
|
||||||
device_type = attrs.ObjectAttr('device_type', linkify=True, grouped_by='manufacturer')
|
device_type = attrs.ObjectAttr('device_type', label=_('Device type'), linkify=True, grouped_by='manufacturer')
|
||||||
description = attrs.TextAttr('description')
|
description = attrs.TextAttr('description', label=_('Description'))
|
||||||
airflow = attrs.TextAttr('get_airflow_display')
|
airflow = attrs.ChoiceAttr('airflow', label=_('Airflow'))
|
||||||
serial = attrs.TextAttr('serial', style='font-monospace')
|
serial = attrs.TextAttr('serial', label=_('Serial number'), style='font-monospace', copy_button=True)
|
||||||
asset_tag = attrs.TextAttr('asset_tag', style='font-monospace')
|
asset_tag = attrs.TextAttr('asset_tag', label=_('Asset tag'), style='font-monospace', copy_button=True)
|
||||||
config_template = attrs.ObjectAttr('config_template', linkify=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 django.views.generic import View
|
||||||
|
|
||||||
from circuits.models import Circuit, CircuitTermination
|
from circuits.models import Circuit, CircuitTermination
|
||||||
from dcim.ui.panels import DevicePanel
|
from dcim.ui import panels
|
||||||
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
from extras.views import ObjectConfigContextView, ObjectRenderConfigView
|
||||||
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
|
from ipam.models import ASN, IPAddress, Prefix, VLANGroup, VLAN
|
||||||
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
from ipam.tables import InterfaceVLANTable, VLANTranslationRuleTable
|
||||||
@@ -2227,7 +2227,8 @@ class DeviceView(generic.ObjectView):
|
|||||||
return {
|
return {
|
||||||
'vc_members': vc_members,
|
'vc_members': vc_members,
|
||||||
'svg_extra': f'highlight=id:{instance.pk}',
|
'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.template.loader import render_to_string
|
||||||
from django.utils.html import escape
|
|
||||||
from django.utils.safestring import mark_safe
|
from django.utils.safestring import mark_safe
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
@@ -10,7 +11,7 @@ from netbox.config import get_config
|
|||||||
# Attributes
|
# Attributes
|
||||||
#
|
#
|
||||||
|
|
||||||
class Attr:
|
class Attr(ABC):
|
||||||
template_name = None
|
template_name = None
|
||||||
placeholder = mark_safe('<span class="text-muted">—</span>')
|
placeholder = mark_safe('<span class="text-muted">—</span>')
|
||||||
|
|
||||||
@@ -19,6 +20,10 @@ class Attr:
|
|||||||
self.label = label
|
self.label = label
|
||||||
self.template_name = template_name or self.template_name
|
self.template_name = template_name or self.template_name
|
||||||
|
|
||||||
|
@abstractmethod
|
||||||
|
def render(self, obj, context=None):
|
||||||
|
pass
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _resolve_attr(obj, path):
|
def _resolve_attr(obj, path):
|
||||||
cur = obj
|
cur = obj
|
||||||
@@ -30,35 +35,65 @@ class Attr:
|
|||||||
|
|
||||||
|
|
||||||
class TextAttr(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)
|
super().__init__(*args, **kwargs)
|
||||||
self.style = style
|
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)
|
value = self._resolve_attr(obj, self.accessor)
|
||||||
if value in (None, ''):
|
if value in (None, ''):
|
||||||
return self.placeholder
|
return self.placeholder
|
||||||
if self.style:
|
return render_to_string(self.template_name, {
|
||||||
return mark_safe(f'<span class="{self.style}">{escape(value)}</span>')
|
**context,
|
||||||
return value
|
'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):
|
class ObjectAttr(Attr):
|
||||||
template_name = 'components/object.html'
|
template_name = 'components/attrs/object.html'
|
||||||
|
|
||||||
def __init__(self, *args, linkify=None, grouped_by=None, **kwargs):
|
def __init__(self, *args, linkify=None, grouped_by=None, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.linkify = linkify
|
self.linkify = linkify
|
||||||
self.grouped_by = grouped_by
|
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)
|
value = self._resolve_attr(obj, self.accessor)
|
||||||
if value is None:
|
if value is None:
|
||||||
return self.placeholder
|
return self.placeholder
|
||||||
group = getattr(value, self.grouped_by, None) if self.grouped_by else None
|
group = getattr(value, self.grouped_by, None) if self.grouped_by else None
|
||||||
|
|
||||||
return render_to_string(self.template_name, {
|
return render_to_string(self.template_name, {
|
||||||
|
**context,
|
||||||
'object': value,
|
'object': value,
|
||||||
'group': group,
|
'group': group,
|
||||||
'linkify': self.linkify,
|
'linkify': self.linkify,
|
||||||
@@ -66,24 +101,30 @@ class ObjectAttr(Attr):
|
|||||||
|
|
||||||
|
|
||||||
class NestedObjectAttr(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)
|
super().__init__(*args, **kwargs)
|
||||||
self.linkify = linkify
|
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)
|
value = self._resolve_attr(obj, self.accessor)
|
||||||
if value is None:
|
if value is None:
|
||||||
return self.placeholder
|
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, {
|
return render_to_string(self.template_name, {
|
||||||
'nodes': value.get_ancestors(include_self=True),
|
**context,
|
||||||
|
'nodes': nodes,
|
||||||
'linkify': self.linkify,
|
'linkify': self.linkify,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
class GPSCoordinatesAttr(Attr):
|
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):
|
def __init__(self, latitude_attr='latitude', longitude_attr='longitude', map_url=True, **kwargs):
|
||||||
kwargs.setdefault('label', _('GPS Coordinates'))
|
kwargs.setdefault('label', _('GPS Coordinates'))
|
||||||
@@ -97,12 +138,14 @@ class GPSCoordinatesAttr(Attr):
|
|||||||
else:
|
else:
|
||||||
self.map_url = None
|
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)
|
latitude = self._resolve_attr(obj, self.latitude_attr)
|
||||||
longitude = self._resolve_attr(obj, self.longitude_attr)
|
longitude = self._resolve_attr(obj, self.longitude_attr)
|
||||||
if latitude is None or longitude is None:
|
if latitude is None or longitude is None:
|
||||||
return self.placeholder
|
return self.placeholder
|
||||||
return render_to_string(self.template_name, {
|
return render_to_string(self.template_name, {
|
||||||
|
**context,
|
||||||
'latitude': latitude,
|
'latitude': latitude,
|
||||||
'longitude': longitude,
|
'longitude': longitude,
|
||||||
'map_url': self.map_url,
|
'map_url': self.map_url,
|
||||||
@@ -115,12 +158,17 @@ class TemplatedAttr(Attr):
|
|||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.context = context or {}
|
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(
|
return render_to_string(
|
||||||
self.template_name,
|
self.template_name,
|
||||||
{
|
{
|
||||||
|
**context,
|
||||||
**self.context,
|
**self.context,
|
||||||
'object': obj,
|
'object': obj,
|
||||||
'value': self._resolve_attr(obj, self.accessor),
|
'value': value,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ class ObjectPanel(Component, metaclass=ObjectDetailsPanelMeta):
|
|||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'label': attr.label or title(name),
|
'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()
|
} 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 %}
|
{% block content %}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col col-12 col-xl-6">
|
<div class="col col-12 col-xl-6">
|
||||||
<div class="card">
|
{{ device_panel }}
|
||||||
<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>
|
|
||||||
{% if vc_members %}
|
{% if vc_members %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">
|
<h2 class="card-header">
|
||||||
@@ -177,83 +69,7 @@
|
|||||||
{% plugin_left_page object %}
|
{% plugin_left_page object %}
|
||||||
</div>
|
</div>
|
||||||
<div class="col col-12 col-xl-6">
|
<div class="col col-12 col-xl-6">
|
||||||
{{ device_panel }}
|
{{ management_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>
|
|
||||||
{% if object.powerports.exists and object.poweroutlets.exists %}
|
{% if object.powerports.exists and object.poweroutlets.exists %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<h2 class="card-header">{% trans "Power Utilization" %}</h2>
|
<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 %}
|
{% load i18n %}
|
||||||
<ol class="breadcrumb" aria-label="breadcrumbs">
|
<ol class="breadcrumb" aria-label="breadcrumbs">
|
||||||
<li class="breadcrumb-item">{{ device.parent_bay.device|linkify }}</li>
|
<li class="breadcrumb-item">{{ value.device|linkify }}</li>
|
||||||
<li class="breadcrumb-item">{{ device.parent_bay }}</li>
|
<li class="breadcrumb-item">{{ value }}</li>
|
||||||
</ol>
|
</ol>
|
||||||
{% else %}
|
{% if value.device.position %}
|
||||||
{{ ''|placeholder }}
|
<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 %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,18 +1,14 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% if value %}
|
<span>
|
||||||
<span>
|
{{ value|linkify }}
|
||||||
{{ 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 %}
|
{% if value and 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" %}">
|
(U{{ object.position|floatformat }} / {{ object.get_face_display }})
|
||||||
<i class="mdi mdi-view-day-outline"></i>
|
{% elif value and object.device_type.u_height %}
|
||||||
</a>
|
<span class="badge text-bg-warning">{% trans "Not racked" %}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% else %}
|
</span>
|
||||||
{{ ''|placeholder }}
|
{% 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 %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user