fix: IP-Bereiche statt Prefixe automatisch verwalten
- nutzbare Hostbereiche aus der CIDR-Maske erzeugen - Netzwerk- und Broadcastadressen bei IPv4 ausschließen - Abgleich-Aktion in die IP-Bereichsliste verschieben - Gerät oder VM in der IP-Adressansicht anzeigen - Bestandsabgleich für IP-Bereiche ergänzen - Plugin-Version auf 1.2.0 erhöhen
This commit is contained in:
@@ -2,10 +2,10 @@
|
|||||||
|
|
||||||
Plugin für **NetBox 4.6.5** mit zwei Funktionen:
|
Plugin für **NetBox 4.6.5** mit zwei Funktionen:
|
||||||
|
|
||||||
- Beim Speichern einer IP-Adresse wird das durch deren CIDR-Maske beschriebene Prefix gesucht und, falls es fehlt, automatisch erstellt. NetBox ordnet die IP danach nativ anhand von Prefix und VRF ein.
|
- Beim Speichern einer IP-Adresse wird der durch deren CIDR-Maske beschriebene nutzbare IP-Bereich gesucht und, falls er fehlt, automatisch erstellt. Bei normalen IPv4-Subnetzen werden Netzwerk- und Broadcastadresse nicht in den Bereich aufgenommen.
|
||||||
- Eine zusätzliche IP-Netzübersicht kann nach Organisation/Region, Standortgruppe, Standort, Lokation, Mandantengruppe und Mandant gefiltert werden.
|
- Eine zusätzliche IP-Netzübersicht kann nach Organisation/Region, Standortgruppe, Standort, Lokation, Mandantengruppe und Mandant gefiltert werden.
|
||||||
- Unter **IPAM → Prefixes/IP-Bereiche** legt die Aktion **Alle fehlenden Bereiche anlegen** die Netze für sämtliche bereits vorhandenen, sichtbaren IP-Adressen an.
|
- Unter **IPAM → IP-Bereiche** legt die Aktion **Alle fehlenden Bereiche anlegen** die Bereiche für sämtliche bereits vorhandenen, sichtbaren IP-Adressen an.
|
||||||
- Unter **Prefix/IP-Bereich → IP-Adressen** wird das zugehörige Gerät beziehungsweise die VM standardmäßig als eigene Spalte angezeigt.
|
- Unter **IP-Bereich → IP-Adressen** wird das zugehörige Gerät beziehungsweise die VM standardmäßig als eigene Spalte angezeigt.
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
@@ -24,9 +24,8 @@ PLUGINS = ["netbox_better_ips"]
|
|||||||
|
|
||||||
PLUGINS_CONFIG = {
|
PLUGINS_CONFIG = {
|
||||||
"netbox_better_ips": {
|
"netbox_better_ips": {
|
||||||
"auto_create_prefix": True,
|
"auto_create_range": True,
|
||||||
"prefix_status": "active",
|
"range_status": "active",
|
||||||
"inherit_scope": True,
|
|
||||||
"inherit_tenant": True,
|
"inherit_tenant": True,
|
||||||
"ignore_host_prefixes": True,
|
"ignore_host_prefixes": True,
|
||||||
}
|
}
|
||||||
@@ -37,22 +36,22 @@ Danach NetBox neu starten. Das Plugin hat keine eigenen Datenbankmodelle und ben
|
|||||||
|
|
||||||
## Verhalten
|
## Verhalten
|
||||||
|
|
||||||
Aus `192.0.2.17/24` wird bei Bedarf `192.0.2.0/24` in derselben VRF erstellt. Bei einer Interface-Zuweisung wird bevorzugt die Lokation, sonst der Standort als Prefix-Scope übernommen. Der Mandant wird von der IP oder dem zugewiesenen Gerät/der VM übernommen. Vorhandene Prefix-Metadaten werden nie überschrieben.
|
Aus `192.0.2.17/24` wird bei Bedarf der IP-Bereich `192.0.2.1/24` bis `192.0.2.254/24` in derselben VRF erstellt. Der Mandant wird von der IP oder dem zugewiesenen Gerät/der VM übernommen. Vorhandene Bereichsmetadaten werden nie überschrieben.
|
||||||
|
|
||||||
Hostmasken (`/32`, `/128`) werden standardmäßig nicht als Prefix erstellt. Dies kann mit `ignore_host_prefixes=False` geändert werden.
|
Hostmasken (`/32`, `/128`) werden standardmäßig nicht als eigener Bereich erstellt. Dies kann mit `ignore_host_prefixes=False` geändert werden.
|
||||||
|
|
||||||
Bereits vorhandene IP-Adressen lassen sich abgleichen:
|
Bereits vorhandene IP-Adressen lassen sich abgleichen:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python /opt/netbox/netbox/manage.py reconcile_ip_prefixes --dry-run
|
python /opt/netbox/netbox/manage.py reconcile_ip_ranges --dry-run
|
||||||
python /opt/netbox/netbox/manage.py reconcile_ip_prefixes
|
python /opt/netbox/netbox/manage.py reconcile_ip_ranges
|
||||||
```
|
```
|
||||||
|
|
||||||
## Berechtigungen
|
## Berechtigungen
|
||||||
|
|
||||||
Für die Übersicht ist die NetBox-Berechtigung `ipam.view_prefix` erforderlich. Die automatische Erstellung läuft serverseitig; stellen Sie sicher, dass dies zu Ihrem Berechtigungs- und Change-Control-Konzept passt.
|
Für die Übersicht ist die NetBox-Berechtigung `ipam.view_prefix` erforderlich. Die automatische Erstellung läuft serverseitig; stellen Sie sicher, dass dies zu Ihrem Berechtigungs- und Change-Control-Konzept passt.
|
||||||
|
|
||||||
Die Aktion für Bestandsdaten benötigt `ipam.view_ipaddress` und `ipam.add_prefix`. Sie verarbeitet nur IP-Adressen, die der ausführende Benutzer sehen darf.
|
Die Aktion für Bestandsdaten benötigt `ipam.view_ipaddress` und `ipam.add_iprange`. Sie verarbeitet nur IP-Adressen, die der ausführende Benutzer sehen darf.
|
||||||
|
|
||||||
## Hinweis zu „Organisation"
|
## Hinweis zu „Organisation"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from .config import BetterIPsConfig
|
from .config import BetterIPsConfig
|
||||||
|
|
||||||
__version__ = "1.1.0"
|
__version__ = "1.2.0"
|
||||||
config = BetterIPsConfig
|
config = BetterIPsConfig
|
||||||
|
|||||||
@@ -6,14 +6,13 @@ class BetterIPsConfig(PluginConfig):
|
|||||||
verbose_name = "NetBox Better IPs"
|
verbose_name = "NetBox Better IPs"
|
||||||
description = "Automatische Prefix-Zuordnung und erweiterte IP-Netzübersicht"
|
description = "Automatische Prefix-Zuordnung und erweiterte IP-Netzübersicht"
|
||||||
author = "LKE"
|
author = "LKE"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
base_url = "better-ips"
|
base_url = "better-ips"
|
||||||
min_version = "4.6.5"
|
min_version = "4.6.5"
|
||||||
max_version = "4.6.99"
|
max_version = "4.6.99"
|
||||||
default_settings = {
|
default_settings = {
|
||||||
"auto_create_prefix": True,
|
"auto_create_range": True,
|
||||||
"prefix_status": "active",
|
"range_status": "active",
|
||||||
"inherit_scope": True,
|
|
||||||
"inherit_tenant": True,
|
"inherit_tenant": True,
|
||||||
"ignore_host_prefixes": True,
|
"ignore_host_prefixes": True,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand
|
||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
|
|
||||||
from netbox_better_ips.services import ensure_prefix_for_ip
|
from netbox_better_ips.services import ensure_range_for_ip
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
help = "Create missing exact prefixes for existing IP addresses"
|
help = "Create missing usable-host IP ranges for existing IP addresses"
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
def add_arguments(self, parser):
|
||||||
parser.add_argument("--dry-run", action="store_true")
|
parser.add_argument("--dry-run", action="store_true")
|
||||||
@@ -13,11 +13,10 @@ class Command(BaseCommand):
|
|||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
created = skipped = 0
|
created = skipped = 0
|
||||||
for address in IPAddress.objects.select_related("tenant", "assigned_object_type").iterator():
|
for address in IPAddress.objects.select_related("tenant", "assigned_object_type").iterator():
|
||||||
prefix, was_created = ensure_prefix_for_ip(address)
|
ip_range, was_created = ensure_range_for_ip(address)
|
||||||
if options["dry_run"] and was_created:
|
if options["dry_run"] and was_created:
|
||||||
prefix.delete()
|
ip_range.delete()
|
||||||
created += int(was_created)
|
created += int(was_created)
|
||||||
skipped += int(prefix is None)
|
skipped += int(ip_range is None)
|
||||||
verb = "würden erstellt" if options["dry_run"] else "erstellt"
|
verb = "würden erstellt" if options["dry_run"] else "erstellt"
|
||||||
self.stdout.write(self.style.SUCCESS(f"{created} Prefixe {verb}; {skipped} Host-Präfixe ignoriert."))
|
self.stdout.write(self.style.SUCCESS(f"{created} IP-Bereiche {verb}; {skipped} Hostmasken ignoriert."))
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
from .reconcile_ip_prefixes import Command
|
||||||
@@ -1,9 +1,8 @@
|
|||||||
import ipaddress
|
import ipaddress
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from django.contrib.contenttypes.models import ContentType
|
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from ipam.models import Prefix
|
from ipam.models import IPRange
|
||||||
from netbox.plugins import get_plugin_config
|
from netbox.plugins import get_plugin_config
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -14,32 +13,6 @@ def network_for_address(address):
|
|||||||
return ipaddress.ip_interface(str(address)).network
|
return ipaddress.ip_interface(str(address)).network
|
||||||
|
|
||||||
|
|
||||||
def _scope_from_ip(ip):
|
|
||||||
"""Derive the most specific supported Prefix scope from an IP assignment."""
|
|
||||||
assigned = getattr(ip, "assigned_object", None)
|
|
||||||
if assigned is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
parent = (
|
|
||||||
getattr(assigned, "parent_object", None)
|
|
||||||
or getattr(assigned, "device", None)
|
|
||||||
or getattr(assigned, "virtual_machine", None)
|
|
||||||
)
|
|
||||||
if parent is None:
|
|
||||||
return None
|
|
||||||
|
|
||||||
location = getattr(parent, "location", None)
|
|
||||||
if location is not None:
|
|
||||||
return location
|
|
||||||
|
|
||||||
site = getattr(parent, "site", None)
|
|
||||||
if site is not None:
|
|
||||||
return site
|
|
||||||
|
|
||||||
cluster = getattr(parent, "cluster", None)
|
|
||||||
return getattr(cluster, "scope", None) if cluster is not None else None
|
|
||||||
|
|
||||||
|
|
||||||
def _tenant_from_ip(ip):
|
def _tenant_from_ip(ip):
|
||||||
tenant = getattr(ip, "tenant", None)
|
tenant = getattr(ip, "tenant", None)
|
||||||
if tenant is not None:
|
if tenant is not None:
|
||||||
@@ -55,37 +28,40 @@ def _tenant_from_ip(ip):
|
|||||||
|
|
||||||
|
|
||||||
@transaction.atomic
|
@transaction.atomic
|
||||||
def ensure_prefix_for_ip(ip, *, config=None):
|
def ensure_range_for_ip(ip, *, config=None):
|
||||||
"""Get or create the exact Prefix represented by an IP address and its mask.
|
"""Get or create the usable-host IPRange represented by an IP and its mask."""
|
||||||
|
|
||||||
NetBox derives hierarchy from address/prefix + VRF. There is deliberately no
|
|
||||||
parent FK to update on IPAddress; creating the missing Prefix is the assignment.
|
|
||||||
"""
|
|
||||||
settings = config or {
|
settings = config or {
|
||||||
key: get_plugin_config("netbox_better_ips", key)
|
key: get_plugin_config("netbox_better_ips", key)
|
||||||
for key in (
|
for key in ("range_status", "inherit_tenant", "ignore_host_prefixes")
|
||||||
"prefix_status", "inherit_scope", "inherit_tenant", "ignore_host_prefixes"
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
network = network_for_address(ip.address)
|
network = network_for_address(ip.address)
|
||||||
if settings.get("ignore_host_prefixes", True) and network.prefixlen == network.max_prefixlen:
|
if settings.get("ignore_host_prefixes", True) and network.prefixlen == network.max_prefixlen:
|
||||||
return None, False
|
return None, False
|
||||||
|
|
||||||
lookup = {"prefix": str(network), "vrf_id": ip.vrf_id}
|
first = int(network.network_address)
|
||||||
existing = Prefix.objects.filter(**lookup).first()
|
last = int(network.broadcast_address)
|
||||||
|
if network.version == 4 and network.prefixlen < 31:
|
||||||
|
first += 1
|
||||||
|
last -= 1
|
||||||
|
elif network.version == 6 and network.prefixlen < 127:
|
||||||
|
first += 1
|
||||||
|
|
||||||
|
start_address = f"{ipaddress.ip_address(first)}/{network.prefixlen}"
|
||||||
|
end_address = f"{ipaddress.ip_address(last)}/{network.prefixlen}"
|
||||||
|
lookup = {
|
||||||
|
"start_address": start_address,
|
||||||
|
"end_address": end_address,
|
||||||
|
"vrf_id": ip.vrf_id,
|
||||||
|
}
|
||||||
|
existing = IPRange.objects.filter(**lookup).first()
|
||||||
if existing:
|
if existing:
|
||||||
return existing, False
|
return existing, False
|
||||||
|
|
||||||
defaults = {"status": settings.get("prefix_status", "active")}
|
defaults = {"status": settings.get("range_status", "active")}
|
||||||
if settings.get("inherit_tenant", True):
|
if settings.get("inherit_tenant", True):
|
||||||
defaults["tenant"] = _tenant_from_ip(ip)
|
defaults["tenant"] = _tenant_from_ip(ip)
|
||||||
if settings.get("inherit_scope", True):
|
|
||||||
scope = _scope_from_ip(ip)
|
|
||||||
if scope is not None:
|
|
||||||
defaults["scope_type"] = ContentType.objects.get_for_model(scope)
|
|
||||||
defaults["scope_id"] = scope.pk
|
|
||||||
|
|
||||||
prefix, created = Prefix.objects.get_or_create(defaults=defaults, **lookup)
|
ip_range, created = IPRange.objects.get_or_create(defaults=defaults, **lookup)
|
||||||
if created:
|
if created:
|
||||||
logger.info("Created missing prefix %s for IP address %s", prefix, ip)
|
logger.info("Created missing IP range %s for IP address %s", ip_range, ip)
|
||||||
return prefix, created
|
return ip_range, created
|
||||||
|
|||||||
@@ -5,18 +5,17 @@ from django.dispatch import receiver
|
|||||||
from ipam.models import IPAddress
|
from ipam.models import IPAddress
|
||||||
from netbox.plugins import get_plugin_config
|
from netbox.plugins import get_plugin_config
|
||||||
|
|
||||||
from .services import ensure_prefix_for_ip
|
from .services import ensure_range_for_ip
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
@receiver(post_save, sender=IPAddress, dispatch_uid="netbox_better_ips.ensure_prefix")
|
@receiver(post_save, sender=IPAddress, dispatch_uid="netbox_better_ips.ensure_range")
|
||||||
def ensure_ip_prefix(sender, instance, raw=False, **kwargs):
|
def ensure_ip_range(sender, instance, raw=False, **kwargs):
|
||||||
if raw or not get_plugin_config("netbox_better_ips", "auto_create_prefix"):
|
if raw or not get_plugin_config("netbox_better_ips", "auto_create_range"):
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
ensure_prefix_for_ip(instance)
|
ensure_range_for_ip(instance)
|
||||||
except Exception:
|
except Exception:
|
||||||
# The IP save must not be rolled back by an optional convenience feature.
|
# The IP save must not be rolled back by an optional convenience feature.
|
||||||
logger.exception("Could not ensure a prefix for IP address %s", instance)
|
logger.exception("Could not ensure an IP range for IP address %s", instance)
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import django_tables2 as tables
|
import django_tables2 as tables
|
||||||
from ipam.tables import AnnotatedIPAddressTable
|
from ipam.tables import IPAddressTable
|
||||||
from utilities.tables import register_table_column
|
from utilities.tables import register_table_column
|
||||||
|
|
||||||
|
|
||||||
@@ -11,13 +11,15 @@ device_or_vm = tables.Column(
|
|||||||
empty_values=(),
|
empty_values=(),
|
||||||
)
|
)
|
||||||
|
|
||||||
register_table_column(device_or_vm, "device_or_vm", AnnotatedIPAddressTable)
|
register_table_column(device_or_vm, "device_or_vm", IPAddressTable)
|
||||||
|
|
||||||
# Registered columns are selectable but not displayed by default. The annotated
|
# Registered columns are selectable but not displayed by default. The annotated
|
||||||
# table is used specifically by Prefix -> IP Addresses, where this information
|
# table is used by IP Range -> IP Addresses, where this information should be
|
||||||
# should be visible immediately.
|
# visible immediately.
|
||||||
if "device_or_vm" not in AnnotatedIPAddressTable.Meta.default_columns:
|
if "device_or_vm" not in IPAddressTable.Meta.default_columns:
|
||||||
AnnotatedIPAddressTable.Meta.default_columns = (
|
IPAddressTable.Meta.default_columns = (
|
||||||
*AnnotatedIPAddressTable.Meta.default_columns,
|
*IPAddressTable.Meta.default_columns,
|
||||||
"device_or_vm",
|
"device_or_vm",
|
||||||
)
|
)
|
||||||
|
if "device_or_vm" not in IPAddressTable.exempt_columns:
|
||||||
|
IPAddressTable.exempt_columns = (*IPAddressTable.exempt_columns, "device_or_vm")
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
from netbox.plugins import PluginTemplateExtension
|
from netbox.plugins import PluginTemplateExtension
|
||||||
|
|
||||||
|
|
||||||
class PrefixListActions(PluginTemplateExtension):
|
class IPRangeListActions(PluginTemplateExtension):
|
||||||
model = "ipam.prefix"
|
model = "ipam.iprange"
|
||||||
|
|
||||||
def list_buttons(self):
|
def list_buttons(self):
|
||||||
return self.render("netbox_better_ips/inc/prefix_list_buttons.html")
|
return self.render("netbox_better_ips/inc/iprange_list_buttons.html")
|
||||||
|
|
||||||
|
|
||||||
template_extensions = (PrefixListActions,)
|
template_extensions = (IPRangeListActions,)
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
{% if perms.ipam.view_prefix %}
|
||||||
|
<a href="{% url 'plugins:netbox_better_ips:network_overview' %}" class="btn btn-outline-primary">
|
||||||
|
<i class="mdi mdi-view-list" aria-hidden="true"></i>
|
||||||
|
Erweiterte Netzübersicht
|
||||||
|
</a>
|
||||||
|
{% endif %}
|
||||||
|
{% if perms.ipam.view_ipaddress and perms.ipam.add_iprange %}
|
||||||
|
<form method="post" action="{% url 'plugins:netbox_better_ips:reconcile_missing_ranges' %}" class="d-inline">
|
||||||
|
{% csrf_token %}
|
||||||
|
<button type="submit" class="btn btn-primary">
|
||||||
|
<i class="mdi mdi-auto-fix" aria-hidden="true"></i>
|
||||||
|
Alle fehlenden Bereiche anlegen
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
{% if perms.ipam.view_ipaddress and perms.ipam.add_prefix %}
|
|
||||||
<form method="post" action="{% url 'plugins:netbox_better_ips:reconcile_missing_prefixes' %}" class="d-inline">
|
|
||||||
{% csrf_token %}
|
|
||||||
<button type="submit" class="btn btn-primary">
|
|
||||||
<i class="mdi mdi-auto-fix" aria-hidden="true"></i>
|
|
||||||
Alle fehlenden Bereiche anlegen
|
|
||||||
</button>
|
|
||||||
</form>
|
|
||||||
{% endif %}
|
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import NetworkOverviewView, reconcile_missing_prefixes
|
from .views import NetworkOverviewView, reconcile_missing_ranges
|
||||||
|
|
||||||
app_name = "netbox_better_ips"
|
app_name = "netbox_better_ips"
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path("networks/", NetworkOverviewView.as_view(), name="network_overview"),
|
path("networks/", NetworkOverviewView.as_view(), name="network_overview"),
|
||||||
path("reconcile/", reconcile_missing_prefixes, name="reconcile_missing_prefixes"),
|
path("reconcile/", reconcile_missing_ranges, name="reconcile_missing_ranges"),
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from ipam.models import IPAddress, Prefix
|
|||||||
import netaddr
|
import netaddr
|
||||||
|
|
||||||
from .forms import NetworkOverviewFilterForm
|
from .forms import NetworkOverviewFilterForm
|
||||||
from .services import ensure_prefix_for_ip
|
from .services import ensure_range_for_ip
|
||||||
|
|
||||||
|
|
||||||
def _descendant_ids(obj):
|
def _descendant_ids(obj):
|
||||||
@@ -95,17 +95,17 @@ class NetworkOverviewView(PermissionRequiredMixin, TemplateView):
|
|||||||
|
|
||||||
|
|
||||||
@require_POST
|
@require_POST
|
||||||
@permission_required(("ipam.view_ipaddress", "ipam.add_prefix"), raise_exception=True)
|
@permission_required(("ipam.view_ipaddress", "ipam.add_iprange"), raise_exception=True)
|
||||||
def reconcile_missing_prefixes(request):
|
def reconcile_missing_ranges(request):
|
||||||
created = skipped = failed = 0
|
created = skipped = failed = 0
|
||||||
addresses = IPAddress.objects.restrict(request.user, "view").select_related(
|
addresses = IPAddress.objects.restrict(request.user, "view").select_related(
|
||||||
"tenant", "assigned_object_type"
|
"tenant", "assigned_object_type"
|
||||||
)
|
)
|
||||||
for address in addresses.iterator():
|
for address in addresses.iterator():
|
||||||
try:
|
try:
|
||||||
prefix, was_created = ensure_prefix_for_ip(address)
|
ip_range, was_created = ensure_range_for_ip(address)
|
||||||
created += int(was_created)
|
created += int(was_created)
|
||||||
skipped += int(prefix is None)
|
skipped += int(ip_range is None)
|
||||||
except Exception:
|
except Exception:
|
||||||
failed += 1
|
failed += 1
|
||||||
|
|
||||||
@@ -119,4 +119,4 @@ def reconcile_missing_prefixes(request):
|
|||||||
request,
|
request,
|
||||||
f"{created} fehlende Bereiche wurden angelegt. {skipped} Host-Adressen wurden ignoriert.",
|
f"{created} fehlende Bereiche wurden angelegt. {skipped} Host-Adressen wurden ignoriert.",
|
||||||
)
|
)
|
||||||
return redirect("ipam:prefix_list")
|
return redirect("ipam:iprange_list")
|
||||||
|
|||||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "netbox-better-ips"
|
name = "netbox-better-ips"
|
||||||
version = "1.1.0"
|
version = "1.2.0"
|
||||||
description = "Automatic prefix creation and an organization-aware IP network overview for NetBox 4.6"
|
description = "Automatic prefix creation and an organization-aware IP network overview for NetBox 4.6"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.12"
|
requires-python = ">=3.12"
|
||||||
|
|||||||
Reference in New Issue
Block a user