Closes #252,#78,#177,#265: Move options from config file to GUI (#262)
* Added some individual options * Added general options * Added show_single_cable_logical_conns and draw_default_layout * Added ignore_cable_type to IndividualOptions * Corrected permission, tiny improvements * Add preselected_device_roles to IndividualOptions * Add preselected_tags * Updated documentation * Update README.md * Corrected queryset for preselected_tags * Update README.md * move supported_termination_types to inside the function to prevent migration issues * removed general options * added new migration script --------- Co-authored-by: Mattijs Vanhaverbeke <mattijs.vanhaverbeke@ebo-enterprises.com>
This commit is contained in:
co-authored by
Mattijs Vanhaverbeke
parent
4fbc2fd8ce
commit
5d20a7ad15
@@ -67,14 +67,17 @@ You might also set the `always_save_coordinates` option to True.
|
||||
|
||||
## Configure
|
||||
|
||||
If you want to override the default values configure the `PLUGINS_CONFIG` in your `netbox configuration.py`.
|
||||
All individual options can be assigned a default value per user directly in the plugin. The default value can be overridden on the filter page.
|
||||
|
||||
The remaining options must be configured in the `PLUGINS_CONFIG` section of your `netbox/configuration.py`.
|
||||
|
||||
Example:
|
||||
```
|
||||
PLUGINS_CONFIG = {
|
||||
'netbox_topology_views': {
|
||||
'preselected_device_roles': ['Router', 'Firewall'],
|
||||
'hide_single_cable_logical_conns': True
|
||||
'static_image_directory': 'netbox_topology_views/img',
|
||||
'allow_coordinates_saving': True,
|
||||
'always_save_coordinates': True
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -82,15 +85,8 @@ PLUGINS_CONFIG = {
|
||||
| Setting | Default value | Description |
|
||||
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
|
||||
| static_image_directory | netbox_topology_views/img | (str or pathlib.Path) Specifies the location that images will be loaded from by default. Must be within `STATIC_ROOT` |
|
||||
| preselected_device_roles | ['Firewall', 'Router', 'Distribution Switch', 'Core Switch', 'Internal Switch', 'Access Switch', 'Server', 'Storage', 'Backup', 'Wireless AP'] | The full name of the device roles you want to pre select in the global view. Note that this is case sensitive |
|
||||
| allow_coordinates_saving | False | (bool) Set to true if you use the custom coordinates fields and want to save the coordinates |
|
||||
| always_save_coordinates | False | (bool) Set if you want to enable the option to save coordinates by default |
|
||||
| ignore_cable_type | [] | The cable types that you want to ignore in the views |
|
||||
| preselected_tags | [] | The name of tags you want to preload |
|
||||
| draw_default_layout | False | (bool) Set to True if you want to load draw the topology on the initial load (when you go to the topology plugin page) |
|
||||
| hide_single_cable_logical_conns | False | (bool) Set to True if you want to hide duplicate cables & logical connections |
|
||||
|
||||
|
||||
|
||||
### Custom Images
|
||||
|
||||
@@ -102,27 +98,27 @@ Go to the plugins tab in the navbar and click topology or go to `$NETBOX_URL/plu
|
||||
|
||||
Select your options for the topology view:
|
||||
|
||||

|
||||

|
||||
|
||||
<dl>
|
||||
<dt>Hide Unconnected</dt>
|
||||
<dd>Hide devices which have no connections.</dd>
|
||||
<dt>Save Coordinates</dt>
|
||||
<dd>Save the coordinates of devices in the topology view.</dd>
|
||||
<dd>Please read the "Configure" chapter to set the allow_coordinates_saving option to True.</dd>
|
||||
<dt>Show Unconnected</dt>
|
||||
<dd>Show devices that have no connections or for which no connection is displayed. This option depends on other parameters like 'Show Cables' and 'Show Logical Connections'.</dd>
|
||||
<dt>Show Circuit Terminations</dt>
|
||||
<dd>Show connections which end at a circuit termination in the topology view. These connections are displayed as blue dashed lines.</dd>
|
||||
<dt>Show Cables</dt>
|
||||
<dd>Show cable connections between devices, including cables connected to interfaces, front / rear ports, etc., in the topology view</dd>
|
||||
<dd>Show connections between interfaces, front / rear ports, etc., that are connected with one or more cables. These connections are displayed as solid lines in the color of the cable.</dd>
|
||||
<dt>Show Logical Connections</dt>
|
||||
<dd>Show logical connections between interfaces (referred to as Interface Connections in NetBox) in the topology view. Where the path between
|
||||
interfaces includes multiple cables (e.g., via patch panels), only the end interface connections are shown, not the
|
||||
intermediate front / rear port connections, etc. This is similar to what was referred to as 'end-to-end' connections in previous versions. </dd>
|
||||
<dd><i>Selecting both 'Show Cables' and 'Show Logical Interfaces' will sometimes result in a topology view with 2 cables per connection as these devices are diretly connected. Select only one of the options or set `hide_single_cable_logical_conns` to True.</i></dd>
|
||||
<dt>Show Circuit Terminations</dt>
|
||||
<dd>Show connections which end at a circuit termination in the topology view.</dd>
|
||||
<dt>Show Wireless Links</dt>
|
||||
<dd>Show wireless connections in the topology view.</dd>
|
||||
intermediate front / rear port connections, etc. This is similar to what was referred to as 'end-to-end' connections in previous versions. These connections are displayed as yellow dotted lines.</dd>
|
||||
<dt>Show redundant Cable and Locigal Connection</dt>
|
||||
<dd>Shows a logical connection (in addition to a cable), even if a cable is directly connected. Leaving this option disabled prevents that redundant display. This option only has an effect if 'Show Logical Connections' is activated.</dd>
|
||||
<dt>Show Power Feeds</dt>
|
||||
<dd>Show power connections from power feeds in the topology view.</dd>
|
||||
<dd>Displays connections between power outlets and power ports. These connections are displayed as solid lines in the color of the cable. This option depends on 'Show Cables'.</dd>
|
||||
<dt>Show Wireless Links</dt>
|
||||
<dd>Displays wireless connections. These connections are displayed as blue dotted lines.</dd>
|
||||
</dl>
|
||||
|
||||
### Update
|
||||
@@ -139,12 +135,21 @@ Clear you browser cache.
|
||||
|
||||
### Permissions
|
||||
|
||||
To view `/plugins/topology-views/` you need the following permissions:
|
||||
To view `/plugins/netbox_topology-views/topology` you need the following permissions:
|
||||
+ dcim | device | can view device
|
||||
+ dcim | site | can view site
|
||||
+ extras | tag | can view tag
|
||||
+ dcim | device role | can view device role
|
||||
|
||||
To view `/plugins/netbox_topology-views/image`:
|
||||
+ dcim | site | view
|
||||
+ dcim | device role | view
|
||||
+ dcim | device role | add
|
||||
+ dcim | device role | change
|
||||
|
||||
To view `/plugins/netbox_topology-views/individualoptions`:
|
||||
+ netbox topology views | individualoptions | change
|
||||
|
||||
## Icons info
|
||||
|
||||
Power icons created by [Freepik - Flaticon](https://www.flaticon.com/free-icons/power).
|
||||
|
||||
@@ -11,25 +11,10 @@ class TopologyViewsConfig(PluginConfig):
|
||||
base_url = "netbox_topology_views"
|
||||
required_settings = []
|
||||
default_settings = {
|
||||
"preselected_device_roles": [
|
||||
"Firewall",
|
||||
"Router",
|
||||
"Distribution Switch",
|
||||
"Core Switch",
|
||||
"Internal Switch",
|
||||
"Access Switch",
|
||||
"Server",
|
||||
"Storage",
|
||||
"Backup",
|
||||
"Wireless AP",
|
||||
],
|
||||
"ignore_cable_type": [],
|
||||
"static_image_directory": "netbox_topology_views/img",
|
||||
"allow_coordinates_saving": False,
|
||||
"always_save_coordinates": False,
|
||||
"preselected_tags": [],
|
||||
"draw_default_layout": False,
|
||||
"hide_single_cable_logical_conns": False,
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
from dcim.models import Device, DeviceRole
|
||||
from rest_framework.serializers import ModelSerializer
|
||||
from netbox.api.serializers import NetBoxModelSerializer
|
||||
|
||||
from netbox_topology_views.models import RoleImage
|
||||
from netbox_topology_views.models import RoleImage, IndividualOptions
|
||||
|
||||
|
||||
class TopologyDummySerializer(ModelSerializer):
|
||||
@@ -20,3 +21,8 @@ class DeviceRoleSerializer(ModelSerializer):
|
||||
class Meta:
|
||||
model = DeviceRole
|
||||
fields = ("name", "slug", "color", "vm_role", "description")
|
||||
|
||||
class IndividualOptionsSerializer(NetBoxModelSerializer):
|
||||
class Meta:
|
||||
model = IndividualOptions
|
||||
fields = ("ignore_cable_type", "show_unconnected", "show_cables", "show_logical_connections", "show_single_cable_logical_conns", "show_circuit", "show_power", "show_wireless", "draw_default_layout")
|
||||
+134
-15
@@ -12,17 +12,14 @@ from dcim.choices import DeviceStatusChoices
|
||||
from tenancy.models import TenantGroup, Tenant
|
||||
from tenancy.forms import TenancyFilterForm
|
||||
from django.conf import settings
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
|
||||
from utilities.forms import (
|
||||
TagFilterField,
|
||||
DynamicModelMultipleChoiceField,
|
||||
MultipleChoiceField,
|
||||
widgets,
|
||||
)
|
||||
|
||||
allow_coordinates_saving = bool(
|
||||
settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]
|
||||
)
|
||||
|
||||
from .models import IndividualOptions
|
||||
|
||||
class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
model = Device
|
||||
@@ -32,11 +29,12 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
(
|
||||
"q",
|
||||
"filter_id",
|
||||
"hide_unconnected",
|
||||
"save_coords",
|
||||
"show_unconnected",
|
||||
"show_cables",
|
||||
"show_circuit",
|
||||
"show_logical_connections",
|
||||
"show_single_cable_logical_conns",
|
||||
"show_power",
|
||||
"show_wireless",
|
||||
),
|
||||
@@ -104,12 +102,27 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
},
|
||||
label=_("Rack"),
|
||||
)
|
||||
hide_unconnected = forms.BooleanField(
|
||||
label=_("Hide Unconnected"), required=False, initial=False
|
||||
status = MultipleChoiceField(
|
||||
choices=DeviceStatusChoices, required=False, label=_("Device Status")
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
|
||||
# options
|
||||
save_coords = forms.BooleanField(
|
||||
label=_("Save Coordinates"),
|
||||
required=False,
|
||||
disabled=(not settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"] or settings.PLUGINS_CONFIG["netbox_topology_views"]["always_save_coordinates"]),
|
||||
initial=(settings.PLUGINS_CONFIG["netbox_topology_views"]["always_save_coordinates"])
|
||||
)
|
||||
show_unconnected = forms.BooleanField(
|
||||
label=_("Show Unconnected"), required=False, initial=False
|
||||
)
|
||||
show_logical_connections = forms.BooleanField(
|
||||
label =_("Show Logical Connections"), required=False, initial=False
|
||||
)
|
||||
show_single_cable_logical_conns = forms.BooleanField(
|
||||
label =_("Show redundant Cable and Locigal Connection"), required=False, initial=False
|
||||
)
|
||||
show_cables = forms.BooleanField(
|
||||
label =_("Show Cables"), required=False, initial=False
|
||||
)
|
||||
@@ -122,12 +135,118 @@ class DeviceFilterForm(TenancyFilterForm, NetBoxModelFilterSetForm):
|
||||
show_power = forms.BooleanField(
|
||||
label=_("Show Power Feeds"), required=False, initial=False
|
||||
)
|
||||
save_coords = forms.BooleanField(
|
||||
label=_("Save Coordinates"),
|
||||
|
||||
class IndividualOptionsForm(NetBoxModelForm):
|
||||
fieldsets = (
|
||||
(
|
||||
None,
|
||||
(
|
||||
"user_id",
|
||||
"ignore_cable_type",
|
||||
"preselected_device_roles",
|
||||
"preselected_tags",
|
||||
"show_unconnected",
|
||||
"show_cables",
|
||||
"show_circuit",
|
||||
"show_logical_connections",
|
||||
"show_single_cable_logical_conns",
|
||||
"show_power",
|
||||
"show_wireless",
|
||||
"draw_default_layout",
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
user_id = forms.CharField(widget=forms.HiddenInput())
|
||||
|
||||
ignore_cable_type = MultipleChoiceField(
|
||||
label=_("Ignore Termination Types"),
|
||||
required=False,
|
||||
choices=IndividualOptions.CHOICES,
|
||||
help_text=_("Choose Termination Types that you want to be ignored. "
|
||||
"If any ignored Termination Type is part of a connection, the "
|
||||
"cable is not displayed.")
|
||||
)
|
||||
preselected_device_roles = DynamicModelMultipleChoiceField(
|
||||
label=_("Preselected Device Role"),
|
||||
queryset=DeviceRole.objects.all(),
|
||||
required=False,
|
||||
disabled=(not allow_coordinates_saving),
|
||||
help_text=_("Select Device Roles that you want to have "
|
||||
"preselected in the filter tab.")
|
||||
)
|
||||
status = MultipleChoiceField(
|
||||
choices=DeviceStatusChoices, required=False, label=_("Device Status")
|
||||
preselected_tags = forms.ModelMultipleChoiceField(
|
||||
label=_("Preselected Tags"),
|
||||
queryset=Device.tags.all(),
|
||||
required=False,
|
||||
widget=widgets.StaticSelectMultiple,
|
||||
help_text=_("Select Tags that you want to have "
|
||||
"preselected in the filter tab.")
|
||||
)
|
||||
tag = TagFilterField(model)
|
||||
show_unconnected = forms.BooleanField(
|
||||
label=_("Show Unconnected"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Draws devices that have no connections or for which no "
|
||||
"connection is displayed. This option depends on other parameters "
|
||||
"like 'Show Cables' and 'Show Logical Connections'.")
|
||||
)
|
||||
show_cables = forms.BooleanField(
|
||||
label =_("Show Cables"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Displays connections between interfaces that are connected "
|
||||
"with one or more cables. These connections are displayed as solid "
|
||||
"lines in the color of the cable.")
|
||||
)
|
||||
show_logical_connections = forms.BooleanField(
|
||||
label =_("Show Logical Connections"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Displays connections between devices that are not "
|
||||
"directly connected (e.g. via patch panels). These connections "
|
||||
"are displayed as yellow dotted lines.")
|
||||
)
|
||||
show_single_cable_logical_conns = forms.BooleanField(
|
||||
label = ("Show redundant Cable and Locigal Connection"),
|
||||
required = False,
|
||||
initial=False,
|
||||
help_text=_("Shows a logical connection (in addition to a cable), "
|
||||
"even if a cable is directly connected. Leaving this option "
|
||||
"disabled prevents that redundant display. This option only "
|
||||
"has an effect if 'Show Logical Connections' is activated.")
|
||||
)
|
||||
show_circuit = forms.BooleanField(
|
||||
label=_("Show Circuit Terminations"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Displays connections between circuit terminations. "
|
||||
"These connections are displayed as blue dashed lines.")
|
||||
)
|
||||
show_power = forms.BooleanField(
|
||||
label=_("Show Power Feeds"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Displays connections between power outlets and power "
|
||||
"ports. These connections are displayed as solid lines in the "
|
||||
"color of the cable. This option depends on 'Show Cables'.")
|
||||
)
|
||||
show_wireless = forms.BooleanField(
|
||||
label =_("Show Wireless Links"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Displays wireless connections. These connections are "
|
||||
"displayed as blue dotted lines.")
|
||||
)
|
||||
draw_default_layout = forms.BooleanField(
|
||||
label = ("Draw Default Layout"),
|
||||
required=False,
|
||||
initial=False,
|
||||
help_text=_("Enable this option if you want to draw the topology on "
|
||||
"the initial load (when you go to the topology plugin page).")
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = IndividualOptions
|
||||
fields = [
|
||||
'user_id', 'ignore_cable_type', 'preselected_device_roles', 'preselected_tags', 'show_unconnected', 'show_cables', 'show_logical_connections', 'show_single_cable_logical_conns', 'show_circuit', 'show_power', 'show_wireless', 'draw_default_layout'
|
||||
]
|
||||
@@ -0,0 +1,42 @@
|
||||
# Generated by Django 4.1.4 on 2023-03-19 14:13
|
||||
|
||||
from django.db import migrations, models
|
||||
import taggit.managers
|
||||
import utilities.json
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0167_module_status'),
|
||||
('extras', '0084_staging'),
|
||||
('netbox_topology_views', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='IndividualOptions',
|
||||
fields=[
|
||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False)),
|
||||
('created', models.DateTimeField(auto_now_add=True, null=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True, null=True)),
|
||||
('custom_field_data', models.JSONField(blank=True, default=dict, encoder=utilities.json.CustomFieldJSONEncoder)),
|
||||
('user_id', models.IntegerField(null=True, unique=True)),
|
||||
('ignore_cable_type', models.CharField(blank=True, max_length=255)),
|
||||
('show_unconnected', models.BooleanField(default=False)),
|
||||
('show_cables', models.BooleanField(default=False)),
|
||||
('show_logical_connections', models.BooleanField(default=False)),
|
||||
('show_single_cable_logical_conns', models.BooleanField(default=False)),
|
||||
('show_circuit', models.BooleanField(default=False)),
|
||||
('show_power', models.BooleanField(default=False)),
|
||||
('show_wireless', models.BooleanField(default=False)),
|
||||
('draw_default_layout', models.BooleanField(default=False)),
|
||||
('preselected_device_roles', models.ManyToManyField(blank=True, db_table='netbox_topology_views_individualoptions_preselected_device', related_name='+', to='dcim.devicerole')),
|
||||
('preselected_tags', models.ManyToManyField(blank=True, db_table='netbox_topology_views_individualoptions_preselected_tag', related_name='+', to='extras.tag')),
|
||||
('tags', taggit.managers.TaggableManager(through='extras.TaggedItem', to='extras.Tag')),
|
||||
],
|
||||
options={
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -2,10 +2,12 @@ from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
from dcim.models import DeviceRole
|
||||
from extras.models import Tag
|
||||
from django.conf import settings
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
from django.templatetags.static import static
|
||||
from netbox.models import NetBoxModel
|
||||
from netbox.models.features import (
|
||||
ChangeLoggingMixin,
|
||||
ExportTemplatesMixin,
|
||||
@@ -91,3 +93,62 @@ class RoleImage(ChangeLoggingMixin, ExportTemplatesMixin, WebhooksMixin):
|
||||
except ValueError:
|
||||
return self.get_default_image(dir)
|
||||
return static(f"/{self.image}")
|
||||
|
||||
class IndividualOptions(NetBoxModel):
|
||||
CHOICES = (
|
||||
('interface', 'interface'),
|
||||
('front port', 'front port'),
|
||||
('rear port', 'rear port'),
|
||||
('power outlet', 'power outlet'),
|
||||
('power port', 'power port'),
|
||||
('console port', 'console port'),
|
||||
('console server port', 'console server port'),
|
||||
)
|
||||
|
||||
user_id = models.IntegerField(
|
||||
null=True,
|
||||
unique=True
|
||||
)
|
||||
ignore_cable_type = models.CharField(
|
||||
max_length = 255,
|
||||
blank = True,
|
||||
)
|
||||
preselected_device_roles = models.ManyToManyField(
|
||||
to='dcim.DeviceRole',
|
||||
related_name='+',
|
||||
blank=True,
|
||||
db_table='netbox_topology_views_individualoptions_preselected_device',
|
||||
)
|
||||
preselected_tags = models.ManyToManyField(
|
||||
to='extras.Tag',
|
||||
related_name='+',
|
||||
blank=True,
|
||||
db_table='netbox_topology_views_individualoptions_preselected_tag',
|
||||
)
|
||||
show_unconnected = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
show_cables = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
show_logical_connections = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
show_single_cable_logical_conns = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
show_circuit = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
show_power = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
show_wireless = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
draw_default_layout = models.BooleanField(
|
||||
default=False
|
||||
)
|
||||
|
||||
def __str___(self):
|
||||
return f"{self.user_id}"
|
||||
@@ -4,7 +4,12 @@ menu = PluginMenu(
|
||||
label='Topology Views',
|
||||
icon_class="mdi mdi-sitemap",
|
||||
groups=(
|
||||
('Topology', (PluginMenuItem(link="plugins:netbox_topology_views:home", link_text="Topology"),),),
|
||||
('CUSTOMIZATION', (PluginMenuItem(link="plugins:netbox_topology_views:images", link_text="Topology Images"),),),
|
||||
('TOPOLOGY', (PluginMenuItem(link="plugins:netbox_topology_views:home", link_text="Topology"),),),
|
||||
('PREFERENCES',
|
||||
(
|
||||
PluginMenuItem(link="plugins:netbox_topology_views:images", link_text="Images"),
|
||||
PluginMenuItem(link="plugins:netbox_topology_views:individualoptions", link_text="Individual Options"),
|
||||
),
|
||||
),
|
||||
),
|
||||
)
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{% extends 'generic/object_edit.html' %}
|
||||
|
||||
{% block title %}Topology Views Individual Options{% endblock %}
|
||||
|
||||
{% block tabs %} {% endblock tabs %}
|
||||
@@ -9,4 +9,5 @@ urlpatterns = (
|
||||
path("", RedirectView.as_view(url="topology/", permanent=True)),
|
||||
path("topology/", views.TopologyHomeView.as_view(), name="home"),
|
||||
path("images/", views.TopologyImagesView.as_view(), name="images"),
|
||||
path("individualoptions/", views.TopologyIndividualOptionsView.as_view(), name="individualoptions"),
|
||||
)
|
||||
|
||||
+110
-70
@@ -18,9 +18,11 @@ from dcim.models import (
|
||||
RearPort,
|
||||
)
|
||||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db.models import Q, QuerySet
|
||||
from django.db.models.functions import Lower
|
||||
from django.http import HttpRequest, HttpResponseRedirect, QueryDict
|
||||
from django.shortcuts import render
|
||||
from django.views.generic import View
|
||||
@@ -28,8 +30,8 @@ from extras.models import Tag
|
||||
from wireless.models import WirelessLink
|
||||
|
||||
from netbox_topology_views.filters import DeviceFilterSet
|
||||
from netbox_topology_views.forms import DeviceFilterForm
|
||||
from netbox_topology_views.models import RoleImage
|
||||
from netbox_topology_views.forms import DeviceFilterForm, IndividualOptionsForm
|
||||
from netbox_topology_views.models import RoleImage, IndividualOptions
|
||||
from netbox_topology_views.utils import (
|
||||
CONF_IMAGE_DIR,
|
||||
find_image_url,
|
||||
@@ -38,15 +40,6 @@ from netbox_topology_views.utils import (
|
||||
image_static_url,
|
||||
)
|
||||
|
||||
supported_termination_types = [
|
||||
"interface",
|
||||
"front port",
|
||||
"rear port",
|
||||
"power outlet",
|
||||
"power port",
|
||||
"console port",
|
||||
"console server port",
|
||||
]
|
||||
|
||||
|
||||
def get_image_for_entity(entity: Union[Device, Circuit, PowerPanel, PowerFeed]):
|
||||
@@ -261,14 +254,21 @@ def create_circuit_termination(termination):
|
||||
|
||||
def get_topology_data(
|
||||
queryset: QuerySet,
|
||||
hide_unconnected: bool,
|
||||
individualOptions: IndividualOptions,
|
||||
show_unconnected: bool,
|
||||
save_coords: bool,
|
||||
show_cables: bool,
|
||||
show_circuit: bool,
|
||||
show_logical_connections: bool,
|
||||
show_single_cable_logical_conns: bool,
|
||||
show_power: bool,
|
||||
show_wireless: bool,
|
||||
):
|
||||
|
||||
supported_termination_types = []
|
||||
for t in IndividualOptions.CHOICES:
|
||||
supported_termination_types.append(t[1])
|
||||
|
||||
if not queryset:
|
||||
return None
|
||||
|
||||
@@ -282,14 +282,8 @@ def get_topology_data(
|
||||
nodes_provider_networks = {}
|
||||
cable_ids = DefaultDict(dict)
|
||||
interface_ids = DefaultDict(dict)
|
||||
ignore_cable_type = settings.PLUGINS_CONFIG["netbox_topology_views"][
|
||||
"ignore_cable_type"
|
||||
]
|
||||
hide_single_cable_logical_conns = bool(
|
||||
settings.PLUGINS_CONFIG["netbox_topology_views"][
|
||||
"hide_single_cable_logical_conns"
|
||||
]
|
||||
)
|
||||
|
||||
ignore_cable_type = individualOptions.ignore_cable_type
|
||||
|
||||
device_ids = [d.pk for d in queryset]
|
||||
site_ids = [d.site_id for d in queryset]
|
||||
@@ -301,7 +295,7 @@ def get_topology_data(
|
||||
for circuit_termination in circuit_terminations:
|
||||
circuit_termination: CircuitTermination
|
||||
if (
|
||||
not hide_unconnected
|
||||
show_unconnected
|
||||
and circuit_termination.circuit_id not in nodes_circuits
|
||||
):
|
||||
nodes_circuits[
|
||||
@@ -358,7 +352,7 @@ def get_topology_data(
|
||||
if termination.device_id in device_ids:
|
||||
circuit_has_connections = True
|
||||
|
||||
if circuit_has_connections and hide_unconnected:
|
||||
if circuit_has_connections and not show_unconnected:
|
||||
if circuit_termination.circuit_id not in nodes_circuits:
|
||||
nodes_circuits[
|
||||
circuit_termination.circuit.pk
|
||||
@@ -376,15 +370,15 @@ def get_topology_data(
|
||||
)
|
||||
|
||||
for power_feed in power_feeds:
|
||||
if not hide_unconnected or (
|
||||
hide_unconnected and power_feed.cable_id is not None
|
||||
if show_unconnected or (
|
||||
not show_unconnected and power_feed.cable_id is not None
|
||||
):
|
||||
if power_feed.power_panel_id not in nodes_powerpanel:
|
||||
nodes_powerpanel[power_feed.power_panel.pk] = power_feed.power_panel
|
||||
|
||||
power_link_name = ""
|
||||
if power_feed.pk not in nodes_powerfeed:
|
||||
if hide_unconnected:
|
||||
if not show_unconnected:
|
||||
if power_feed.link_peers[0].device_id in device_ids:
|
||||
nodes_powerfeed[power_feed.pk] = power_feed
|
||||
power_link_name = power_feed.link_peers[0].name
|
||||
@@ -438,7 +432,7 @@ def get_topology_data(
|
||||
# print('Destination interface already exists, ignoring')
|
||||
continue
|
||||
|
||||
if hide_single_cable_logical_conns and interface.cable_id==destination.cable_id and show_cables:
|
||||
if not show_single_cable_logical_conns and interface.cable_id==destination.cable_id and show_cables:
|
||||
# interface connection is the same as the cable connection, ignore this connection
|
||||
continue
|
||||
|
||||
@@ -569,7 +563,7 @@ def get_topology_data(
|
||||
)
|
||||
|
||||
for qs_device in queryset:
|
||||
if qs_device.pk not in nodes_devices and not hide_unconnected:
|
||||
if qs_device.pk not in nodes_devices and show_unconnected:
|
||||
nodes_devices[qs_device.pk] = qs_device
|
||||
|
||||
results = {}
|
||||
@@ -598,16 +592,27 @@ class TopologyHomeView(PermissionRequiredMixin, View):
|
||||
self.model = self.queryset.model
|
||||
topo_data = None
|
||||
|
||||
individualOptions, created = IndividualOptions.objects.get_or_create(
|
||||
user_id=request.user.id,
|
||||
)
|
||||
|
||||
if request.GET:
|
||||
save_coords = False
|
||||
if "save_coords" in request.GET:
|
||||
if request.GET["save_coords"] == "on":
|
||||
save_coords = True
|
||||
# General options overrides
|
||||
if save_coords == True and settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"] == False:
|
||||
save_coords = False
|
||||
messages.warning(request, "Coordinate saving not allowed. Setting has been overridden")
|
||||
elif settings.PLUGINS_CONFIG["netbox_topology_views"]["always_save_coordinates"] == True:
|
||||
save_coords = True
|
||||
|
||||
hide_unconnected = False
|
||||
if "hide_unconnected" in request.GET:
|
||||
if request.GET["hide_unconnected"] == "on":
|
||||
hide_unconnected = True
|
||||
# Individual options
|
||||
show_unconnected = False
|
||||
if "show_unconnected" in request.GET:
|
||||
if request.GET["show_unconnected"] == "on":
|
||||
show_unconnected = True
|
||||
|
||||
show_power = False
|
||||
if "show_power" in request.GET:
|
||||
@@ -624,6 +629,11 @@ class TopologyHomeView(PermissionRequiredMixin, View):
|
||||
if request.GET["show_logical_connections"] == "on" :
|
||||
show_logical_connections = True
|
||||
|
||||
show_single_cable_logical_conns = False
|
||||
if "show_single_cable_logical_conns" in request.GET:
|
||||
if request.GET["show_single_cable_logical_conns"] == "on" :
|
||||
show_single_cable_logical_conns = True
|
||||
|
||||
show_cables = False
|
||||
if "show_cables" in request.GET:
|
||||
if request.GET["show_cables"] == "on" :
|
||||
@@ -634,60 +644,45 @@ class TopologyHomeView(PermissionRequiredMixin, View):
|
||||
if request.GET["show_wireless"] == "on" :
|
||||
show_wireless = True
|
||||
|
||||
if "draw_init" in request.GET:
|
||||
if request.GET["draw_init"].lower() == "true":
|
||||
topo_data = get_topology_data(
|
||||
self.queryset,
|
||||
hide_unconnected,
|
||||
save_coords,
|
||||
show_cables,
|
||||
show_circuit,
|
||||
show_logical_connections,
|
||||
show_power,
|
||||
show_wireless,
|
||||
)
|
||||
else:
|
||||
if not "draw_init" in request.GET or "draw_init" in request.GET and request.GET["draw_init"].lower() == "true":
|
||||
topo_data = get_topology_data(
|
||||
self.queryset,
|
||||
hide_unconnected,
|
||||
individualOptions,
|
||||
show_unconnected,
|
||||
save_coords,
|
||||
show_cables,
|
||||
show_circuit,
|
||||
show_logical_connections,
|
||||
show_single_cable_logical_conns,
|
||||
show_power,
|
||||
show_wireless,
|
||||
)
|
||||
|
||||
else:
|
||||
preselected_device_roles = settings.PLUGINS_CONFIG["netbox_topology_views"][
|
||||
"preselected_device_roles"
|
||||
]
|
||||
preselected_tags = settings.PLUGINS_CONFIG["netbox_topology_views"][
|
||||
"preselected_tags"
|
||||
]
|
||||
always_save_coordinates = bool(
|
||||
settings.PLUGINS_CONFIG["netbox_topology_views"][
|
||||
"always_save_coordinates"
|
||||
]
|
||||
)
|
||||
|
||||
q_device_role_id = DeviceRole.objects.filter(
|
||||
name__in=preselected_device_roles
|
||||
).values_list("id", flat=True)
|
||||
q_tags = Tag.objects.filter(name__in=preselected_tags).values_list(
|
||||
"name", flat=True
|
||||
)
|
||||
# No GET-Request in URL. We most likely came here from the navigation menu.
|
||||
preselected_device_roles = IndividualOptions.objects.get(id=individualOptions.id).preselected_device_roles.all().values_list('id', flat=True)
|
||||
preselected_tags = IndividualOptions.objects.get(id=individualOptions.id).preselected_tags.all().values_list(Lower('name'), flat=True)
|
||||
|
||||
q = QueryDict(mutable=True)
|
||||
q.setlist("device_role_id", list(q_device_role_id))
|
||||
q.setlist("tag", list(q_tags))
|
||||
q["draw_init"] = settings.PLUGINS_CONFIG["netbox_topology_views"][
|
||||
"draw_default_layout"
|
||||
]
|
||||
if always_save_coordinates:
|
||||
q["save_coords"] = "on"
|
||||
q.setlist("device_role_id", list(preselected_device_roles))
|
||||
q.setlist("tag", list(preselected_tags))
|
||||
|
||||
if individualOptions.show_unconnected: q['show_unconnected'] = "on"
|
||||
if individualOptions.show_cables: q['show_cables'] = "on"
|
||||
if individualOptions.show_logical_connections: q['show_logical_connections'] = "on"
|
||||
if individualOptions.show_single_cable_logical_conns: q['show_single_cable_logical_conns'] = "on"
|
||||
if individualOptions.show_circuit: q['show_circuit'] = "on"
|
||||
if individualOptions.show_power: q['show_power'] = "on"
|
||||
if individualOptions.show_wireless: q['show_wireless'] = "on"
|
||||
if individualOptions.draw_default_layout:
|
||||
q['draw_init'] = "true"
|
||||
else:
|
||||
q['draw_init'] = "false"
|
||||
|
||||
query_string = q.urlencode()
|
||||
return HttpResponseRedirect(f"{request.path}?{query_string}")
|
||||
|
||||
|
||||
if is_htmx(request):
|
||||
return render(
|
||||
request,
|
||||
@@ -768,3 +763,48 @@ class TopologyImagesView(PermissionRequiredMixin, View):
|
||||
"images": images,
|
||||
},
|
||||
)
|
||||
|
||||
class TopologyIndividualOptionsView(PermissionRequiredMixin, View):
|
||||
permission_required = 'netbox_topology_views.change_individualoptions'
|
||||
|
||||
def post(self, request):
|
||||
instance = IndividualOptions.objects.get(user_id=request.user.id)
|
||||
form = IndividualOptionsForm(request.POST, instance=instance)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
messages.success(request, "Options have been sucessfully saved")
|
||||
else:
|
||||
messages.error(request, form.errors)
|
||||
|
||||
return HttpResponseRedirect("./")
|
||||
|
||||
def get(self, request):
|
||||
queryset, created = IndividualOptions.objects.get_or_create(
|
||||
user_id=request.user.id,
|
||||
)
|
||||
|
||||
form = IndividualOptionsForm(
|
||||
initial={
|
||||
'user_id': request.user.id,
|
||||
'ignore_cable_type': tuple(queryset.ignore_cable_type.translate({ord(i): None for i in '[]\''}).split(', ')),
|
||||
'preselected_device_roles': IndividualOptions.objects.get(id=queryset.id).preselected_device_roles.all(),
|
||||
'preselected_tags': IndividualOptions.objects.get(id=queryset.id).preselected_tags.all(),
|
||||
'show_unconnected': queryset.show_unconnected,
|
||||
'show_cables': queryset.show_cables,
|
||||
'show_logical_connections': queryset.show_logical_connections,
|
||||
'show_single_cable_logical_conns': queryset.show_single_cable_logical_conns,
|
||||
'show_circuit': queryset.show_circuit,
|
||||
'show_power': queryset.show_power,
|
||||
'show_wireless': queryset.show_wireless,
|
||||
'draw_default_layout': queryset.draw_default_layout,
|
||||
},
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"netbox_topology_views/individual_options.html",
|
||||
{
|
||||
"form": form,
|
||||
"object": queryset,
|
||||
},
|
||||
)
|
||||
Reference in New Issue
Block a user