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.
- netbox_ddns/__init__.py +3 -3
- netbox_ddns/admin.py +0 -156
- netbox_ddns/api/serializers.py +20 -2
- netbox_ddns/api/views.py +1 -0
- netbox_ddns/background_tasks.py +28 -5
- netbox_ddns/filtersets.py +23 -1
- netbox_ddns/forms.py +54 -3
- netbox_ddns/migrations/0011_server_created_server_custom_field_data_and_more.py +36 -0
- netbox_ddns/migrations/0012_zone_created_zone_custom_field_data_and_more.py +36 -0
- netbox_ddns/migrations/0013_reversezone_created_reversezone_custom_field_data_and_more.py +36 -0
- netbox_ddns/migrations/0014_alter_extradnsname_name.py +19 -0
- netbox_ddns/migrations/0015_server_protocol.py +18 -0
- netbox_ddns/models.py +61 -9
- netbox_ddns/navigation.py +62 -0
- netbox_ddns/tables.py +78 -32
- netbox_ddns/template_content.py +40 -3
- netbox_ddns/templates/netbox_ddns/extradnsname.html +23 -131
- netbox_ddns/templates/netbox_ddns/ipaddress/dns_extra.html +1 -1
- netbox_ddns/templates/netbox_ddns/managed_dns_names.html +17 -0
- netbox_ddns/templates/netbox_ddns/reversezone.html +51 -0
- netbox_ddns/templates/netbox_ddns/server.html +74 -0
- netbox_ddns/templates/netbox_ddns/update_reverse_zone.html +11 -0
- netbox_ddns/templates/netbox_ddns/update_zone.html +11 -0
- netbox_ddns/templates/netbox_ddns/zone.html +53 -0
- netbox_ddns/urls.py +32 -14
- netbox_ddns/utils.py +95 -0
- netbox_ddns/validators.py +6 -0
- netbox_ddns/views.py +268 -84
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/METADATA +7 -5
- netbox_ddns-1.7.0.dist-info/RECORD +50 -0
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info}/WHEEL +1 -1
- netbox_ddns-1.4.0.dist-info/RECORD +0 -38
- {netbox_ddns-1.4.0.dist-info → netbox_ddns-1.7.0.dist-info/licenses}/LICENSE.txt +0 -0
- {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,
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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'
|
|
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=[
|
|
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.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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=
|
|
44
|
-
|
|
45
|
-
|
|
87
|
+
template_code=MANAGED_DNS_ACTIONS,
|
|
88
|
+
verbose_name='Actions',
|
|
89
|
+
orderable=False,
|
|
46
90
|
)
|
|
47
91
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
92
|
+
def render_zone(self, value):
|
|
93
|
+
return value if value else '—'
|
|
94
|
+
|
|
95
|
+
class Meta:
|
|
96
|
+
attrs = {'class': 'table table-hover object-list'}
|
netbox_ddns/template_content.py
CHANGED
|
@@ -5,9 +5,45 @@ from netbox.plugins.templates import PluginTemplateExtension
|
|
|
5
5
|
from . import tables
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
|
|
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.
|
|
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/
|
|
2
|
-
{% load
|
|
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">·</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">
|
|
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:
|
|
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 %}
|