Move from hard-coded CatalogZone SOA values (refresh, retry, expire and minimum)
to DB driven to permit user modification: modified: netbox_dns_bridge/models.py Add 4 cols for values. modified: netbox_dns_bridge/migrations/0003_catalogzone_and_per_zone_identifier.py Adjust migration. This expects that the last migration the user has is 0002 (<=v1.5.8) modified: netbox_dns_bridge/jobs/notify.py modified: netbox_dns_bridge/management/commands/dns_transfer_endpoint/catalog_zone_manager.py Update to use new db values for SOA creation. Change UI to permit editing for SOA values and disable member identifier editing. modified: netbox_dns_bridge/forms.py modified: netbox_dns_bridge/navigation.py new file: netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_edit.html new file: netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_list.html new file: netbox_dns_bridge/templates/netbox_dns_bridge/catalog_zone_members.html deleted: netbox_dns_bridge/templates/netbox_dns_bridge/catz_overview.html modified: netbox_dns_bridge/tables.py modified: netbox_dns_bridge/urls.py modified: netbox_dns_bridge/views.py modified: netbox_dns_bridge/filtersets.py GUI components are entirely AI generated. Plumbing also with AI assist but code verified.
This commit is contained in:
1 parent
69781e2b08
commit
a3f53767e4
14 files changed
+209
-63
No files matched your search
@@ -5,6 +5,8 @@ import netbox_dns.models
|
||||
from netbox.filtersets import NetBoxModelFilterSet
|
||||
from netbox_dns.choices import ZoneStatusChoices
|
||||
|
||||
from .models import CatalogZone
|
||||
|
||||
|
||||
class ZoneFilterSet(NetBoxModelFilterSet):
|
||||
view = django_filters.ModelMultipleChoiceFilter(
|
||||
@@ -27,3 +29,19 @@ class ZoneFilterSet(NetBoxModelFilterSet):
|
||||
Q(name__icontains=value)
|
||||
| Q(catz_identifier__name__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
class CatalogZoneFilterSet(NetBoxModelFilterSet):
|
||||
view = django_filters.ModelMultipleChoiceFilter(
|
||||
field_name="view",
|
||||
queryset=netbox_dns.models.View.objects.all(),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CatalogZone
|
||||
fields = ("id", "view")
|
||||
|
||||
def search(self, queryset, name, value):
|
||||
if not value.strip():
|
||||
return queryset
|
||||
return queryset.filter(Q(view__name__icontains=value))
|
||||
@@ -2,12 +2,40 @@ from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
import netbox_dns.models
|
||||
from netbox.forms import NetBoxModelFilterSetForm
|
||||
from netbox.forms import NetBoxModelFilterSetForm, NetBoxModelForm
|
||||
from netbox_dns.choices import ZoneStatusChoices
|
||||
from utilities.forms.fields import DynamicModelMultipleChoiceField
|
||||
|
||||
from .models import CatalogZone
|
||||
|
||||
class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
|
||||
|
||||
class CatalogZoneForm(NetBoxModelForm):
|
||||
soa_serial = forms.IntegerField(label=_("SOA Serial"))
|
||||
soa_refresh = forms.IntegerField(label=_("Refresh"))
|
||||
soa_retry = forms.IntegerField(label=_("Retry"))
|
||||
soa_expire = forms.IntegerField(label=_("Expire"))
|
||||
soa_minimum = forms.IntegerField(label=_("Minimum"))
|
||||
|
||||
view = forms.ModelChoiceField(
|
||||
queryset=netbox_dns.models.View.objects.all(),
|
||||
disabled=True,
|
||||
required=False,
|
||||
label=_("View"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = CatalogZone
|
||||
fields = (
|
||||
"view",
|
||||
"soa_serial",
|
||||
"soa_refresh",
|
||||
"soa_retry",
|
||||
"soa_expire",
|
||||
"soa_minimum",
|
||||
)
|
||||
|
||||
|
||||
class CatalogZoneMemberFilterForm(NetBoxModelFilterSetForm):
|
||||
model = netbox_dns.models.Zone
|
||||
|
||||
q = forms.CharField(required=False, label=_("Search"))
|
||||
@@ -19,3 +47,13 @@ class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
|
||||
choices=ZoneStatusChoices,
|
||||
required=False,
|
||||
)
|
||||
|
||||
|
||||
class CatalogZoneFilterForm(NetBoxModelFilterSetForm):
|
||||
model = CatalogZone
|
||||
|
||||
q = forms.CharField(required=False, label=_("Search"))
|
||||
view = DynamicModelMultipleChoiceField(
|
||||
queryset=netbox_dns.models.View.objects.all(),
|
||||
required=False,
|
||||
)
|
||||
@@ -51,6 +51,10 @@ def _send_notify(
|
||||
notify_over_tcp,
|
||||
port,
|
||||
log_prefix="NOTIFY",
|
||||
soa_refresh=60,
|
||||
soa_retry=10,
|
||||
soa_expire=1209600,
|
||||
soa_minimum=0,
|
||||
):
|
||||
"""Build a DNS NOTIFY message for *zone_name* and send it to every target IP."""
|
||||
fqdn = dns.name.from_text(zone_name, dns.name.root).to_text()
|
||||
@@ -61,7 +65,7 @@ def _send_notify(
|
||||
soa_rdata = dns.rdata.from_text(
|
||||
dns.rdataclass.IN,
|
||||
dns.rdatatype.SOA,
|
||||
f"invalid. invalid. {soa_serial} 60 10 1209600 0",
|
||||
f"invalid. invalid. {soa_serial} {soa_refresh} {soa_retry} {soa_expire} {soa_minimum}",
|
||||
)
|
||||
msg.answer.append(dns.rrset.from_rdata(fqdn, 0, soa_rdata))
|
||||
msg.use_tsig(keyring={tsig_key.name: tsig_key}, keyname=tsig_key.name)
|
||||
@@ -95,6 +99,10 @@ class SendClientNotify(JobRunner):
|
||||
view_name = kwargs["view_name"]
|
||||
zone_name = kwargs["zone_name"]
|
||||
soa_serial = kwargs["soa_serial"]
|
||||
soa_refresh = kwargs.get("soa_refresh", 60)
|
||||
soa_retry = kwargs.get("soa_retry", 10)
|
||||
soa_expire = kwargs.get("soa_expire", 1209600)
|
||||
soa_minimum = kwargs.get("soa_minimum", 0)
|
||||
|
||||
try:
|
||||
view = View.objects.get(name=view_name)
|
||||
@@ -111,6 +119,8 @@ class SendClientNotify(JobRunner):
|
||||
_send_notify(
|
||||
zone_name, soa_serial, clients, tsig_key,
|
||||
view_name, notify_over_tcp, port, log_prefix="NOTIFY",
|
||||
soa_refresh=soa_refresh, soa_retry=soa_retry,
|
||||
soa_expire=soa_expire, soa_minimum=soa_minimum,
|
||||
)
|
||||
|
||||
except View.DoesNotExist as e:
|
||||
@@ -278,17 +288,31 @@ def schedule_catalog_zone_notify(view: View):
|
||||
return
|
||||
|
||||
try:
|
||||
serial = view.catalog_zone.soa_serial
|
||||
catalog_zone = view.catalog_zone
|
||||
except CatalogZone.DoesNotExist:
|
||||
return
|
||||
|
||||
serial = catalog_zone.soa_serial
|
||||
soa_refresh = catalog_zone.soa_refresh
|
||||
soa_retry = catalog_zone.soa_retry
|
||||
soa_expire = catalog_zone.soa_expire
|
||||
soa_minimum = catalog_zone.soa_minimum
|
||||
|
||||
SendClientNotify.enqueue(
|
||||
zone_name="catz",
|
||||
soa_serial=serial,
|
||||
view_name=view.name,
|
||||
soa_refresh=soa_refresh,
|
||||
soa_retry=soa_retry,
|
||||
soa_expire=soa_expire,
|
||||
soa_minimum=soa_minimum,
|
||||
)
|
||||
SendClientNotify.enqueue(
|
||||
zone_name=f"{view.name}.catz",
|
||||
soa_serial=serial,
|
||||
view_name=view.name,
|
||||
soa_refresh=soa_refresh,
|
||||
soa_retry=soa_retry,
|
||||
soa_expire=soa_expire,
|
||||
soa_minimum=soa_minimum,
|
||||
)
|
||||
@@ -174,7 +174,9 @@ def _create_soa_rdataset(view: NBView) -> dns.rdataset:
|
||||
rdata = dns.rdata.from_text(
|
||||
rclass,
|
||||
rtype,
|
||||
f"invalid. invalid. {serial} 60 10 1209600 0",
|
||||
f"invalid. invalid. {serial} {serial_obj.soa_refresh} "
|
||||
f"{serial_obj.soa_retry} {serial_obj.soa_expire} "
|
||||
f"{serial_obj.soa_minimum}",
|
||||
)
|
||||
|
||||
# Create Rdataset and add the RDATA to it
|
||||
|
||||
@@ -88,6 +88,10 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
),
|
||||
("soa_serial", models.IntegerField(default=1)),
|
||||
("soa_refresh", models.IntegerField(default=60)),
|
||||
("soa_retry", models.IntegerField(default=10)),
|
||||
("soa_expire", models.IntegerField(default=1209600)),
|
||||
("soa_minimum", models.IntegerField(default=0)),
|
||||
(
|
||||
"view",
|
||||
models.OneToOneField(
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.db import models
|
||||
from django.urls import reverse
|
||||
|
||||
# import netbox.models
|
||||
import netbox_dns.models
|
||||
@@ -13,6 +14,10 @@ class CatalogZone(models.Model):
|
||||
related_name="catalog_zone",
|
||||
)
|
||||
soa_serial = models.IntegerField(default=1)
|
||||
soa_refresh = models.IntegerField(default=60)
|
||||
soa_retry = models.IntegerField(default=10)
|
||||
soa_expire = models.IntegerField(default=1209600)
|
||||
soa_minimum = models.IntegerField(default=0)
|
||||
|
||||
class Meta:
|
||||
ordering = ("view__name",)
|
||||
@@ -20,6 +25,9 @@ class CatalogZone(models.Model):
|
||||
def __str__(self):
|
||||
return f"{self.view.name}: {self.soa_serial}"
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse("plugins:netbox_dns_bridge:catalogzone_edit", args=[self.pk])
|
||||
|
||||
|
||||
class CatalogZoneMemberIdentifier(models.Model):
|
||||
_netbox_private = True
|
||||
|
||||
@@ -1,13 +1,20 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from netbox.plugins import PluginMenu, PluginMenuItem
|
||||
|
||||
information_menu_item = PluginMenuItem(
|
||||
link="plugins:netbox_dns_bridge:catz",
|
||||
catalog_zones_menu_item = PluginMenuItem(
|
||||
link="plugins:netbox_dns_bridge:catalog_zones",
|
||||
link_text=_("Catalog Zones"),
|
||||
permissions=[],
|
||||
staff_only=True,
|
||||
)
|
||||
|
||||
catalog_zone_members_menu_item = PluginMenuItem(
|
||||
link="plugins:netbox_dns_bridge:catalog_zone_members",
|
||||
link_text=_("Catalog Zone Members"),
|
||||
permissions=[],
|
||||
staff_only=True,
|
||||
)
|
||||
|
||||
notify_menu_item = PluginMenuItem(
|
||||
link="plugins:netbox_dns_bridge:notify",
|
||||
link_text=_("Notify"),
|
||||
@@ -18,7 +25,10 @@ notify_menu_item = PluginMenuItem(
|
||||
menu = PluginMenu(
|
||||
label=_("DNS Bridge"),
|
||||
groups=(
|
||||
(_("Information"), (information_menu_item, notify_menu_item)),
|
||||
(
|
||||
_("Information"),
|
||||
(catalog_zones_menu_item, catalog_zone_members_menu_item, notify_menu_item),
|
||||
),
|
||||
),
|
||||
icon_class="mdi mdi-bridge",
|
||||
)
|
||||
@@ -4,8 +4,10 @@ from django.utils.translation import gettext_lazy as _
|
||||
import netbox_dns.models
|
||||
from netbox.tables import NetBoxTable
|
||||
|
||||
from .models import CatalogZone
|
||||
|
||||
class CatalogZoneTable(NetBoxTable):
|
||||
|
||||
class CatalogZoneMemberTable(NetBoxTable):
|
||||
name = tables.Column(linkify=True)
|
||||
view = tables.Column(
|
||||
accessor="view__name",
|
||||
@@ -22,3 +24,35 @@ class CatalogZoneTable(NetBoxTable):
|
||||
model = netbox_dns.models.Zone
|
||||
fields = ("name", "view", "status", "catz_identifier")
|
||||
default_columns = ("name", "view", "status", "catz_identifier")
|
||||
|
||||
|
||||
class CatalogZoneTable(NetBoxTable):
|
||||
view = tables.Column(
|
||||
accessor="view__name",
|
||||
verbose_name=_("View"),
|
||||
linkify=True,
|
||||
)
|
||||
soa_serial = tables.Column(verbose_name=_("SOA Serial"))
|
||||
soa_refresh = tables.Column(verbose_name=_("Refresh"))
|
||||
soa_retry = tables.Column(verbose_name=_("Retry"))
|
||||
soa_expire = tables.Column(verbose_name=_("Expire"))
|
||||
soa_minimum = tables.Column(verbose_name=_("Minimum"))
|
||||
|
||||
class Meta(NetBoxTable.Meta):
|
||||
model = CatalogZone
|
||||
fields = (
|
||||
"view",
|
||||
"soa_serial",
|
||||
"soa_refresh",
|
||||
"soa_retry",
|
||||
"soa_expire",
|
||||
"soa_minimum",
|
||||
)
|
||||
default_columns = (
|
||||
"view",
|
||||
"soa_serial",
|
||||
"soa_refresh",
|
||||
"soa_retry",
|
||||
"soa_expire",
|
||||
"soa_minimum",
|
||||
)
|
||||
@@ -0,0 +1,4 @@
|
||||
{% extends 'generic/object_edit.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Edit Catalog Zone" %}{% endblock %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{% extends 'generic/object_list.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Catalog Zones" %}{% endblock %}
|
||||
@@ -0,0 +1,4 @@
|
||||
{% extends 'generic/object_list.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Catalog Zone Members" %}{% endblock %}
|
||||
@@ -1,37 +0,0 @@
|
||||
{% extends 'generic/object_list.html' %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Catalog Zone Info" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<h5 class="card-header">{% trans "Catalog Zone SOA Serial Numbers" %}</h5>
|
||||
<div class="card-body">
|
||||
{% if catalog_zones %}
|
||||
<table class="table table-sm">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{% trans "View" %}</th>
|
||||
<th>{% trans "SOA Serial" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cz in catalog_zones %}
|
||||
<tr>
|
||||
<td>{{ cz.view.name }}</td>
|
||||
<td>{{ cz.soa_serial }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% else %}
|
||||
<span class="text-muted">{% trans "No catalog zones initialized." %}</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ block.super }}
|
||||
{% endblock %}
|
||||
@@ -1,8 +1,23 @@
|
||||
from django.urls import path
|
||||
|
||||
from .views import CatzOverviewView, NotifyOverviewView
|
||||
from .views import (
|
||||
CatalogZoneEditView,
|
||||
CatalogZoneListView,
|
||||
CatalogZoneMembersView,
|
||||
NotifyOverviewView,
|
||||
)
|
||||
|
||||
urlpatterns = [
|
||||
path("catz/", CatzOverviewView.as_view(), name="catz"),
|
||||
path("catalog-zones/", CatalogZoneListView.as_view(), name="catalog_zones"),
|
||||
path(
|
||||
"catalog-zones/<int:pk>/edit/",
|
||||
CatalogZoneEditView.as_view(),
|
||||
name="catalogzone_edit",
|
||||
),
|
||||
path(
|
||||
"catalog-zone-members/",
|
||||
CatalogZoneMembersView.as_view(),
|
||||
name="catalog_zone_members",
|
||||
),
|
||||
path("notify/", NotifyOverviewView.as_view(), name="notify"),
|
||||
]
|
||||
+33
-15
@@ -4,34 +4,52 @@ from django.utils.decorators import method_decorator
|
||||
from django.views import View
|
||||
|
||||
import netbox_dns.models
|
||||
from netbox.views.generic import ObjectListView
|
||||
from netbox.views.generic import ObjectEditView, ObjectListView
|
||||
|
||||
from .filtersets import ZoneFilterSet
|
||||
from .forms import CatalogZoneFilterForm
|
||||
from .filtersets import CatalogZoneFilterSet, ZoneFilterSet
|
||||
from .forms import (
|
||||
CatalogZoneFilterForm,
|
||||
CatalogZoneForm,
|
||||
CatalogZoneMemberFilterForm,
|
||||
)
|
||||
from .models import CatalogZone, SeenTransferClients
|
||||
from .tables import CatalogZoneTable
|
||||
from .tables import CatalogZoneMemberTable, CatalogZoneTable
|
||||
|
||||
|
||||
@method_decorator(
|
||||
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
||||
)
|
||||
class CatzOverviewView(ObjectListView):
|
||||
class CatalogZoneListView(ObjectListView):
|
||||
queryset = CatalogZone.objects.select_related("view").order_by("view__name")
|
||||
table = CatalogZoneTable
|
||||
filterset = CatalogZoneFilterSet
|
||||
filterset_form = CatalogZoneFilterForm
|
||||
template_name = "netbox_dns_bridge/catalog_zone_list.html"
|
||||
actions = ("edit", "export")
|
||||
|
||||
|
||||
@method_decorator(
|
||||
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
||||
)
|
||||
class CatalogZoneEditView(ObjectEditView):
|
||||
queryset = CatalogZone.objects.select_related("view")
|
||||
form = CatalogZoneForm
|
||||
template_name = "netbox_dns_bridge/catalog_zone_edit.html"
|
||||
|
||||
|
||||
@method_decorator(
|
||||
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
||||
)
|
||||
class CatalogZoneMembersView(ObjectListView):
|
||||
queryset = netbox_dns.models.Zone.objects.select_related(
|
||||
"view", "catz_identifier"
|
||||
).order_by("view__name", "name")
|
||||
table = CatalogZoneTable
|
||||
table = CatalogZoneMemberTable
|
||||
filterset = ZoneFilterSet
|
||||
filterset_form = CatalogZoneFilterForm
|
||||
template_name = "netbox_dns_bridge/catz_overview.html"
|
||||
filterset_form = CatalogZoneMemberFilterForm
|
||||
template_name = "netbox_dns_bridge/catalog_zone_members.html"
|
||||
actions = ()
|
||||
|
||||
def get_extra_context(self, request):
|
||||
context = super().get_extra_context(request)
|
||||
context["catalog_zones"] = CatalogZone.objects.select_related(
|
||||
"view"
|
||||
).order_by("view__name")
|
||||
return context
|
||||
|
||||
|
||||
@method_decorator(
|
||||
user_passes_test(lambda u: u.is_superuser), name="dispatch"
|
||||
|
||||
Reference in new issue
Block a user