From 1acd567706eeed03fe1f6c865c113301605f903c Mon Sep 17 00:00:00 2001 From: Jeremy Stretch Date: Thu, 30 Oct 2025 15:29:23 -0400 Subject: [PATCH] Add site panel --- netbox/dcim/ui/panels.py | 13 +++ netbox/dcim/views.py | 1 + netbox/netbox/ui/attrs.py | 38 ++++++++ .../templates/components/attrs/address.html | 8 ++ .../templates/components/attrs/timezone.html | 6 ++ netbox/templates/dcim/site.html | 96 ++----------------- 6 files changed, 72 insertions(+), 90 deletions(-) create mode 100644 netbox/templates/components/attrs/address.html create mode 100644 netbox/templates/components/attrs/timezone.html diff --git a/netbox/dcim/ui/panels.py b/netbox/dcim/ui/panels.py index 8f0d3b90d..56a557acb 100644 --- a/netbox/dcim/ui/panels.py +++ b/netbox/dcim/ui/panels.py @@ -44,3 +44,16 @@ class DeviceManagementPanel(ObjectPanel): label=_('Out-of-band IP'), template_name='dcim/device/attrs/ipaddress.html', ) + + +class SitePanel(ObjectPanel): + region = attrs.NestedObjectAttr('region', label=_('Region'), linkify=True) + group = attrs.NestedObjectAttr('group', label=_('Group'), linkify=True) + status = attrs.ChoiceAttr('status', label=_('Status')) + tenant = attrs.ObjectAttr('tenant', label=_('Tenant'), linkify=True, grouped_by='group') + facility = attrs.TextAttr('facility', label=_('Facility')) + description = attrs.TextAttr('description', label=_('Description')) + timezone = attrs.TimezoneAttr('time_zone', label=_('Timezone')) + physical_address = attrs.AddressAttr('physical_address', label=_('Physical address'), map_url=True) + shipping_address = attrs.AddressAttr('shipping_address', label=_('Shipping address'), map_url=True) + gps_coordinates = attrs.GPSCoordinatesAttr() diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index d7ebeca11..4e751d33f 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -465,6 +465,7 @@ class SiteView(GetRelatedModelsMixin, generic.ObjectView): def get_extra_context(self, request, instance): return { + 'site_panel': panels.SitePanel(instance, _('Site')), 'related_models': self.get_related_models( request, instance, diff --git a/netbox/netbox/ui/attrs.py b/netbox/netbox/ui/attrs.py index 233beef60..679cc5d66 100644 --- a/netbox/netbox/ui/attrs.py +++ b/netbox/netbox/ui/attrs.py @@ -123,6 +123,30 @@ class NestedObjectAttr(Attr): }) +class AddressAttr(Attr): + template_name = 'components/attrs/address.html' + + def __init__(self, *args, map_url=True, **kwargs): + super().__init__(*args, **kwargs) + if map_url is True: + self.map_url = get_config().MAPS_URL + elif map_url: + self.map_url = map_url + else: + self.map_url = None + + def render(self, obj, context=None): + context = context or {} + value = self._resolve_attr(obj, self.accessor) + if value in (None, ''): + return self.placeholder + return render_to_string(self.template_name, { + **context, + 'value': value, + 'map_url': self.map_url, + }) + + class GPSCoordinatesAttr(Attr): template_name = 'components/attrs/gps_coordinates.html' @@ -152,6 +176,20 @@ class GPSCoordinatesAttr(Attr): }) +class TimezoneAttr(Attr): + template_name = 'components/attrs/timezone.html' + + def render(self, obj, context=None): + context = context or {} + value = self._resolve_attr(obj, self.accessor) + if value in (None, ''): + return self.placeholder + return render_to_string(self.template_name, { + **context, + 'value': value, + }) + + class TemplatedAttr(Attr): def __init__(self, *args, context=None, **kwargs): diff --git a/netbox/templates/components/attrs/address.html b/netbox/templates/components/attrs/address.html new file mode 100644 index 000000000..08f46fc43 --- /dev/null +++ b/netbox/templates/components/attrs/address.html @@ -0,0 +1,8 @@ +{% load i18n %} +{% load l10n %} +{{ value|linebreaksbr }} +{% if map_url %} + + {% trans "Map" %} + +{% endif %} diff --git a/netbox/templates/components/attrs/timezone.html b/netbox/templates/components/attrs/timezone.html new file mode 100644 index 000000000..7492d72ad --- /dev/null +++ b/netbox/templates/components/attrs/timezone.html @@ -0,0 +1,6 @@ +{% load i18n %} +{% load tz %} +
+ {{ value }} ({% trans "UTC" %} {{ value|tzoffset }})
+ {% trans "Local time" %}: {% timezone value %}{% now 'Y-m-d H:i' %}{% endtimezone %} +
diff --git a/netbox/templates/dcim/site.html b/netbox/templates/dcim/site.html index cf65961d9..f4e9a5d02 100644 --- a/netbox/templates/dcim/site.html +++ b/netbox/templates/dcim/site.html @@ -24,100 +24,16 @@ {% block content %}
-
-

{% trans "Site" %}

- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
{% trans "Region" %} - {% nested_tree object.region %} -
{% trans "Group" %} - {% nested_tree object.group %} -
{% trans "Status" %}{% badge object.get_status_display bg_color=object.get_status_color %}
{% trans "Tenant" %} - {% if object.tenant.group %} - {{ object.tenant.group|linkify }} / - {% endif %} - {{ object.tenant|linkify|placeholder }} -
{% trans "Facility" %}{{ object.facility|placeholder }}
{% trans "Description" %}{{ object.description|placeholder }}
{% trans "Time Zone" %} - {% if object.time_zone %} - {{ object.time_zone }} ({% trans "UTC" %} {{ object.time_zone|tzoffset }})
- {% trans "Site time" %}: {% timezone object.time_zone %}{% now 'Y-m-d H:i' %}{% endtimezone %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Physical Address" %} - {% if object.physical_address %} - {{ object.physical_address|linebreaksbr }} - {% if config.MAPS_URL %} - - {% trans "Map" %} - - {% endif %} - {% else %} - {{ ''|placeholder }} - {% endif %} -
{% trans "Shipping Address" %}{{ object.shipping_address|linebreaksbr|placeholder }}
{% trans "GPS Coordinates" %} - {% if object.latitude and object.longitude %} - {% if config.MAPS_URL %} - - {% endif %} - {{ object.latitude }}, {{ object.longitude }} - {% else %} - {{ ''|placeholder }} - {% endif %} -
-
+ {{ site_panel }} {% include 'inc/panels/custom_fields.html' %} {% include 'inc/panels/tags.html' %} {% include 'inc/panels/comments.html' %} {% plugin_left_page object %} -
-
- {% include 'inc/panels/related_objects.html' with filter_name='site_id' %} - {% include 'inc/panels/image_attachments.html' %} - {% plugin_right_page object %} +
+
+ {% include 'inc/panels/related_objects.html' with filter_name='site_id' %} + {% include 'inc/panels/image_attachments.html' %} + {% plugin_right_page object %}