mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 10:58:37 -06:00
Fixes #166: Full DNS support
This commit is contained in:
parent
c643e3a74f
commit
5ea721a6aa
19
docs/data-model/dns.md
Normal file
19
docs/data-model/dns.md
Normal file
@ -0,0 +1,19 @@
|
||||
The DNS component of NetBox deals with the management of DNS zones.
|
||||
|
||||
# Zones
|
||||
|
||||
A zone corresponds to a zone file in a DNS server, it stores the SOA (Start Of Authority) record and other records that are stored as Record objects.
|
||||
|
||||
As zones are readable through the REST API, it is possible to write some external script which automatically generates zone files for a DNS server,
|
||||
this feature is not directly provided by NetBox though.
|
||||
|
||||
---
|
||||
|
||||
# Record
|
||||
|
||||
Each Record object represents a DNS record, i.e. a link between a hostname and a resource, which can be either an IP address or a text value,
|
||||
for instance another hostname if the record is of CNAME type.
|
||||
|
||||
Records must be linked to an existing zone, and hold either an existing IP address link or a text value.
|
||||
|
||||
Reverse DNS is not supported by Record objects, but by the "Host Name" field in IP addresses.
|
@ -3,6 +3,7 @@
|
||||
NetBox is an open source web application designed to help manage and document computer networks. Initially conceived by the network engineering team at [DigitalOcean](https://www.digitalocean.com/), NetBox was developed specifically to address the needs of network and infrastructure engineers. It encompasses the following aspects of network management:
|
||||
|
||||
* **IP address management (IPAM)** - IP networks and addresses, VRFs, and VLANs
|
||||
* **DNS management** - DNS zones and records
|
||||
* **Equipment racks** - Organized by group and site
|
||||
* **Devices** - Types of devices and where they are installed
|
||||
* **Connections** - Network, console, and power connections among devices
|
||||
|
1
netbox/dns/__init__.py
Normal file
1
netbox/dns/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
default_app_config = 'dns.apps.DNSConfig'
|
17
netbox/dns/admin.py
Normal file
17
netbox/dns/admin.py
Normal file
@ -0,0 +1,17 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import (
|
||||
Zone, Record,
|
||||
)
|
||||
|
||||
|
||||
@admin.register(Zone)
|
||||
class ZoneAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial']
|
||||
prepopulated_fields = {
|
||||
'soa_name': ['name'],
|
||||
}
|
||||
|
||||
@admin.register(Record)
|
||||
class RecordAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'zone', 'record_type', 'priority', 'address', 'value']
|
0
netbox/dns/api/__init__.py
Normal file
0
netbox/dns/api/__init__.py
Normal file
38
netbox/dns/api/serializers.py
Normal file
38
netbox/dns/api/serializers.py
Normal file
@ -0,0 +1,38 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from ipam.api.serializers import IPAddressNestedSerializer
|
||||
from dns.models import Zone, Record
|
||||
|
||||
#
|
||||
# Zones
|
||||
#
|
||||
|
||||
class ZoneSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model=Zone
|
||||
fields = ['id', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum']
|
||||
|
||||
class ZoneNestedSerializer(ZoneSerializer):
|
||||
|
||||
class Meta(ZoneSerializer.Meta):
|
||||
fields = ['id', 'name']
|
||||
|
||||
|
||||
#
|
||||
# Records
|
||||
#
|
||||
|
||||
class RecordSerializer(serializers.ModelSerializer):
|
||||
|
||||
zone = ZoneNestedSerializer()
|
||||
address = IPAddressNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model=Record
|
||||
fields = ['id', 'name', 'record_type', 'priority', 'zone', 'address', 'value']
|
||||
|
||||
class RecordNestedSerializer(RecordSerializer):
|
||||
|
||||
class Meta(RecordSerializer.Meta):
|
||||
fields = ['id', 'name', 'record_type', 'zone']
|
15
netbox/dns/api/urls.py
Normal file
15
netbox/dns/api/urls.py
Normal file
@ -0,0 +1,15 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from .views import *
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
# Zones
|
||||
url(r'^zones/$', ZoneListView.as_view(), name='zone_list'),
|
||||
url(r'^zones/(?P<pk>\d+)/$', ZoneDetailView.as_view(), name='zone_detail'),
|
||||
|
||||
# Records
|
||||
url(r'^records/$', RecordListView.as_view(), name='record_list'),
|
||||
url(r'^records/(?P<pk>\d+)/$', RecordDetailView.as_view(), name='record_detail'),
|
||||
|
||||
]
|
44
netbox/dns/api/views.py
Normal file
44
netbox/dns/api/views.py
Normal file
@ -0,0 +1,44 @@
|
||||
from rest_framework import generics
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from dns.models import Zone, Record
|
||||
from dns import filters
|
||||
|
||||
from . import serializers
|
||||
|
||||
#
|
||||
# Zones
|
||||
#
|
||||
|
||||
class ZoneListView(generics.ListAPIView):
|
||||
"""
|
||||
List all zones
|
||||
"""
|
||||
queryset = Zone.objects.all()
|
||||
serializer_class = serializers.ZoneSerializer
|
||||
filter_class = filters.ZoneFilter
|
||||
|
||||
class ZoneDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single zone
|
||||
"""
|
||||
queryset = Zone.objects.all()
|
||||
serializer_class = serializers.ZoneSerializer
|
||||
|
||||
#
|
||||
# Records
|
||||
#
|
||||
|
||||
class RecordListView(generics.ListAPIView):
|
||||
"""
|
||||
List all records
|
||||
"""
|
||||
queryset = Record.objects.all()
|
||||
serializer_class = serializers.RecordSerializer
|
||||
|
||||
class RecordDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single record
|
||||
"""
|
||||
queryset = Record.objects.all()
|
||||
serializer_class = serializers.RecordSerializer
|
6
netbox/dns/apps.py
Normal file
6
netbox/dns/apps.py
Normal file
@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class DNSConfig(AppConfig):
|
||||
name = 'dns'
|
||||
verbose_name='DNS'
|
36
netbox/dns/filters.py
Normal file
36
netbox/dns/filters.py
Normal file
@ -0,0 +1,36 @@
|
||||
import django_filters
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from .models import (
|
||||
Zone,
|
||||
Record,
|
||||
)
|
||||
|
||||
class ZoneFilter(django_filters.FilterSet):
|
||||
name = django_filters.CharFilter(
|
||||
name='name',
|
||||
lookup_type='icontains',
|
||||
label='Name',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Zone
|
||||
fields = ['name']
|
||||
|
||||
class RecordFilter(django_filters.FilterSet):
|
||||
zone__name = django_filters.ModelMultipleChoiceFilter(
|
||||
name='zone__name',
|
||||
to_field_name='name',
|
||||
lookup_type='icontains',
|
||||
queryset=Zone.objects.all(),
|
||||
label='Zone (name)',
|
||||
)
|
||||
name = django_filters.CharFilter(
|
||||
name='name',
|
||||
lookup_type='icontains',
|
||||
label='Name',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model=Record
|
||||
field = ['name','record_type','value']
|
37
netbox/dns/fixtures/dns.json
Normal file
37
netbox/dns/fixtures/dns.json
Normal file
@ -0,0 +1,37 @@
|
||||
[
|
||||
{
|
||||
"model": "dns.zone",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "foo.net",
|
||||
"ttl": 10800,
|
||||
"soa_name": "@",
|
||||
"soa_contact": "ns@foo.net. noc@foo.net.",
|
||||
"soa_serial": "2016070401",
|
||||
"soa_refresh": 3600,
|
||||
"soa_retry": 3600,
|
||||
"soa_expire": 604800,
|
||||
"soa_minimum": 1800
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dns.record",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "@",
|
||||
"record_type": "NS",
|
||||
"zone": 1,
|
||||
"value": "ns.foo.net."
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dns.record",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "www",
|
||||
"record_type": "A",
|
||||
"zone": 1,
|
||||
"address": 1
|
||||
}
|
||||
}
|
||||
]
|
109
netbox/dns/forms.py
Normal file
109
netbox/dns/forms.py
Normal file
@ -0,0 +1,109 @@
|
||||
from django import forms
|
||||
from django.db.models import Count
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from utilities.forms import (
|
||||
BootstrapMixin, ConfirmationForm, APISelect, Livesearch, CSVDataField, BulkImportForm,
|
||||
)
|
||||
|
||||
from .models import (
|
||||
Zone,
|
||||
Record,
|
||||
)
|
||||
|
||||
#
|
||||
# Zones
|
||||
#
|
||||
|
||||
class ZoneForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model=Zone
|
||||
fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum']
|
||||
labels = {
|
||||
'soa_name': 'SOA Name',
|
||||
'soa_contact': 'SOA Contact',
|
||||
'soa_serial': 'SOA Serial',
|
||||
'soa_refresh': 'SOA Refresh',
|
||||
'soa_retry': 'SOA Retry',
|
||||
'soa_expire': 'SOA Expire',
|
||||
'soa_minimum': 'SOA Minimum',
|
||||
}
|
||||
|
||||
class ZoneFromCSVForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model=Zone
|
||||
fields = ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum']
|
||||
|
||||
class ZoneImportForm(BulkImportForm, BootstrapMixin):
|
||||
csv = CSVDataField(csv_form=ZoneFromCSVForm)
|
||||
|
||||
class ZoneBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Zone.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
name = forms.CharField(max_length=100, required=False, label='Name')
|
||||
ttl = forms.IntegerField(required=False, label='TTL')
|
||||
soa_name = forms.CharField(max_length=100, required=False, label='SOA Name')
|
||||
soa_contact = forms.CharField(max_length=100, required=False, label='SOA Contact')
|
||||
soa_refresh = forms.IntegerField(required=False, label='SOA Refresh')
|
||||
soa_retry = forms.IntegerField(required=False, label='SOA Retry')
|
||||
soa_expire = forms.IntegerField(required=False, label='SOA Expire')
|
||||
soa_minimum = forms.IntegerField(required=False, label='SOA Minimum')
|
||||
|
||||
|
||||
class ZoneBulkDeleteForm(ConfirmationForm):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Zone.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
|
||||
class ZoneFilterForm(forms.Form, BootstrapMixin):
|
||||
pass
|
||||
|
||||
#
|
||||
# Records
|
||||
#
|
||||
|
||||
class RecordForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model=Record
|
||||
fields = ['name', 'record_type', 'priority', 'zone', 'address', 'value']
|
||||
labels = {
|
||||
'record_type': 'Type',
|
||||
}
|
||||
|
||||
class RecordFromCSVForm(forms.ModelForm):
|
||||
|
||||
zone = forms.ModelChoiceField(queryset=Zone.objects.all(), to_field_name='name', error_messages={'invalid_choice': 'Zone not found.'})
|
||||
address = forms.ModelChoiceField(queryset=IPAddress.objects.all(), to_field_name='address', error_messages={'invalid_choice': 'IP Address not found.'}, required=False)
|
||||
|
||||
class Meta:
|
||||
model=Record
|
||||
fields = ['zone', 'name', 'record_type', 'priority', 'address', 'value']
|
||||
|
||||
class RecordImportForm(BulkImportForm, BootstrapMixin):
|
||||
csv = CSVDataField(csv_form=RecordFromCSVForm)
|
||||
|
||||
class RecordBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Record.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
name = forms.CharField(max_length=100, required=False, label='Name')
|
||||
record_type = forms.CharField(max_length=100, required=False, label='Type')
|
||||
priority = forms.IntegerField(required=False)
|
||||
zone = forms.ModelChoiceField(queryset=Zone.objects.all(), required=False)
|
||||
address = forms.ModelChoiceField(queryset=IPAddress.objects.all(), required=False)
|
||||
value = forms.CharField(max_length=100, required=False)
|
||||
|
||||
class RecordBulkDeleteForm(ConfirmationForm):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Record.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
|
||||
def record_zone_choices():
|
||||
zone_choices = Zone.objects.annotate(record_count=Count('records'))
|
||||
return [(z.name, '{} ({})'.format(z.name, z.record_count)) for z in zone_choices]
|
||||
|
||||
#def record_name_choices():
|
||||
#name_choices =
|
||||
|
||||
class RecordFilterForm(forms.Form, BootstrapMixin):
|
||||
zone__name = forms.MultipleChoiceField(required=False, choices=record_zone_choices, label='Zone',
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
#name = forms.MultipleChoiceField(required=False, choices=record_name_choices, label='Name', widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
record_type = forms.CharField(max_length=100, required=False, label='Type')
|
||||
|
59
netbox/dns/migrations/0001_initial.py
Normal file
59
netbox/dns/migrations/0001_initial.py
Normal file
@ -0,0 +1,59 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-19 10:44
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
initial = True
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0004_ipam_vlangroup_uniqueness'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Record',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('record_type', models.CharField(max_length=10)),
|
||||
('priority', models.PositiveIntegerField(blank=True)),
|
||||
('value', models.CharField(blank=True, max_length=100)),
|
||||
('address', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='records', to='ipam.IPAddress')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.CreateModel(
|
||||
name='Zone',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('name', models.CharField(max_length=100)),
|
||||
('ttl', models.PositiveIntegerField()),
|
||||
('soa_name', models.CharField(max_length=100)),
|
||||
('soa_contact', models.CharField(max_length=100)),
|
||||
('soa_serial', models.CharField(max_length=100)),
|
||||
('soa_refresh', models.PositiveIntegerField()),
|
||||
('soa_retry', models.PositiveIntegerField()),
|
||||
('soa_expire', models.PositiveIntegerField()),
|
||||
('soa_minimum', models.PositiveIntegerField()),
|
||||
],
|
||||
options={
|
||||
'ordering': ['name'],
|
||||
},
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='record',
|
||||
name='zone',
|
||||
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='dns.Zone'),
|
||||
),
|
||||
]
|
20
netbox/dns/migrations/0002_auto_20160719_1058.py
Normal file
20
netbox/dns/migrations/0002_auto_20160719_1058.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-19 10:58
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dns', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='record',
|
||||
name='priority',
|
||||
field=models.PositiveIntegerField(blank=True, null=True),
|
||||
),
|
||||
]
|
0
netbox/dns/migrations/__init__.py
Normal file
0
netbox/dns/migrations/__init__.py
Normal file
83
netbox/dns/models.py
Normal file
83
netbox/dns/models.py
Normal file
@ -0,0 +1,83 @@
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from utilities.models import CreatedUpdatedModel
|
||||
|
||||
class Zone(CreatedUpdatedModel):
|
||||
"""
|
||||
A Zone represents a DNS zone. It contains SOA data but no records, records are represented as Record objects.
|
||||
"""
|
||||
name=models.CharField(max_length=100)
|
||||
ttl=models.PositiveIntegerField()
|
||||
soa_name=models.CharField(max_length=100)
|
||||
soa_contact=models.CharField(max_length=100)
|
||||
soa_serial=models.CharField(max_length=100)
|
||||
soa_refresh=models.PositiveIntegerField()
|
||||
soa_retry=models.PositiveIntegerField()
|
||||
soa_expire=models.PositiveIntegerField()
|
||||
soa_minimum=models.PositiveIntegerField()
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dns:zone', args=[self.pk])
|
||||
|
||||
def to_csv(self):
|
||||
return ','.join([
|
||||
self.name,
|
||||
str(self.ttl),
|
||||
self.soa_name,
|
||||
self.soa_contact,
|
||||
self.soa_serial,
|
||||
str(self.soa_refresh),
|
||||
str(self.soa_retry),
|
||||
str(self.soa_expire),
|
||||
str(self.soa_minimum),
|
||||
])
|
||||
|
||||
|
||||
|
||||
class Record(CreatedUpdatedModel):
|
||||
"""
|
||||
A Record represents a DNS record, i.e. a row in a DNS zone.
|
||||
"""
|
||||
name=models.CharField(max_length=100)
|
||||
record_type=models.CharField(max_length=10)
|
||||
priority=models.PositiveIntegerField(blank=True, null=True)
|
||||
zone=models.ForeignKey('Zone', related_name='records', on_delete=models.CASCADE)
|
||||
address=models.ForeignKey('ipam.IPAddress', related_name='records', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
value=models.CharField(max_length=100, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('dns:record', args=[self.pk])
|
||||
|
||||
def clean(self):
|
||||
if not self.address and not self.value:
|
||||
raise ValidationError("DNS records must have either an IP address or a text value")
|
||||
|
||||
def to_csv(self):
|
||||
return ','.join([
|
||||
self.zone.name,
|
||||
self.name,
|
||||
self.record_type,
|
||||
str(self.priority) if self.priority else '',
|
||||
str(self.address) if self.address else '',
|
||||
str(self.value) if self.value else '',
|
||||
])
|
||||
|
||||
#def to_json(self):
|
||||
# return JSON
|
52
netbox/dns/tables.py
Normal file
52
netbox/dns/tables.py
Normal file
@ -0,0 +1,52 @@
|
||||
import django_tables2 as tables
|
||||
from django_tables2.utils import Accessor
|
||||
|
||||
from utilities.tables import BaseTable, ToggleColumn
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from .models import Zone, Record
|
||||
|
||||
#
|
||||
# Zones
|
||||
#
|
||||
|
||||
class ZoneTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('dns:zone', args=[Accessor('pk')], verbose_name='Name')
|
||||
record_count = tables.Column(verbose_name='Records')
|
||||
ttl = tables.Column(verbose_name='TTL')
|
||||
soa_name = tables.Column(verbose_name='SOA Name')
|
||||
soa_contact = tables.Column(verbose_name='SOA Contact')
|
||||
soa_serial = tables.Column(verbose_name='SOA Serial')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Zone
|
||||
fields = ('pk', 'name', 'ttl', 'soa_name', 'soa_contact', 'soa_serial')
|
||||
|
||||
#
|
||||
# Records
|
||||
#
|
||||
|
||||
class RecordTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name')
|
||||
record_type = tables.Column(verbose_name='Type')
|
||||
priority = tables.Column(verbose_name='Priority')
|
||||
address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('address.pk')], verbose_name='IP Address')
|
||||
value = tables.Column(verbose_name='Text Value')
|
||||
zone = tables.LinkColumn('dns:zone', args=[Accessor('zone.pk')], verbose_name='Zone')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model=Record
|
||||
fields = ('pk', 'name', 'record_type', 'priority', 'address', 'value')
|
||||
|
||||
class RecordBriefTable(BaseTable):
|
||||
name = tables.LinkColumn('dns:record', args=[Accessor('pk')], verbose_name='Name')
|
||||
record_type = tables.Column(verbose_name='Type')
|
||||
priority = tables.Column(verbose_name='Priority')
|
||||
zone = tables.LinkColumn('dns:zone', args=[Accessor('zone.pk')], verbose_name='Zone')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model=Record
|
||||
fields = ('name', 'record_type', 'priority', 'zone')
|
||||
|
3
netbox/dns/tests.py
Normal file
3
netbox/dns/tests.py
Normal file
@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
27
netbox/dns/urls.py
Normal file
27
netbox/dns/urls.py
Normal file
@ -0,0 +1,27 @@
|
||||
from django.conf.urls import url
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
|
||||
# Zones
|
||||
url(r'^zones/$', views.ZoneListView.as_view(), name='zone_list'),
|
||||
url(r'^zones/add/$', views.ZoneEditView.as_view(), name='zone_add'),
|
||||
url(r'^zones/import/$', views.ZoneBulkImportView.as_view(), name='zone_import'),
|
||||
url(r'^zones/edit/$', views.ZoneBulkEditView.as_view(), name='zone_bulk_edit'),
|
||||
url(r'^zones/delete/$', views.ZoneBulkDeleteView.as_view(), name='zone_bulk_delete'),
|
||||
url(r'^zones/(?P<pk>\d+)/$', views.zone, name='zone'),
|
||||
url(r'^zones/(?P<pk>\d+)/edit/$', views.ZoneEditView.as_view(), name='zone_edit'),
|
||||
url(r'^zones/(?P<pk>\d+)/delete/$', views.ZoneDeleteView.as_view(), name='zone_delete'),
|
||||
|
||||
# Records
|
||||
url(r'^records/$', views.RecordListView.as_view(), name='record_list'),
|
||||
url(r'^records/add/$', views.RecordEditView.as_view(), name='record_add'),
|
||||
url(r'^records/import/$', views.RecordBulkImportView.as_view(), name='record_import'),
|
||||
url(r'^records/edit/$', views.RecordBulkEditView.as_view(), name='record_bulk_edit'),
|
||||
url(r'^records/delete/$', views.RecordBulkDeleteView.as_view(), name='record_bulk_delete'),
|
||||
url(r'^records/(?P<pk>\d+)/$', views.record, name='record'),
|
||||
url(r'^records/(?P<pk>\d+)/edit/$', views.RecordEditView.as_view(), name='record_edit'),
|
||||
url(r'^records/(?P<pk>\d+)/delete/$', views.RecordDeleteView.as_view(), name='record_delete'),
|
||||
|
||||
]
|
140
netbox/dns/views.py
Normal file
140
netbox/dns/views.py
Normal file
@ -0,0 +1,140 @@
|
||||
from django_tables2 import RequestConfig
|
||||
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from utilities.paginator import EnhancedPaginator
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
|
||||
from . import filters, forms, tables
|
||||
from .models import Zone, Record
|
||||
|
||||
#
|
||||
# Zones
|
||||
#
|
||||
|
||||
class ZoneListView(ObjectListView):
|
||||
queryset = Zone.objects.annotate(record_count=Count('records'))
|
||||
filter = filters.ZoneFilter
|
||||
filter_form = forms.ZoneFilterForm
|
||||
table = tables.ZoneTable
|
||||
edit_permissions = ['dns.change_zone', 'dns.delete_zone']
|
||||
template_name = 'dns/zone_list.html'
|
||||
|
||||
def zone(request, pk):
|
||||
|
||||
zone = get_object_or_404(Zone.objects.all(), pk=pk)
|
||||
records = Record.objects.filter(zone=zone)
|
||||
record_count = len(records)
|
||||
|
||||
return render(request, 'dns/zone.html', {
|
||||
'zone': zone,
|
||||
'records': records,
|
||||
'record_count': record_count,
|
||||
})
|
||||
|
||||
class ZoneEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'dns.change_zone'
|
||||
model = Zone
|
||||
form_class = forms.ZoneForm
|
||||
cancel_url = 'dns:zone_list'
|
||||
|
||||
class ZoneDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
permission_required = 'dns.delete_zone'
|
||||
model = Zone
|
||||
redirect_url = 'dns:zone_list'
|
||||
|
||||
|
||||
class ZoneBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
permission_required = 'dns.add_zone'
|
||||
form = forms.ZoneImportForm
|
||||
table = tables.ZoneTable
|
||||
template_name = 'dns/zone_import.html'
|
||||
obj_list_url = 'dns:zone_list'
|
||||
|
||||
class ZoneBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dns.change_zone'
|
||||
cls = Zone
|
||||
form = forms.ZoneBulkEditForm
|
||||
template_name = 'dns/zone_bulk_edit.html'
|
||||
default_redirect_url = 'dns:zone_list'
|
||||
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
for field in ['name', 'ttl', 'soa_name', 'soa_contact', 'soa_refresh', 'soa_retry', 'soa_expire', 'soa_minimum']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
|
||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
||||
|
||||
|
||||
class ZoneBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dns.delete_zone'
|
||||
cls = Zone
|
||||
form = forms.ZoneBulkDeleteForm
|
||||
default_redirect_url = 'dns:zone_list'
|
||||
|
||||
#
|
||||
# Records
|
||||
#
|
||||
|
||||
class RecordListView(ObjectListView):
|
||||
queryset = Record.objects.all()
|
||||
filter = filters.RecordFilter
|
||||
filter_form = forms.RecordFilterForm
|
||||
table = tables.RecordTable
|
||||
edit_permissions = ['dns.change_record', 'dns.delete_record']
|
||||
template_name = 'dns/record_list.html'
|
||||
|
||||
def record(request, pk):
|
||||
|
||||
record = get_object_or_404(Record.objects.all(), pk=pk)
|
||||
|
||||
return render(request, 'dns/record.html', {
|
||||
'record': record,
|
||||
})
|
||||
|
||||
class RecordEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'dns.change_record'
|
||||
model = Record
|
||||
form_class = forms.RecordForm
|
||||
cancel_url = 'dns:record_list'
|
||||
|
||||
class RecordDeleteView(PermissionRequiredMixin, ObjectDeleteView):
|
||||
permission_required = 'dns.delete_record'
|
||||
model = Record
|
||||
redirect_url = 'dns:record_list'
|
||||
|
||||
class RecordBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
permission_required = 'dns.add_record'
|
||||
form = forms.RecordImportForm
|
||||
table = tables.RecordTable
|
||||
template_name = 'dns/record_import.html'
|
||||
obj_list_url = 'dns:record_list'
|
||||
|
||||
class RecordBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dns.change_record'
|
||||
cls = Record
|
||||
form = forms.RecordBulkEditForm
|
||||
template_name = 'dns/record_bulk_edit.html'
|
||||
default_redirect_url = 'dns:record_list'
|
||||
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
for field in ['name', 'record_type', 'priority', 'zone', 'address', 'value']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
|
||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
||||
|
||||
class RecordBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'dns.delete_record'
|
||||
cls = Record
|
||||
form = forms.RecordBulkEditForm
|
||||
default_redirect_url = 'dns:record_list'
|
@ -142,7 +142,7 @@ class IPAddressSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['id', 'family', 'address', 'vrf', 'interface', 'description', 'nat_inside', 'nat_outside']
|
||||
fields = ['id', 'family', 'address', 'vrf', 'hostname', 'interface', 'description', 'nat_inside', 'nat_outside']
|
||||
|
||||
|
||||
class IPAddressNestedSerializer(IPAddressSerializer):
|
||||
|
@ -70,6 +70,7 @@
|
||||
"family": 4,
|
||||
"address": "10.0.255.1/32",
|
||||
"vrf": null,
|
||||
"hostname": "foo.net",
|
||||
"interface": 3,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -84,6 +85,7 @@
|
||||
"family": 4,
|
||||
"address": "169.254.254.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 4,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -98,6 +100,7 @@
|
||||
"family": 4,
|
||||
"address": "10.0.255.2/32",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 185,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -112,6 +115,7 @@
|
||||
"family": 4,
|
||||
"address": "169.254.1.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 213,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -126,6 +130,7 @@
|
||||
"family": 4,
|
||||
"address": "10.0.254.1/24",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 12,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -140,6 +145,7 @@
|
||||
"family": 4,
|
||||
"address": "10.15.21.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 218,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -154,6 +160,7 @@
|
||||
"family": 4,
|
||||
"address": "10.15.21.2/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 9,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -168,6 +175,7 @@
|
||||
"family": 4,
|
||||
"address": "10.15.22.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 8,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -182,6 +190,7 @@
|
||||
"family": 4,
|
||||
"address": "10.15.20.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 7,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -196,6 +205,7 @@
|
||||
"family": 4,
|
||||
"address": "10.16.20.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 216,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -210,6 +220,7 @@
|
||||
"family": 4,
|
||||
"address": "10.15.22.2/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 206,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -224,6 +235,7 @@
|
||||
"family": 4,
|
||||
"address": "10.16.22.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 217,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -238,6 +250,7 @@
|
||||
"family": 4,
|
||||
"address": "10.16.22.2/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 205,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -252,6 +265,7 @@
|
||||
"family": 4,
|
||||
"address": "10.16.20.2/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 211,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -266,6 +280,7 @@
|
||||
"family": 4,
|
||||
"address": "10.15.22.2/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 212,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -280,6 +295,7 @@
|
||||
"family": 4,
|
||||
"address": "10.0.254.2/32",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 188,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -294,6 +310,7 @@
|
||||
"family": 4,
|
||||
"address": "169.254.1.1/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 200,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
@ -308,6 +325,7 @@
|
||||
"family": 4,
|
||||
"address": "169.254.1.2/31",
|
||||
"vrf": null,
|
||||
"hostname": "",
|
||||
"interface": 194,
|
||||
"nat_inside": null,
|
||||
"description": ""
|
||||
|
@ -311,10 +311,11 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['address', 'vrf', 'nat_device', 'nat_inside', 'description']
|
||||
fields = ['address', 'vrf', 'hostname', 'nat_device', 'nat_inside', 'description']
|
||||
help_texts = {
|
||||
'address': "IPv4 or IPv6 address and mask",
|
||||
'vrf': "VRF (if applicable)",
|
||||
'hostname': "Reverse DNS host name",
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
@ -367,7 +368,7 @@ class IPAddressFromCSVForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['address', 'vrf', 'device', 'interface_name', 'is_primary', 'description']
|
||||
fields = ['address', 'vrf', 'hostname', 'device', 'interface_name', 'is_primary', 'description']
|
||||
|
||||
def clean(self):
|
||||
|
||||
@ -414,6 +415,7 @@ class IPAddressBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=IPAddress.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, label='VRF',
|
||||
help_text="Select the VRF to assign, or check below to remove VRF assignment")
|
||||
hostname = forms.CharField(max_length=100, required=False)
|
||||
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
||||
description = forms.CharField(max_length=50, required=False)
|
||||
|
||||
|
20
netbox/ipam/migrations/0005_ipaddress_hostname.py
Normal file
20
netbox/ipam/migrations/0005_ipaddress_hostname.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-19 15:37
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0004_ipam_vlangroup_uniqueness'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='hostname',
|
||||
field=models.CharField(blank=True, max_length=100),
|
||||
),
|
||||
]
|
20
netbox/ipam/migrations/0006_auto_20160720_0941.py
Normal file
20
netbox/ipam/migrations/0006_auto_20160720_0941.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.7 on 2016-07-20 09:41
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('ipam', '0005_ipaddress_hostname'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='ipaddress',
|
||||
name='hostname',
|
||||
field=models.CharField(blank=True, max_length=100, verbose_name=b'Host Name'),
|
||||
),
|
||||
]
|
@ -304,6 +304,7 @@ class IPAddress(CreatedUpdatedModel):
|
||||
address = IPAddressField()
|
||||
vrf = models.ForeignKey('VRF', related_name='ip_addresses', on_delete=models.PROTECT, blank=True, null=True,
|
||||
verbose_name='VRF')
|
||||
hostname = models.CharField(max_length=100, blank=True, verbose_name='Host Name')
|
||||
interface = models.ForeignKey(Interface, related_name='ip_addresses', on_delete=models.CASCADE, blank=True,
|
||||
null=True)
|
||||
nat_inside = models.OneToOneField('self', related_name='nat_outside', on_delete=models.SET_NULL, blank=True,
|
||||
@ -354,6 +355,7 @@ class IPAddress(CreatedUpdatedModel):
|
||||
return ','.join([
|
||||
str(self.address),
|
||||
self.vrf.rd if self.vrf else '',
|
||||
self.hostname if self.hostname else '',
|
||||
self.device.identifier if self.device else '',
|
||||
self.interface.name if self.interface else '',
|
||||
'True' if is_primary else '',
|
||||
|
@ -160,6 +160,7 @@ class IPAddressTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
address = tables.LinkColumn('ipam:ipaddress', args=[Accessor('pk')], verbose_name='IP Address')
|
||||
vrf = tables.Column(orderable=False, default='Global', verbose_name='VRF')
|
||||
hostname = tables.Column(verbose_name='Host Name')
|
||||
device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
|
||||
verbose_name='Device')
|
||||
interface = tables.Column(orderable=False, verbose_name='Interface')
|
||||
@ -167,7 +168,7 @@ class IPAddressTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = IPAddress
|
||||
fields = ('pk', 'address', 'vrf', 'device', 'interface', 'description')
|
||||
fields = ('pk', 'address', 'vrf', 'hostname', 'device', 'interface', 'description')
|
||||
|
||||
|
||||
class IPAddressBriefTable(BaseTable):
|
||||
|
@ -6,6 +6,8 @@ from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from dcim.models import Device
|
||||
from dns.models import Zone, Record
|
||||
from dns.tables import RecordBriefTable
|
||||
from utilities.paginator import EnhancedPaginator
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
@ -409,11 +411,17 @@ def ipaddress(request, pk):
|
||||
.filter(vrf=ipaddress.vrf, address__net_contained_or_equal=str(ipaddress.address))
|
||||
related_ips_table = tables.IPAddressBriefTable(related_ips)
|
||||
|
||||
# Related DNS records
|
||||
dns_records = Record.objects.filter(address=ipaddress)
|
||||
dns_records_table = RecordBriefTable(dns_records)
|
||||
|
||||
|
||||
return render(request, 'ipam/ipaddress.html', {
|
||||
'ipaddress': ipaddress,
|
||||
'parent_prefixes_table': parent_prefixes_table,
|
||||
'duplicate_ips_table': duplicate_ips_table,
|
||||
'related_ips_table': related_ips_table,
|
||||
'dns_records_table': dns_records_table,
|
||||
})
|
||||
|
||||
|
||||
|
@ -106,6 +106,7 @@ INSTALLED_APPS = (
|
||||
'circuits',
|
||||
'dcim',
|
||||
'ipam',
|
||||
'dns',
|
||||
'extras',
|
||||
'secrets',
|
||||
'users',
|
||||
|
@ -21,6 +21,7 @@ urlpatterns = [
|
||||
url(r'^circuits/', include('circuits.urls', namespace='circuits')),
|
||||
url(r'^dcim/', include('dcim.urls', namespace='dcim')),
|
||||
url(r'^ipam/', include('ipam.urls', namespace='ipam')),
|
||||
url(r'^dns/', include('dns.urls', namespace='dns')),
|
||||
url(r'^secrets/', include('secrets.urls', namespace='secrets')),
|
||||
url(r'^profile/', include('users.urls', namespace='users')),
|
||||
|
||||
@ -28,6 +29,7 @@ urlpatterns = [
|
||||
url(r'^api/circuits/', include('circuits.api.urls', namespace='circuits-api')),
|
||||
url(r'^api/dcim/', include('dcim.api.urls', namespace='dcim-api')),
|
||||
url(r'^api/ipam/', include('ipam.api.urls', namespace='ipam-api')),
|
||||
url(r'^api/dns/', include('dns.api.urls', namespace='dns-api')),
|
||||
url(r'^api/secrets/', include('secrets.api.urls', namespace='secrets-api')),
|
||||
url(r'^api/docs/', include('rest_framework_swagger.urls')),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
|
@ -6,6 +6,7 @@ from circuits.models import Provider, Circuit
|
||||
from dcim.models import Site, Rack, Device, ConsolePort, PowerPort, InterfaceConnection
|
||||
from extras.models import UserAction
|
||||
from ipam.models import Aggregate, Prefix, IPAddress, VLAN
|
||||
from dns.models import Zone, Record
|
||||
from secrets.models import Secret
|
||||
|
||||
|
||||
@ -27,6 +28,10 @@ def home(request):
|
||||
'ipaddress_count': IPAddress.objects.count(),
|
||||
'vlan_count': VLAN.objects.count(),
|
||||
|
||||
# DNS
|
||||
'zone_count': Zone.objects.count(),
|
||||
'record_count': Record.objects.count(),
|
||||
|
||||
# Circuits
|
||||
'provider_count': Provider.objects.count(),
|
||||
'circuit_count': Circuit.objects.count(),
|
||||
|
@ -156,6 +156,22 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/dns/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">DNS <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'dns:record_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Records</a></li>
|
||||
{% if perms.dns.add_record %}
|
||||
<li><a href="{% url 'dns:record_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a record</a></li>
|
||||
<li><a href="{% url 'dns:record_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import records</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'dns:zone_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Zones</a></li>
|
||||
{% if perms.dns.add_zone %}
|
||||
<li><a href="{% url 'dns:zone_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a zone</a></li>
|
||||
<li><a href="{% url 'dns:zone_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import zones</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/ipam/vlan' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">VLANs <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
|
105
netbox/templates/dns/record.html
Normal file
105
netbox/templates/dns/record.html
Normal file
@ -0,0 +1,105 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}Record {{ record }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dns:zone_list' %}">Zones</a></li>
|
||||
<li><a href="{% url 'dns:zone' pk=record.zone.pk %}">{{ record.zone }}</a></li>
|
||||
<li><a href="{% url 'dns:record_list' %}">Records</a></li>
|
||||
<li>{{ record }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'dns:record_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Record" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if perms.dns.change_record %}
|
||||
<a href="{% url 'dns:record_edit' pk=record.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
Edit this record
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dns.delete_record %}
|
||||
<a href="{% url 'dns:record_delete' pk=record.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
Delete this record
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1>{{ record }}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Record</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>Zone</td>
|
||||
<td><a href="{% url 'dns:zone' pk=record.zone.pk %}">{{ record.zone }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ record.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td>{{ record.record_type }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Priority</td>
|
||||
<td>
|
||||
{% if record.priority %}
|
||||
{{ record.priority }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>IP Address</td>
|
||||
<td>
|
||||
{% if record.address %}
|
||||
<a href="{% url 'ipam:ipaddress' pk=record.address.pk %}">{{ record.address }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Value</td>
|
||||
<td>
|
||||
{% if record.value %}
|
||||
{{ record.value }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
<td>{{ record.created }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Updated</td>
|
||||
<td>{{ record.last_updated }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
17
netbox/templates/dns/record_bulk_edit.html
Normal file
17
netbox/templates/dns/record_bulk_edit.html
Normal file
@ -0,0 +1,17 @@
|
||||
{% extends 'utilities/bulk_edit_form.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Record Bulk Edit{% endblock %}
|
||||
|
||||
{% block select_objects_table %}
|
||||
{% for record in selected_objects %}
|
||||
<tr>
|
||||
<td><a href="{% url 'dns:record' pk=record.pk %}">{{ record.name }}</a></td>
|
||||
<td>{{ record.record_type }}</td>
|
||||
<td>{{ record.priority }}</td>
|
||||
<td>{{ record.address }}</td>
|
||||
<td>{{ record.value }}</td>
|
||||
<td>{{ record.zone }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
67
netbox/templates/dns/record_import.html
Normal file
67
netbox/templates/dns/record_import.html
Normal file
@ -0,0 +1,67 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Record Import{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Record Import</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form action="." method="post" class="form">
|
||||
{% csrf_token %}
|
||||
{% render_form form %}
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
<a href="{% url obj_list_url %}" class="btn btn-default">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>CSV Format</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Zone name</td>
|
||||
<td>Name of zone</td>
|
||||
<td>foo.net</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Name of record, @ for origin</td>
|
||||
<td>www</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td>Type of record</td>
|
||||
<td>AAAA</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Priority</td>
|
||||
<td>Priority level of record (optional)</td>
|
||||
<td>30</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Address</td>
|
||||
<td>IP address value</td>
|
||||
<td>192.168.1.110/16</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Value</td>
|
||||
<td>Text value, for CNAME records for instance</td>
|
||||
<td>foo.net</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>foo.net,www,AAAA,,192.168.1.110/16,</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
47
netbox/templates/dns/record_list.html
Normal file
47
netbox/templates/dns/record_list.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load helpers %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Records{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
{% if perms.dns.add_record %}
|
||||
<a href="{% url 'dns:record_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add a record
|
||||
</a>
|
||||
<a href="{% url 'dns:record_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
Import records
|
||||
</a>
|
||||
{% endif %}
|
||||
{% include 'inc/export_button.html' with obj_type='Records' %}
|
||||
</div>
|
||||
<h1>Records</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dns:record_bulk_edit' bulk_delete_url='dns:record_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'dns:record_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="name" class="form-control" placeholder="Name" {% if request.GET.q %}value="{{ request.GET.q }}" {% endif %}/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
142
netbox/templates/dns/zone.html
Normal file
142
netbox/templates/dns/zone.html
Normal file
@ -0,0 +1,142 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}Zone {{ zone }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dns:zone_list' %}">Zones</a></li>
|
||||
<li>{{ zone }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'dns:zone_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Zone" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if perms.dns.change_zone %}
|
||||
<a href="{% url 'dns:zone_edit' pk=zone.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
Edit this zone
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dns.delete_zone %}
|
||||
<a href="{% url 'dns:zone_delete' pk=zone.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
Delete this zone
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1>{{ zone }}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Zone</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>{{ zone.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Records</td>
|
||||
<td>{{ record_count }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TTL</td>
|
||||
<td>{{ zone.ttl }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Name</td>
|
||||
<td>{{ zone.soa_name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Contact</td>
|
||||
<td>{{ zone.soa_contact }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Serial</td>
|
||||
<td>{{ zone.soa_serial }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Refresh</td>
|
||||
<td>{{ zone.soa_refresh }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Retry</td>
|
||||
<td>{{ zone.soa_retry }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Expire</td>
|
||||
<td>{{ zone.soa_expire }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Minimum</td>
|
||||
<td>{{ zone.soa_minimum }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
<td>{{ zone.created }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Updated</td>
|
||||
<td>{{ zone.last_updated }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Records</strong>
|
||||
</div>
|
||||
{% if records %}
|
||||
<table class="table table-hover panel-body">
|
||||
{% for r in records %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'dns:record' pk=r.pk %}">{{ r }}</a>
|
||||
</td>
|
||||
<td>{{ r.record_type }}</td>
|
||||
<td>
|
||||
{% if r.priority %}
|
||||
{{ r.priority }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.address %}
|
||||
<a href="{% url 'ipam:ipaddress' pk=r.address.pk %}">{{ r.address }}</a>
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{% if r.value %}
|
||||
{{ r.value }}
|
||||
{% else %}
|
||||
-
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="panel-body text-muted">None</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
19
netbox/templates/dns/zone_bulk_edit.html
Normal file
19
netbox/templates/dns/zone_bulk_edit.html
Normal file
@ -0,0 +1,19 @@
|
||||
{% extends 'utilities/bulk_edit_form.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Zone Bulk Edit{% endblock %}
|
||||
|
||||
{% block select_objects_table %}
|
||||
{% for zone in selected_objects %}
|
||||
<tr>
|
||||
<td><a href="{% url 'dns:zone' pk=zone.pk %}">{{ zone.name }}</a></td>
|
||||
<td>{{ zone.ttl }}</td>
|
||||
<td>{{ zone.soa_name }}</td>
|
||||
<td>{{ zone.soa_contact }}</td>
|
||||
<td>{{ zone.soa_refresh }}</td>
|
||||
<td>{{ zone.soa_retry }}</td>
|
||||
<td>{{ zone.soa_expire }}</td>
|
||||
<td>{{ zone.soa_minimum }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
82
netbox/templates/dns/zone_import.html
Normal file
82
netbox/templates/dns/zone_import.html
Normal file
@ -0,0 +1,82 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Zone Import{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>Zone Import</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<form action="." method="post" class="form">
|
||||
{% csrf_token %}
|
||||
{% render_form form %}
|
||||
<div class="form-group">
|
||||
<button type="submit" class="btn btn-primary">Submit</button>
|
||||
<a href="{% url obj_list_url %}" class="btn btn-default">Cancel</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h4>CSV Format</h4>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Field</th>
|
||||
<th>Description</th>
|
||||
<th>Example</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Name</td>
|
||||
<td>Name of zone</td>
|
||||
<td>foo.net</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TTL</td>
|
||||
<td>Time to live, in seconds</td>
|
||||
<td>10800</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Name</td>
|
||||
<td>SOA name field, @ for origin</td>
|
||||
<td>@</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Contact</td>
|
||||
<td>SOA contact field</td>
|
||||
<td>ns.foo.net. noc.foo.net.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Serial</td>
|
||||
<td>Serial code of zone (string)</td>
|
||||
<td>2016070401</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Refresh</td>
|
||||
<td>SOA refresh time field, in seconds</td>
|
||||
<td>3600</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Retry</td>
|
||||
<td>SOA retry time field, in seconds</td>
|
||||
<td>3600</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Expire</td>
|
||||
<td>SOA expire time field, in seconds</td>
|
||||
<td>604800</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>SOA Minimum</td>
|
||||
<td>SOA minimum time field, in seconds</td>
|
||||
<td>1800</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>foo.net,10800,@,ns.foo.net. noc.foo.net.,2016070401,3600,3600,604800,1800</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
46
netbox/templates/dns/zone_list.html
Normal file
46
netbox/templates/dns/zone_list.html
Normal file
@ -0,0 +1,46 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load helpers %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Zones{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="pull-right">
|
||||
{% if perms.dns.add_zone %}
|
||||
<a href="{% url 'dns:zone_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add a zone
|
||||
</a>
|
||||
<a href="{% url 'dns:zone_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
Import zones
|
||||
</a>
|
||||
{% endif %}
|
||||
{% include 'inc/export_button.html' with obj_type='Zones' %}
|
||||
</div>
|
||||
<h1>Zones</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dns:zone_bulk_edit' bulk_delete_url='dns:zone_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'dns:zone_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="name" class="form-control" placeholder="Name" {% if request.GET.q %}value="{{ request.GET.q }}" {% endif %}/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
@ -122,6 +122,23 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>DNS</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.zone_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dns:zone_list' %}">Zones</a></h4>
|
||||
<p class="list-group-item-text text-muted">Domain name system zones</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.record_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dns:record_list' %}">Records</a></h4>
|
||||
<p class="list-group-item-text text-muted">Links between a hostname and a resource</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Circuits</strong>
|
||||
|
@ -64,6 +64,16 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Host Name</td>
|
||||
<td>
|
||||
{% if ipaddress.hostname %}
|
||||
<span>{{ ipaddress.hostname }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
@ -130,6 +140,9 @@
|
||||
{% with heading='Related IP Addresses' %}
|
||||
{% render_table related_ips_table 'panel_table.html' %}
|
||||
{% endwith %}
|
||||
{% with heading='Related DNS Records' %}
|
||||
{% render_table dns_records_table 'panel_table.html' %}
|
||||
{% endwith %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -8,6 +8,7 @@
|
||||
<tr>
|
||||
<td><a href="{% url 'ipam:ipaddress' pk=ipaddress.pk %}">{{ ipaddress }}</a></td>
|
||||
<td>{{ ipaddress.vrf }}</td>
|
||||
<td>{{ ipaddress.hostname }}</td>
|
||||
<td>{{ ipaddress.interface.device }}</td>
|
||||
<td>{{ ipaddress.interface }}</td>
|
||||
<td>{{ ipaddress.description }}</td>
|
||||
|
@ -8,6 +8,7 @@
|
||||
<div class="panel-body">
|
||||
{% render_field form.address %}
|
||||
{% render_field form.vrf %}
|
||||
{% render_field form.hostname %}
|
||||
{% if obj %}
|
||||
<div class="form-group">
|
||||
<label class="col-md-3 control-label">Device</label>
|
||||
|
@ -38,6 +38,11 @@
|
||||
<td>VRF route distinguisher (optional)</td>
|
||||
<td>65000:123</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Host Name</td>
|
||||
<td>Reverse DNS host name</td>
|
||||
<td>foo.com</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Device</td>
|
||||
<td>Device name (optional)</td>
|
||||
@ -61,7 +66,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>192.0.2.42/24,65000:123,switch12,ge-0/0/31,True,Management IP</pre>
|
||||
<pre>192.0.2.42/24,65000:123,foo.com,switch12,ge-0/0/31,True,Management IP</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
Loading…
Reference in New Issue
Block a user