mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-20 10:16:42 -06:00
commit
9889e120bd
9
docs/data-model/tenancy.md
Normal file
9
docs/data-model/tenancy.md
Normal file
@ -0,0 +1,9 @@
|
||||
NetBox supports the concept of individual tenants within its parent organization. Typically, these are used to represent individual customers or internal departments.
|
||||
|
||||
# Tenants
|
||||
|
||||
A tenant represents a discrete organization. Certain resources within NetBox can be assigned to a tenant. This makes it very convenient to track which resources are assigned to which customers, for instance.
|
||||
|
||||
### Tenant Groups
|
||||
|
||||
Tenants are grouped by type. For instance, you might create one group called "Customers" and one called "Acquisitions."
|
@ -163,6 +163,18 @@ Are you sure you want to do this?
|
||||
Type 'yes' to continue, or 'no' to cancel: yes
|
||||
```
|
||||
|
||||
# Load Initial Data (Optional)
|
||||
|
||||
NetBox ships with some initial data to help you get started: RIR definitions, common devices roles, etc. You can delete any seed data that you don't want to keep.
|
||||
|
||||
!!! note
|
||||
This step is optional. It's perfectly fine to start using NetBox without using this initial data if you'd rather create everything from scratch.
|
||||
|
||||
```
|
||||
# ./manage.py loaddata initial_data
|
||||
Installed 43 object(s) from 4 fixture(s)
|
||||
```
|
||||
|
||||
# Test the Application
|
||||
|
||||
At this point, NetBox should be able to run. We can verify this by starting a development instance:
|
||||
|
@ -17,6 +17,7 @@ pages:
|
||||
- 'DCIM': 'data-model/dcim.md'
|
||||
- 'IPAM': 'data-model/ipam.md'
|
||||
- 'Secrets': 'data-model/secrets.md'
|
||||
- 'Tenancy': 'data-model/tenancy.md'
|
||||
- 'Extras': 'data-model/extras.md'
|
||||
- 'API Integration': 'api-integration.md'
|
||||
|
||||
|
@ -21,10 +21,11 @@ class CircuitTypeAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(Circuit)
|
||||
class CircuitAdmin(admin.ModelAdmin):
|
||||
list_display = ['cid', 'provider', 'type', 'site', 'install_date', 'port_speed', 'commit_rate', 'xconnect_id']
|
||||
list_filter = ['provider']
|
||||
list_display = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'commit_rate',
|
||||
'xconnect_id']
|
||||
list_filter = ['provider', 'type', 'tenant']
|
||||
exclude = ['interface']
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(CircuitAdmin, self).get_queryset(request)
|
||||
return qs.select_related('provider', 'type', 'site')
|
||||
return qs.select_related('provider', 'type', 'tenant', 'site')
|
||||
|
@ -2,6 +2,7 @@ from rest_framework import serializers
|
||||
|
||||
from circuits.models import Provider, CircuitType, Circuit
|
||||
from dcim.api.serializers import SiteNestedSerializer, InterfaceNestedSerializer
|
||||
from tenancy.api.serializers import TenantNestedSerializer
|
||||
|
||||
|
||||
#
|
||||
@ -45,13 +46,14 @@ class CircuitTypeNestedSerializer(CircuitTypeSerializer):
|
||||
class CircuitSerializer(serializers.ModelSerializer):
|
||||
provider = ProviderNestedSerializer()
|
||||
type = CircuitTypeNestedSerializer()
|
||||
tenant = TenantNestedSerializer()
|
||||
site = SiteNestedSerializer()
|
||||
interface = InterfaceNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = ['id', 'cid', 'provider', 'type', 'site', 'interface', 'install_date', 'port_speed', 'commit_rate',
|
||||
'xconnect_id', 'comments']
|
||||
fields = ['id', 'cid', 'provider', 'type', 'tenant', 'site', 'interface', 'install_date', 'port_speed',
|
||||
'commit_rate', 'xconnect_id', 'comments']
|
||||
|
||||
|
||||
class CircuitNestedSerializer(CircuitSerializer):
|
||||
|
@ -42,7 +42,7 @@ class CircuitListView(generics.ListAPIView):
|
||||
"""
|
||||
List circuits (filterable)
|
||||
"""
|
||||
queryset = Circuit.objects.select_related('type', 'provider', 'site', 'interface__device')
|
||||
queryset = Circuit.objects.select_related('type', 'tenant', 'provider', 'site', 'interface__device')
|
||||
serializer_class = serializers.CircuitSerializer
|
||||
filter_class = CircuitFilter
|
||||
|
||||
@ -51,5 +51,5 @@ class CircuitDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single circuit
|
||||
"""
|
||||
queryset = Circuit.objects.select_related('type', 'provider', 'site', 'interface__device')
|
||||
queryset = Circuit.objects.select_related('type', 'tenant', 'provider', 'site', 'interface__device')
|
||||
serializer_class = serializers.CircuitSerializer
|
||||
|
@ -3,6 +3,7 @@ import django_filters
|
||||
from django.db.models import Q
|
||||
|
||||
from dcim.models import Site
|
||||
from tenancy.models import Tenant
|
||||
from .models import Provider, Circuit, CircuitType
|
||||
|
||||
|
||||
@ -28,10 +29,10 @@ class ProviderFilter(django_filters.FilterSet):
|
||||
fields = ['q', 'name', 'account', 'asn']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(account__icontains=value)
|
||||
Q(account__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
@ -62,6 +63,17 @@ class CircuitFilter(django_filters.FilterSet):
|
||||
to_field_name='slug',
|
||||
label='Circuit type (slug)',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
@ -79,5 +91,9 @@ class CircuitFilter(django_filters.FilterSet):
|
||||
fields = ['q', 'provider_id', 'provider', 'type_id', 'type', 'site_id', 'site', 'interface', 'install_date']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
return queryset.filter(cid__icontains=value)
|
||||
return queryset.filter(
|
||||
Q(cid__icontains=value) |
|
||||
Q(xconnect_id__icontains=value) |
|
||||
Q(pp_info__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
26
netbox/circuits/fixtures/initial_data.json
Normal file
26
netbox/circuits/fixtures/initial_data.json
Normal file
@ -0,0 +1,26 @@
|
||||
[
|
||||
{
|
||||
"model": "circuits.circuittype",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Internet",
|
||||
"slug": "internet"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "circuits.circuittype",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Private WAN",
|
||||
"slug": "private-wan"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "circuits.circuittype",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Out-of-Band",
|
||||
"slug": "out-of-band"
|
||||
}
|
||||
}
|
||||
]
|
@ -2,6 +2,8 @@ from django import forms
|
||||
from django.db.models import Count
|
||||
|
||||
from dcim.models import Site, Device, Interface, Rack, IFACE_FF_VIRTUAL
|
||||
from tenancy.forms import bulkedit_tenant_choices
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, Livesearch, SmallTextarea, SlugField,
|
||||
)
|
||||
@ -99,7 +101,7 @@ class CircuitForm(forms.ModelForm, BootstrapMixin):
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = [
|
||||
'cid', 'type', 'provider', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date',
|
||||
'cid', 'type', 'provider', 'tenant', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date',
|
||||
'port_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments'
|
||||
]
|
||||
help_texts = {
|
||||
@ -160,13 +162,15 @@ class CircuitFromCSVForm(forms.ModelForm):
|
||||
error_messages={'invalid_choice': 'Provider not found.'})
|
||||
type = forms.ModelChoiceField(CircuitType.objects.all(), to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Invalid circuit type.'})
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
site = forms.ModelChoiceField(Site.objects.all(), to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Site not found.'})
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = ['cid', 'provider', 'type', 'site', 'install_date', 'port_speed', 'commit_rate', 'xconnect_id',
|
||||
'pp_info']
|
||||
fields = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'commit_rate',
|
||||
'xconnect_id', 'pp_info']
|
||||
|
||||
|
||||
class CircuitImportForm(BulkImportForm, BootstrapMixin):
|
||||
@ -177,6 +181,7 @@ class CircuitBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Circuit.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
type = forms.ModelChoiceField(queryset=CircuitType.objects.all(), required=False)
|
||||
provider = forms.ModelChoiceField(queryset=Provider.objects.all(), required=False)
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
port_speed = forms.IntegerField(required=False, label='Port speed (Kbps)')
|
||||
commit_rate = forms.IntegerField(required=False, label='Commit rate (Kbps)')
|
||||
comments = CommentField()
|
||||
@ -192,6 +197,11 @@ def circuit_provider_choices():
|
||||
return [(p.slug, u'{} ({})'.format(p.name, p.circuit_count)) for p in provider_choices]
|
||||
|
||||
|
||||
def circuit_tenant_choices():
|
||||
tenant_choices = Tenant.objects.annotate(circuit_count=Count('circuits'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in tenant_choices]
|
||||
|
||||
|
||||
def circuit_site_choices():
|
||||
site_choices = Site.objects.annotate(circuit_count=Count('circuits'))
|
||||
return [(s.slug, u'{} ({})'.format(s.name, s.circuit_count)) for s in site_choices]
|
||||
@ -201,5 +211,7 @@ class CircuitFilterForm(forms.Form, BootstrapMixin):
|
||||
type = forms.MultipleChoiceField(required=False, choices=circuit_type_choices)
|
||||
provider = forms.MultipleChoiceField(required=False, choices=circuit_provider_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
tenant = forms.MultipleChoiceField(required=False, choices=circuit_tenant_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
site = forms.MultipleChoiceField(required=False, choices=circuit_site_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
|
22
netbox/circuits/migrations/0004_circuit_add_tenant.py
Normal file
22
netbox/circuits/migrations/0004_circuit_add_tenant.py
Normal file
@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
('circuits', '0003_provider_32bit_asn_support'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='circuit',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='circuits', to='tenancy.Tenant'),
|
||||
),
|
||||
]
|
@ -3,6 +3,7 @@ from django.db import models
|
||||
|
||||
from dcim.fields import ASNField
|
||||
from dcim.models import Site, Interface
|
||||
from tenancy.models import Tenant
|
||||
from utilities.models import CreatedUpdatedModel
|
||||
|
||||
|
||||
@ -66,6 +67,7 @@ class Circuit(CreatedUpdatedModel):
|
||||
cid = models.CharField(max_length=50, verbose_name='Circuit ID')
|
||||
provider = models.ForeignKey('Provider', related_name='circuits', on_delete=models.PROTECT)
|
||||
type = models.ForeignKey('CircuitType', related_name='circuits', on_delete=models.PROTECT)
|
||||
tenant = models.ForeignKey(Tenant, related_name='circuits', blank=True, null=True, on_delete=models.PROTECT)
|
||||
site = models.ForeignKey(Site, related_name='circuits', on_delete=models.PROTECT)
|
||||
interface = models.OneToOneField(Interface, related_name='circuit', blank=True, null=True)
|
||||
install_date = models.DateField(blank=True, null=True, verbose_name='Date installed')
|
||||
@ -90,6 +92,7 @@ class Circuit(CreatedUpdatedModel):
|
||||
self.cid,
|
||||
self.provider.name,
|
||||
self.type.name,
|
||||
self.tenant.name if self.tenant else '',
|
||||
self.site.name,
|
||||
self.install_date.isoformat() if self.install_date else '',
|
||||
str(self.port_speed),
|
||||
|
@ -6,9 +6,9 @@ from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
|
||||
|
||||
CIRCUITTYPE_EDIT_LINK = """
|
||||
CIRCUITTYPE_ACTIONS = """
|
||||
{% if perms.circuit.change_circuittype %}
|
||||
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}">Edit</a>
|
||||
<a href="{% url 'circuits:circuittype_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -21,11 +21,12 @@ class ProviderTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('circuits:provider', args=[Accessor('slug')], verbose_name='Name')
|
||||
asn = tables.Column(verbose_name='ASN')
|
||||
account = tables.Column(verbose_name='Account')
|
||||
circuit_count = tables.Column(accessor=Accessor('count_circuits'), verbose_name='Circuits')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Provider
|
||||
fields = ('pk', 'name', 'asn', 'circuit_count')
|
||||
fields = ('pk', 'name', 'asn', 'account', 'circuit_count')
|
||||
|
||||
|
||||
#
|
||||
@ -37,11 +38,12 @@ class CircuitTypeTable(BaseTable):
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
circuit_count = tables.Column(verbose_name='Circuits')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=CIRCUITTYPE_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=CIRCUITTYPE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = CircuitType
|
||||
fields = ('pk', 'name', 'circuit_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'circuit_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -53,10 +55,11 @@ class CircuitTable(BaseTable):
|
||||
cid = tables.LinkColumn('circuits:circuit', args=[Accessor('pk')], verbose_name='ID')
|
||||
type = tables.Column(verbose_name='Type')
|
||||
provider = tables.LinkColumn('circuits:provider', args=[Accessor('provider.slug')], verbose_name='Provider')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
port_speed_human = tables.Column(verbose_name='Port Speed')
|
||||
commit_rate_human = tables.Column(verbose_name='Commit Rate')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Circuit
|
||||
fields = ('pk', 'cid', 'type', 'provider', 'site', 'port_speed_human', 'commit_rate_human')
|
||||
fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'site', 'port_speed_human', 'commit_rate_human')
|
||||
|
@ -2,6 +2,7 @@ from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
|
||||
from extras.models import Graph, GRAPH_TYPE_PROVIDER
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
@ -27,10 +28,12 @@ def provider(request, slug):
|
||||
|
||||
provider = get_object_or_404(Provider, slug=slug)
|
||||
circuits = Circuit.objects.filter(provider=provider).select_related('site', 'interface__device')
|
||||
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists()
|
||||
|
||||
return render(request, 'circuits/provider.html', {
|
||||
'provider': provider,
|
||||
'circuits': circuits,
|
||||
'show_graphs': show_graphs,
|
||||
})
|
||||
|
||||
|
||||
@ -109,7 +112,7 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
#
|
||||
|
||||
class CircuitListView(ObjectListView):
|
||||
queryset = Circuit.objects.select_related('provider', 'type', 'site')
|
||||
queryset = Circuit.objects.select_related('provider', 'type', 'tenant', 'site')
|
||||
filter = filters.CircuitFilter
|
||||
filter_form = forms.CircuitFilterForm
|
||||
table = tables.CircuitTable
|
||||
@ -159,6 +162,10 @@ class CircuitBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['tenant'] == 0:
|
||||
fields_to_update['tenant'] = None
|
||||
elif form.cleaned_data['tenant']:
|
||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
||||
for field in ['type', 'provider', 'port_speed', 'commit_rate', 'comments']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
|
@ -6,6 +6,7 @@ from dcim.models import (
|
||||
DeviceRole, Interface, InterfaceConnection, InterfaceTemplate, Manufacturer, Platform, PowerOutlet,
|
||||
PowerOutletTemplate, PowerPort, PowerPortTemplate, Rack, RackGroup, RACK_FACE_FRONT, RACK_FACE_REAR, Site,
|
||||
)
|
||||
from tenancy.api.serializers import TenantNestedSerializer
|
||||
|
||||
|
||||
#
|
||||
@ -13,10 +14,11 @@ from dcim.models import (
|
||||
#
|
||||
|
||||
class SiteSerializer(serializers.ModelSerializer):
|
||||
tenant = TenantNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = ['id', 'name', 'slug', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments',
|
||||
fields = ['id', 'name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments',
|
||||
'count_prefixes', 'count_vlans', 'count_racks', 'count_devices', 'count_circuits']
|
||||
|
||||
|
||||
@ -52,10 +54,11 @@ class RackGroupNestedSerializer(RackGroupSerializer):
|
||||
class RackSerializer(serializers.ModelSerializer):
|
||||
site = SiteNestedSerializer()
|
||||
group = RackGroupNestedSerializer()
|
||||
tenant = TenantNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'u_height', 'comments']
|
||||
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments']
|
||||
|
||||
|
||||
class RackNestedSerializer(RackSerializer):
|
||||
@ -69,8 +72,8 @@ class RackDetailSerializer(RackSerializer):
|
||||
rear_units = serializers.SerializerMethodField()
|
||||
|
||||
class Meta(RackSerializer.Meta):
|
||||
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'u_height', 'comments', 'front_units',
|
||||
'rear_units']
|
||||
fields = ['id', 'name', 'facility_id', 'display_name', 'site', 'group', 'tenant', 'u_height', 'comments',
|
||||
'front_units', 'rear_units']
|
||||
|
||||
def get_front_units(self, obj):
|
||||
units = obj.get_rack_units(face=RACK_FACE_FRONT)
|
||||
@ -218,6 +221,7 @@ class DeviceIPAddressNestedSerializer(serializers.ModelSerializer):
|
||||
class DeviceSerializer(serializers.ModelSerializer):
|
||||
device_type = DeviceTypeNestedSerializer()
|
||||
device_role = DeviceRoleNestedSerializer()
|
||||
tenant = TenantNestedSerializer()
|
||||
platform = PlatformNestedSerializer()
|
||||
rack = RackNestedSerializer()
|
||||
primary_ip = DeviceIPAddressNestedSerializer()
|
||||
@ -227,8 +231,8 @@ class DeviceSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'platform', 'serial', 'rack', 'position',
|
||||
'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
|
||||
fields = ['id', 'name', 'display_name', 'device_type', 'device_role', 'tenant', 'platform', 'serial', 'rack',
|
||||
'position', 'face', 'parent_device', 'status', 'primary_ip', 'primary_ip4', 'primary_ip6', 'comments']
|
||||
|
||||
def get_parent_device(self, obj):
|
||||
try:
|
||||
|
@ -27,7 +27,7 @@ class SiteListView(generics.ListAPIView):
|
||||
"""
|
||||
List all sites
|
||||
"""
|
||||
queryset = Site.objects.all()
|
||||
queryset = Site.objects.select_related('tenant')
|
||||
serializer_class = serializers.SiteSerializer
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ class SiteDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single site
|
||||
"""
|
||||
queryset = Site.objects.all()
|
||||
queryset = Site.objects.select_related('tenant')
|
||||
serializer_class = serializers.SiteSerializer
|
||||
|
||||
|
||||
@ -47,7 +47,7 @@ class RackGroupListView(generics.ListAPIView):
|
||||
"""
|
||||
List all rack groups
|
||||
"""
|
||||
queryset = RackGroup.objects.all()
|
||||
queryset = RackGroup.objects.select_related('site')
|
||||
serializer_class = serializers.RackGroupSerializer
|
||||
filter_class = filters.RackGroupFilter
|
||||
|
||||
@ -56,7 +56,7 @@ class RackGroupDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single rack group
|
||||
"""
|
||||
queryset = RackGroup.objects.all()
|
||||
queryset = RackGroup.objects.select_related('site')
|
||||
serializer_class = serializers.RackGroupSerializer
|
||||
|
||||
|
||||
@ -68,7 +68,7 @@ class RackListView(generics.ListAPIView):
|
||||
"""
|
||||
List racks (filterable)
|
||||
"""
|
||||
queryset = Rack.objects.select_related('site')
|
||||
queryset = Rack.objects.select_related('site', 'group', 'tenant')
|
||||
serializer_class = serializers.RackSerializer
|
||||
filter_class = filters.RackFilter
|
||||
|
||||
@ -77,7 +77,7 @@ class RackDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single rack
|
||||
"""
|
||||
queryset = Rack.objects.select_related('site')
|
||||
queryset = Rack.objects.select_related('site', 'group', 'tenant')
|
||||
serializer_class = serializers.RackDetailSerializer
|
||||
|
||||
|
||||
@ -193,8 +193,9 @@ class DeviceListView(generics.ListAPIView):
|
||||
"""
|
||||
List devices (filterable)
|
||||
"""
|
||||
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'platform', 'rack__site')\
|
||||
.prefetch_related('primary_ip4__nat_outside', 'primary_ip6__nat_outside')
|
||||
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'platform',
|
||||
'rack__site', 'parent_bay').prefetch_related('primary_ip4__nat_outside',
|
||||
'primary_ip6__nat_outside')
|
||||
serializer_class = serializers.DeviceSerializer
|
||||
filter_class = filters.DeviceFilter
|
||||
renderer_classes = api_settings.DEFAULT_RENDERER_CLASSES + [BINDZoneRenderer, FlatJSONRenderer]
|
||||
@ -204,7 +205,8 @@ class DeviceDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single device
|
||||
"""
|
||||
queryset = Device.objects.all()
|
||||
queryset = Device.objects.select_related('device_type__manufacturer', 'device_role', 'tenant', 'platform',
|
||||
'rack__site', 'parent_bay')
|
||||
serializer_class = serializers.DeviceSerializer
|
||||
|
||||
|
||||
|
@ -6,6 +6,7 @@ from .models import (
|
||||
ConsolePort, ConsoleServerPort, Device, DeviceRole, DeviceType, Interface, InterfaceConnection, Manufacturer,
|
||||
Platform, PowerOutlet, PowerPort, Rack, RackGroup, Site,
|
||||
)
|
||||
from tenancy.models import Tenant
|
||||
|
||||
|
||||
class SiteFilter(django_filters.FilterSet):
|
||||
@ -13,17 +14,27 @@ class SiteFilter(django_filters.FilterSet):
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = ['q', 'name', 'facility', 'asn']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
qs_filter = Q(name__icontains=value) | Q(facility__icontains=value) | Q(physical_address__icontains=value) | \
|
||||
Q(shipping_address__icontains=value)
|
||||
Q(shipping_address__icontains=value) | Q(comments__icontains=value)
|
||||
try:
|
||||
qs_filter |= Q(asn=int(value))
|
||||
qs_filter |= Q(asn=int(value.strip()))
|
||||
except ValueError:
|
||||
pass
|
||||
return queryset.filter(qs_filter)
|
||||
@ -74,16 +85,27 @@ class RackFilter(django_filters.FilterSet):
|
||||
to_field_name='slug',
|
||||
label='Group',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = ['q', 'site_id', 'site', 'u_height']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(facility_id__icontains=value)
|
||||
Q(facility_id__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
||||
|
||||
@ -143,6 +165,17 @@ class DeviceFilter(django_filters.FilterSet):
|
||||
to_field_name='slug',
|
||||
label='Role (slug)',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
device_type_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='device_type',
|
||||
queryset=DeviceType.objects.all(),
|
||||
@ -200,11 +233,11 @@ class DeviceFilter(django_filters.FilterSet):
|
||||
'is_network_device']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(serial__icontains=value) |
|
||||
Q(modules__serial__icontains=value)
|
||||
Q(modules__serial__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
).distinct()
|
||||
|
||||
|
||||
|
201
netbox/dcim/fixtures/initial_data.json
Normal file
201
netbox/dcim/fixtures/initial_data.json
Normal file
@ -0,0 +1,201 @@
|
||||
[
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Console Server",
|
||||
"slug": "console-server",
|
||||
"color": "teal"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Core Switch",
|
||||
"slug": "core-switch",
|
||||
"color": "blue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Distribution Switch",
|
||||
"slug": "distribution-switch",
|
||||
"color": "blue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "Access Switch",
|
||||
"slug": "access-switch",
|
||||
"color": "blue"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Management Switch",
|
||||
"slug": "management-switch",
|
||||
"color": "orange"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "Firewall",
|
||||
"slug": "firewall",
|
||||
"color": "red"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "Router",
|
||||
"slug": "router",
|
||||
"color": "purple"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"name": "Server",
|
||||
"slug": "server",
|
||||
"color": "medium_gray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.devicerole",
|
||||
"pk": 9,
|
||||
"fields": {
|
||||
"name": "PDU",
|
||||
"slug": "pdu",
|
||||
"color": "dark_gray"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "APC",
|
||||
"slug": "apc"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Cisco",
|
||||
"slug": "cisco"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Dell",
|
||||
"slug": "dell"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "HP",
|
||||
"slug": "hp"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Juniper",
|
||||
"slug": "juniper"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "Arista",
|
||||
"slug": "arista"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 7,
|
||||
"fields": {
|
||||
"name": "Opengear",
|
||||
"slug": "opengear"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.manufacturer",
|
||||
"pk": 8,
|
||||
"fields": {
|
||||
"name": "Super Micro",
|
||||
"slug": "super-micro"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.platform",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Cisco IOS",
|
||||
"slug": "cisco-ios",
|
||||
"rpc_client": "cisco-ios"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.platform",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Cisco NX-OS",
|
||||
"slug": "cisco-nx-os",
|
||||
"rpc_client": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.platform",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Juniper Junos",
|
||||
"slug": "juniper-junos",
|
||||
"rpc_client": "juniper-junos"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.platform",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "Arista EOS",
|
||||
"slug": "arista-eos",
|
||||
"rpc_client": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.platform",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "Linux",
|
||||
"slug": "linux",
|
||||
"rpc_client": ""
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "dcim.platform",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "Opengear",
|
||||
"slug": "opengear",
|
||||
"rpc_client": "opengear"
|
||||
}
|
||||
}
|
||||
]
|
@ -4,6 +4,8 @@ from django import forms
|
||||
from django.db.models import Count, Q
|
||||
|
||||
from ipam.models import IPAddress
|
||||
from tenancy.forms import bulkedit_tenant_choices
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import (
|
||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, ExpandableNameField,
|
||||
FlexibleModelChoiceField, Livesearch, SelectWithDisabled, SmallTextarea, SlugField,
|
||||
@ -38,6 +40,15 @@ def get_device_by_name_or_pk(name):
|
||||
return device
|
||||
|
||||
|
||||
def bulkedit_platform_choices():
|
||||
choices = [
|
||||
(None, '---------'),
|
||||
(0, 'None'),
|
||||
]
|
||||
choices += [(p.pk, p.name) for p in Platform.objects.all()]
|
||||
return choices
|
||||
|
||||
|
||||
#
|
||||
# Sites
|
||||
#
|
||||
@ -48,7 +59,7 @@ class SiteForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = ['name', 'slug', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments']
|
||||
fields = ['name', 'slug', 'tenant', 'facility', 'asn', 'physical_address', 'shipping_address', 'comments']
|
||||
widgets = {
|
||||
'physical_address': SmallTextarea(attrs={'rows': 3}),
|
||||
'shipping_address': SmallTextarea(attrs={'rows': 3}),
|
||||
@ -63,16 +74,33 @@ class SiteForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
|
||||
class SiteFromCSVForm(forms.ModelForm):
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
|
||||
class Meta:
|
||||
model = Site
|
||||
fields = ['name', 'slug', 'facility', 'asn']
|
||||
fields = ['name', 'slug', 'tenant', 'facility', 'asn']
|
||||
|
||||
|
||||
class SiteImportForm(BulkImportForm, BootstrapMixin):
|
||||
csv = CSVDataField(csv_form=SiteFromCSVForm)
|
||||
|
||||
|
||||
class SiteBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Site.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
|
||||
|
||||
def site_tenant_choices():
|
||||
tenant_choices = Tenant.objects.annotate(site_count=Count('sites'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.site_count)) for t in tenant_choices]
|
||||
|
||||
|
||||
class SiteFilterForm(forms.Form, BootstrapMixin):
|
||||
tenant = forms.MultipleChoiceField(required=False, choices=site_tenant_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
|
||||
|
||||
#
|
||||
# Rack groups
|
||||
#
|
||||
@ -107,7 +135,7 @@ class RackForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = ['site', 'group', 'name', 'facility_id', 'u_height', 'comments']
|
||||
fields = ['site', 'group', 'name', 'facility_id', 'tenant', 'u_height', 'comments']
|
||||
help_texts = {
|
||||
'site': "The site at which the rack exists",
|
||||
'name': "Organizational rack name",
|
||||
@ -135,10 +163,12 @@ class RackFromCSVForm(forms.ModelForm):
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Site not found.'})
|
||||
group_name = forms.CharField(required=False)
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
|
||||
class Meta:
|
||||
model = Rack
|
||||
fields = ['site', 'group_name', 'name', 'facility_id', 'u_height']
|
||||
fields = ['site', 'group_name', 'name', 'facility_id', 'tenant', 'u_height']
|
||||
|
||||
def clean(self):
|
||||
|
||||
@ -161,6 +191,7 @@ class RackBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Rack.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||
group = forms.ModelChoiceField(queryset=RackGroup.objects.all(), required=False)
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
u_height = forms.IntegerField(required=False, label='Height (U)')
|
||||
comments = CommentField()
|
||||
|
||||
@ -175,11 +206,18 @@ def rack_group_choices():
|
||||
return [(g.pk, u'{} ({})'.format(g, g.rack_count)) for g in group_choices]
|
||||
|
||||
|
||||
def rack_tenant_choices():
|
||||
tenant_choices = Tenant.objects.annotate(rack_count=Count('racks'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.rack_count)) for t in tenant_choices]
|
||||
|
||||
|
||||
class RackFilterForm(forms.Form, BootstrapMixin):
|
||||
site = forms.MultipleChoiceField(required=False, choices=rack_site_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
group_id = forms.MultipleChoiceField(required=False, choices=rack_group_choices, label='Rack Group',
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
tenant = forms.MultipleChoiceField(required=False, choices=rack_tenant_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
|
||||
|
||||
#
|
||||
@ -203,8 +241,8 @@ class DeviceTypeForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = DeviceType
|
||||
fields = ['manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server', 'is_pdu',
|
||||
'is_network_device', 'subdevice_role']
|
||||
fields = ['manufacturer', 'model', 'slug', 'part_number', 'u_height', 'is_full_depth', 'is_console_server',
|
||||
'is_pdu', 'is_network_device', 'subdevice_role']
|
||||
|
||||
|
||||
class DeviceTypeBulkEditForm(forms.Form, BootstrapMixin):
|
||||
@ -324,7 +362,7 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = Device
|
||||
fields = ['name', 'device_role', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
|
||||
fields = ['name', 'device_role', 'tenant', 'device_type', 'serial', 'site', 'rack', 'position', 'face', 'status',
|
||||
'platform', 'primary_ip4', 'primary_ip6', 'comments']
|
||||
help_texts = {
|
||||
'device_role': "The function this device serves",
|
||||
@ -410,6 +448,8 @@ class DeviceForm(forms.ModelForm, BootstrapMixin):
|
||||
class BaseDeviceFromCSVForm(forms.ModelForm):
|
||||
device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Invalid device role.'})
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
manufacturer = forms.ModelChoiceField(queryset=Manufacturer.objects.all(), to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Invalid manufacturer.'})
|
||||
model_name = forms.CharField()
|
||||
@ -441,8 +481,8 @@ class DeviceFromCSVForm(BaseDeviceFromCSVForm):
|
||||
face = forms.CharField(required=False)
|
||||
|
||||
class Meta(BaseDeviceFromCSVForm.Meta):
|
||||
fields = ['name', 'device_role', 'manufacturer', 'model_name', 'platform', 'serial', 'site', 'rack_name',
|
||||
'position', 'face']
|
||||
fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'site',
|
||||
'rack_name', 'position', 'face']
|
||||
|
||||
def clean(self):
|
||||
|
||||
@ -477,7 +517,7 @@ class ChildDeviceFromCSVForm(BaseDeviceFromCSVForm):
|
||||
device_bay_name = forms.CharField(required=False)
|
||||
|
||||
class Meta(BaseDeviceFromCSVForm.Meta):
|
||||
fields = ['name', 'device_role', 'manufacturer', 'model_name', 'platform', 'serial', 'parent',
|
||||
fields = ['name', 'device_role', 'tenant', 'manufacturer', 'model_name', 'platform', 'serial', 'parent',
|
||||
'device_bay_name']
|
||||
|
||||
def clean(self):
|
||||
@ -512,8 +552,9 @@ class DeviceBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Device.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
device_type = forms.ModelChoiceField(queryset=DeviceType.objects.all(), required=False, label='Type')
|
||||
device_role = forms.ModelChoiceField(queryset=DeviceRole.objects.all(), required=False, label='Role')
|
||||
platform = forms.ModelChoiceField(queryset=Platform.objects.all(), required=False, label='Platform')
|
||||
platform_delete = forms.BooleanField(required=False, label='Set platform to "none"')
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
platform = forms.TypedChoiceField(choices=bulkedit_platform_choices, coerce=int, required=False,
|
||||
label='Platform')
|
||||
status = forms.ChoiceField(choices=FORM_STATUS_CHOICES, required=False, initial='', label='Status')
|
||||
serial = forms.CharField(max_length=50, required=False, label='Serial Number')
|
||||
|
||||
@ -533,6 +574,11 @@ def device_role_choices():
|
||||
return [(r.slug, u'{} ({})'.format(r.name, r.device_count)) for r in role_choices]
|
||||
|
||||
|
||||
def device_tenant_choices():
|
||||
tenant_choices = Tenant.objects.annotate(device_count=Count('devices'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.device_count)) for t in tenant_choices]
|
||||
|
||||
|
||||
def device_type_choices():
|
||||
type_choices = DeviceType.objects.select_related('manufacturer').annotate(device_count=Count('instances'))
|
||||
return [(t.pk, u'{} ({})'.format(t, t.device_count)) for t in type_choices]
|
||||
@ -550,6 +596,8 @@ class DeviceFilterForm(forms.Form, BootstrapMixin):
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
role = forms.MultipleChoiceField(required=False, choices=device_role_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
tenant = forms.MultipleChoiceField(required=False, choices=device_tenant_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
device_type_id = forms.MultipleChoiceField(required=False, choices=device_type_choices, label='Type',
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
platform = forms.MultipleChoiceField(required=False, choices=device_platform_choices)
|
||||
|
32
netbox/dcim/migrations/0012_site_rack_device_add_tenant.py
Normal file
32
netbox/dcim/migrations/0012_site_rack_device_add_tenant.py
Normal file
@ -0,0 +1,32 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-26 21:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
('dcim', '0011_devicetype_part_number'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='device',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='devices', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='rack',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='racks', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='site',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='sites', to='tenancy.Tenant'),
|
||||
),
|
||||
]
|
@ -8,6 +8,7 @@ from django.db import models
|
||||
from django.db.models import Count, Q, ObjectDoesNotExist
|
||||
|
||||
from extras.rpc import RPC_CLIENTS
|
||||
from tenancy.models import Tenant
|
||||
from utilities.fields import NullableCharField
|
||||
from utilities.managers import NaturalOrderByManager
|
||||
from utilities.models import CreatedUpdatedModel
|
||||
@ -152,6 +153,7 @@ class Site(CreatedUpdatedModel):
|
||||
"""
|
||||
name = models.CharField(max_length=50, unique=True)
|
||||
slug = models.SlugField(unique=True)
|
||||
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='sites', on_delete=models.PROTECT)
|
||||
facility = models.CharField(max_length=50, blank=True)
|
||||
asn = ASNField(blank=True, null=True, verbose_name='ASN')
|
||||
physical_address = models.CharField(max_length=200, blank=True)
|
||||
@ -173,6 +175,7 @@ class Site(CreatedUpdatedModel):
|
||||
return ','.join([
|
||||
self.name,
|
||||
self.slug,
|
||||
self.tenant.name if self.tenant else '',
|
||||
self.facility,
|
||||
str(self.asn),
|
||||
])
|
||||
@ -237,6 +240,7 @@ class Rack(CreatedUpdatedModel):
|
||||
facility_id = NullableCharField(max_length=30, blank=True, null=True, verbose_name='Facility ID')
|
||||
site = models.ForeignKey('Site', related_name='racks', on_delete=models.PROTECT)
|
||||
group = models.ForeignKey('RackGroup', related_name='racks', blank=True, null=True, on_delete=models.SET_NULL)
|
||||
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='racks', on_delete=models.PROTECT)
|
||||
u_height = models.PositiveSmallIntegerField(default=42, verbose_name='Height (U)')
|
||||
comments = models.TextField(blank=True)
|
||||
|
||||
@ -272,6 +276,7 @@ class Rack(CreatedUpdatedModel):
|
||||
self.group.name if self.group else '',
|
||||
self.name,
|
||||
self.facility_id or '',
|
||||
self.tenant.name if self.tenant else '',
|
||||
str(self.u_height),
|
||||
])
|
||||
|
||||
@ -631,6 +636,7 @@ class Device(CreatedUpdatedModel):
|
||||
"""
|
||||
device_type = models.ForeignKey('DeviceType', related_name='instances', on_delete=models.PROTECT)
|
||||
device_role = models.ForeignKey('DeviceRole', related_name='devices', on_delete=models.PROTECT)
|
||||
tenant = models.ForeignKey(Tenant, blank=True, null=True, related_name='devices', on_delete=models.PROTECT)
|
||||
platform = models.ForeignKey('Platform', related_name='devices', blank=True, null=True, on_delete=models.SET_NULL)
|
||||
name = NullableCharField(max_length=50, blank=True, null=True, unique=True)
|
||||
serial = models.CharField(max_length=50, blank=True, verbose_name='Serial number')
|
||||
@ -724,6 +730,7 @@ class Device(CreatedUpdatedModel):
|
||||
return ','.join([
|
||||
self.name or '',
|
||||
self.device_role.name,
|
||||
self.tenant.name if self.tenant else '',
|
||||
self.device_type.manufacturer.name,
|
||||
self.device_type.model,
|
||||
self.platform.name if self.platform else '',
|
||||
|
@ -16,27 +16,27 @@ DEVICE_LINK = """
|
||||
</a>
|
||||
"""
|
||||
|
||||
RACKGROUP_EDIT_LINK = """
|
||||
RACKGROUP_ACTIONS = """
|
||||
{% if perms.dcim.change_rackgroup %}
|
||||
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}">Edit</a>
|
||||
<a href="{% url 'dcim:rackgroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
DEVICEROLE_EDIT_LINK = """
|
||||
DEVICEROLE_ACTIONS = """
|
||||
{% if perms.dcim.change_devicerole %}
|
||||
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}">Edit</a>
|
||||
<a href="{% url 'dcim:devicerole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
MANUFACTURER_EDIT_LINK = """
|
||||
MANUFACTURER_ACTIONS = """
|
||||
{% if perms.dcim.change_manufacturer %}
|
||||
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}">Edit</a>
|
||||
<a href="{% url 'dcim:manufacturer_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
PLATFORM_EDIT_LINK = """
|
||||
PLATFORM_ACTIONS = """
|
||||
{% if perms.dcim.change_platform %}
|
||||
<a href="{% url 'dcim:platform_edit' slug=record.slug %}">Edit</a>
|
||||
<a href="{% url 'dcim:platform_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -59,8 +59,10 @@ UTILIZATION_GRAPH = """
|
||||
#
|
||||
|
||||
class SiteTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('dcim:site', args=[Accessor('slug')], verbose_name='Name')
|
||||
facility = tables.Column(verbose_name='Facility')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
asn = tables.Column(verbose_name='ASN')
|
||||
rack_count = tables.Column(accessor=Accessor('count_racks'), orderable=False, verbose_name='Racks')
|
||||
device_count = tables.Column(accessor=Accessor('count_devices'), orderable=False, verbose_name='Devices')
|
||||
@ -70,8 +72,8 @@ class SiteTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Site
|
||||
fields = ('name', 'facility', 'asn', 'rack_count', 'device_count', 'prefix_count', 'vlan_count',
|
||||
'circuit_count')
|
||||
fields = ('pk', 'name', 'facility', 'tenant', 'asn', 'rack_count', 'device_count', 'prefix_count',
|
||||
'vlan_count', 'circuit_count')
|
||||
|
||||
|
||||
#
|
||||
@ -84,11 +86,12 @@ class RackGroupTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
rack_count = tables.Column(verbose_name='Racks')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=RACKGROUP_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=RACKGROUP_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = RackGroup
|
||||
fields = ('pk', 'name', 'site', 'rack_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'site', 'rack_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -101,14 +104,16 @@ class RackTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
facility_id = tables.Column(verbose_name='Facility ID')
|
||||
u_height = tables.Column(verbose_name='Height (U)')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
u_height = tables.TemplateColumn("{{ record.u_height }}U", verbose_name='Height')
|
||||
devices = tables.Column(accessor=Accessor('device_count'), verbose_name='Devices')
|
||||
u_consumed = tables.Column(accessor=Accessor('u_consumed'), verbose_name='Used (U)')
|
||||
u_consumed = tables.TemplateColumn("{{ record.u_consumed|default:'0' }}U", verbose_name='Used')
|
||||
utilization = tables.TemplateColumn(UTILIZATION_GRAPH, orderable=False, verbose_name='Utilization')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Rack
|
||||
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'u_height', 'devices', 'u_consumed', 'utilization')
|
||||
fields = ('pk', 'name', 'site', 'group', 'facility_id', 'tenant', 'u_height', 'devices', 'u_consumed',
|
||||
'utilization')
|
||||
|
||||
|
||||
class RackImportTable(BaseTable):
|
||||
@ -116,11 +121,12 @@ class RackImportTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
facility_id = tables.Column(verbose_name='Facility ID')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
u_height = tables.Column(verbose_name='Height (U)')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Rack
|
||||
fields = ('site', 'group', 'name', 'facility_id', 'u_height')
|
||||
fields = ('site', 'group', 'name', 'facility_id', 'tenant', 'u_height')
|
||||
|
||||
|
||||
#
|
||||
@ -132,11 +138,12 @@ class ManufacturerTable(BaseTable):
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
devicetype_count = tables.Column(verbose_name='Device Types')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=MANUFACTURER_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=MANUFACTURER_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Manufacturer
|
||||
fields = ('pk', 'name', 'devicetype_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'devicetype_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -228,11 +235,12 @@ class DeviceRoleTable(BaseTable):
|
||||
device_count = tables.Column(verbose_name='Devices')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
color = tables.Column(verbose_name='Color')
|
||||
edit = tables.TemplateColumn(template_code=DEVICEROLE_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=DEVICEROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = DeviceRole
|
||||
fields = ('pk', 'name', 'device_count', 'slug', 'color')
|
||||
fields = ('pk', 'name', 'device_count', 'slug', 'color', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -244,11 +252,11 @@ class PlatformTable(BaseTable):
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
device_count = tables.Column(verbose_name='Devices')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=PLATFORM_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=PLATFORM_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Platform
|
||||
fields = ('pk', 'name', 'device_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'device_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -259,6 +267,7 @@ class DeviceTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
status = tables.TemplateColumn(template_code=STATUS_ICON, verbose_name='')
|
||||
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
|
||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
|
||||
device_role = tables.Column(verbose_name='Role')
|
||||
@ -268,11 +277,12 @@ class DeviceTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Device
|
||||
fields = ('pk', 'name', 'status', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
|
||||
fields = ('pk', 'name', 'status', 'tenant', 'site', 'rack', 'device_role', 'device_type', 'primary_ip')
|
||||
|
||||
|
||||
class DeviceImportTable(BaseTable):
|
||||
name = tables.TemplateColumn(template_code=DEVICE_LINK, verbose_name='Name')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
site = tables.Column(accessor=Accessor('rack.site'), verbose_name='Site')
|
||||
rack = tables.LinkColumn('dcim:rack', args=[Accessor('rack.pk')], verbose_name='Rack')
|
||||
position = tables.Column(verbose_name='Position')
|
||||
@ -281,7 +291,7 @@ class DeviceImportTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Device
|
||||
fields = ('name', 'site', 'rack', 'position', 'device_role', 'device_type')
|
||||
fields = ('name', 'tenant', 'site', 'rack', 'position', 'device_role', 'device_type')
|
||||
empty_text = False
|
||||
|
||||
|
||||
|
@ -15,6 +15,7 @@ class SiteTest(APITestCase):
|
||||
'id',
|
||||
'name',
|
||||
'slug',
|
||||
'tenant',
|
||||
'facility',
|
||||
'asn',
|
||||
'physical_address',
|
||||
@ -40,6 +41,7 @@ class SiteTest(APITestCase):
|
||||
'display_name',
|
||||
'site',
|
||||
'group',
|
||||
'tenant',
|
||||
'u_height',
|
||||
'comments'
|
||||
]
|
||||
@ -115,6 +117,7 @@ class RackTest(APITestCase):
|
||||
'display_name',
|
||||
'site',
|
||||
'group',
|
||||
'tenant',
|
||||
'u_height',
|
||||
'comments'
|
||||
]
|
||||
@ -126,6 +129,7 @@ class RackTest(APITestCase):
|
||||
'display_name',
|
||||
'site',
|
||||
'group',
|
||||
'tenant',
|
||||
'u_height',
|
||||
'comments',
|
||||
'front_units',
|
||||
@ -311,6 +315,7 @@ class DeviceTest(APITestCase):
|
||||
'display_name',
|
||||
'device_type',
|
||||
'device_role',
|
||||
'tenant',
|
||||
'platform',
|
||||
'serial',
|
||||
'rack',
|
||||
@ -388,6 +393,7 @@ class DeviceTest(APITestCase):
|
||||
'rack_name',
|
||||
'serial',
|
||||
'status',
|
||||
'tenant',
|
||||
]
|
||||
|
||||
response = self.client.get(endpoint)
|
||||
|
@ -15,6 +15,7 @@ urlpatterns = [
|
||||
url(r'^sites/$', views.SiteListView.as_view(), name='site_list'),
|
||||
url(r'^sites/add/$', views.SiteEditView.as_view(), name='site_add'),
|
||||
url(r'^sites/import/$', views.SiteBulkImportView.as_view(), name='site_import'),
|
||||
url(r'^sites/edit/$', views.SiteBulkEditView.as_view(), name='site_bulk_edit'),
|
||||
url(r'^sites/(?P<slug>[\w-]+)/$', views.site, name='site'),
|
||||
url(r'^sites/(?P<slug>[\w-]+)/edit/$', views.SiteEditView.as_view(), name='site_edit'),
|
||||
url(r'^sites/(?P<slug>[\w-]+)/delete/$', views.SiteDeleteView.as_view(), name='site_delete'),
|
||||
|
@ -15,7 +15,7 @@ from django.views.generic import View
|
||||
|
||||
from ipam.models import Prefix, IPAddress, VLAN
|
||||
from circuits.models import Circuit
|
||||
from extras.models import TopologyMap
|
||||
from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
@ -61,9 +61,11 @@ def expand_pattern(string):
|
||||
#
|
||||
|
||||
class SiteListView(ObjectListView):
|
||||
queryset = Site.objects.all()
|
||||
queryset = Site.objects.select_related('tenant')
|
||||
filter = filters.SiteFilter
|
||||
filter_form = forms.SiteFilterForm
|
||||
table = tables.SiteTable
|
||||
edit_permissions = ['dcim.change_rack', 'dcim.delete_rack']
|
||||
template_name = 'dcim/site_list.html'
|
||||
|
||||
|
||||
@ -79,12 +81,14 @@ def site(request, slug):
|
||||
}
|
||||
rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
|
||||
topology_maps = TopologyMap.objects.filter(site=site)
|
||||
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_SITE).exists()
|
||||
|
||||
return render(request, 'dcim/site.html', {
|
||||
'site': site,
|
||||
'stats': stats,
|
||||
'rack_groups': rack_groups,
|
||||
'topology_maps': topology_maps,
|
||||
'show_graphs': show_graphs,
|
||||
})
|
||||
|
||||
|
||||
@ -110,6 +114,24 @@ class SiteBulkImportView(PermissionRequiredMixin, BulkImportView):
|
||||
obj_list_url = 'dcim:site_list'
|
||||
|
||||
|
||||
class SiteBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
permission_required = 'dcim.change_site'
|
||||
cls = Site
|
||||
form = forms.SiteBulkEditForm
|
||||
template_name = 'dcim/site_bulk_edit.html'
|
||||
default_redirect_url = 'dcim:site_list'
|
||||
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['tenant'] == 0:
|
||||
fields_to_update['tenant'] = None
|
||||
elif form.cleaned_data['tenant']:
|
||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
||||
|
||||
return self.cls.objects.filter(pk__in=pk_list).update(**fields_to_update)
|
||||
|
||||
|
||||
#
|
||||
# Rack groups
|
||||
#
|
||||
@ -141,7 +163,8 @@ class RackGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
#
|
||||
|
||||
class RackListView(ObjectListView):
|
||||
queryset = Rack.objects.select_related('site').prefetch_related('devices__device_type').annotate(device_count=Count('devices', distinct=True), u_consumed=Sum('devices__device_type__u_height'))
|
||||
queryset = Rack.objects.select_related('site').prefetch_related('devices__device_type')\
|
||||
.annotate(device_count=Count('devices', distinct=True), u_consumed=Sum('devices__device_type__u_height'))
|
||||
filter = filters.RackFilter
|
||||
filter_form = forms.RackFilterForm
|
||||
table = tables.RackTable
|
||||
@ -153,7 +176,7 @@ def rack(request, pk):
|
||||
|
||||
rack = get_object_or_404(Rack, pk=pk)
|
||||
|
||||
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True)\
|
||||
nonracked_devices = Device.objects.filter(rack=rack, position__isnull=True, parent_bay__isnull=True)\
|
||||
.select_related('device_type__manufacturer')
|
||||
next_rack = Rack.objects.filter(site=rack.site, name__gt=rack.name).order_by('name').first()
|
||||
prev_rack = Rack.objects.filter(site=rack.site, name__lt=rack.name).order_by('-name').first()
|
||||
@ -200,7 +223,11 @@ class RackBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
for field in ['site', 'group', 'u_height', 'comments']:
|
||||
if form.cleaned_data['tenant'] == 0:
|
||||
fields_to_update['tenant'] = None
|
||||
elif form.cleaned_data['tenant']:
|
||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
||||
for field in ['site', 'group', 'tenant', 'u_height', 'comments']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
|
||||
@ -560,6 +587,9 @@ def device(request, pk):
|
||||
related_devices = Device.objects.filter(name__istartswith=base_name).exclude(pk=device.pk)\
|
||||
.select_related('rack', 'device_type__manufacturer')[:10]
|
||||
|
||||
# Show graph button on interfaces only if at least one graph has been created.
|
||||
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_INTERFACE).exists()
|
||||
|
||||
return render(request, 'dcim/device.html', {
|
||||
'device': device,
|
||||
'console_ports': console_ports,
|
||||
@ -572,6 +602,7 @@ def device(request, pk):
|
||||
'ip_addresses': ip_addresses,
|
||||
'secrets': secrets,
|
||||
'related_devices': related_devices,
|
||||
'show_graphs': show_graphs,
|
||||
})
|
||||
|
||||
|
||||
@ -625,14 +656,15 @@ class DeviceBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['platform']:
|
||||
fields_to_update['platform'] = form.cleaned_data['platform']
|
||||
elif form.cleaned_data['platform_delete']:
|
||||
fields_to_update['platform'] = None
|
||||
for field in ['tenant', 'platform']:
|
||||
if form.cleaned_data[field] == 0:
|
||||
fields_to_update[field] = None
|
||||
elif form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
if form.cleaned_data['status']:
|
||||
status = form.cleaned_data['status']
|
||||
fields_to_update['status'] = True if status == 'True' else False
|
||||
for field in ['device_type', 'device_role', 'serial']:
|
||||
for field in ['tenant', 'device_type', 'device_role', 'serial']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
|
||||
|
@ -7,7 +7,12 @@ from .models import (
|
||||
|
||||
@admin.register(VRF)
|
||||
class VRFAdmin(admin.ModelAdmin):
|
||||
list_display = ['name', 'rd']
|
||||
list_display = ['name', 'rd', 'tenant', 'enforce_unique']
|
||||
list_filter = ['tenant']
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(VRFAdmin, self).get_queryset(request)
|
||||
return qs.select_related('tenant')
|
||||
|
||||
|
||||
@admin.register(Role)
|
||||
@ -35,7 +40,7 @@ class AggregateAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(Prefix)
|
||||
class PrefixAdmin(admin.ModelAdmin):
|
||||
list_display = ['prefix', 'vrf', 'site', 'status', 'role', 'vlan']
|
||||
list_display = ['prefix', 'vrf', 'tenant', 'site', 'status', 'role', 'vlan']
|
||||
list_filter = ['family', 'site', 'status', 'role']
|
||||
search_fields = ['prefix']
|
||||
|
||||
@ -46,7 +51,7 @@ class PrefixAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(IPAddress)
|
||||
class IPAddressAdmin(admin.ModelAdmin):
|
||||
list_display = ['address', 'vrf', 'nat_inside']
|
||||
list_display = ['address', 'vrf', 'tenant', 'nat_inside']
|
||||
list_filter = ['family']
|
||||
fields = ['address', 'vrf', 'device', 'interface', 'nat_inside']
|
||||
readonly_fields = ['interface', 'device', 'nat_inside']
|
||||
@ -67,10 +72,10 @@ class VLANGroupAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(VLAN)
|
||||
class VLANAdmin(admin.ModelAdmin):
|
||||
list_display = ['site', 'vid', 'name', 'status', 'role']
|
||||
list_filter = ['site', 'status', 'role']
|
||||
list_display = ['site', 'vid', 'name', 'tenant', 'status', 'role']
|
||||
list_filter = ['site', 'tenant', 'status', 'role']
|
||||
search_fields = ['vid', 'name']
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(VLANAdmin, self).get_queryset(request)
|
||||
return qs.select_related('site', 'role')
|
||||
return qs.select_related('site', 'tenant', 'role')
|
||||
|
@ -2,6 +2,7 @@ from rest_framework import serializers
|
||||
|
||||
from dcim.api.serializers import SiteNestedSerializer, InterfaceNestedSerializer
|
||||
from ipam.models import VRF, Role, RIR, Aggregate, Prefix, IPAddress, VLAN, VLANGroup
|
||||
from tenancy.api.serializers import TenantNestedSerializer
|
||||
|
||||
|
||||
#
|
||||
@ -9,10 +10,11 @@ from ipam.models import VRF, Role, RIR, Aggregate, Prefix, IPAddress, VLAN, VLAN
|
||||
#
|
||||
|
||||
class VRFSerializer(serializers.ModelSerializer):
|
||||
tenant = TenantNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = ['id', 'name', 'rd', 'enforce_unique', 'description']
|
||||
fields = ['id', 'name', 'rd', 'tenant', 'enforce_unique', 'description']
|
||||
|
||||
|
||||
class VRFNestedSerializer(VRFSerializer):
|
||||
@ -21,6 +23,15 @@ class VRFNestedSerializer(VRFSerializer):
|
||||
fields = ['id', 'name', 'rd']
|
||||
|
||||
|
||||
class VRFTenantSerializer(VRFSerializer):
|
||||
"""
|
||||
Include tenant serializer. Useful for determining tenant inheritance for Prefixes and IPAddresses.
|
||||
"""
|
||||
|
||||
class Meta(VRFSerializer.Meta):
|
||||
fields = ['id', 'name', 'rd', 'tenant']
|
||||
|
||||
|
||||
#
|
||||
# Roles
|
||||
#
|
||||
@ -98,11 +109,12 @@ class VLANGroupNestedSerializer(VLANGroupSerializer):
|
||||
class VLANSerializer(serializers.ModelSerializer):
|
||||
site = SiteNestedSerializer()
|
||||
group = VLANGroupNestedSerializer()
|
||||
tenant = TenantNestedSerializer()
|
||||
role = RoleNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = ['id', 'site', 'group', 'vid', 'name', 'status', 'role', 'description', 'display_name']
|
||||
fields = ['id', 'site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description', 'display_name']
|
||||
|
||||
|
||||
class VLANNestedSerializer(VLANSerializer):
|
||||
@ -117,13 +129,14 @@ class VLANNestedSerializer(VLANSerializer):
|
||||
|
||||
class PrefixSerializer(serializers.ModelSerializer):
|
||||
site = SiteNestedSerializer()
|
||||
vrf = VRFNestedSerializer()
|
||||
vrf = VRFTenantSerializer()
|
||||
tenant = TenantNestedSerializer()
|
||||
vlan = VLANNestedSerializer()
|
||||
role = RoleNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = Prefix
|
||||
fields = ['id', 'family', 'prefix', 'site', 'vrf', 'vlan', 'status', 'role', 'description']
|
||||
fields = ['id', 'family', 'prefix', 'site', 'vrf', 'tenant', 'vlan', 'status', 'role', 'description']
|
||||
|
||||
|
||||
class PrefixNestedSerializer(PrefixSerializer):
|
||||
@ -137,12 +150,13 @@ class PrefixNestedSerializer(PrefixSerializer):
|
||||
#
|
||||
|
||||
class IPAddressSerializer(serializers.ModelSerializer):
|
||||
vrf = VRFNestedSerializer()
|
||||
vrf = VRFTenantSerializer()
|
||||
tenant = TenantNestedSerializer()
|
||||
interface = InterfaceNestedSerializer()
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['id', 'family', 'address', 'vrf', 'interface', 'description', 'nat_inside', 'nat_outside']
|
||||
fields = ['id', 'family', 'address', 'vrf', 'tenant', 'interface', 'description', 'nat_inside', 'nat_outside']
|
||||
|
||||
|
||||
class IPAddressNestedSerializer(IPAddressSerializer):
|
||||
|
@ -14,7 +14,7 @@ class VRFListView(generics.ListAPIView):
|
||||
"""
|
||||
List all VRFs
|
||||
"""
|
||||
queryset = VRF.objects.all()
|
||||
queryset = VRF.objects.select_related('tenant')
|
||||
serializer_class = serializers.VRFSerializer
|
||||
filter_class = filters.VRFFilter
|
||||
|
||||
@ -23,7 +23,7 @@ class VRFDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single VRF
|
||||
"""
|
||||
queryset = VRF.objects.all()
|
||||
queryset = VRF.objects.select_related('tenant')
|
||||
serializer_class = serializers.VRFSerializer
|
||||
|
||||
|
||||
@ -96,7 +96,7 @@ class PrefixListView(generics.ListAPIView):
|
||||
"""
|
||||
List prefixes (filterable)
|
||||
"""
|
||||
queryset = Prefix.objects.select_related('site', 'vrf', 'vlan', 'role')
|
||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||
serializer_class = serializers.PrefixSerializer
|
||||
filter_class = filters.PrefixFilter
|
||||
|
||||
@ -105,7 +105,7 @@ class PrefixDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single prefix
|
||||
"""
|
||||
queryset = Prefix.objects.select_related('site', 'vrf', 'vlan', 'role')
|
||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role')
|
||||
serializer_class = serializers.PrefixSerializer
|
||||
|
||||
|
||||
@ -117,7 +117,7 @@ class IPAddressListView(generics.ListAPIView):
|
||||
"""
|
||||
List IP addresses (filterable)
|
||||
"""
|
||||
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'nat_inside')\
|
||||
queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside')\
|
||||
.prefetch_related('nat_outside')
|
||||
serializer_class = serializers.IPAddressSerializer
|
||||
filter_class = filters.IPAddressFilter
|
||||
@ -127,7 +127,7 @@ class IPAddressDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single IP address
|
||||
"""
|
||||
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'nat_inside')\
|
||||
queryset = IPAddress.objects.select_related('vrf__tenant', 'tenant', 'interface__device', 'nat_inside')\
|
||||
.prefetch_related('nat_outside')
|
||||
serializer_class = serializers.IPAddressSerializer
|
||||
|
||||
@ -140,7 +140,7 @@ class VLANGroupListView(generics.ListAPIView):
|
||||
"""
|
||||
List all VLAN groups
|
||||
"""
|
||||
queryset = VLANGroup.objects.all()
|
||||
queryset = VLANGroup.objects.select_related('site')
|
||||
serializer_class = serializers.VLANGroupSerializer
|
||||
filter_class = filters.VLANGroupFilter
|
||||
|
||||
@ -149,7 +149,7 @@ class VLANGroupDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single VLAN group
|
||||
"""
|
||||
queryset = VLANGroup.objects.all()
|
||||
queryset = VLANGroup.objects.select_related('site')
|
||||
serializer_class = serializers.VLANGroupSerializer
|
||||
|
||||
|
||||
@ -161,7 +161,7 @@ class VLANListView(generics.ListAPIView):
|
||||
"""
|
||||
List VLANs (filterable)
|
||||
"""
|
||||
queryset = VLAN.objects.select_related('site', 'role')
|
||||
queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
|
||||
serializer_class = serializers.VLANSerializer
|
||||
filter_class = filters.VLANFilter
|
||||
|
||||
@ -170,5 +170,5 @@ class VLANDetailView(generics.RetrieveAPIView):
|
||||
"""
|
||||
Retrieve a single VLAN
|
||||
"""
|
||||
queryset = VLAN.objects.select_related('site', 'role')
|
||||
queryset = VLAN.objects.select_related('site', 'group', 'tenant', 'role')
|
||||
serializer_class = serializers.VLANSerializer
|
||||
|
@ -2,17 +2,42 @@ import django_filters
|
||||
from netaddr import IPNetwork
|
||||
from netaddr.core import AddrFormatError
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from dcim.models import Site, Device, Interface
|
||||
from tenancy.models import Tenant
|
||||
|
||||
from .models import RIR, Aggregate, VRF, Prefix, IPAddress, VLAN, VLANGroup, Role
|
||||
|
||||
|
||||
class VRFFilter(django_filters.FilterSet):
|
||||
q = django_filters.MethodFilter(
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
name = django_filters.CharFilter(
|
||||
name='name',
|
||||
lookup_type='icontains',
|
||||
label='Name',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
|
||||
def search(self, queryset, value):
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(rd__icontains=value) |
|
||||
Q(description__icontains=value)
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = VRF
|
||||
@ -20,6 +45,10 @@ class VRFFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
class AggregateFilter(django_filters.FilterSet):
|
||||
q = django_filters.MethodFilter(
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
rir_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='rir',
|
||||
queryset=RIR.objects.all(),
|
||||
@ -36,6 +65,15 @@ class AggregateFilter(django_filters.FilterSet):
|
||||
model = Aggregate
|
||||
fields = ['family', 'rir_id', 'rir', 'date_added']
|
||||
|
||||
def search(self, queryset, value):
|
||||
qs_filter = Q(description__icontains=value)
|
||||
try:
|
||||
prefix = str(IPNetwork(value.strip()).cidr)
|
||||
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
||||
except AddrFormatError:
|
||||
pass
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
|
||||
class PrefixFilter(django_filters.FilterSet):
|
||||
q = django_filters.MethodFilter(
|
||||
@ -55,6 +93,14 @@ class PrefixFilter(django_filters.FilterSet):
|
||||
action='_vrf',
|
||||
label='VRF',
|
||||
)
|
||||
tenant_id = django_filters.MethodFilter(
|
||||
action='_tenant_id',
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.MethodFilter(
|
||||
action='_tenant',
|
||||
label='Tenant',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
@ -92,12 +138,13 @@ class PrefixFilter(django_filters.FilterSet):
|
||||
fields = ['family', 'site_id', 'site', 'vrf', 'vrf_id', 'vlan_id', 'vlan_vid', 'status', 'role_id', 'role']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
qs_filter = Q(description__icontains=value)
|
||||
try:
|
||||
query = str(IPNetwork(value).cidr)
|
||||
return queryset.filter(prefix__net_contains_or_equals=query)
|
||||
prefix = str(IPNetwork(value.strip()).cidr)
|
||||
qs_filter |= Q(prefix__net_contains_or_equals=prefix)
|
||||
except AddrFormatError:
|
||||
return queryset.none()
|
||||
pass
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
def search_by_parent(self, queryset, value):
|
||||
value = value.strip()
|
||||
@ -120,6 +167,24 @@ class PrefixFilter(django_filters.FilterSet):
|
||||
return queryset.filter(vrf__isnull=True)
|
||||
return queryset.filter(vrf__pk=value)
|
||||
|
||||
def _tenant(self, queryset, value):
|
||||
if str(value) == '':
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(tenant__slug=value) |
|
||||
Q(tenant__isnull=True, vrf__tenant__slug=value)
|
||||
)
|
||||
|
||||
def _tenant_id(self, queryset, value):
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
return queryset.none()
|
||||
return queryset.filter(
|
||||
Q(tenant__pk=value) |
|
||||
Q(tenant__isnull=True, vrf__tenant__pk=value)
|
||||
)
|
||||
|
||||
|
||||
class IPAddressFilter(django_filters.FilterSet):
|
||||
q = django_filters.MethodFilter(
|
||||
@ -135,6 +200,14 @@ class IPAddressFilter(django_filters.FilterSet):
|
||||
action='_vrf',
|
||||
label='VRF',
|
||||
)
|
||||
tenant_id = django_filters.MethodFilter(
|
||||
action='_tenant_id',
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.MethodFilter(
|
||||
action='_tenant',
|
||||
label='Tenant',
|
||||
)
|
||||
device_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='interface__device',
|
||||
queryset=Device.objects.all(),
|
||||
@ -157,12 +230,13 @@ class IPAddressFilter(django_filters.FilterSet):
|
||||
fields = ['q', 'family', 'vrf_id', 'vrf', 'device_id', 'device', 'interface_id']
|
||||
|
||||
def search(self, queryset, value):
|
||||
value = value.strip()
|
||||
qs_filter = Q(description__icontains=value)
|
||||
try:
|
||||
query = str(IPNetwork(value))
|
||||
return queryset.filter(address__net_host=query)
|
||||
ipaddress = str(IPNetwork(value.strip()))
|
||||
qs_filter |= Q(address__net_host=ipaddress)
|
||||
except AddrFormatError:
|
||||
return queryset.none()
|
||||
pass
|
||||
return queryset.filter(qs_filter)
|
||||
|
||||
def _vrf(self, queryset, value):
|
||||
if str(value) == '':
|
||||
@ -175,6 +249,24 @@ class IPAddressFilter(django_filters.FilterSet):
|
||||
return queryset.filter(vrf__isnull=True)
|
||||
return queryset.filter(vrf__pk=value)
|
||||
|
||||
def _tenant(self, queryset, value):
|
||||
if str(value) == '':
|
||||
return queryset
|
||||
return queryset.filter(
|
||||
Q(tenant__slug=value) |
|
||||
Q(tenant__isnull=True, vrf__tenant__slug=value)
|
||||
)
|
||||
|
||||
def _tenant_id(self, queryset, value):
|
||||
try:
|
||||
value = int(value)
|
||||
except ValueError:
|
||||
return queryset.none()
|
||||
return queryset.filter(
|
||||
Q(tenant__pk=value) |
|
||||
Q(tenant__isnull=True, vrf__tenant__pk=value)
|
||||
)
|
||||
|
||||
|
||||
class VLANGroupFilter(django_filters.FilterSet):
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
@ -195,6 +287,10 @@ class VLANGroupFilter(django_filters.FilterSet):
|
||||
|
||||
|
||||
class VLANFilter(django_filters.FilterSet):
|
||||
q = django_filters.MethodFilter(
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
@ -226,6 +322,17 @@ class VLANFilter(django_filters.FilterSet):
|
||||
name='vid',
|
||||
label='VLAN number (1-4095)',
|
||||
)
|
||||
tenant_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
label='Tenant (ID)',
|
||||
)
|
||||
tenant = django_filters.ModelMultipleChoiceFilter(
|
||||
name='tenant',
|
||||
queryset=Tenant.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='role',
|
||||
queryset=Role.objects.all(),
|
||||
@ -241,3 +348,11 @@ class VLANFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = ['site_id', 'site', 'vid', 'name', 'status', 'role_id', 'role']
|
||||
|
||||
def search(self, queryset, value):
|
||||
qs_filter = Q(name__icontains=value) | Q(description__icontains=value)
|
||||
try:
|
||||
qs_filter |= Q(vid=int(value))
|
||||
except ValueError:
|
||||
pass
|
||||
return queryset.filter(qs_filter)
|
||||
|
125
netbox/ipam/fixtures/initial_data.json
Normal file
125
netbox/ipam/fixtures/initial_data.json
Normal file
@ -0,0 +1,125 @@
|
||||
[
|
||||
{
|
||||
"model": "ipam.aggregate",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:20.938Z",
|
||||
"family": 4,
|
||||
"prefix": "10.0.0.0/8",
|
||||
"rir": 6,
|
||||
"date_added": null,
|
||||
"description": "Private IPv4 space"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.aggregate",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:32.679Z",
|
||||
"family": 4,
|
||||
"prefix": "172.16.0.0/12",
|
||||
"rir": 6,
|
||||
"date_added": null,
|
||||
"description": "Private IPv4 space"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.aggregate",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"created": "2016-08-01",
|
||||
"last_updated": "2016-08-01T15:22:42.289Z",
|
||||
"family": 4,
|
||||
"prefix": "192.168.0.0/16",
|
||||
"rir": 6,
|
||||
"date_added": null,
|
||||
"description": "Private IPv4 space"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "ARIN",
|
||||
"slug": "arin"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "RIPE",
|
||||
"slug": "ripe"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "APNIC",
|
||||
"slug": "apnic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "LACNIC",
|
||||
"slug": "lacnic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"name": "AFRINIC",
|
||||
"slug": "afrinic"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.rir",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"name": "RFC 1918",
|
||||
"slug": "rfc-1918"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.role",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Production",
|
||||
"slug": "production",
|
||||
"weight": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.role",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "Development",
|
||||
"slug": "development",
|
||||
"weight": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.role",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "Management",
|
||||
"slug": "management",
|
||||
"weight": 1000
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "ipam.role",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "Backup",
|
||||
"slug": "backup",
|
||||
"weight": 1000
|
||||
}
|
||||
}
|
||||
]
|
@ -4,6 +4,8 @@ from django import forms
|
||||
from django.db.models import Count
|
||||
|
||||
from dcim.models import Site, Device, Interface
|
||||
from tenancy.forms import bulkedit_tenant_choices
|
||||
from tenancy.models import Tenant
|
||||
from utilities.forms import BootstrapMixin, APISelect, Livesearch, CSVDataField, BulkImportForm, SlugField
|
||||
|
||||
from .models import (
|
||||
@ -15,6 +17,18 @@ FORM_PREFIX_STATUS_CHOICES = (('', '---------'),) + PREFIX_STATUS_CHOICES
|
||||
FORM_VLAN_STATUS_CHOICES = (('', '---------'),) + VLAN_STATUS_CHOICES
|
||||
|
||||
|
||||
def bulkedit_vrf_choices():
|
||||
"""
|
||||
Include an option to assign the object to the global table.
|
||||
"""
|
||||
choices = [
|
||||
(None, '---------'),
|
||||
(0, 'Global'),
|
||||
]
|
||||
choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
||||
return choices
|
||||
|
||||
|
||||
#
|
||||
# VRFs
|
||||
#
|
||||
@ -23,7 +37,7 @@ class VRFForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = ['name', 'rd', 'enforce_unique', 'description']
|
||||
fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
||||
labels = {
|
||||
'rd': "RD",
|
||||
}
|
||||
@ -33,10 +47,12 @@ class VRFForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
|
||||
class VRFFromCSVForm(forms.ModelForm):
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
|
||||
class Meta:
|
||||
model = VRF
|
||||
fields = ['name', 'rd', 'enforce_unique', 'description']
|
||||
fields = ['name', 'rd', 'tenant', 'enforce_unique', 'description']
|
||||
|
||||
|
||||
class VRFImportForm(BulkImportForm, BootstrapMixin):
|
||||
@ -45,9 +61,20 @@ class VRFImportForm(BulkImportForm, BootstrapMixin):
|
||||
|
||||
class VRFBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=VRF.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
description = forms.CharField(max_length=100, required=False)
|
||||
|
||||
|
||||
def vrf_tenant_choices():
|
||||
tenant_choices = Tenant.objects.annotate(vrf_count=Count('vrfs'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.vrf_count)) for t in tenant_choices]
|
||||
|
||||
|
||||
class VRFFilterForm(forms.Form, BootstrapMixin):
|
||||
tenant = forms.MultipleChoiceField(required=False, choices=vrf_tenant_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
|
||||
|
||||
#
|
||||
# RIRs
|
||||
#
|
||||
@ -131,7 +158,7 @@ class PrefixForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = Prefix
|
||||
fields = ['prefix', 'vrf', 'site', 'vlan', 'status', 'role', 'description']
|
||||
fields = ['prefix', 'vrf', 'tenant', 'site', 'vlan', 'status', 'role', 'description']
|
||||
help_texts = {
|
||||
'prefix': "IPv4 or IPv6 network",
|
||||
'vrf': "VRF (if applicable)",
|
||||
@ -172,6 +199,8 @@ class PrefixForm(forms.ModelForm, BootstrapMixin):
|
||||
class PrefixFromCSVForm(forms.ModelForm):
|
||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
||||
error_messages={'invalid_choice': 'VRF not found.'})
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Site not found.'})
|
||||
vlan_group_name = forms.CharField(required=False)
|
||||
@ -182,7 +211,8 @@ class PrefixFromCSVForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = Prefix
|
||||
fields = ['prefix', 'vrf', 'site', 'vlan_group_name', 'vlan_vid', 'status_name', 'role', 'description']
|
||||
fields = ['prefix', 'vrf', 'tenant', 'site', 'vlan_group_name', 'vlan_vid', 'status_name', 'role',
|
||||
'description']
|
||||
|
||||
def clean(self):
|
||||
|
||||
@ -228,18 +258,16 @@ class PrefixImportForm(BulkImportForm, BootstrapMixin):
|
||||
class PrefixBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=Prefix.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||
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")
|
||||
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
||||
vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
status = forms.ChoiceField(choices=FORM_PREFIX_STATUS_CHOICES, required=False)
|
||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||
description = forms.CharField(max_length=100, required=False)
|
||||
|
||||
|
||||
def prefix_vrf_choices():
|
||||
vrf_choices = [('', 'All'), (0, 'Global')]
|
||||
vrf_choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
||||
return vrf_choices
|
||||
vrf_choices = VRF.objects.annotate(prefix_count=Count('prefixes'))
|
||||
return [(v.pk, u'{} ({})'.format(v.name, v.prefix_count)) for v in vrf_choices]
|
||||
|
||||
|
||||
def prefix_site_choices():
|
||||
@ -261,12 +289,16 @@ def prefix_role_choices():
|
||||
|
||||
class PrefixFilterForm(forms.Form, BootstrapMixin):
|
||||
parent = forms.CharField(required=False, label='Search Within')
|
||||
vrf = forms.ChoiceField(required=False, choices=prefix_vrf_choices, label='VRF')
|
||||
status = forms.MultipleChoiceField(required=False, choices=prefix_status_choices)
|
||||
vrf = forms.MultipleChoiceField(required=False, choices=prefix_vrf_choices, label='VRF',
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
tenant = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), required=False,
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
status = forms.MultipleChoiceField(required=False, choices=prefix_status_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
site = forms.MultipleChoiceField(required=False, choices=prefix_site_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
role = forms.MultipleChoiceField(required=False, choices=prefix_role_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
expand = forms.BooleanField(required=False, label='Expand prefix hierarchy')
|
||||
|
||||
|
||||
@ -289,7 +321,7 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['address', 'vrf', 'nat_device', 'nat_inside', 'description']
|
||||
fields = ['address', 'vrf', 'tenant', 'nat_device', 'nat_inside', 'description']
|
||||
help_texts = {
|
||||
'address': "IPv4 or IPv6 address and mask",
|
||||
'vrf': "VRF (if applicable)",
|
||||
@ -338,6 +370,8 @@ class IPAddressForm(forms.ModelForm, BootstrapMixin):
|
||||
class IPAddressFromCSVForm(forms.ModelForm):
|
||||
vrf = forms.ModelChoiceField(queryset=VRF.objects.all(), required=False, to_field_name='rd',
|
||||
error_messages={'invalid_choice': 'VRF not found.'})
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Device not found.'})
|
||||
interface_name = forms.CharField(required=False)
|
||||
@ -345,7 +379,7 @@ class IPAddressFromCSVForm(forms.ModelForm):
|
||||
|
||||
class Meta:
|
||||
model = IPAddress
|
||||
fields = ['address', 'vrf', 'device', 'interface_name', 'is_primary', 'description']
|
||||
fields = ['address', 'vrf', 'tenant', 'device', 'interface_name', 'is_primary', 'description']
|
||||
|
||||
def clean(self):
|
||||
|
||||
@ -390,9 +424,8 @@ class IPAddressImportForm(BulkImportForm, BootstrapMixin):
|
||||
|
||||
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")
|
||||
vrf_global = forms.BooleanField(required=False, label='Set VRF to global')
|
||||
vrf = forms.TypedChoiceField(choices=bulkedit_vrf_choices, coerce=int, required=False, label='VRF')
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
description = forms.CharField(max_length=100, required=False)
|
||||
|
||||
|
||||
@ -401,14 +434,16 @@ def ipaddress_family_choices():
|
||||
|
||||
|
||||
def ipaddress_vrf_choices():
|
||||
vrf_choices = [('', 'All'), (0, 'Global')]
|
||||
vrf_choices += [(v.pk, v.name) for v in VRF.objects.all()]
|
||||
return vrf_choices
|
||||
vrf_choices = VRF.objects.annotate(ipaddress_count=Count('ip_addresses'))
|
||||
return [(v.pk, u'{} ({})'.format(v.name, v.ipaddress_count)) for v in vrf_choices]
|
||||
|
||||
|
||||
class IPAddressFilterForm(forms.Form, BootstrapMixin):
|
||||
family = forms.ChoiceField(required=False, choices=ipaddress_family_choices, label='Address Family')
|
||||
vrf = forms.ChoiceField(required=False, choices=ipaddress_vrf_choices, label='VRF')
|
||||
vrf = forms.MultipleChoiceField(required=False, choices=ipaddress_vrf_choices, label='VRF',
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
tenant = forms.ModelMultipleChoiceField(queryset=Tenant.objects.all(), required=False,
|
||||
widget=forms.SelectMultiple(attrs={'size': 6}))
|
||||
|
||||
|
||||
#
|
||||
@ -444,7 +479,7 @@ class VLANForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = ['site', 'group', 'vid', 'name', 'description', 'status', 'role']
|
||||
fields = ['site', 'group', 'vid', 'name', 'tenant', 'status', 'role', 'description']
|
||||
help_texts = {
|
||||
'site': "The site at which this VLAN exists",
|
||||
'group': "VLAN group (optional)",
|
||||
@ -475,13 +510,15 @@ class VLANFromCSVForm(forms.ModelForm):
|
||||
error_messages={'invalid_choice': 'Device not found.'})
|
||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'VLAN group not found.'})
|
||||
tenant = forms.ModelChoiceField(Tenant.objects.all(), to_field_name='name', required=False,
|
||||
error_messages={'invalid_choice': 'Tenant not found.'})
|
||||
status_name = forms.ChoiceField(choices=[(s[1], s[0]) for s in VLAN_STATUS_CHOICES])
|
||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False, to_field_name='name',
|
||||
error_messages={'invalid_choice': 'Invalid role.'})
|
||||
|
||||
class Meta:
|
||||
model = VLAN
|
||||
fields = ['site', 'group', 'vid', 'name', 'status_name', 'role', 'description']
|
||||
fields = ['site', 'group', 'vid', 'name', 'tenant', 'status_name', 'role', 'description']
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
m = super(VLANFromCSVForm, self).save(commit=False)
|
||||
@ -500,6 +537,7 @@ class VLANBulkEditForm(forms.Form, BootstrapMixin):
|
||||
pk = forms.ModelMultipleChoiceField(queryset=VLAN.objects.all(), widget=forms.MultipleHiddenInput)
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), required=False)
|
||||
group = forms.ModelChoiceField(queryset=VLANGroup.objects.all(), required=False)
|
||||
tenant = forms.TypedChoiceField(choices=bulkedit_tenant_choices, coerce=int, required=False, label='Tenant')
|
||||
status = forms.ChoiceField(choices=FORM_VLAN_STATUS_CHOICES, required=False)
|
||||
role = forms.ModelChoiceField(queryset=Role.objects.all(), required=False)
|
||||
description = forms.CharField(max_length=100, required=False)
|
||||
@ -515,6 +553,11 @@ def vlan_group_choices():
|
||||
return [(g.pk, u'{} ({})'.format(g, g.vlan_count)) for g in group_choices]
|
||||
|
||||
|
||||
def vlan_tenant_choices():
|
||||
tenant_choices = Tenant.objects.annotate(vrf_count=Count('vlans'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.vrf_count)) for t in tenant_choices]
|
||||
|
||||
|
||||
def vlan_status_choices():
|
||||
status_counts = {}
|
||||
for status in VLAN.objects.values('status').annotate(count=Count('status')).order_by('status'):
|
||||
@ -532,6 +575,8 @@ class VLANFilterForm(forms.Form, BootstrapMixin):
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
group_id = forms.MultipleChoiceField(required=False, choices=vlan_group_choices, label='VLAN Group',
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
tenant = forms.MultipleChoiceField(required=False, choices=vlan_tenant_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
status = forms.MultipleChoiceField(required=False, choices=vlan_status_choices)
|
||||
role = forms.MultipleChoiceField(required=False, choices=vlan_role_choices,
|
||||
widget=forms.SelectMultiple(attrs={'size': 8}))
|
||||
|
27
netbox/ipam/migrations/0006_vrf_vlan_add_tenant.py
Normal file
27
netbox/ipam/migrations/0006_vrf_vlan_add_tenant.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-27 14:39
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
('ipam', '0005_auto_20160725_1842'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='vlan',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vlans', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='vrf',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='vrfs', to='tenancy.Tenant'),
|
||||
),
|
||||
]
|
27
netbox/ipam/migrations/0007_prefix_ipaddress_add_tenant.py
Normal file
27
netbox/ipam/migrations/0007_prefix_ipaddress_add_tenant.py
Normal file
@ -0,0 +1,27 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.9.8 on 2016-07-28 15:32
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('tenancy', '0001_initial'),
|
||||
('ipam', '0006_vrf_vlan_add_tenant'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='ipaddress',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='ip_addresses', to='tenancy.Tenant'),
|
||||
),
|
||||
migrations.AddField(
|
||||
model_name='prefix',
|
||||
name='tenant',
|
||||
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.PROTECT, related_name='prefixes', to='tenancy.Tenant'),
|
||||
),
|
||||
]
|
@ -7,6 +7,7 @@ from django.core.validators import MaxValueValidator, MinValueValidator
|
||||
from django.db import models
|
||||
|
||||
from dcim.models import Interface
|
||||
from tenancy.models import Tenant
|
||||
from utilities.models import CreatedUpdatedModel
|
||||
|
||||
from .fields import IPNetworkField, IPAddressField
|
||||
@ -46,6 +47,7 @@ class VRF(CreatedUpdatedModel):
|
||||
"""
|
||||
name = models.CharField(max_length=50)
|
||||
rd = models.CharField(max_length=21, unique=True, verbose_name='Route distinguisher')
|
||||
tenant = models.ForeignKey(Tenant, related_name='vrfs', blank=True, null=True, on_delete=models.PROTECT)
|
||||
enforce_unique = models.BooleanField(default=True, verbose_name='Enforce unique space',
|
||||
help_text="Prevent duplicate prefixes/IP addresses within this VRF")
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
@ -65,6 +67,8 @@ class VRF(CreatedUpdatedModel):
|
||||
return ','.join([
|
||||
self.name,
|
||||
self.rd,
|
||||
self.tenant.name if self.tenant else '',
|
||||
'True' if self.enforce_unique else '',
|
||||
self.description,
|
||||
])
|
||||
|
||||
@ -229,6 +233,7 @@ class Prefix(CreatedUpdatedModel):
|
||||
site = models.ForeignKey('dcim.Site', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True)
|
||||
vrf = models.ForeignKey('VRF', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True,
|
||||
verbose_name='VRF')
|
||||
tenant = models.ForeignKey(Tenant, related_name='prefixes', blank=True, null=True, on_delete=models.PROTECT)
|
||||
vlan = models.ForeignKey('VLAN', related_name='prefixes', on_delete=models.PROTECT, blank=True, null=True,
|
||||
verbose_name='VLAN')
|
||||
status = models.PositiveSmallIntegerField('Status', choices=PREFIX_STATUS_CHOICES, default=1)
|
||||
@ -291,7 +296,7 @@ class Prefix(CreatedUpdatedModel):
|
||||
|
||||
class IPAddress(CreatedUpdatedModel):
|
||||
"""
|
||||
An IPAddress represents an individual IPV4 or IPv6 address and its mask. The mask length should match what is
|
||||
An IPAddress represents an individual IPv4 or IPv6 address and its mask. The mask length should match what is
|
||||
configured in the real world. (Typically, only loopback interfaces are configured with /32 or /128 masks.) Like
|
||||
Prefixes, IPAddresses can optionally be assigned to a VRF. An IPAddress can optionally be assigned to an Interface.
|
||||
Interfaces can have zero or more IPAddresses assigned to them.
|
||||
@ -304,6 +309,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')
|
||||
tenant = models.ForeignKey(Tenant, related_name='ip_addresses', blank=True, null=True, on_delete=models.PROTECT)
|
||||
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,
|
||||
@ -407,9 +413,10 @@ class VLAN(CreatedUpdatedModel):
|
||||
MaxValueValidator(4094)
|
||||
])
|
||||
name = models.CharField(max_length=64)
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
tenant = models.ForeignKey(Tenant, related_name='vlans', blank=True, null=True, on_delete=models.PROTECT)
|
||||
status = models.PositiveSmallIntegerField('Status', choices=VLAN_STATUS_CHOICES, default=1)
|
||||
role = models.ForeignKey('Role', related_name='vlans', on_delete=models.SET_NULL, blank=True, null=True)
|
||||
description = models.CharField(max_length=100, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['site', 'group', 'vid']
|
||||
@ -438,6 +445,7 @@ class VLAN(CreatedUpdatedModel):
|
||||
self.group.name if self.group else '',
|
||||
str(self.vid),
|
||||
self.name,
|
||||
self.tenant.name if self.tenant else '',
|
||||
self.get_status_display(),
|
||||
self.role.name if self.role else '',
|
||||
self.description,
|
||||
|
@ -6,8 +6,10 @@ from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import Aggregate, IPAddress, Prefix, RIR, Role, VLAN, VLANGroup, VRF
|
||||
|
||||
|
||||
RIR_EDIT_LINK = """
|
||||
{% if perms.ipam.change_rir %}<a href="{% url 'ipam:rir_edit' slug=record.slug %}">Edit</a>{% endif %}
|
||||
RIR_ACTIONS = """
|
||||
{% if perms.ipam.change_rir %}
|
||||
<a href="{% url 'ipam:rir_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
UTILIZATION_GRAPH = """
|
||||
@ -15,8 +17,10 @@ UTILIZATION_GRAPH = """
|
||||
{% utilization_graph record.get_utilization %}
|
||||
"""
|
||||
|
||||
ROLE_EDIT_LINK = """
|
||||
{% if perms.ipam.change_role %}<a href="{% url 'ipam:role_edit' slug=record.slug %}">Edit</a>{% endif %}
|
||||
ROLE_ACTIONS = """
|
||||
{% if perms.ipam.change_role %}
|
||||
<a href="{% url 'ipam:role_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
PREFIX_LINK = """
|
||||
@ -43,9 +47,19 @@ STATUS_LABEL = """
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
VLANGROUP_EDIT_LINK = """
|
||||
VLANGROUP_ACTIONS = """
|
||||
{% if perms.ipam.change_vlangroup %}
|
||||
<a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}">Edit</a>
|
||||
<a href="{% url 'ipam:vlangroup_edit' pk=record.pk %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
TENANT_LINK = """
|
||||
{% if record.tenant %}
|
||||
<a href="{% url 'tenancy:tenant' slug=record.tenant.slug %}">{{ record.tenant }}</a>
|
||||
{% elif record.vrf.tenant %}
|
||||
<a href="{% url 'tenancy:tenant' slug=record.vrf.tenant.slug %}">{{ record.vrf.tenant }}</a>*
|
||||
{% else %}
|
||||
—
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -58,11 +72,12 @@ class VRFTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
name = tables.LinkColumn('ipam:vrf', args=[Accessor('pk')], verbose_name='Name')
|
||||
rd = tables.Column(verbose_name='RD')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
description = tables.Column(orderable=False, verbose_name='Description')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = VRF
|
||||
fields = ('pk', 'name', 'rd', 'description')
|
||||
fields = ('pk', 'name', 'rd', 'tenant', 'description')
|
||||
|
||||
|
||||
#
|
||||
@ -74,11 +89,11 @@ class RIRTable(BaseTable):
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
aggregate_count = tables.Column(verbose_name='Aggregates')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=RIR_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=RIR_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = RIR
|
||||
fields = ('pk', 'name', 'aggregate_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'aggregate_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -109,11 +124,11 @@ class RoleTable(BaseTable):
|
||||
prefix_count = tables.Column(accessor=Accessor('count_prefixes'), orderable=False, verbose_name='Prefixes')
|
||||
vlan_count = tables.Column(accessor=Accessor('count_vlans'), orderable=False, verbose_name='VLANs')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=ROLE_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=ROLE_ACTIONS, attrs={'td': {'class': 'text-right'}}, verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Role
|
||||
fields = ('pk', 'name', 'prefix_count', 'vlan_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'prefix_count', 'vlan_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -124,14 +139,15 @@ class PrefixTable(BaseTable):
|
||||
pk = ToggleColumn()
|
||||
status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
|
||||
prefix = tables.TemplateColumn(PREFIX_LINK, verbose_name='Prefix')
|
||||
vrf = tables.Column(orderable=False, default='Global', verbose_name='VRF')
|
||||
vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global', verbose_name='VRF')
|
||||
tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant')
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
role = tables.Column(verbose_name='Role')
|
||||
description = tables.Column(orderable=False, verbose_name='Description')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Prefix
|
||||
fields = ('pk', 'prefix', 'status', 'vrf', 'site', 'role', 'description')
|
||||
fields = ('pk', 'prefix', 'status', 'vrf', 'tenant', 'site', 'role', 'description')
|
||||
|
||||
|
||||
class PrefixBriefTable(BaseTable):
|
||||
@ -143,6 +159,7 @@ class PrefixBriefTable(BaseTable):
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Prefix
|
||||
fields = ('prefix', 'status', 'site', 'role')
|
||||
orderable = False
|
||||
|
||||
|
||||
#
|
||||
@ -152,7 +169,8 @@ class PrefixBriefTable(BaseTable):
|
||||
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')
|
||||
vrf = tables.LinkColumn('ipam:vrf', args=[Accessor('vrf.pk')], default='Global', verbose_name='VRF')
|
||||
tenant = tables.TemplateColumn(TENANT_LINK, verbose_name='Tenant')
|
||||
device = tables.LinkColumn('dcim:device', args=[Accessor('interface.device.pk')], orderable=False,
|
||||
verbose_name='Device')
|
||||
interface = tables.Column(orderable=False, verbose_name='Interface')
|
||||
@ -160,7 +178,7 @@ class IPAddressTable(BaseTable):
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = IPAddress
|
||||
fields = ('pk', 'address', 'vrf', 'device', 'interface', 'description')
|
||||
fields = ('pk', 'address', 'vrf', 'tenant', 'device', 'interface', 'description')
|
||||
|
||||
|
||||
class IPAddressBriefTable(BaseTable):
|
||||
@ -186,11 +204,12 @@ class VLANGroupTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
vlan_count = tables.Column(verbose_name='VLANs')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=VLANGROUP_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=VLANGROUP_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = VLANGroup
|
||||
fields = ('pk', 'name', 'site', 'vlan_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'site', 'vlan_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
@ -203,9 +222,10 @@ class VLANTable(BaseTable):
|
||||
site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')], verbose_name='Site')
|
||||
group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
|
||||
name = tables.Column(verbose_name='Name')
|
||||
tenant = tables.LinkColumn('tenancy:tenant', args=[Accessor('tenant.slug')], verbose_name='Tenant')
|
||||
status = tables.TemplateColumn(STATUS_LABEL, verbose_name='Status')
|
||||
role = tables.Column(verbose_name='Role')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = VLAN
|
||||
fields = ('pk', 'vid', 'site', 'group', 'name', 'status', 'role')
|
||||
fields = ('pk', 'vid', 'site', 'group', 'name', 'tenant', 'status', 'role')
|
||||
|
@ -36,8 +36,9 @@ def add_available_prefixes(parent, prefix_list):
|
||||
#
|
||||
|
||||
class VRFListView(ObjectListView):
|
||||
queryset = VRF.objects.all()
|
||||
queryset = VRF.objects.select_related('tenant')
|
||||
filter = filters.VRFFilter
|
||||
filter_form = forms.VRFFilterForm
|
||||
table = tables.VRFTable
|
||||
edit_permissions = ['ipam.change_vrf', 'ipam.delete_vrf']
|
||||
template_name = 'ipam/vrf_list.html'
|
||||
@ -47,10 +48,11 @@ def vrf(request, pk):
|
||||
|
||||
vrf = get_object_or_404(VRF.objects.all(), pk=pk)
|
||||
prefixes = Prefix.objects.filter(vrf=vrf)
|
||||
prefix_table = tables.PrefixBriefTable(prefixes)
|
||||
|
||||
return render(request, 'ipam/vrf.html', {
|
||||
'vrf': vrf,
|
||||
'prefixes': prefixes,
|
||||
'prefix_table': prefix_table,
|
||||
})
|
||||
|
||||
|
||||
@ -85,6 +87,10 @@ class VRFBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['tenant'] == 0:
|
||||
fields_to_update['tenant'] = None
|
||||
elif form.cleaned_data['tenant']:
|
||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
||||
for field in ['description']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
@ -145,7 +151,7 @@ class AggregateListView(ObjectListView):
|
||||
if a.prefix.version == 4:
|
||||
ipv4_total += a.prefix.size
|
||||
elif a.prefix.version == 6:
|
||||
ipv6_total += a.prefix.size / 2**64
|
||||
ipv6_total += a.prefix.size / 2 ** 64
|
||||
|
||||
return {
|
||||
'ipv4_total': ipv4_total,
|
||||
@ -248,7 +254,7 @@ class RoleBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
#
|
||||
|
||||
class PrefixListView(ObjectListView):
|
||||
queryset = Prefix.objects.select_related('site', 'role')
|
||||
queryset = Prefix.objects.select_related('site', 'vrf__tenant', 'role')
|
||||
filter = filters.PrefixFilter
|
||||
filter_form = forms.PrefixFilterForm
|
||||
table = tables.PrefixTable
|
||||
@ -271,7 +277,8 @@ def prefix(request, pk):
|
||||
aggregate = None
|
||||
|
||||
# Count child IP addresses
|
||||
ipaddress_count = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix)).count()
|
||||
ipaddress_count = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\
|
||||
.count()
|
||||
|
||||
# Parent prefixes table
|
||||
parent_prefixes = Prefix.objects.filter(vrf=prefix.vrf, prefix__net_contains=str(prefix.prefix))\
|
||||
@ -336,10 +343,11 @@ class PrefixBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['vrf']:
|
||||
fields_to_update['vrf'] = form.cleaned_data['vrf']
|
||||
elif form.cleaned_data['vrf_global']:
|
||||
fields_to_update['vrf'] = None
|
||||
for field in ['vrf', 'tenant']:
|
||||
if form.cleaned_data[field] == 0:
|
||||
fields_to_update[field] = None
|
||||
elif form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
for field in ['site', 'status', 'role', 'description']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
@ -358,7 +366,7 @@ def prefix_ipaddresses(request, pk):
|
||||
prefix = get_object_or_404(Prefix.objects.all(), pk=pk)
|
||||
|
||||
# Find all IPAddresses belonging to this Prefix
|
||||
ipaddresses = IPAddress.objects.filter(address__net_contained_or_equal=str(prefix.prefix))\
|
||||
ipaddresses = IPAddress.objects.filter(vrf=prefix.vrf, address__net_contained_or_equal=str(prefix.prefix))\
|
||||
.select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
|
||||
|
||||
ip_table = tables.IPAddressTable(ipaddresses)
|
||||
@ -378,7 +386,7 @@ def prefix_ipaddresses(request, pk):
|
||||
#
|
||||
|
||||
class IPAddressListView(ObjectListView):
|
||||
queryset = IPAddress.objects.select_related('vrf', 'interface__device', 'primary_ip4_for', 'primary_ip6_for')
|
||||
queryset = IPAddress.objects.select_related('vrf__tenant', 'interface__device')
|
||||
filter = filters.IPAddressFilter
|
||||
filter_form = forms.IPAddressFilterForm
|
||||
table = tables.IPAddressTable
|
||||
@ -460,10 +468,11 @@ class IPAddressBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['vrf']:
|
||||
fields_to_update['vrf'] = form.cleaned_data['vrf']
|
||||
elif form.cleaned_data['vrf_global']:
|
||||
fields_to_update['vrf'] = None
|
||||
for field in ['vrf', 'tenant']:
|
||||
if form.cleaned_data[field] == 0:
|
||||
fields_to_update[field] = None
|
||||
elif form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
for field in ['description']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
@ -520,10 +529,11 @@ def vlan(request, pk):
|
||||
|
||||
vlan = get_object_or_404(VLAN.objects.select_related('site', 'role'), pk=pk)
|
||||
prefixes = Prefix.objects.filter(vlan=vlan)
|
||||
prefix_table = tables.PrefixBriefTable(prefixes)
|
||||
|
||||
return render(request, 'ipam/vlan.html', {
|
||||
'vlan': vlan,
|
||||
'prefixes': prefixes,
|
||||
'prefix_table': prefix_table,
|
||||
})
|
||||
|
||||
|
||||
@ -558,6 +568,10 @@ class VLANBulkEditView(PermissionRequiredMixin, BulkEditView):
|
||||
def update_objects(self, pk_list, form):
|
||||
|
||||
fields_to_update = {}
|
||||
if form.cleaned_data['tenant'] == 0:
|
||||
fields_to_update['tenant'] = None
|
||||
elif form.cleaned_data['tenant']:
|
||||
fields_to_update['tenant'] = form.cleaned_data['tenant']
|
||||
for field in ['site', 'group', 'status', 'role', 'description']:
|
||||
if form.cleaned_data[field]:
|
||||
fields_to_update[field] = form.cleaned_data[field]
|
||||
|
@ -12,7 +12,7 @@ except ImportError:
|
||||
"the documentation.")
|
||||
|
||||
|
||||
VERSION = '1.3.2'
|
||||
VERSION = '1.4.0'
|
||||
|
||||
# Import local configuration
|
||||
for setting in ['ALLOWED_HOSTS', 'DATABASE', 'SECRET_KEY']:
|
||||
@ -108,6 +108,7 @@ INSTALLED_APPS = (
|
||||
'ipam',
|
||||
'extras',
|
||||
'secrets',
|
||||
'tenancy',
|
||||
'users',
|
||||
'utilities',
|
||||
)
|
||||
|
@ -22,6 +22,7 @@ urlpatterns = [
|
||||
url(r'^dcim/', include('dcim.urls', namespace='dcim')),
|
||||
url(r'^ipam/', include('ipam.urls', namespace='ipam')),
|
||||
url(r'^secrets/', include('secrets.urls', namespace='secrets')),
|
||||
url(r'^tenancy/', include('tenancy.urls', namespace='tenancy')),
|
||||
url(r'^profile/', include('users.urls', namespace='users')),
|
||||
|
||||
# API
|
||||
@ -29,6 +30,7 @@ urlpatterns = [
|
||||
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/secrets/', include('secrets.api.urls', namespace='secrets-api')),
|
||||
url(r'^api/tenancy/', include('tenancy.api.urls', namespace='tenancy-api')),
|
||||
url(r'^api/docs/', include('rest_framework_swagger.urls')),
|
||||
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
|
||||
|
||||
|
@ -5,16 +5,20 @@ from django.shortcuts import render
|
||||
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 ipam.models import Aggregate, Prefix, IPAddress, VLAN, VRF
|
||||
from secrets.models import Secret
|
||||
from tenancy.models import Tenant
|
||||
|
||||
|
||||
def home(request):
|
||||
|
||||
stats = {
|
||||
|
||||
# DCIM
|
||||
# Organization
|
||||
'site_count': Site.objects.count(),
|
||||
'tenant_count': Tenant.objects.count(),
|
||||
|
||||
# DCIM
|
||||
'rack_count': Rack.objects.count(),
|
||||
'device_count': Device.objects.count(),
|
||||
'interface_connections_count': InterfaceConnection.objects.count(),
|
||||
@ -22,6 +26,7 @@ def home(request):
|
||||
'power_connections_count': PowerPort.objects.filter(power_outlet__isnull=False).count(),
|
||||
|
||||
# IPAM
|
||||
'vrf_count': VRF.objects.count(),
|
||||
'aggregate_count': Aggregate.objects.count(),
|
||||
'prefix_count': Prefix.objects.count(),
|
||||
'ipaddress_count': IPAddress.objects.count(),
|
||||
|
@ -1,10 +1,16 @@
|
||||
import django_filters
|
||||
|
||||
from django.db.models import Q
|
||||
|
||||
from .models import Secret, SecretRole
|
||||
from dcim.models import Device
|
||||
|
||||
|
||||
class SecretFilter(django_filters.FilterSet):
|
||||
q = django_filters.MethodFilter(
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
role_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='role',
|
||||
queryset=SecretRole.objects.all(),
|
||||
@ -26,3 +32,9 @@ class SecretFilter(django_filters.FilterSet):
|
||||
class Meta:
|
||||
model = Secret
|
||||
fields = ['name', 'role_id', 'role', 'device']
|
||||
|
||||
def search(self, queryset, value):
|
||||
return queryset.filter(
|
||||
Q(name__icontains=value) |
|
||||
Q(device__name__icontains=value)
|
||||
)
|
||||
|
42
netbox/secrets/fixtures/initial_data.json
Normal file
42
netbox/secrets/fixtures/initial_data.json
Normal file
@ -0,0 +1,42 @@
|
||||
[
|
||||
{
|
||||
"model": "secrets.secretrole",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"name": "Login Credentials",
|
||||
"slug": "login-credentials",
|
||||
"users": [],
|
||||
"groups": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "secrets.secretrole",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"name": "RADIUS Key",
|
||||
"slug": "radius-key",
|
||||
"users": [],
|
||||
"groups": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "secrets.secretrole",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"name": "SNMPv2 Community",
|
||||
"slug": "snmpv2-community",
|
||||
"users": [],
|
||||
"groups": []
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "secrets.secretrole",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"name": "SNMPv3 Credentials",
|
||||
"slug": "snmpv3-credentials",
|
||||
"users": [],
|
||||
"groups": []
|
||||
}
|
||||
}
|
||||
]
|
@ -6,9 +6,9 @@ from utilities.tables import BaseTable, ToggleColumn
|
||||
from .models import SecretRole, Secret
|
||||
|
||||
|
||||
SECRETROLE_EDIT_LINK = """
|
||||
SECRETROLE_ACTIONS = """
|
||||
{% if perms.secrets.change_secretrole %}
|
||||
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}">Edit</a>
|
||||
<a href="{% url 'secrets:secretrole_edit' slug=record.slug %}" class="btn btn-xs btn-warning"><i class="glyphicon glyphicon-pencil" aria-hidden="true"></i></a>
|
||||
{% endif %}
|
||||
"""
|
||||
|
||||
@ -22,11 +22,12 @@ class SecretRoleTable(BaseTable):
|
||||
name = tables.LinkColumn(verbose_name='Name')
|
||||
secret_count = tables.Column(verbose_name='Secrets')
|
||||
slug = tables.Column(verbose_name='Slug')
|
||||
edit = tables.TemplateColumn(template_code=SECRETROLE_EDIT_LINK, verbose_name='')
|
||||
actions = tables.TemplateColumn(template_code=SECRETROLE_ACTIONS, attrs={'td': {'class': 'text-right'}},
|
||||
verbose_name='')
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = SecretRole
|
||||
fields = ('pk', 'name', 'secret_count', 'slug', 'edit')
|
||||
fields = ('pk', 'name', 'secret_count', 'slug', 'actions')
|
||||
|
||||
|
||||
#
|
||||
|
@ -13,7 +13,7 @@
|
||||
<div class="panel panel-danger" style="margin-top: 200px">
|
||||
<div class="panel-heading">
|
||||
<strong>
|
||||
<i class="glyphicon glyphicon-warning-sign"></i>
|
||||
<i class="fa fa-warning"></i>
|
||||
Server Error
|
||||
</strong>
|
||||
</div>
|
||||
|
@ -24,173 +24,182 @@
|
||||
<div id="navbar" class="navbar-collapse collapse">
|
||||
{% if request.user.is_authenticated or not settings.LOGIN_REQUIRED %}
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown{% if request.path|startswith:'/dcim/sites/' %} active{% endif %}">
|
||||
{% if perms.dcim.add_site %}
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Sites <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'dcim:site_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Sites</a></li>
|
||||
<li><a href="{% url 'dcim:site_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Site</a></li>
|
||||
<li><a href="{% url 'dcim:site_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Sites</a></li>
|
||||
</ul>
|
||||
{% else %}
|
||||
<a href="{% url 'dcim:site_list' %}">Sites</a>
|
||||
{% endif %}
|
||||
<li class="dropdown{% if request.path|startswith:'/dcim/sites/' or 'tenancy' in request.path %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Organization <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'dcim:site_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Sites</a></li>
|
||||
{% if perms.dcim.add_site %}
|
||||
<li><a href="{% url 'dcim:site_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Site</a></li>
|
||||
<li><a href="{% url 'dcim:site_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Sites</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'tenancy:tenant_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Tenants</a></li>
|
||||
{% if perms.tenancy.add_tenant %}
|
||||
<li><a href="{% url 'tenancy:tenant_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Tenant</a></li>
|
||||
<li><a href="{% url 'tenancy:tenant_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Tenants</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'tenancy:tenantgroup_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Tenant Groups</a></li>
|
||||
{% if perms.tenancy.add_tenantgroup %}
|
||||
<li><a href="{% url 'tenancy:tenantgroup_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Tenant Group</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/dcim/rack' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Racks <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'dcim:rack_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Racks</a></li>
|
||||
<li><a href="{% url 'dcim:rack_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Racks</a></li>
|
||||
{% if perms.dcim.add_rack %}
|
||||
<li><a href="{% url 'dcim:rack_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack</a></li>
|
||||
<li><a href="{% url 'dcim:rack_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Racks</a></li>
|
||||
<li><a href="{% url 'dcim:rack_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Rack</a></li>
|
||||
<li><a href="{% url 'dcim:rack_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Racks</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'dcim:rackgroup_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Rack Groups</a></li>
|
||||
<li><a href="{% url 'dcim:rackgroup_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Rack Groups</a></li>
|
||||
{% if perms.dcim.add_rackgroup %}
|
||||
<li><a href="{% url 'dcim:rackgroup_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Rack Group</a></li>
|
||||
<li><a href="{% url 'dcim:rackgroup_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Rack Group</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/dcim/device' or request.path|startswith:'/dcim/manufacturers/' or request.path|startswith:'/dcim/platforms/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Devices <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'dcim:device_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Devices</a></li>
|
||||
<li><a href="{% url 'dcim:device_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Devices</a></li>
|
||||
{% if perms.dcim.add_device %}
|
||||
<li><a href="{% url 'dcim:device_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device</a></li>
|
||||
<li><a href="{% url 'dcim:device_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Devices</a></li>
|
||||
<li><a href="{% url 'dcim:device_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Device</a></li>
|
||||
<li><a href="{% url 'dcim:device_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Devices</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_device or perms.ipam.add_devicetype %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'dcim:devicetype_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Device Types</a></li>
|
||||
<li><a href="{% url 'dcim:devicetype_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Device Types</a></li>
|
||||
{% if perms.dcim.add_devicetype %}
|
||||
<li><a href="{% url 'dcim:devicetype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Type</a></li>
|
||||
<li><a href="{% url 'dcim:devicetype_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Device Type</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'dcim:devicerole_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Device Roles</a></li>
|
||||
<li><a href="{% url 'dcim:devicerole_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Device Roles</a></li>
|
||||
{% if perms.dcim.add_devicerole %}
|
||||
<li><a href="{% url 'dcim:devicerole_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Device Role</a></li>
|
||||
<li><a href="{% url 'dcim:devicerole_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Device Role</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_devicerole or perms.dcim.add_manufacturer %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'dcim:manufacturer_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Manufacturers</a></li>
|
||||
<li><a href="{% url 'dcim:manufacturer_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Manufacturers</a></li>
|
||||
{% if perms.dcim.add_manufacturer %}
|
||||
<li><a href="{% url 'dcim:manufacturer_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Manufacturer</a></li>
|
||||
<li><a href="{% url 'dcim:manufacturer_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Manufacturer</a></li>
|
||||
{% endif %}
|
||||
{% if perms.dcim.add_manufacturer or perms.dcim.add_platform %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'dcim:platform_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Platforms</a></li>
|
||||
<li><a href="{% url 'dcim:platform_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Platforms</a></li>
|
||||
{% if perms.dcim.add_platform %}
|
||||
<li><a href="{% url 'dcim:platform_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Platform</a></li>
|
||||
<li><a href="{% url 'dcim:platform_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Platform</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/dcim/console-connections/' or request.path|startswith:'/dcim/power-connections/' or request.path|startswith:'/dcim/interface-connections/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Connections <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'dcim:console_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Console Connections</a></li>
|
||||
<li><a href="{% url 'dcim:console_connections_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Console Connections</a></li>
|
||||
{% if perms.dcim.change_consoleport %}
|
||||
<li><a href="{% url 'dcim:console_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Console Connections</a></li>
|
||||
<li><a href="{% url 'dcim:console_connections_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Console Connections</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.change_consoleport or perms.ipam.change_powerport %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'dcim:power_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Power Connections</a></li>
|
||||
<li><a href="{% url 'dcim:power_connections_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Power Connections</a></li>
|
||||
{% if perms.dcim.change_powerport %}
|
||||
<li><a href="{% url 'dcim:power_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Power Connections</a></li>
|
||||
<li><a href="{% url 'dcim:power_connections_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Power Connections</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.change_powerport or perms.ipam.add_interfaceconnection %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'dcim:interface_connections_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Interface Connections</a></li>
|
||||
<li><a href="{% url 'dcim:interface_connections_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Interface Connections</a></li>
|
||||
{% if perms.dcim.add_interfaceconnection %}
|
||||
<li><a href="{% url 'dcim:interface_connections_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Interface Connections</a></li>
|
||||
<li><a href="{% url 'dcim:interface_connections_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Interface Connections</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/ipam/' and not request.path|startswith:'/ipam/vlan' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">IP Space <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'ipam:ipaddress_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> IP Addresses</a></li>
|
||||
<li><a href="{% url 'ipam:ipaddress_list' %}"><i class="fa fa-search" aria-hidden="true"></i> IP Addresses</a></li>
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
<li><a href="{% url 'ipam:ipaddress_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add an IP</a></li>
|
||||
<li><a href="{% url 'ipam:ipaddress_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import IPs</a></li>
|
||||
<li><a href="{% url 'ipam:ipaddress_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add an IP</a></li>
|
||||
<li><a href="{% url 'ipam:ipaddress_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import IPs</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_ipaddress or perms.ipam.add_prefix %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'ipam:prefix_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Prefixes</a></li>
|
||||
<li><a href="{% url 'ipam:prefix_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Prefixes</a></li>
|
||||
{% if perms.ipam.add_prefix %}
|
||||
<li><a href="{% url 'ipam:prefix_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Prefix</a></li>
|
||||
<li><a href="{% url 'ipam:prefix_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Prefixes</a></li>
|
||||
<li><a href="{% url 'ipam:prefix_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Prefix</a></li>
|
||||
<li><a href="{% url 'ipam:prefix_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Prefixes</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_prefix or perms.ipam.add_aggregate %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'ipam:aggregate_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Aggregates</a></li>
|
||||
<li><a href="{% url 'ipam:aggregate_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Aggregates</a></li>
|
||||
{% if perms.ipam.add_aggregate %}
|
||||
<li><a href="{% url 'ipam:aggregate_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add an Aggregate</a></li>
|
||||
<li><a href="{% url 'ipam:aggregate_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Aggregates</a></li>
|
||||
<li><a href="{% url 'ipam:aggregate_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add an Aggregate</a></li>
|
||||
<li><a href="{% url 'ipam:aggregate_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Aggregates</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_aggregate or perms.ipam.add_vrf %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'ipam:vrf_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> VRFs</a></li>
|
||||
<li><a href="{% url 'ipam:vrf_list' %}"><i class="fa fa-search" aria-hidden="true"></i> VRFs</a></li>
|
||||
{% if perms.ipam.add_vrf %}
|
||||
<li><a href="{% url 'ipam:vrf_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a VRF</a></li>
|
||||
<li><a href="{% url 'ipam:vrf_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import VRFs</a></li>
|
||||
<li><a href="{% url 'ipam:vrf_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a VRF</a></li>
|
||||
<li><a href="{% url 'ipam:vrf_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import VRFs</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'ipam:rir_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> RIRs</a></li>
|
||||
<li><a href="{% url 'ipam:rir_list' %}"><i class="fa fa-search" aria-hidden="true"></i> RIRs</a></li>
|
||||
{% if perms.ipam.add_rir %}
|
||||
<li><a href="{% url 'ipam:rir_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a RIR</a></li>
|
||||
<li><a href="{% url 'ipam:rir_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a RIR</a></li>
|
||||
{% endif %}
|
||||
{% if perms.ipam.add_rir or perms.ipam.add_role %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'ipam:role_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Prefix/VLAN Roles</a></li>
|
||||
<li><a href="{% url 'ipam:role_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Prefix/VLAN Roles</a></li>
|
||||
{% if perms.ipam.add_role %}
|
||||
<li><a href="{% url 'ipam:role_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Role</a></li>
|
||||
<li><a href="{% url 'ipam:role_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Role</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">
|
||||
<li><a href="{% url 'ipam:vlan_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> VLANs</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_list' %}"><i class="fa fa-search" aria-hidden="true"></i> VLANs</a></li>
|
||||
{% if perms.ipam.add_vlan %}
|
||||
<li><a href="{% url 'ipam:vlan_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a VLAN</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import VLANs</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a VLAN</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import VLANs</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'ipam:vlangroup_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> VLAN Groups</a></li>
|
||||
<li><a href="{% url 'ipam:vlangroup_list' %}"><i class="fa fa-search" aria-hidden="true"></i> VLAN Groups</a></li>
|
||||
{% if perms.ipam.add_vlangroup %}
|
||||
<li><a href="{% url 'ipam:vlangroup_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a VLAN Group</a></li>
|
||||
<li><a href="{% url 'ipam:vlangroup_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a VLAN Group</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
<li class="dropdown{% if request.path|startswith:'/circuits/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Circuits <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'circuits:provider_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Providers</a></li>
|
||||
<li><a href="{% url 'circuits:provider_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Providers</a></li>
|
||||
{% if perms.circuits.add_provider %}
|
||||
<li><a href="{% url 'circuits:provider_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Provider</a></li>
|
||||
<li><a href="{% url 'circuits:provider_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Providers</a></li>
|
||||
<li><a href="{% url 'circuits:provider_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Provider</a></li>
|
||||
<li><a href="{% url 'circuits:provider_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Providers</a></li>
|
||||
{% endif %}
|
||||
{% if perms.circuits.add_circuit or perms.circuits.add_provider %}
|
||||
<li class="divider"></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'circuits:circuit_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Circuits</a></li>
|
||||
<li><a href="{% url 'circuits:circuit_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Circuits</a></li>
|
||||
{% if perms.circuits.add_circuit %}
|
||||
<li><a href="{% url 'circuits:circuit_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Circuit</a></li>
|
||||
<li><a href="{% url 'circuits:circuit_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Circuits</a></li>
|
||||
<li><a href="{% url 'circuits:circuit_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Circuit</a></li>
|
||||
<li><a href="{% url 'circuits:circuit_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Circuits</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'circuits:circuittype_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Circuit Types</a></li>
|
||||
<li><a href="{% url 'circuits:circuittype_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Circuit Types</a></li>
|
||||
{% if perms.circuits.add_circuittype %}
|
||||
<li><a href="{% url 'circuits:circuittype_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Circuit Type</a></li>
|
||||
<li><a href="{% url 'circuits:circuittype_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Circuit Type</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
@ -198,14 +207,14 @@
|
||||
<li class="dropdown{% if request.path|startswith:'/secrets/' %} active{% endif %}">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Secrets <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
<li><a href="{% url 'secrets:secret_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Secrets</a></li>
|
||||
<li><a href="{% url 'secrets:secret_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Secrets</a></li>
|
||||
{% if perms.secrets.add_secret %}
|
||||
<li><a href="{% url 'secrets:secret_import' %}"><i class="glyphicon glyphicon-import" aria-hidden="true"></i> Import Secrets</a></li>
|
||||
<li><a href="{% url 'secrets:secret_import' %}"><i class="fa fa-download" aria-hidden="true"></i> Import Secrets</a></li>
|
||||
{% endif %}
|
||||
<li class="divider"></li>
|
||||
<li><a href="{% url 'secrets:secretrole_list' %}"><i class="glyphicon glyphicon-search" aria-hidden="true"></i> Secret Roles</a></li>
|
||||
<li><a href="{% url 'secrets:secretrole_list' %}"><i class="fa fa-search" aria-hidden="true"></i> Secret Roles</a></li>
|
||||
{% if perms.secrets.add_secretrole %}
|
||||
<li><a href="{% url 'secrets:secretrole_add' %}"><i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add a Secret Role</a></li>
|
||||
<li><a href="{% url 'secrets:secretrole_add' %}"><i class="fa fa-plus" aria-hidden="true"></i> Add a Secret Role</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</li>
|
||||
@ -215,12 +224,12 @@
|
||||
<ul class="nav navbar-nav navbar-right">
|
||||
{% if request.user.is_authenticated %}
|
||||
{% if request.user.is_staff %}
|
||||
<li><a href="{% url 'admin:index' %}"><i class="glyphicon glyphicon-cog" aria-hidden="true"></i> Admin</a></li>
|
||||
<li><a href="{% url 'admin:index' %}"><i class="fa fa-cogs" aria-hidden="true"></i> Admin</a></li>
|
||||
{% endif %}
|
||||
<li><a href="{% url 'users:profile' %}"><i class="glyphicon glyphicon-user" aria-hidden="true"></i> Profile</a></li>
|
||||
<li><a href="{% url 'logout' %}"><i class="glyphicon glyphicon-log-out" aria-hidden="true"></i> Log out</a></li>
|
||||
<li><a href="{% url 'users:profile' %}"><i class="fa fa-user" aria-hidden="true"></i> Profile</a></li>
|
||||
<li><a href="{% url 'logout' %}"><i class="fa fa-sign-out" aria-hidden="true"></i> Log out</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'login' %}?next={{ request.path }}"><i class="glyphicon glyphicon-log-in" aria-hidden="true"></i> Log in</a></li>
|
||||
<li><a href="{% url 'login' %}?next={{ request.path }}"><i class="fa fa-sign-in" aria-hidden="true"></i> Log in</a></li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -1,24 +1,24 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load helpers %}
|
||||
|
||||
{% block title %}{{ circuit.provider }} Circuit {{ circuit.cid }}{% endblock %}
|
||||
{% block title %}{{ circuit.provider }} - {{ circuit.cid }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:site' slug=circuit.site.slug %}">{{ circuit.site }}</a></li>
|
||||
<li><a href="{% url 'circuits:circuit_list' %}?site={{ circuit.site.slug }}">Circuits</a></li>
|
||||
<li>{{ circuit }}</li>
|
||||
<li><a href="{% url 'circuits:circuit_list' %}">Circuits</a></li>
|
||||
<li><a href="{% url 'circuits:circuit_list' %}?provider={{ circuit.provider.slug }}">{{ circuit.provider }}</a></li>
|
||||
<li>{{ circuit.cid }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'circuits:circuit_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Circuit ID" />
|
||||
<input type="text" name="q" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -28,18 +28,18 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.circuits.change_circuit %}
|
||||
<a href="{% url 'circuits:circuit_edit' pk=circuit.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this circuit
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.circuits.delete_circuit %}
|
||||
<a href="{% url 'circuits:circuit_delete' pk=circuit.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this circuit
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1>{{ circuit.provider }} Circuit {{ circuit.cid }}</h1>
|
||||
<h1>{{ circuit.provider }} - {{ circuit.cid }}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
@ -57,6 +57,67 @@
|
||||
<td>Circuit ID</td>
|
||||
<td>{{ circuit.cid }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Type</td>
|
||||
<td><a href="{{ circuit.type.get_absolute_url }}">{{ circuit.type }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if circuit.tenant %}
|
||||
<a href="{{ circuit.tenant.get_absolute_url }}">{{ circuit.tenant }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Install Date</td>
|
||||
<td>
|
||||
{% if circuit.install_date %}
|
||||
{{ circuit.install_date }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Port Speed</td>
|
||||
<td>
|
||||
{% if circuit.port_speed %}
|
||||
{{ circuit.port_speed_human }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Commit Rate</td>
|
||||
<td>
|
||||
{% if circuit.commit_speed %}
|
||||
{{ circuit.commit_speed_human }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
<td>{{ circuit.created }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Updated</td>
|
||||
<td>{{ circuit.last_updated }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Termination</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>Site</td>
|
||||
<td>
|
||||
@ -73,44 +134,34 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Install Date</td>
|
||||
<td>{{ circuit.install_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Port Speed</td>
|
||||
<td>{{ circuit.port_speed_human }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Commit Rate</td>
|
||||
<td>{% if circuit.commit_rate %}{{ circuit.commit_rate_human }}{% else %}<span class="text-muted">N/A</span>{% endif %}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cross-Connect</td>
|
||||
<td>{{ circuit.xconnect_id }}</td>
|
||||
<td>
|
||||
{% if circuit.xconnect_id %}
|
||||
{{ circuit.xconnect_id }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Patch Panel/Port</td>
|
||||
<td>{{ circuit.pp_info }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
<td>{{ circuit.created }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Last Updated</td>
|
||||
<td>{{ circuit.last_updated }}</td>
|
||||
<td>
|
||||
{% if circuit.pp_info %}
|
||||
{{ circuit.pp_info }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Comments</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if circuit.comments %}
|
||||
{% if circuit.comments %}
|
||||
{{ circuit.comments|gfm }}
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
|
@ -9,13 +9,19 @@
|
||||
{% render_field form.provider %}
|
||||
{% render_field form.cid %}
|
||||
{% render_field form.type %}
|
||||
{% render_field form.tenant %}
|
||||
{% render_field form.install_date %}
|
||||
{% render_field form.port_speed %}
|
||||
{% render_field form.commit_rate %}
|
||||
{% render_field form.xconnect_id %}
|
||||
{% render_field form.pp_info %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Bandwidth</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.port_speed %}
|
||||
{% render_field form.commit_rate %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Termination</strong></div>
|
||||
<div class="panel-body">
|
||||
|
@ -43,6 +43,11 @@
|
||||
<td>Circuit type</td>
|
||||
<td>Transit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>Strickland Propane</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Site</td>
|
||||
<td>Site name</td>
|
||||
@ -76,7 +81,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>IC-603122,TeliaSonera,Transit,ASH-4,2016-02-23,10000,2000,937649,PP8371 ports 13/14</pre>
|
||||
<pre>IC-603122,TeliaSonera,Transit,Strickland Propane,ASH-4,2016-02-23,10000,2000,937649,PP8371 ports 13/14</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.circuits.add_circuit %}
|
||||
<a href="{% url 'circuits:circuit_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a circuit
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -19,23 +19,7 @@
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:circuit_bulk_edit' bulk_delete_url='circuits:circuit_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 'circuits:circuit_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.circuits.add_circuittype %}
|
||||
<a href="{% url 'circuits:circuittype_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a circuit type
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -6,27 +6,41 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'circuits:provider_list' %}">Providers</a></li>
|
||||
<li>{{ provider }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'circuits:provider_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ provider.name }}" data-url="{% url 'circuits-api:provider_graphs' pk=provider.pk %}" title="Show graphs">
|
||||
<i class="glyphicon glyphicon-signal" aria-hidden="true"></i>
|
||||
Graphs
|
||||
</button>
|
||||
{% if show_graphs %}
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ provider.name }}" data-url="{% url 'circuits-api:provider_graphs' pk=provider.pk %}" title="Show graphs">
|
||||
<i class="fa fa-signal" aria-hidden="true"></i>
|
||||
Graphs
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if perms.circuits.change_provider %}
|
||||
<a href="{% url 'circuits:provider_edit' slug=provider.slug %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this provider
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.circuits.delete_provider %}
|
||||
<a href="{% url 'circuits:provider_delete' slug=provider.slug %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this provider
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -41,25 +55,53 @@
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>ASN</td>
|
||||
<td>{{ provider.asn }}</td>
|
||||
<td>
|
||||
{% if provider.asn %}
|
||||
{{ provider.asn }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Account</td>
|
||||
<td>{{ provider.account }}</td>
|
||||
<td>
|
||||
{% if provider.account %}
|
||||
{{ provider.account }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Customer Portal</td>
|
||||
<td>
|
||||
<a href="{{ provider.portal_url }}">{{ provider.portal_url }}</a>
|
||||
{% if provider.portal_url %}
|
||||
<a href="{{ provider.portal_url }}">{{ provider.portal_url }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>NOC Contact</td>
|
||||
<td>{{ provider.noc_contact|linebreaksbr }}</td>
|
||||
<td>
|
||||
{% if provider.noc_contact %}
|
||||
{{ provider.noc_contact|linebreaksbr }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Admin Contact</td>
|
||||
<td>{{ provider.admin_contact|linebreaksbr }}</td>
|
||||
<td>
|
||||
{% if provider.admin_contact %}
|
||||
{{ provider.admin_contact|linebreaksbr }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
@ -76,7 +118,7 @@
|
||||
<strong>Comments</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if provider.comments %}
|
||||
{% if provider.comments %}
|
||||
{{ provider.comments|gfm }}
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
|
@ -6,7 +6,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.circuits.add_provider %}
|
||||
<a href="{% url 'circuits:provider_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a provider
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -18,23 +18,7 @@
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='circuits:provider_bulk_edit' bulk_delete_url='circuits:provider_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 'circuits:provider_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.change_consoleport %}
|
||||
<a href="{% url 'dcim:console_connections_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import connections
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -14,6 +14,16 @@
|
||||
<strong>Device</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if device.tenant %}
|
||||
<a href="{{ device.tenant.get_absolute_url }}">{{ device.tenant }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Site</td>
|
||||
<td>
|
||||
@ -55,7 +65,7 @@
|
||||
{% if device.serial %}
|
||||
<span>{{ device.serial }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -86,7 +96,7 @@
|
||||
{% if device.platform %}
|
||||
<span>{{ device.platform }}</span>
|
||||
{% else %}
|
||||
<span class="text-warning">Not assigned</span>
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -111,7 +121,7 @@
|
||||
<span>(NAT: {{ device.primary_ip4.nat_outside.address.ip }})</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -126,7 +136,7 @@
|
||||
<span>(NAT: {{ device.primary_ip6.nat_outside.address.ip }})</span>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -257,7 +267,7 @@
|
||||
<strong>Comments</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if device.comments %}
|
||||
{% if device.comments %}
|
||||
{{ device.comments|gfm }}
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
|
@ -9,6 +9,7 @@
|
||||
<td><a href="{% url 'dcim:device' pk=device.pk %}">{{ device }}</a></td>
|
||||
<td>{{ device.device_type }}</td>
|
||||
<td>{{ device.device_role }}</td>
|
||||
<td>{{ device.tenant }}</td>
|
||||
<td>{{ device.serial }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -7,6 +7,7 @@
|
||||
<div class="panel-body">
|
||||
{% render_field form.name %}
|
||||
{% render_field form.device_role %}
|
||||
{% render_field form.tenant %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
|
@ -36,6 +36,11 @@
|
||||
<td>Functional role of device</td>
|
||||
<td>ToR Switch</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>Pied Piper</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Device manufacturer</td>
|
||||
<td>Hardware manufacturer</td>
|
||||
@ -79,7 +84,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>rack101_sw1,ToR Switch,Juniper,EX4300-48T,Juniper Junos,CAB00577291,Ashburn-VA,R101,21,Rear</pre>
|
||||
<pre>rack101_sw1,ToR Switch,Pied Piper,Juniper,EX4300-48T,Juniper Junos,CAB00577291,Ashburn-VA,R101,21,Rear</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -107,7 +107,7 @@
|
||||
</div>
|
||||
{% if perms.dcim.add_module %}
|
||||
<a href="{% url 'dcim:module_add' pk=device.pk %}" class="btn btn-success">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a Module
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,11 +7,11 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_device %}
|
||||
<a href="{% url 'dcim:device_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a device
|
||||
</a>
|
||||
<a href="{% url 'dcim:device_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import devices
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -23,24 +23,7 @@
|
||||
{% include 'dcim/inc/device_table.html' with bulk_edit_url='dcim:device_bulk_edit' bulk_delete_url='dcim:device_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'dcim:device_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Name or serial" {% 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_devicerole %}
|
||||
<a href="{% url 'dcim:devicerole_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a device role
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -19,13 +19,13 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.change_devicetype %}
|
||||
<a href="{% url 'dcim:devicetype_edit' pk=devicetype.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this device type
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_devicetype %}
|
||||
<a href="{% url 'dcim:devicetype_delete' pk=devicetype.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this device type
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_devicetype %}
|
||||
<a href="{% url 'dcim:devicetype_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a device type
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -16,10 +16,10 @@
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'dcim:device_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Device name or serial" />
|
||||
<input type="text" name="q" class="form-control" placeholder="Search devices" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
|
@ -34,10 +34,12 @@
|
||||
</td>
|
||||
{% endif %}
|
||||
<td class="text-right">
|
||||
{% if iface.circuit or iface.connection %}
|
||||
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface_graphs' pk=iface.pk %}" title="Show graphs">
|
||||
<i class="glyphicon glyphicon-signal" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% if show_graphs %}
|
||||
{% if iface.circuit or iface.connection %}
|
||||
<button type="button" class="btn btn-primary btn-xs" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ device.name }} - {{ iface.name }}" data-url="{% url 'dcim-api:interface_graphs' pk=iface.pk %}" title="Show graphs">
|
||||
<i class="glyphicon glyphicon-signal" aria-hidden="true"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if perms.dcim.change_interface %}
|
||||
{% if iface.is_physical %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_interfaceconnection %}
|
||||
<a href="{% url 'dcim:interface_connections_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import connections
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_manufacturer %}
|
||||
<a href="{% url 'dcim:manufacturer_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a manufacturer
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_platform %}
|
||||
<a href="{% url 'dcim:platform_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a platform
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.change_powerport %}
|
||||
<a href="{% url 'dcim:power_connections_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import connections
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -8,18 +8,18 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:site' slug=rack.site.slug %}">{{ rack.site }}</a></li>
|
||||
<li><a href="{% url 'dcim:rack_list' %}?site={{ rack.site.slug }}">Racks</a></li>
|
||||
<li><a href="{% url 'dcim:rack_list' %}">Racks</a></li>
|
||||
<li><a href="{% url 'dcim:rack_list' %}?site={{ rack.site.slug }}">{{ rack.site }}</a></li>
|
||||
<li>{{ rack }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'dcim:rack_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Rack name or ID" />
|
||||
<input type="text" name="q" class="form-control" placeholder="Search racks" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -29,25 +29,25 @@
|
||||
<div class="pull-right">
|
||||
{% if prev_rack %}
|
||||
<a href="{% url 'dcim:rack' pk=prev_rack.pk %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-chevron-left" aria-hidden="true"></span>
|
||||
<span class="fa fa-chevron-left" aria-hidden="true"></span>
|
||||
Previous Rack
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if next_rack %}
|
||||
<a href="{% url 'dcim:rack' pk=next_rack.pk %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-chevron-right" aria-hidden="true"></span>
|
||||
<span class="fa fa-chevron-right" aria-hidden="true"></span>
|
||||
Next Rack
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.change_rack %}
|
||||
<a href="{% url 'dcim:rack_edit' pk=rack.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this rack
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_rack %}
|
||||
<a href="{% url 'dcim:rack_delete' pk=rack.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this rack
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -81,6 +81,16 @@
|
||||
<td>
|
||||
{% if rack.facility_id %}
|
||||
<span>{{ rack.facility_id }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if rack.tenant %}
|
||||
<a href="{{ rack.tenant.get_absolute_url }}">{{ rack.tenant }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
@ -146,7 +156,7 @@
|
||||
<strong>Comments</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if rack.comments %}
|
||||
{% if rack.comments %}
|
||||
{{ rack.comments|gfm }}
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
|
@ -9,6 +9,7 @@
|
||||
<td><a href="{% url 'dcim:rack' pk=rack.pk %}">{{ rack }}</a></td>
|
||||
<td>{{ rack.facility_id }}</td>
|
||||
<td>{{ rack.site }}</td>
|
||||
<td>{{ rack.tenant }}</td>
|
||||
<td>{{ rack.u_height }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
|
@ -9,6 +9,7 @@
|
||||
{% render_field form.group %}
|
||||
{% render_field form.name %}
|
||||
{% render_field form.facility_id %}
|
||||
{% render_field form.tenant %}
|
||||
{% render_field form.u_height %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -48,6 +48,11 @@
|
||||
<td>Rack ID assigned by the facility (optional)</td>
|
||||
<td>J12.100</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>Pied Piper</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Height</td>
|
||||
<td>Height in rack units</td>
|
||||
@ -56,7 +61,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>DC-4,Cage 1400,R101,J12.100,42</pre>
|
||||
<pre>DC-4,Cage 1400,R101,J12.100,Pied Piper,42</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -7,11 +7,11 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_rack %}
|
||||
<a href="{% url 'dcim:rack_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a rack
|
||||
</a>
|
||||
<a href="{% url 'dcim:rack_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import racks
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -23,24 +23,7 @@
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:rack_bulk_edit' bulk_delete_url='dcim:rack_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'dcim:rack_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_rackgroup %}
|
||||
<a href="{% url 'dcim:rackgroup_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a rack group
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -16,10 +16,10 @@
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'dcim:site_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Search" />
|
||||
<input type="text" name="q" class="form-control" placeholder="Search sites" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -27,38 +27,62 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ site.name }}" data-url="{% url 'dcim-api:site_graphs' pk=site.pk %}" title="Show graphs">
|
||||
<i class="glyphicon glyphicon-signal" aria-hidden="true"></i>
|
||||
Graphs
|
||||
</button>
|
||||
{% if show_graphs %}
|
||||
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#graphs_modal" data-obj="{{ site.name }}" data-url="{% url 'dcim-api:site_graphs' pk=site.pk %}" title="Show graphs">
|
||||
<i class="fa fa-signal" aria-hidden="true"></i>
|
||||
Graphs
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if perms.dcim.change_site %}
|
||||
<a href="{% url 'dcim:site_edit' slug=site.slug %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this site
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_site %}
|
||||
<a href="{% url 'dcim:site_delete' slug=site.slug %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this site
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
<h1>{{ site.name }}</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-7">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Site</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if site.tenant %}
|
||||
<a href="{{ site.tenant.get_absolute_url }}">{{ site.tenant }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Facility</td>
|
||||
<td>{{ site.facility }}</td>
|
||||
<td>
|
||||
{% if site.facility %}
|
||||
{{ site.facility }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>AS Number</td>
|
||||
<td>{{ site.asn }}</td>
|
||||
<td>
|
||||
{% if site.asn %}
|
||||
{{ site.asn }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Physical Address</td>
|
||||
@ -69,8 +93,10 @@
|
||||
<i class="glyphicon glyphicon-map-marker"></i> Map it
|
||||
</a>
|
||||
</div>
|
||||
<span>{{ site.physical_address|linebreaksbr }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
<span>{{ site.physical_address|linebreaksbr }}</span>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
@ -79,7 +105,7 @@
|
||||
{% if site.shipping_address %}
|
||||
<span>{{ site.shipping_address|linebreaksbr }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">See physical address</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -98,7 +124,7 @@
|
||||
<strong>Comments</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
{% if site.comments %}
|
||||
{% if site.comments %}
|
||||
{{ site.comments|gfm }}
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
@ -106,43 +132,33 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="col-md-5">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Stats</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
<tr>
|
||||
<td>Racks</td>
|
||||
<td>
|
||||
<a href="{% url 'dcim:rack_list' %}?site={{ site.slug }}">{{ stats.rack_count }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Devices</td>
|
||||
<td>
|
||||
<a href="{% url 'dcim:device_list' %}?site={{ site.slug }}">{{ stats.device_count }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prefixes</td>
|
||||
<td>
|
||||
<a href="{% url 'ipam:prefix_list' %}?site={{ site.slug }}">{{ stats.prefix_count }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>VLANs</td>
|
||||
<td>
|
||||
<a href="{% url 'ipam:vlan_list' %}?site={{ site.slug }}">{{ stats.vlan_count }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Circuits</td>
|
||||
<td>
|
||||
<a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}">{{ stats.circuit_count }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="row panel-body">
|
||||
<div class="col-md-4 text-center">
|
||||
<h2><a href="{% url 'dcim:rack_list' %}?site={{ site.slug }}" class="btn {% if stats.rack_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.rack_count }}</a></h2>
|
||||
<p>Racks</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<h2><a href="{% url 'dcim:device_list' %}?site={{ site.slug }}" class="btn {% if stats.device_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.device_count }}</a></h2>
|
||||
<p>Devices</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<h2><a href="{% url 'ipam:prefix_list' %}?site={{ site.slug }}" class="btn {% if stats.prefix_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.prefix_count }}</a></h2>
|
||||
<p>Prefixes</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<h2><a href="{% url 'ipam:vlan_list' %}?site={{ site.slug }}" class="btn {% if stats.vlan_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.vlan_count }}</a></h2>
|
||||
<p>VLANs</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-center">
|
||||
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}" class="btn {% if stats.circuit_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.circuit_count }}</a></h2>
|
||||
<p>Circuits</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
|
13
netbox/templates/dcim/site_bulk_edit.html
Normal file
13
netbox/templates/dcim/site_bulk_edit.html
Normal file
@ -0,0 +1,13 @@
|
||||
{% extends 'utilities/bulk_edit_form.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Site Bulk Edit{% endblock %}
|
||||
|
||||
{% block select_objects_table %}
|
||||
{% for site in selected_objects %}
|
||||
<tr>
|
||||
<td><a href="{% url 'dcim:site' slug=site.slug %}">{{ site.slug }}</a></td>
|
||||
<td>{{ site.tenant }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
@ -7,6 +7,7 @@
|
||||
<div class="panel-body">
|
||||
{% render_field form.name %}
|
||||
{% render_field form.slug %}
|
||||
{% render_field form.tenant %}
|
||||
{% render_field form.facility %}
|
||||
{% render_field form.asn %}
|
||||
{% render_field form.physical_address %}
|
||||
|
@ -38,6 +38,11 @@
|
||||
<td>URL-friendly name</td>
|
||||
<td>ash4-south</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>Pied Piper</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Facility</td>
|
||||
<td>Name of the hosting facility (optional)</td>
|
||||
@ -51,7 +56,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>ASH-4 South,ash4-south,Equinix DC6,65000</pre>
|
||||
<pre>ASH-4 South,ash4-south,Pied Piper,Equinix DC6,65000</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -1,5 +1,4 @@
|
||||
{% extends '_base.html' %}
|
||||
{% load render_table from django_tables2 %}
|
||||
|
||||
{% block title %}Sites{% endblock %}
|
||||
|
||||
@ -7,36 +6,24 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_site %}
|
||||
<a href="{% url 'dcim:site_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a site
|
||||
</a>
|
||||
<a href="{% url 'dcim:site_import' %}" class="btn btn-info">
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import sites
|
||||
</a>
|
||||
{% endif %}
|
||||
{% include 'inc/export_button.html' with obj_type='sites' %}
|
||||
</div>
|
||||
<h1>Sites</h1>
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
{% render_table table 'table.html' %}
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='dcim:site_bulk_edit' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'dcim:site_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -6,10 +6,10 @@
|
||||
<div class="col-md-4">
|
||||
<form action="{% url 'dcim:device_list' %}" method="get">
|
||||
<div class="input-group input-group-lg">
|
||||
<input type="text" name="q" placeholder="Device name or serial" class="form-control" />
|
||||
<input type="text" name="q" placeholder="Search devices" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
Devices
|
||||
</button>
|
||||
</span>
|
||||
@ -20,11 +20,11 @@
|
||||
<div class="col-md-4">
|
||||
<form action="{% url 'ipam:prefix_list' %}" method="get">
|
||||
<div class="input-group input-group-lg">
|
||||
<input type="text" name="q" placeholder="IP or network" class="form-control" />
|
||||
<input type="text" name="q" placeholder="Search prefixes" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
IP
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
Prefixes
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -34,10 +34,10 @@
|
||||
<div class="col-md-4">
|
||||
<form action="{% url 'circuits:circuit_list' %}" method="get">
|
||||
<div class="input-group input-group-lg">
|
||||
<input type="text" name="q" placeholder="Circuit ID" class="form-control" />
|
||||
<input type="text" name="q" placeholder="Search circuits" class="form-control" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
Circuits
|
||||
</button>
|
||||
</span>
|
||||
@ -50,7 +50,7 @@
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>DCIM</strong>
|
||||
<strong>Organization</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
@ -58,6 +58,18 @@
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:site_list' %}">Sites</a></h4>
|
||||
<p class="list-group-item-text text-muted">Geographic locations</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.tenant_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'tenancy:tenant_list' %}">Tenants</a></h4>
|
||||
<p class="list-group-item-text text-muted">Customers or departments</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>DCIM</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.rack_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'dcim:rack_list' %}">Racks</a></h4>
|
||||
@ -79,20 +91,6 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if perms.secrets %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Secrets</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.secret_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'secrets:secret_list' %}">Secrets</a></h4>
|
||||
<p class="list-group-item-text text-muted">Sensitive data (such as passwords) which has been stored securely</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<div class="panel panel-default">
|
||||
@ -100,6 +98,11 @@
|
||||
<strong>IPAM</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.vrf_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:vrf_list' %}">VRFs</a></h4>
|
||||
<p class="list-group-item-text text-muted">Virtual routing and forwarding tables</p>
|
||||
</div>
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.aggregate_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></h4>
|
||||
@ -141,6 +144,20 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
{% if perms.secrets %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Secrets</strong>
|
||||
</div>
|
||||
<div class="list-group">
|
||||
<div class="list-group-item">
|
||||
<span class="badge pull-right">{{ stats.secret_count }}</span>
|
||||
<h4 class="list-group-item-heading"><a href="{% url 'secrets:secret_list' %}">Secrets</a></h4>
|
||||
<p class="list-group-item-text text-muted">Sensitive data (such as passwords) which has been stored securely</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Recent Activity</strong>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<h1>Import Completed</h1>
|
||||
{% render_table table %}
|
||||
<a href="{{ request.path }}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import more
|
||||
</a>
|
||||
{% endblock %}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% if export_templates %}
|
||||
<div class="btn-group">
|
||||
<button type="button" class="btn btn-success dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="glyphicon glyphicon-export" aria-hidden="true"></span>
|
||||
<span class="fa fa-upload" aria-hidden="true"></span>
|
||||
Export {{ obj_type }} <span class="caret"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu">
|
||||
@ -14,7 +14,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
<a href="?{% if request.GET %}{{ request.GET.urlencode }}&{% endif %}export" class="btn btn-success">
|
||||
<span class="glyphicon glyphicon-export" aria-hidden="true"></span>
|
||||
<span class="fa fa-upload" aria-hidden="true"></span>
|
||||
Export {{ obj_type }}
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-filter" aria-hidden="true"></span>
|
||||
<span class="fa fa-filter" aria-hidden="true"></span>
|
||||
<strong>Filter</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
@ -19,7 +19,7 @@
|
||||
{% endfor %}
|
||||
<div class="text-right">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span> Apply filters
|
||||
<span class="fa fa-search" aria-hidden="true"></span> Apply filters
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
|
18
netbox/templates/inc/search_panel.html
Normal file
18
netbox/templates/inc/search_panel.html
Normal file
@ -0,0 +1,18 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="." method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Search" {% if request.GET.q %}value="{{ request.GET.q }}" {% endif %}/>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
@ -5,24 +5,36 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'ipam:aggregate_list' %}">Aggregates</a></li>
|
||||
<li><a href="{% url 'ipam:aggregate_list' %}?rir={{ aggregate.rir.slug }}">{{ aggregate.rir }}</a></li>
|
||||
<li>{{ aggregate }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'ipam:aggregate_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Search aggregates" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.change_aggregate %}
|
||||
<a href="{% url 'ipam:aggregate_edit' pk=aggregate.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this aggregate
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_aggregate %}
|
||||
<a href="{% url 'ipam:aggregate_delete' pk=aggregate.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this aggregate
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -51,17 +63,17 @@
|
||||
{% if aggregate.date_added %}
|
||||
<span>{{ aggregate.date_added }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
{% if aggregate.description %}
|
||||
{% if aggregate.description %}
|
||||
<span>{{ aggregate.description }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -8,7 +8,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_aggregate %}
|
||||
<a href="{% url 'ipam:aggregate_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add an aggregate
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -22,6 +22,7 @@
|
||||
<p class="text-right">IPv6 total: <strong>{{ ipv6_total|intcomma }} /64s</strong></p>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
{% include 'inc/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,11 +1,9 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
{% if prefix.site %}
|
||||
<li><a href="{% url 'dcim:site' slug=prefix.site.slug %}">{{ prefix.site }}</a></li>
|
||||
<li><a href="{% url 'ipam:prefix_list' %}?site={{ prefix.site.slug }}">Prefixes</a></li>
|
||||
{% else %}
|
||||
<li><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></li>
|
||||
<li><a href="{% url 'ipam:prefix_list' %}">Prefixes</a></li>
|
||||
{% if prefix.vrf %}
|
||||
<li><a href="{% url 'ipam:prefix_list' %}?vrf={{ prefix.vrf.pk }}">{{ prefix.vrf }}</a></li>
|
||||
{% endif %}
|
||||
<li>{{ prefix }}</li>
|
||||
</ol>
|
||||
@ -13,10 +11,10 @@
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'ipam:prefix_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Network or IP" />
|
||||
<input type="text" name="q" class="form-control" placeholder="Search prefixes" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -26,19 +24,19 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_add' %}?address={{ prefix.prefix }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}" class="btn btn-success">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add an IP Address
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.change_prefix %}
|
||||
<a href="{% url 'ipam:prefix_edit' pk=prefix.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this prefix
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_prefix %}
|
||||
<a href="{% url 'ipam:prefix_delete' pk=prefix.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this prefix
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,21 +7,20 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
{% for p in parent_prefixes %}
|
||||
<li><a href="{% url 'ipam:prefix' pk=p.pk %}">{{ p }}</a></li>
|
||||
{% empty %}
|
||||
<li><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></li>
|
||||
{% endfor %}
|
||||
<li>{{ ipaddress.address.ip }}</li>
|
||||
<li><a href="{% url 'ipam:ipaddress_list' %}">IP Addresses</a></li>
|
||||
{% if ipaddress.vrf %}
|
||||
<li><a href="{% url 'ipam:ipaddress_list' %}?vrf={{ ipaddress.vrf.pk }}">{{ ipaddress.vrf }}</a></li>
|
||||
{% endif %}
|
||||
<li>{{ ipaddress }}</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'ipam:ipaddress_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="IP address" />
|
||||
<input type="text" name="q" class="form-control" placeholder="Search IPs" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -31,13 +30,13 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.change_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_edit' pk=ipaddress.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this IP
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_delete' pk=ipaddress.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this IP
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -65,15 +64,28 @@
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if ipaddress.description %}
|
||||
<span>{{ ipaddress.description }}</span>
|
||||
{% if ipaddress.tenant %}
|
||||
<a href="{{ ipaddress.tenant.get_absolute_url }}">{{ ipaddress.tenant }}</a>
|
||||
{% elif ipaddress.vrf.tenant %}
|
||||
<a href="{{ ipaddress.vrf.tenant.get_absolute_url }}">{{ ipaddress.vrf.tenant }}</a>
|
||||
<label class="label label-info">Inherited</label>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
{% if ipaddress.description %}
|
||||
<span>{{ ipaddress.description }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Assignment</td>
|
||||
<td>
|
||||
|
@ -7,7 +7,8 @@
|
||||
{% for ipaddress in selected_objects %}
|
||||
<tr>
|
||||
<td><a href="{% url 'ipam:ipaddress' pk=ipaddress.pk %}">{{ ipaddress }}</a></td>
|
||||
<td>{{ ipaddress.vrf }}</td>
|
||||
<td>{{ ipaddress.vrf|default:"Global" }}</td>
|
||||
<td>{{ ipaddress.tenant }}</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.tenant %}
|
||||
{% 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>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>ABC01</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,ABC01,switch12,ge-0/0/31,True,Management IP</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -8,11 +8,11 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_ipaddress %}
|
||||
<a href="{% url 'ipam:ipaddress_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add an IP
|
||||
</a>
|
||||
<a href="{% url 'ipam:ipaddress_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import IPs
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -24,24 +24,7 @@
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:ipaddress_bulk_edit' bulk_delete_url='ipam:ipaddress_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'ipam:ipaddress_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="IP address" {% 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -26,6 +26,19 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if prefix.tenant %}
|
||||
<a href="{{ prefix.tenant.get_absolute_url }}">{{ prefix.tenant }}</a>
|
||||
{% elif prefix.vrf.tenant %}
|
||||
<a href="{{ prefix.vrf.tenant.get_absolute_url }}">{{ prefix.vrf.tenant }}</a>
|
||||
<label class="label label-info">Inherited</label>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Aggregate</td>
|
||||
<td>
|
||||
@ -42,7 +55,7 @@
|
||||
{% if prefix.site %}
|
||||
<a href="{% url 'dcim:site' slug=prefix.site.slug %}">{{ prefix.site }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">Not assigned</span>
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -52,7 +65,7 @@
|
||||
{% if prefix.vlan %}
|
||||
<a href="{% url 'ipam:vlan' pk=prefix.vlan.pk %}">{{ prefix.vlan.display_name }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">Not assigned</span>
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -64,15 +77,21 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Role</td>
|
||||
<td>{{ prefix.role }}</td>
|
||||
<td>
|
||||
{% if prefix.role %}
|
||||
<span>{{ prefix.role }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
{% if prefix.description %}
|
||||
{% if prefix.description %}
|
||||
<span>{{ prefix.description }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -108,7 +127,7 @@
|
||||
{% include 'utilities/obj_table.html' with table=child_prefix_table table_template='panel_table.html' heading='Child Prefixes' parent=prefix bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
||||
{% elif prefix.new_subnet %}
|
||||
<a href="{% url 'ipam:prefix_add' %}?prefix={{ prefix.new_subnet }}{% if prefix.vrf %}&vrf={{ prefix.vrf.pk }}{% endif %}{% if prefix.site %}&site={{ prefix.site.pk }}{% endif %}" class="btn btn-success">
|
||||
<i class="glyphicon glyphicon-plus" aria-hidden="true"></i> Add Child Prefix
|
||||
<i class="fa fa-plus" aria-hidden="true"></i> Add Child Prefix
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
@ -8,6 +8,7 @@
|
||||
<tr>
|
||||
<td><a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a></td>
|
||||
<td>{{ prefix.vrf|default:"Global" }}</td>
|
||||
<td>{{ prefix.tenant }}</td>
|
||||
<td>{{ prefix.site }}</td>
|
||||
<td>{{ prefix.status }}</td>
|
||||
<td>{{ prefix.role }}</td>
|
||||
|
@ -38,6 +38,11 @@
|
||||
<td>VRF route distinguisher (optional)</td>
|
||||
<td>65000:123</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>ABC01</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Site</td>
|
||||
<td>Name of assigned site (optional)</td>
|
||||
@ -71,7 +76,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>192.168.42.0/24,65000:123,HQ,Customers,801,Active,Customer,7th floor WiFi</pre>
|
||||
<pre>192.168.42.0/24,65000:123,ABC01,HQ,Customers,801,Active,Customer,7th floor WiFi</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -8,11 +8,11 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_prefix %}
|
||||
<a href="{% url 'ipam:prefix_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a prefix
|
||||
</a>
|
||||
<a href="{% url 'ipam:prefix_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import prefixes
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -24,24 +24,7 @@
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:prefix_bulk_edit' bulk_delete_url='ipam:prefix_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<strong>Search</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'ipam:prefix_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="q" class="form-control" placeholder="Network" {% 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_rir %}
|
||||
<a href="{% url 'ipam:rir_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a RIR
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,7 +7,7 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.dcim.add_devicerole %}
|
||||
<a href="{% url 'ipam:role_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a role
|
||||
</a>
|
||||
{% endif %}
|
||||
|
@ -7,18 +7,21 @@
|
||||
<div class="row">
|
||||
<div class="col-md-9">
|
||||
<ol class="breadcrumb">
|
||||
<li><a href="{% url 'dcim:site' slug=vlan.site.slug %}">{{ vlan.site }}</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}">VLANs</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_list' %}">VLANs</a></li>
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}">{{ vlan.site }}</a></li>
|
||||
{% if vlan.group %}
|
||||
<li><a href="{% url 'ipam:vlan_list' %}?site={{ vlan.site.slug }}&group={{ vlan.group.slug }}">{{ vlan.group.name }}</a></li>
|
||||
{% endif %}
|
||||
<li>{{ vlan.name }} ({{ vlan.vid }})</li>
|
||||
</ol>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<form action="{% url 'ipam:vlan_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="vid" class="form-control" placeholder="VLAN ID search" />
|
||||
<input type="text" name="q" class="form-control" placeholder="Search VLANs" />
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<span class="fa fa-search" aria-hidden="true"></span>
|
||||
</button>
|
||||
</span>
|
||||
</div>
|
||||
@ -28,13 +31,13 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.change_vlan %}
|
||||
<a href="{% url 'ipam:vlan_edit' pk=vlan.pk %}" class="btn btn-warning">
|
||||
<span class="glyphicon glyphicon-pencil" aria-hidden="true"></span>
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this VLAN
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.ipam.delete_vlan %}
|
||||
<a href="{% url 'ipam:vlan_delete' pk=vlan.pk %}" class="btn btn-danger">
|
||||
<span class="glyphicon glyphicon-trash" aria-hidden="true"></span>
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
Delete this VLAN
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -70,10 +73,10 @@
|
||||
<td>{{ vlan.name }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>Tenant</td>
|
||||
<td>
|
||||
{% if vlan.description %}
|
||||
{{ vlan.description }}
|
||||
{% if vlan.tenant %}
|
||||
<a href="{{ vlan.tenant.get_absolute_url }}">{{ vlan.tenant }}</a>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
@ -87,7 +90,23 @@
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Role</td>
|
||||
<td>{{ vlan.role }}</td>
|
||||
<td>
|
||||
{% if vlan.role %}
|
||||
<span>{{ vlan.role }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">None</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Description</td>
|
||||
<td>
|
||||
{% if vlan.description %}
|
||||
{{ vlan.description }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Created</td>
|
||||
@ -105,26 +124,7 @@
|
||||
<div class="panel-heading">
|
||||
<strong>Prefixes</strong>
|
||||
</div>
|
||||
{% if prefixes %}
|
||||
<table class="table table-hover panel-body">
|
||||
{% for p in prefixes %}
|
||||
<tr>
|
||||
<td>
|
||||
<a href="{% url 'ipam:prefix' pk=p.pk %}">{{ p }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if p.site %}
|
||||
<a href="{% url 'dcim:site' slug=p.site.slug %}">{{ p.site }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ p.get_status_display }}</td>
|
||||
<td>{{ p.role }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="panel-body text-muted">None</div>
|
||||
{% endif %}
|
||||
{% render_table prefix_table %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -9,7 +9,8 @@
|
||||
<td><a href="{% url 'ipam:vlan' pk=vlan.pk %}">{{ vlan.vid }}</a></td>
|
||||
<td>{{ vlan.name }}</td>
|
||||
<td>{{ vlan.site }}</td>
|
||||
<td>{{ vlan.status }}</td>
|
||||
<td>{{ vlan.tenant }}</td>
|
||||
<td>{{ vlan.get_status_display }}</td>
|
||||
<td>{{ vlan.role }}</td>
|
||||
<td>{{ vlan.description }}</td>
|
||||
</tr>
|
||||
|
@ -48,6 +48,11 @@
|
||||
<td>Configured VLAN name</td>
|
||||
<td>Cameras</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Tenant</td>
|
||||
<td>Name of tenant (optional)</td>
|
||||
<td>Internal</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>Current status</td>
|
||||
@ -66,7 +71,7 @@
|
||||
</tbody>
|
||||
</table>
|
||||
<h4>Example</h4>
|
||||
<pre>LAS2,Backend Network,1400,Cameras,Active,Security,Security team only</pre>
|
||||
<pre>LAS2,Backend Network,1400,Cameras,Internal,Active,Security,Security team only</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -8,11 +8,11 @@
|
||||
<div class="pull-right">
|
||||
{% if perms.ipam.add_vlan %}
|
||||
<a href="{% url 'ipam:vlan_add' %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
Add a VLAN
|
||||
</a>
|
||||
<a href="{% url 'ipam:vlan_import' %}" class="btn btn-info">
|
||||
<span class="glyphicon glyphicon-import" aria-hidden="true"></span>
|
||||
<span class="fa fa-download" aria-hidden="true"></span>
|
||||
Import VLANs
|
||||
</a>
|
||||
{% endif %}
|
||||
@ -24,24 +24,7 @@
|
||||
{% include 'utilities/obj_table.html' with bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %}
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="glyphicon glyphicon-search" aria-hidden="true"></span>
|
||||
<strong>Search by ID</strong>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<form action="{% url 'ipam:vlan_list' %}" method="get">
|
||||
<div class="input-group">
|
||||
<input type="text" name="vid" class="form-control" placeholder="VLAN ID" {% if request.GET.vid %}value="{{ request.GET.vid }}" {% 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/search_panel.html' %}
|
||||
{% include 'inc/filter_panel.html' %}
|
||||
</div>
|
||||
</div>
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user