feat: add personalized navigation and tenant filtering

This commit is contained in:
2026-07-28 16:40:50 +02:00
commit 54f6a3f6c9
27 changed files with 1142 additions and 0 deletions
@@ -0,0 +1,6 @@
{% load static %}
<link rel="stylesheet" href="{% static 'netbox_utilities/netbox_utilities.css' %}">
{% if navigation_enabled %}
{{ navigation_preferences|json_script:"netbox-utilities-navigation-data" }}
<script src="{% static 'netbox_utilities/navigation.js' %}" defer></script>
{% endif %}
@@ -0,0 +1,88 @@
{% extends 'base/layout.html' %}
{% block title %}Navigation personalisieren{% endblock %}
{% block header %}
<div class="container-fluid mt-3">
<div class="d-flex justify-content-between align-items-center">
<h1>Navigation personalisieren</h1>
</div>
</div>
{% endblock header %}
{% block content %}
<div class="row justify-content-center">
<div class="col col-md-10 col-xl-8">
<div class="card">
<div class="card-header"><h2 class="card-title">Linke Navigation</h2></div>
<div class="card-body">
{% if not feature_enabled %}
<div class="alert alert-warning mb-0">
Die Navigationspersonalisierung wurde durch die NetBox-Konfiguration deaktiviert.
</div>
{% else %}
<p class="text-secondary">
Verschiebe Menüs mit den Pfeilen und blende nicht benötigte Bereiche aus.
Berechtigungen von NetBox werden dadurch nicht verändert.
</p>
<form method="post" id="navigation-preferences-form">
{% csrf_token %}
<div class="list-group mb-3" id="navigation-menu-editor">
{% for menu in menus %}
<div class="list-group-item d-flex align-items-center gap-3 navigation-menu-row">
<input type="hidden" name="menu_order" value="{{ menu.key }}">
<i class="{{ menu.icon_class }} fs-2" aria-hidden="true"></i>
<span class="flex-fill fw-medium">{{ menu.label }}</span>
<label class="form-check form-switch mb-0" title="Menü ausblenden">
<input
class="form-check-input"
type="checkbox"
name="hidden_menus"
value="{{ menu.key }}"
{% if menu.hidden %}checked{% endif %}
>
<span class="form-check-label">Ausblenden</span>
</label>
<div class="btn-group" role="group" aria-label="Reihenfolge ändern">
<button class="btn btn-sm btn-outline-secondary move-up" type="button" title="Nach oben">
<i class="mdi mdi-arrow-up" aria-hidden="true"></i>
</button>
<button class="btn btn-sm btn-outline-secondary move-down" type="button" title="Nach unten">
<i class="mdi mdi-arrow-down" aria-hidden="true"></i>
</button>
</div>
</div>
{% empty %}
<div class="alert alert-info mb-0">Für diesen Benutzer sind keine Menüs verfügbar.</div>
{% endfor %}
</div>
<div class="d-flex justify-content-between">
<button class="btn btn-outline-danger" type="submit" name="reset" value="1">
Zurücksetzen
</button>
<button class="btn btn-primary" type="submit">Speichern</button>
</div>
</form>
{% endif %}
</div>
</div>
</div>
</div>
<script>
(() => {
const editor = document.getElementById('navigation-menu-editor');
if (!editor) return;
editor.addEventListener('click', (event) => {
const button = event.target.closest('.move-up, .move-down');
if (!button) return;
const row = button.closest('.navigation-menu-row');
if (button.classList.contains('move-up') && row.previousElementSibling) {
editor.insertBefore(row, row.previousElementSibling);
}
if (button.classList.contains('move-down') && row.nextElementSibling) {
editor.insertBefore(row.nextElementSibling, row);
}
});
})();
</script>
{% endblock content %}
@@ -0,0 +1,38 @@
{% extends 'base/layout.html' %}
{% block title %}NetBox Utilities Einstellungen{% endblock %}
{% block header %}
<div class="container-fluid mt-3"><h1>NetBox Utilities Einstellungen</h1></div>
{% endblock header %}
{% block content %}
<div class="row justify-content-center">
<div class="col col-md-9 col-xl-7">
<div class="card">
<div class="card-header"><h2 class="card-title">Mandantenfilter</h2></div>
<div class="card-body">
{% if config_forces_disabled %}
<div class="alert alert-warning">
Der Mandantenfilter ist in <code>PLUGINS_CONFIG</code> fest deaktiviert und kann hier nicht aktiviert werden.
</div>
{% endif %}
<form method="post">
{% csrf_token %}
{% if form.non_field_errors %}<div class="alert alert-danger">{{ form.non_field_errors }}</div>{% endif %}
<div class="form-check form-switch">
{{ form.tenant_filter_enabled }}
<label class="form-check-label fw-bold" for="{{ form.tenant_filter_enabled.id_for_label }}">
{{ form.tenant_filter_enabled.label }}
</label>
</div>
<div class="form-text mb-3">{{ form.tenant_filter_enabled.help_text }}</div>
<button class="btn btn-primary" type="submit"{% if config_forces_disabled %} disabled{% endif %}>
Speichern
</button>
</form>
</div>
</div>
</div>
</div>
{% endblock content %}
@@ -0,0 +1,44 @@
<div class="nav-item dropdown netbox-utilities-tenant">
<button
class="nav-link px-2"
type="button"
data-bs-toggle="dropdown"
aria-expanded="false"
title="Globaler Mandantenfilter"
aria-label="Globaler Mandantenfilter"
>
<i class="mdi mdi-domain" aria-hidden="true"></i>
<span class="d-none d-xl-inline ms-1 tenant-filter-label">
{% if selected_tenant %}{{ selected_tenant }}{% else %}Alle Mandanten{% endif %}
</span>
<i class="mdi mdi-chevron-down" aria-hidden="true"></i>
</button>
<div class="dropdown-menu dropdown-menu-end p-3 tenant-filter-menu">
<form action="{% url 'plugins:netbox_utilities:select_tenant' %}" method="post">
{% csrf_token %}
<input type="hidden" name="next" value="{{ return_url }}">
<label class="form-label fw-bold">
Mandant
</label>
<select
class="form-select"
name="tenant"
onchange="this.form.submit()"
aria-label="Mandant global auswählen"
>
<option value="">Alle Mandanten</option>
{% for tenant in tenants %}
<option value="{{ tenant.pk }}"{% if selected_tenant.pk == tenant.pk %} selected{% endif %}>
{{ tenant.name }}
</option>
{% endfor %}
</select>
<noscript>
<button class="btn btn-primary w-100 mt-2" type="submit">Anwenden</button>
</noscript>
<p class="text-secondary small mb-0 mt-2">
Wirkt auf mandantenfähige Listen und die globale Suche.
</p>
</form>
</div>
</div>