netbox-ddns 1.4.0__py3-none-any.whl → 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. netbox_ddns/__init__.py +3 -3
  2. netbox_ddns/admin.py +0 -156
  3. netbox_ddns/api/serializers.py +20 -2
  4. netbox_ddns/api/views.py +1 -0
  5. netbox_ddns/background_tasks.py +28 -5
  6. netbox_ddns/filtersets.py +23 -1
  7. netbox_ddns/forms.py +54 -3
  8. netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py +36 -0
  9. netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py +36 -0
  10. netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py +36 -0
  11. netbox_ddns/migrations/0014_alter_extradnsname_name.py +19 -0
  12. netbox_ddns/migrations/0015_server_protocol.py +18 -0
  13. netbox_ddns/models.py +61 -9
  14. netbox_ddns/navigation.py +62 -0
  15. netbox_ddns/tables.py +78 -32
  16. netbox_ddns/template_content.py +40 -3
  17. netbox_ddns/templates/netbox_ddns/extradnsname.html +23 -131
  18. netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html +1 -1
  19. netbox_ddns/templates/netbox_ddns/managed_dns_names.html +17 -0
  20. netbox_ddns/templates/netbox_ddns/reversezone.html +51 -0
  21. netbox_ddns/templates/netbox_ddns/server.html +74 -0
  22. netbox_ddns/templates/netbox_ddns/update_reverse_zone.html +11 -0
  23. netbox_ddns/templates/netbox_ddns/update_zone.html +11 -0
  24. netbox_ddns/templates/netbox_ddns/zone.html +53 -0
  25. netbox_ddns/urls.py +32 -14
  26. netbox_ddns/utils.py +95 -0
  27. netbox_ddns/validators.py +6 -0
  28. netbox_ddns/views.py +268 -84
  29. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/METADATA +7 -5
  30. netbox_ddns-1.7.0.dist-info/RECORD +50 -0
  31. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/WHEEL +1 -1
  32. netbox_ddns-1.4.0.dist-info/RECORD +0 -38
  33. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info/licenses}/LICENSE.txt +0 -0
  34. {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/top_level.txt +0 -0
netbox_ddns/models.py CHANGED
@@ -4,6 +4,7 @@ import logging
4
4
  import socket
5
5
  from django.core.exceptions import ValidationError
6
6
  from django.db import models
7
+ from django.db.models import Q
7
8
  from django.db.models.functions import Length
8
9
  from django.utils.html import format_html
9
10
  from django.utils.translation import gettext_lazy as _
@@ -15,8 +16,10 @@ from typing import Optional
15
16
  from netbox.models import NetBoxModel
16
17
  from ipam.fields import IPNetworkField
17
18
  from ipam.models import IPAddress
19
+ from utilities.querysets import RestrictedQuerySet
18
20
  from .utils import normalize_fqdn
19
- from .validators import HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, MaxValueValidator
21
+ from .validators import DNSNameValidator, HostnameAddressValidator, HostnameValidator, validate_base64, MinValueValidator, \
22
+ MaxValueValidator
20
23
 
21
24
  logger = logging.getLogger('netbox_ddns')
22
25
 
@@ -61,8 +64,11 @@ def get_rcode_display(code):
61
64
  else:
62
65
  return _('Unknown response: {}').format(code)
63
66
 
67
+ class Protocol(models.TextChoices):
68
+ UDP = 'udp', _('UDP')
69
+ TCP = 'tcp', _('TCP')
64
70
 
65
- class Server(models.Model):
71
+ class Server(NetBoxModel):
66
72
  server = models.CharField(
67
73
  verbose_name=_('DDNS Server'),
68
74
  max_length=255,
@@ -92,6 +98,12 @@ class Server(models.Model):
92
98
  validators=[validate_base64],
93
99
  help_text=_('in base64 notation'),
94
100
  )
101
+ protocol = models.CharField(
102
+ verbose_name=_('Protocol'),
103
+ max_length=3,
104
+ choices=Protocol.choices,
105
+ default=Protocol.UDP,
106
+ )
95
107
 
96
108
  class Meta:
97
109
  unique_together = (
@@ -111,6 +123,9 @@ class Server(models.Model):
111
123
  # Ensure trailing dots from domain-style fields
112
124
  self.tsig_key_name = normalize_fqdn(self.tsig_key_name.lower().rstrip('.'))
113
125
 
126
+ def get_absolute_url(self):
127
+ return reverse('plugins:netbox_ddns:server', args=[self.pk])
128
+
114
129
  @property
115
130
  def address(self) -> Optional[str]:
116
131
  addrinfo = socket.getaddrinfo(self.server, self.server_port, proto=socket.IPPROTO_UDP)
@@ -129,7 +144,7 @@ class Server(models.Model):
129
144
  )
130
145
 
131
146
 
132
- class ZoneQuerySet(models.QuerySet):
147
+ class ZoneQuerySet(RestrictedQuerySet):
133
148
  def find_for_dns_name(self, dns_name: str) -> Optional['Zone']:
134
149
  # Generate all possible zones
135
150
  zones = []
@@ -141,7 +156,7 @@ class ZoneQuerySet(models.QuerySet):
141
156
  return self.filter(name__in=zones).order_by(Length('name').desc()).first()
142
157
 
143
158
 
144
- class Zone(models.Model):
159
+ class Zone(NetBoxModel):
145
160
  name = models.CharField(
146
161
  verbose_name=_('zone name'),
147
162
  max_length=255,
@@ -167,6 +182,28 @@ class Zone(models.Model):
167
182
  def __str__(self):
168
183
  return self.name
169
184
 
185
+ def get_managed_ip_address(self):
186
+ # Find all more-specific zones
187
+ more_specifics = Zone.objects.filter(name__endswith=self.name).exclude(pk=self.pk)
188
+
189
+ # Find all IPAddress objects in this self but not in the more-specifics
190
+ ip_addresses = IPAddress.objects.filter(Q(dns_name__endswith=self.name) |
191
+ Q(dns_name__endswith=self.name.rstrip('.')))
192
+ for more_specific in more_specifics:
193
+ ip_addresses = ip_addresses.exclude(Q(dns_name__endswith=more_specific.name) |
194
+ Q(dns_name__endswith=more_specific.name.rstrip('.')))
195
+ return ip_addresses
196
+
197
+ def get_managed_extra_dns_name(self):
198
+ # Find all more-specific zones
199
+ more_specifics = Zone.objects.filter(name__endswith=self.name).exclude(pk=self.pk)
200
+
201
+ # Find all ExtraDNSName objects in this zone but not in the more-specifics
202
+ extra_names = ExtraDNSName.objects.filter(name__endswith=self.name)
203
+ for more_specific in more_specifics:
204
+ extra_names = extra_names.exclude(name__endswith=more_specific.name)
205
+ return extra_names
206
+
170
207
  def clean(self):
171
208
  # Ensure trailing dots from domain-style fields
172
209
  self.name = normalize_fqdn(self.name)
@@ -174,8 +211,11 @@ class Zone(models.Model):
174
211
  def get_updater(self):
175
212
  return self.server.create_update(self.name)
176
213
 
214
+ def get_absolute_url(self):
215
+ return reverse('plugins:netbox_ddns:zone', args=[self.pk])
216
+
177
217
 
178
- class ReverseZoneQuerySet(models.QuerySet):
218
+ class ReverseZoneQuerySet(RestrictedQuerySet):
179
219
  def find_for_address(self, address: ip.IPAddress) -> Optional['ReverseZone']:
180
220
  # Find the zone, if any
181
221
  zones = list(ReverseZone.objects.filter(prefix__net_contains=address))
@@ -186,7 +226,7 @@ class ReverseZoneQuerySet(models.QuerySet):
186
226
  return zones[-1]
187
227
 
188
228
 
189
- class ReverseZone(models.Model):
229
+ class ReverseZone(NetBoxModel):
190
230
  prefix = IPNetworkField(
191
231
  verbose_name=_('prefix'),
192
232
  unique=True,
@@ -214,7 +254,19 @@ class ReverseZone(models.Model):
214
254
  verbose_name_plural = _('reverse zones')
215
255
 
216
256
  def __str__(self):
217
- return f'for {self.prefix}'
257
+ return f'{self.prefix}'
258
+
259
+ def get_managed_ip_address(self):
260
+ # Find all more-specific zones
261
+ more_specifics = ReverseZone.objects.filter(prefix__net_contained=self.prefix).exclude(pk=self.pk)
262
+ # Find all IPAddress objects in this zone but not in the more-specifics
263
+ ip_addresses = IPAddress.objects.filter(address__net_contained_or_equal=self.prefix)
264
+ for more_specific in more_specifics:
265
+ ip_addresses = ip_addresses.exclude(address__net_contained_or_equal=more_specific.prefix)
266
+ return ip_addresses
267
+
268
+ def get_absolute_url(self):
269
+ return reverse('plugins:netbox_ddns:reversezone', args=[self.pk])
218
270
 
219
271
  def record_name(self, address: ip.IPAddress):
220
272
  record_name = self.name
@@ -334,7 +386,7 @@ class ExtraDNSName(NetBoxModel):
334
386
  name = models.CharField(
335
387
  verbose_name=_('DNS name'),
336
388
  max_length=255,
337
- validators=[HostnameValidator()],
389
+ validators=[DNSNameValidator()],
338
390
  )
339
391
 
340
392
  last_update = models.DateTimeField(
@@ -367,7 +419,7 @@ class ExtraDNSName(NetBoxModel):
367
419
  return self.name
368
420
 
369
421
  def get_absolute_url(self):
370
- return reverse('plugins:netbox_ddns:extradnsname', args=[self.ip_address.pk, self.pk])
422
+ return reverse('plugins:netbox_ddns:extradnsname', args=[self.pk])
371
423
 
372
424
  def clean(self):
373
425
  # Ensure trailing dots from domain-style fields
@@ -0,0 +1,62 @@
1
+ from netbox.plugins import PluginMenuItem, PluginMenuButton, PluginMenu
2
+
3
+ menu = PluginMenu(
4
+ label='DDNS',
5
+ groups=(
6
+ ('Configuration', (
7
+ PluginMenuItem(
8
+ link='plugins:netbox_ddns:managed_dns_name_list',
9
+ link_text='DNS Names',
10
+ ),
11
+ PluginMenuItem(
12
+ link='plugins:netbox_ddns:server_list',
13
+ link_text='DDNS Servers',
14
+ buttons=[
15
+ PluginMenuButton(
16
+ link='plugins:netbox_ddns:server_add',
17
+ title='Add',
18
+ icon_class='mdi mdi-plus-thick',
19
+ )
20
+ ]
21
+ ),
22
+ PluginMenuItem(
23
+ link='plugins:netbox_ddns:zone_list',
24
+ link_text='Forward Zones',
25
+ buttons=[
26
+ PluginMenuButton(
27
+ link='plugins:netbox_ddns:zone_add',
28
+ title='Add',
29
+ icon_class='mdi mdi-plus-thick',
30
+ )
31
+ ]
32
+ ),
33
+ PluginMenuItem(
34
+ link='plugins:netbox_ddns:reversezone_list',
35
+ link_text='Reverse Zones',
36
+ buttons=[
37
+ PluginMenuButton(
38
+ link='plugins:netbox_ddns:reversezone_add',
39
+ title='Add',
40
+ icon_class='mdi mdi-plus-thick',
41
+ )
42
+ ]
43
+ ),
44
+ ),
45
+ ),
46
+ ('Extra DNS Names', (
47
+ PluginMenuItem(
48
+ link='plugins:netbox_ddns:extradnsname_list',
49
+ link_text='Extra DNS names',
50
+ buttons=[
51
+ PluginMenuButton(
52
+ link='plugins:netbox_ddns:extradnsname_add',
53
+ title='Add',
54
+ icon_class='mdi mdi-plus-thick',
55
+ )
56
+ ]
57
+ ),
58
+ ),
59
+ ),
60
+ ),
61
+ icon_class='mdi mdi-router'
62
+ )
netbox_ddns/tables.py CHANGED
@@ -1,13 +1,23 @@
1
1
  import django_tables2 as tables
2
+ from django_tables2 import LinkColumn, RelatedLinkColumn
2
3
 
3
- from netbox_ddns.models import ExtraDNSName
4
- try:
5
- # NetBox >= 3.2.0
6
- from netbox.tables import BaseTable
7
- from netbox.tables.columns import ToggleColumn, DateTimeColumn
8
- except ImportError:
9
- # NetBox < 3.2.0
10
- from utilities.tables import BaseTable, ToggleColumn, DateTimeColumn
4
+ from netbox_ddns.models import ExtraDNSName, Server, ReverseZone, Zone
5
+
6
+ from netbox.tables import NetBoxTable
7
+
8
+ MANAGED_DNS_ACTIONS = """
9
+ <a href="{{ record.view_url }}" class="btn btn-sm btn-outline-primary" title="View">
10
+ <i class="mdi mdi-eye"></i>
11
+ </a>
12
+ <a href="{{ record.edit_url }}" class="btn btn-sm btn-outline-warning" title="Edit">
13
+ <i class="mdi mdi-pencil"></i>
14
+ </a>
15
+ {% if record.delete_url %}
16
+ <a href="{{ record.delete_url }}" class="btn btn-sm btn-outline-danger" title="Delete">
17
+ <i class="mdi mdi-delete"></i>
18
+ </a>
19
+ {% endif %}
20
+ """
11
21
 
12
22
  FORWARD_DNS = """
13
23
  {% if record.forward_action is not None %}
@@ -18,33 +28,69 @@ FORWARD_DNS = """
18
28
  {% endif %}
19
29
  """
20
30
 
21
- ACTIONS = """
22
- {% if perms.netbox_ddns.change_extradnsname %}
23
- <a href="{% url 'plugins:netbox_ddns:extradnsname_edit' ipaddress_pk=record.ip_address.pk pk=record.pk %}"
24
- class="btn btn-warning">
25
- <i class="mdi mdi-pencil" aria-hidden="true"></i>
26
- </a>
27
- {% endif %}
28
- {% if perms.netbox_ddns.delete_extradnsname %}
29
- <a href="{% url 'plugins:netbox_ddns:extradnsname_delete' ipaddress_pk=record.ip_address.pk pk=record.pk %}"
30
- class="btn btn-danger">
31
- <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i>
32
- </a>
33
- {% endif %}
34
- """
35
31
 
32
+ class ReverseZoneTable(NetBoxTable):
33
+ name = LinkColumn()
34
+ server = RelatedLinkColumn()
35
+
36
+ class Meta(NetBoxTable.Meta):
37
+ model = ReverseZone
38
+ fields = ("id", "name", "prefix", "server", "ttl")
39
+
40
+
41
+ class ZoneTable(NetBoxTable):
42
+ name = LinkColumn()
43
+ server = RelatedLinkColumn()
44
+
45
+ class Meta(NetBoxTable.Meta):
46
+ model = Zone
47
+ fields = ("id", "name", "server", "ttl")
48
+
49
+
50
+ class ServerTable(NetBoxTable):
51
+ server = LinkColumn()
36
52
 
37
- class PrefixTable(BaseTable):
38
- pk = ToggleColumn()
39
- name = tables.Column()
40
- last_update = DateTimeColumn()
53
+ class Meta(NetBoxTable.Meta):
54
+ model = Server
55
+ fields = ("id", "server", "server_port", "protocol", "tsig_key_name", "tsig_algorithm")
56
+
57
+
58
+ class ExtraDNSNameTable(NetBoxTable):
59
+ ip_address = RelatedLinkColumn()
60
+ name = LinkColumn()
41
61
  forward_dns = tables.TemplateColumn(template_code=FORWARD_DNS)
62
+
63
+ class Meta(NetBoxTable.Meta):
64
+ model = ExtraDNSName
65
+ fields = ('id', 'name', 'ip_address', 'last_update', 'forward_dns')
66
+
67
+
68
+ class ManagedDNSNameTable(tables.Table):
69
+ """Table for primary DNS names (from IPAddress.dns_name)."""
70
+ dns_name = tables.Column(
71
+ accessor='dns_name',
72
+ linkify=lambda record: record.view_url,
73
+ )
74
+ ip_address = tables.Column(
75
+ accessor='ip_address',
76
+ linkify=lambda record: record.ip_address.get_absolute_url(),
77
+ )
78
+ zone = tables.Column(
79
+ accessor='zone',
80
+ linkify=lambda record: record.zone.get_absolute_url() if record.zone else None,
81
+ )
82
+ forward_status = tables.TemplateColumn(
83
+ template_code='{{ record.forward_status_html|safe }}',
84
+ verbose_name='Forward DNS',
85
+ )
42
86
  actions = tables.TemplateColumn(
43
- template_code=ACTIONS,
44
- attrs={'td': {'class': 'text-right text-nowrap noprint'}},
45
- verbose_name=''
87
+ template_code=MANAGED_DNS_ACTIONS,
88
+ verbose_name='Actions',
89
+ orderable=False,
46
90
  )
47
91
 
48
- class Meta(BaseTable.Meta):
49
- model = ExtraDNSName
50
- fields = ('pk', 'name', 'last_update', 'forward_dns', 'actions')
92
+ def render_zone(self, value):
93
+ return value if value else '—'
94
+
95
+ class Meta:
96
+ attrs = {'class': 'table table-hover object-list'}
@@ -5,9 +5,45 @@ from netbox.plugins.templates import PluginTemplateExtension
5
5
  from . import tables
6
6
 
7
7
 
8
- # noinspection PyAbstractClass
8
+ class ReverseZoneRecreate(PluginTemplateExtension):
9
+ # NetBox up to 4.1
10
+ model = 'netbox_ddns.reversezone'
11
+ # NetBox from 4.2, required to be present as of NetBox 4.3
12
+ models = [model]
13
+
14
+ def buttons(self):
15
+ """
16
+ A button to force DNS re-provisioning
17
+ """
18
+ context = {
19
+ 'perms': PermWrapper(self.context['request'].user),
20
+ }
21
+ context.update(csrf(self.context['request']))
22
+ return self.render('netbox_ddns/update_reverse_zone.html', context)
23
+
24
+
25
+ class ZoneRecreate(PluginTemplateExtension):
26
+ # NetBox up to 4.1
27
+ model = 'netbox_ddns.zone'
28
+ # NetBox from 4.2, required to be present as of NetBox 4.3
29
+ models = [model]
30
+
31
+ def buttons(self):
32
+ """
33
+ A button to force DNS re-provisioning
34
+ """
35
+ context = {
36
+ 'perms': PermWrapper(self.context['request'].user),
37
+ }
38
+ context.update(csrf(self.context['request']))
39
+ return self.render('netbox_ddns/update_zone.html', context)
40
+
41
+
9
42
  class DNSInfo(PluginTemplateExtension):
43
+ # NetBox up to 4.1
10
44
  model = 'ipam.ipaddress'
45
+ # NetBox from 4.2, required to be present as of NetBox 4.3
46
+ models = [model]
11
47
 
12
48
  def buttons(self):
13
49
  """
@@ -23,7 +59,8 @@ class DNSInfo(PluginTemplateExtension):
23
59
  """
24
60
  An info-box with the status of the DNS modifications and records
25
61
  """
26
- extra_dns_name_table = tables.PrefixTable(list(self.context['object'].extradnsname_set.all()), orderable=False)
62
+ extra_dns_name_table = tables.ExtraDNSNameTable(list(self.context['object'].extradnsname_set.all()),
63
+ exclude=["id", "ip_address"], orderable=False)
27
64
 
28
65
  return (
29
66
  self.render('netbox_ddns/ipaddress/dns_info.html') +
@@ -34,4 +71,4 @@ class DNSInfo(PluginTemplateExtension):
34
71
  )
35
72
 
36
73
 
37
- template_extensions = [DNSInfo]
74
+ template_extensions = [DNSInfo, ZoneRecreate, ReverseZoneRecreate]
@@ -1,134 +1,11 @@
1
- {% extends 'generic/_base.html' %}
2
- {% load buttons %}
3
- {% load custom_links %}
4
- {% load helpers %}
5
- {% load perms %}
6
- {% load plugins %}
7
- {% load tabs %}
8
- {% load i18n %}
9
-
10
- {% comment %}
11
- Blocks:
12
- - page-header: Content displayed above the primary page content
13
- - breadcrumbs: Breadcrumb list items(HTML
14
- <li> elements)
15
- - object_identifier: Unique identifier for the object
16
- - title: Page title
17
- - subtitle: Additional context displayed below the title
18
- - controls: Control elements displayed between the header and content
19
- - control-buttons: Action buttons (add/edit/delete/etc.)
20
- - extra_controls: Any additional action buttons to display
21
- - tabs: Page tabs
22
- - content: Primary page content
23
- - modals: Any pre-loaded modals
24
-
25
- Context:
26
- - object: The object being viewed
27
- {% endcomment %}
28
-
29
- {% block page-header %}
30
- <div class="container-fluid">
31
- <div class="d-flex justify-content-between align-items-center mt-2">
32
-
33
- {# Object identifier #}
34
- <code class="d-block text-muted bg-transparent px-0">
35
- {% block object_identifier %}
36
- {{ object|meta:"app_label" }}.{{ object|meta:"model_name" }}:{{ object.pk }}
37
- {% if object.slug %}({{ object.slug }}){% endif %}
38
- {% endblock object_identifier %}
39
- </code>
40
-
41
- </div>
42
- </div>
43
- {{ block.super }}
44
- {% endblock page-header %}
45
-
46
- {% block title %}{{ object }}{% endblock %}
47
-
48
- {% block subtitle %}
49
- <div class="text-secondary fs-5">
50
- {% trans "Created" %} {{ object.created|isodatetime:"minutes" }}
51
- {% if object.last_updated %}
52
- <span class="separator">&middot;</span>
53
- {% trans "Updated" %} {{ object.last_updated|isodatetime:"minutes" }}
54
- {% endif %}
55
- </div>
56
- {% endblock subtitle %}
57
-
58
- {% block controls %}
59
- <div class="btn-list justify-content-end mb-2">
60
- {% plugin_buttons object %}
61
-
62
- {# Add/edit/delete/etc. buttons #}
63
- {% block control-buttons %}
64
-
65
- {# Extra buttons #}
66
- {% block extra_controls %}{% endblock %}
67
-
68
- {# Default buttons #}
69
- {% if perms.extras.add_bookmark and object.bookmarks %}
70
- {% bookmark_button object %}
71
- {% endif %}
72
- {% if perms.extras.add_subscription and object.subscriptions %}
73
- {% subscribe_button object %}
74
- {% endif %}
75
- {% if request.user|can_change:object %}
76
- {% load i18n %}
77
- <a href="{% url 'plugins:netbox_ddns:extradnsname_edit' ipaddress_pk=object.ip_address.pk pk=object.pk %}"
78
- class="btn btn-yellow" role="button">
79
- <i class="mdi mdi-pencil" aria-hidden="true"></i> {% trans "Edit" %}
80
- </a>
81
- {% endif %}
82
- {% if request.user|can_delete:object %}
83
- {% load i18n %}
84
- <a href="#"
85
- hx-get="{% url 'plugins:netbox_ddns:extradnsname_delete' ipaddress_pk=object.ip_address.pk pk=object.pk %}"
86
- hx-target="#htmx-modal-content"
87
- hx-swap="innerHTML"
88
- hx-select="form"
89
- class="btn btn-red"
90
- data-bs-toggle="modal"
91
- data-bs-target="#htmx-modal"
92
- >
93
- <i class="mdi mdi-trash-can-outline" aria-hidden="true"></i> {% trans "Delete" %}
94
- </a>
95
-
96
- {% endif %}
97
- {% endblock control-buttons %}
98
- </div>
99
-
100
- {# Custom links #}
101
- <div class="d-flex justify-content-end">
102
- <div class="btn-list">
103
- {% block custom-links %}
104
- {% custom_links object %}
105
- {% endblock custom-links %}
106
- </div>
107
- </div>
108
- {% endblock controls %}
109
-
110
- {% block tabs %}
111
- <ul class="nav nav-tabs" role="presentation">
112
- {# Primary tab #}
113
- <li class="nav-item">
114
- <a class="nav-link{% if not tab %} active{% endif %}" href="{{ object.get_absolute_url }}">
115
- {{ object|meta:"verbose_name"|bettertitle }}</a>
116
- </li>
117
-
118
- {# Include tabs for registered model views #}
119
- {% model_view_tabs object %}
120
- </ul>
121
- {% endblock tabs %}
122
-
123
- {% block alerts %}
124
- {% plugin_alerts object %}
125
- {% endblock alerts %}
1
+ {% extends 'generic/object.html' %}
2
+ {% load render_table from django_tables2 %}
126
3
 
127
4
  {% block content %}
128
5
  <div class="row mb-3">
129
6
  <div class="col col-md-6">
130
7
  <div class="card">
131
- <h5 class="card-header">Access List</h5>
8
+ <h5 class="card-header">Extra DNS name</h5>
132
9
  <div class="card-body">
133
10
  <table class="table table-hover attr-table">
134
11
  <tr>
@@ -158,12 +35,27 @@
158
35
  </table>
159
36
  </div>
160
37
  </div>
38
+ <div class="card">
39
+ <h5 class="card-header">Managed by</h5>
40
+ <div class="card-body">
41
+ <table class="table table-hover attr-table">
42
+ <tr>
43
+ <th scope="row">DDNS Server</th>
44
+ <td>
45
+ <a href="{{ server.get_absolute_url }}">{{ server }}</a>
46
+ </td>
47
+ </tr>
48
+ <tr>
49
+ <th scope="row">Zone</th>
50
+ <td>
51
+ <a href="{{ zone.get_absolute_url }}">{{ zone }}</a>
52
+ </td>
53
+ </tr>
54
+ </table>
55
+ </div>
56
+ </div>
161
57
  {% include 'inc/panels/custom_fields.html' %}
162
58
  </div>
163
59
  <div class="col col-md-6">{% include 'inc/panels/tags.html' %}</div>
164
60
  </div>
165
- {% endblock content %}
166
-
167
- {% block modals %}
168
- {% include 'inc/htmx_modal.html' %}
169
- {% endblock modals %}
61
+ {% endblock content %}
@@ -10,7 +10,7 @@
10
10
 
11
11
  {% if perms.netbox_ddns.add_extradnsname %}
12
12
  <div class="card-footer text-right noprint">
13
- <a href="{% url 'plugins:netbox_ddns:extradnsname_create' ipaddress_pk=object.pk %}"
13
+ <a href="{% url 'plugins:netbox_ddns:extradnsname_ip_address_create' ipaddress_pk=object.pk %}"
14
14
  class="btn btn-primary">
15
15
  <span class="mdi mdi-plus" aria-hidden="true"></span>Add extra DNS name
16
16
  </a>
@@ -0,0 +1,17 @@
1
+ {% extends 'base/layout.html' %}
2
+ {% load render_table from django_tables2 %}
3
+
4
+ {% block title %}DNS Names{% endblock %}
5
+
6
+ {% block content %}
7
+ <div class="row">
8
+ <div class="col col-12">
9
+ <div class="card">
10
+ <h5 class="card-header">DNS Names</h5>
11
+ <div class="card-body table-responsive">
12
+ {% render_table table 'inc/table.html' %}
13
+ </div>
14
+ </div>
15
+ </div>
16
+ </div>
17
+ {% endblock content %}
@@ -0,0 +1,51 @@
1
+ {% extends 'generic/object.html' %}
2
+ {% load render_table from django_tables2 %}
3
+
4
+ {% block content %}
5
+ <div class="row">
6
+ <div class="col col-md-6">
7
+ <div class="card">
8
+ <h2 class="card-header">Reverse Zone</h2>
9
+ <table class="table table-hover attr-table">
10
+ <tr>
11
+ <th scope="row">Name</th>
12
+ <td>
13
+ {{ object.name }}
14
+ </td>
15
+ </tr>
16
+ <tr>
17
+ <th scope="row">Prefix</th>
18
+ <td>
19
+ {{ object.prefix }}
20
+ </td>
21
+ </tr>
22
+ <tr>
23
+ <th scope="row">TTL</th>
24
+ <td>
25
+ {{ object.ttl }}
26
+ </td>
27
+ </tr>
28
+ <tr>
29
+ <th scope="row">Server</th>
30
+ <td>
31
+ <a href="{{ object.server.get_absolute_url }}">
32
+ {{ object.server }}
33
+ </a>
34
+ </td>
35
+ </tr>
36
+ </table>
37
+ </div>
38
+ <div class="card">
39
+ <div class="justify-content-between card-header">
40
+ <h1 class="card-title">IP Addresses</h1>
41
+ </div>
42
+ <div class="card-body table-responsive">
43
+ {% render_table ip_address_table %}
44
+ </div>
45
+ </div>
46
+ {% include 'inc/panels/custom_fields.html' %}
47
+ {% include 'inc/panels/tags.html' %}
48
+ </div>
49
+ </div>
50
+
51
+ {% endblock content %}