diff --git a/netbox_topology_views/forms.py b/netbox_topology_views/forms.py index 5cbcd39..cca9f30 100644 --- a/netbox_topology_views/forms.py +++ b/netbox_topology_views/forms.py @@ -230,7 +230,6 @@ class DeviceFilterForm( choices=BOOLEAN_WITH_BLANK_CHOICES ) ) - tag = TagFilterField(model) # options @@ -361,6 +360,27 @@ class DeviceFilterForm( ) ) + +class RackElevationFilterForm(forms.Form): + """Compact selector for the rack elevation view.""" + + site_id = DynamicModelMultipleChoiceField( + queryset=Site.objects.all(), required=False, label=_("Site") + ) + location_id = DynamicModelMultipleChoiceField( + queryset=Location.objects.all(), required=False, label=_("Location"), + query_params={"site_id": "$site_id"}, + ) + rack_id = DynamicModelMultipleChoiceField( + queryset=Rack.objects.all(), required=False, label=_("Racks"), + query_params={"site_id": "$site_id", "location_id": "$location_id"}, + help_text=_("Select one or more racks. If empty, all matching racks are shown."), + ) + include_empty = forms.BooleanField( + required=False, initial=False, label=_("Include empty racks") + ) + + class CoordinateGroupsForm(NetBoxModelForm): fieldsets = ( FieldSet('name', 'description', name=_("Group Details")), diff --git a/netbox_topology_views/navigation.py b/netbox_topology_views/navigation.py index 3a338ee..d1e2af5 100644 --- a/netbox_topology_views/navigation.py +++ b/netbox_topology_views/navigation.py @@ -84,6 +84,11 @@ menu = PluginMenu( PluginMenuItem(link="plugins:netbox_topology_views:home", link_text="Topology", permissions=["dcim.view_site", "dcim.view_device"]), ), ), + ('WEITERE ANSICHTEN', + ( + PluginMenuItem(link="plugins:netbox_topology_views:rack_elevation", link_text="Rack-Ansicht", permissions=["dcim.view_rack", "dcim.view_device", "dcim.view_cable"]), + ), + ), ('COORDINATES', ( PluginMenuItem(link="plugins:netbox_topology_views:coordinategroup_list", link_text="Coordinate Groups", buttons=coordinategroup_buttons, permissions=['netbox_topology_views.view_coordinategroup']), diff --git a/netbox_topology_views/static/netbox_topology_views/css/app.css b/netbox_topology_views/static/netbox_topology_views/css/app.css index b9e95d1..c2e3159 100644 --- a/netbox_topology_views/static/netbox_topology_views/css/app.css +++ b/netbox_topology_views/static/netbox_topology_views/css/app.css @@ -1 +1 @@ -#visgraph{height:64vh;border:1px solid #ced4da}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529;border:1px solid #495057}.image-dropdown img{width:64px;height:64px}.image-dropdown-content{display:flex;flex-wrap:wrap;gap:.5rem;padding-inline:.5rem;width:50vw;max-width:32rem}.image-dropdown-content>img{cursor:pointer} +#visgraph{height:64vh;border:1px solid #ced4da}html[data-netbox-color-mode=dark] #visgraph{background-color:#212529;border:1px solid #495057}.image-dropdown img{width:64px;height:64px}.image-dropdown-content{display:flex;flex-wrap:wrap;gap:.5rem;padding-inline:.5rem;width:50vw;max-width:32rem}.image-dropdown-content>img{cursor:pointer}.rack-elevation-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(34rem,1fr));gap:1rem}.rack-card{border:1px solid var(--tblr-border-color,#ced4da);border-radius:.4rem;background:var(--tblr-bg-surface,#fff);padding:1rem}.rack-card>header{display:flex;justify-content:space-between;margin-bottom:.75rem}.rack-faces{display:grid;grid-template-columns:1fr 1fr;gap:.75rem}.rack-face-title{text-align:center;font-size:.75rem;text-transform:uppercase;margin-bottom:.25rem}.rack-frame{display:flex;height:calc(var(--rack-units)*14px);min-height:280px;max-height:70vh;border:4px solid #3b4654;background:repeating-linear-gradient(to top,rgba(127,127,127,.14) 0,rgba(127,127,127,.14) 1px,transparent 1px,transparent calc(100%/var(--rack-units)))}.rack-unit-labels{width:2rem;display:flex;flex-direction:column;justify-content:space-around;font-size:.55rem;text-align:center;color:var(--tblr-secondary,#667382)}.rack-device-area{position:relative;flex:1;border-left:1px solid #3b4654}.rack-device{position:absolute;left:3px;right:3px;min-height:2px;overflow:hidden;padding:1px 5px;border:2px solid #1685fc;border-radius:3px;background:#162e45;color:#fff;line-height:1.1;text-decoration:none}.rack-device:hover{filter:brightness(1.25);color:#fff;z-index:2}.rack-device span,.rack-device small{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.rack-device small{font-size:.6rem;opacity:.8}@media(max-width:700px){.rack-elevation-grid{grid-template-columns:1fr}.rack-faces{grid-template-columns:1fr}} diff --git a/netbox_topology_views/static/netbox_topology_views/img/aggregation-switch.svg b/netbox_topology_views/static/netbox_topology_views/img/aggregation-switch.svg new file mode 100644 index 0000000..ac744a2 --- /dev/null +++ b/netbox_topology_views/static/netbox_topology_views/img/aggregation-switch.svg @@ -0,0 +1 @@ + diff --git a/netbox_topology_views/static/netbox_topology_views/img/leaf-switch.svg b/netbox_topology_views/static/netbox_topology_views/img/leaf-switch.svg new file mode 100644 index 0000000..7c449b8 --- /dev/null +++ b/netbox_topology_views/static/netbox_topology_views/img/leaf-switch.svg @@ -0,0 +1 @@ + diff --git a/netbox_topology_views/static/netbox_topology_views/img/network-switch.svg b/netbox_topology_views/static/netbox_topology_views/img/network-switch.svg new file mode 100644 index 0000000..cf79aa0 --- /dev/null +++ b/netbox_topology_views/static/netbox_topology_views/img/network-switch.svg @@ -0,0 +1 @@ + diff --git a/netbox_topology_views/static/netbox_topology_views/img/spine-switch.svg b/netbox_topology_views/static/netbox_topology_views/img/spine-switch.svg new file mode 100644 index 0000000..b407ef4 --- /dev/null +++ b/netbox_topology_views/static/netbox_topology_views/img/spine-switch.svg @@ -0,0 +1 @@ + diff --git a/netbox_topology_views/static_dev/css/app.scss b/netbox_topology_views/static_dev/css/app.scss index 93a8afe..6f5bd6f 100644 --- a/netbox_topology_views/static_dev/css/app.scss +++ b/netbox_topology_views/static_dev/css/app.scss @@ -26,3 +26,32 @@ html[data-netbox-color-mode=dark] #visgraph { cursor: pointer; } } + +.rack-elevation-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(34rem, 1fr)); + gap: 1rem; +} + +.rack-card { + border: 1px solid var(--tblr-border-color, #ced4da); + border-radius: .4rem; + background: var(--tblr-bg-surface, #fff); + padding: 1rem; +} + +.rack-card > header { display: flex; justify-content: space-between; margin-bottom: .75rem; } +.rack-faces { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem; } +.rack-face-title { text-align: center; font-size: .75rem; text-transform: uppercase; margin-bottom: .25rem; } +.rack-frame { display: flex; height: calc(var(--rack-units) * 14px); min-height: 280px; max-height: 70vh; border: 4px solid #3b4654; background: repeating-linear-gradient(to top, rgba(127,127,127,.14) 0, rgba(127,127,127,.14) 1px, transparent 1px, transparent calc(100% / var(--rack-units))); } +.rack-unit-labels { width: 2rem; display: flex; flex-direction: column; justify-content: space-around; font-size: .55rem; text-align: center; color: var(--tblr-secondary, #667382); } +.rack-device-area { position: relative; flex: 1; border-left: 1px solid #3b4654; } +.rack-device { position: absolute; left: 3px; right: 3px; min-height: 2px; overflow: hidden; padding: 1px 5px; border: 2px solid #1685fc; border-radius: 3px; background: #162e45; color: #fff; line-height: 1.1; text-decoration: none; } +.rack-device:hover { filter: brightness(1.25); color: #fff; z-index: 2; } +.rack-device span, .rack-device small { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.rack-device small { font-size: .6rem; opacity: .8; } + +@media (max-width: 700px) { + .rack-elevation-grid { grid-template-columns: 1fr; } + .rack-faces { grid-template-columns: 1fr; } +} diff --git a/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html b/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html new file mode 100644 index 0000000..35a930f --- /dev/null +++ b/netbox_topology_views/templates/netbox_topology_views/rack_elevation.html @@ -0,0 +1,93 @@ +{% extends 'generic/_base.html' %} +{% load helpers %} +{% load static %} + +{% block title %}Rack-Ansicht{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +
| Kabel | A-Seite | B-Seite | Typ | Status |
|---|---|---|---|---|
| {{ row.cable.label|default:row.cable }} | +{% if row.a %}{{ row.a.device_name }} {{ row.a.name }}{% else %}—{% endif %} |
+ {% if row.b %}{{ row.b.device_name }} {{ row.b.name }}{% else %}—{% endif %} |
+ {{ row.cable.type|default:'—' }} | {{ row.cable.get_status_display }} | +
| Keine Verkabelung gefunden. | ||||