mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-27 19:08:38 -06:00
Add Multipoint terminations to a circuit
This commit is contained in:
parent
8341800a85
commit
36f615d1c4
3
.gitignore
vendored
3
.gitignore
vendored
@ -5,3 +5,6 @@ configuration.py
|
||||
!upgrade.sh
|
||||
fabfile.py
|
||||
*.swp
|
||||
*.bak
|
||||
gunicorn_config.py
|
||||
netbox/static
|
||||
|
@ -1,6 +1,6 @@
|
||||
from django.contrib import admin
|
||||
|
||||
from .models import Provider, CircuitType, Circuit
|
||||
from .models import Provider, CircuitType, Circuit, Termination
|
||||
|
||||
|
||||
@admin.register(Provider)
|
||||
@ -21,11 +21,20 @@ class CircuitTypeAdmin(admin.ModelAdmin):
|
||||
|
||||
@admin.register(Circuit)
|
||||
class CircuitAdmin(admin.ModelAdmin):
|
||||
list_display = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed_human',
|
||||
'upstream_speed_human', 'commit_rate_human', 'xconnect_id']
|
||||
list_display = ['cid', 'provider', 'type', 'tenant','install_date']
|
||||
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', 'tenant', 'site')
|
||||
return qs.select_related('provider', 'type', 'tenant')
|
||||
|
||||
@admin.register(Termination)
|
||||
class TerminationAdmin(admin.ModelAdmin):
|
||||
list_display = ['tid', 'circuit', 'site', 'port_speed_human',
|
||||
'upstream_speed_human', 'commit_rate_human', 'xconnect_id']
|
||||
list_filter = ['site', 'tid', 'circuit']
|
||||
exclude = ['interface']
|
||||
|
||||
def get_queryset(self, request):
|
||||
qs = super(TerminationAdmin, self).get_queryset(request)
|
||||
return qs.select_related('site', 'circuit')
|
||||
|
@ -13,17 +13,17 @@ class ProviderFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
action='search',
|
||||
label='Search',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='circuits__site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='circuits__site',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
# site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
# name='circuits__site',
|
||||
# queryset=Site.objects.all(),
|
||||
# label='Site',
|
||||
# )
|
||||
# site = django_filters.ModelMultipleChoiceFilter(
|
||||
# name='circuits__site',
|
||||
# queryset=Site.objects.all(),
|
||||
# to_field_name='slug',
|
||||
# label='Site (slug)',
|
||||
# )
|
||||
|
||||
class Meta:
|
||||
model = Provider
|
||||
@ -75,26 +75,13 @@ class CircuitFilter(CustomFieldFilterSet, django_filters.FilterSet):
|
||||
to_field_name='slug',
|
||||
label='Tenant (slug)',
|
||||
)
|
||||
site_id = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
label='Site (ID)',
|
||||
)
|
||||
site = django_filters.ModelMultipleChoiceFilter(
|
||||
name='site',
|
||||
queryset=Site.objects.all(),
|
||||
to_field_name='slug',
|
||||
label='Site (slug)',
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = ['q', 'provider_id', 'provider', 'type_id', 'type', 'site_id', 'site', 'interface', 'install_date']
|
||||
fields = ['q', 'provider_id', 'provider', 'type_id', 'type', 'install_date']
|
||||
|
||||
def search(self, queryset, value):
|
||||
return queryset.filter(
|
||||
Q(cid__icontains=value) |
|
||||
Q(xconnect_id__icontains=value) |
|
||||
Q(pp_info__icontains=value) |
|
||||
Q(comments__icontains=value)
|
||||
)
|
||||
|
@ -9,7 +9,7 @@ from utilities.forms import (
|
||||
APISelect, BootstrapMixin, BulkImportForm, CommentField, CSVDataField, Livesearch, SmallTextarea, SlugField,
|
||||
)
|
||||
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
from .models import Circuit, CircuitType, Provider, Termination
|
||||
|
||||
|
||||
#
|
||||
@ -85,40 +85,209 @@ class CircuitTypeForm(forms.ModelForm, BootstrapMixin):
|
||||
#
|
||||
|
||||
class CircuitForm(BootstrapMixin, CustomFieldForm):
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.Select(attrs={'filter-for': 'rack'}))
|
||||
rack = forms.ModelChoiceField(queryset=Rack.objects.all(), required=False, label='Rack',
|
||||
widget=APISelect(api_url='/api/dcim/racks/?site_id={{site}}',
|
||||
attrs={'filter-for': 'device'}))
|
||||
device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
|
||||
widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
|
||||
attrs={'filter-for': 'interface'}))
|
||||
livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
|
||||
query_key='q', query_url='dcim-api:device_list', field_to_update='device')
|
||||
)
|
||||
interface = forms.ModelChoiceField(queryset=Interface.objects.all(), required=False, label='Interface',
|
||||
widget=APISelect(api_url='/api/dcim/devices/{{device}}/interfaces/?type=physical',
|
||||
disabled_indicator='is_connected'))
|
||||
# site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.Select(attrs={'filter-for': 'rack'}))
|
||||
# rack = forms.ModelChoiceField(queryset=Rack.objects.all(), required=False, label='Rack',
|
||||
# widget=APISelect(api_url='/api/dcim/racks/?site_id={{site}}',
|
||||
# attrs={'filter-for': 'device'}))
|
||||
# device = forms.ModelChoiceField(queryset=Device.objects.all(), required=False, label='Device',
|
||||
# widget=APISelect(api_url='/api/dcim/devices/?rack_id={{rack}}',
|
||||
# attrs={'filter-for': 'interface'}))
|
||||
# livesearch = forms.CharField(required=False, label='Device', widget=Livesearch(
|
||||
# query_key='q', query_url='dcim-api:device_list', field_to_update='device')
|
||||
# )
|
||||
# interface = forms.ModelChoiceField(queryset=Interface.objects.all(), required=False, label='Interface',
|
||||
# widget=APISelect(api_url='/api/dcim/devices/{{device}}/interfaces/?type=physical',
|
||||
# disabled_indicator='is_connected'))
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = Circuit
|
||||
fields = [
|
||||
'cid', 'type', 'provider', 'tenant', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date',
|
||||
'port_speed', 'upstream_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments'
|
||||
'cid', 'type', 'provider', 'tenant', 'install_date', 'comments'
|
||||
]
|
||||
# fields = [
|
||||
# 'cid', 'type', 'provider', 'tenant', 'site', 'rack', 'device', 'livesearch', 'interface', 'install_date',
|
||||
# 'port_speed', 'upstream_speed', 'commit_rate', 'xconnect_id', 'pp_info', 'comments'
|
||||
# ]
|
||||
help_texts = {
|
||||
'cid': "Unique circuit ID",
|
||||
'install_date': "Format: YYYY-MM-DD",
|
||||
'port_speed': "Physical circuit speed",
|
||||
'commit_rate': "Commited rate",
|
||||
'xconnect_id': "ID of the local cross-connect",
|
||||
'pp_info': "Patch panel ID and port number(s)"
|
||||
# 'port_speed': "Physical circuit speed",
|
||||
# 'commit_rate': "Commited rate",
|
||||
# 'xconnect_id': "ID of the local cross-connect",
|
||||
# 'pp_info': "Patch panel ID and port number(s)"
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(CircuitForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# # If this circuit has been assigned to an interface, initialize rack and device
|
||||
# if self.instance.interface:
|
||||
# self.initial['rack'] = self.instance.interface.device.rack
|
||||
# self.initial['device'] = self.instance.interface.device
|
||||
#
|
||||
# # Limit rack choices
|
||||
# if self.is_bound:
|
||||
# self.fields['rack'].queryset = Rack.objects.filter(site__pk=self.data['site'])
|
||||
# elif self.initial.get('site'):
|
||||
# self.fields['rack'].queryset = Rack.objects.filter(site=self.initial['site'])
|
||||
# else:
|
||||
# self.fields['rack'].choices = []
|
||||
#
|
||||
# # Limit device choices
|
||||
# if self.is_bound and self.data.get('rack'):
|
||||
# self.fields['device'].queryset = Device.objects.filter(rack=self.data['rack'])
|
||||
# elif self.initial.get('rack'):
|
||||
# self.fields['device'].queryset = Device.objects.filter(rack=self.initial['rack'])
|
||||
# else:
|
||||
# self.fields['device'].choices = []
|
||||
#
|
||||
# # Limit interface choices
|
||||
# if self.is_bound and self.data.get('device'):
|
||||
# interfaces = Interface.objects.filter(device=self.data['device'])\
|
||||
# .exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
# self.fields['interface'].widget.attrs['initial'] = self.data.get('interface')
|
||||
# elif self.initial.get('device'):
|
||||
# interfaces = Interface.objects.filter(device=self.initial['device'])\
|
||||
# .exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
# self.fields['interface'].widget.attrs['initial'] = self.initial.get('interface')
|
||||
# else:
|
||||
# interfaces = []
|
||||
# self.fields['interface'].choices = [
|
||||
# (iface.id, {
|
||||
# 'label': iface.name,
|
||||
# 'disabled': iface.is_connected and iface.id != self.fields['interface'].widget.attrs.get('initial'),
|
||||
# }) for iface in interfaces
|
||||
# ]
|
||||
|
||||
|
||||
class CircuitFromCSVForm(forms.ModelForm):
|
||||
provider = forms.ModelChoiceField(Provider.objects.all(), to_field_name='name',
|
||||
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', 'tenant', 'install_date']
|
||||
# fields = ['cid', 'provider', 'type', 'tenant', 'site', 'install_date', 'port_speed', 'upstream_speed',
|
||||
# 'commit_rate', 'xconnect_id', 'pp_info']
|
||||
|
||||
|
||||
class CircuitImportForm(BulkImportForm, BootstrapMixin):
|
||||
csv = CSVDataField(csv_form=CircuitFromCSVForm)
|
||||
|
||||
|
||||
class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||
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()
|
||||
|
||||
|
||||
def circuit_type_choices():
|
||||
type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]
|
||||
|
||||
|
||||
def circuit_provider_choices():
|
||||
provider_choices = Provider.objects.annotate(circuit_count=Count('circuits'))
|
||||
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]
|
||||
|
||||
|
||||
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
model = Circuit
|
||||
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}))
|
||||
|
||||
#
|
||||
# Terminations
|
||||
#
|
||||
class TerminationForm(BootstrapMixin, CustomFieldForm):
|
||||
site = forms.ModelChoiceField(queryset=Site.objects.all(), widget=forms.Select(attrs={'filter-for': 'rack'}))
|
||||
rack = forms.ModelChoiceField(
|
||||
queryset=Rack.objects.all(),
|
||||
required=False,
|
||||
label='Rack',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/racks/?site_id={{site}}',
|
||||
attrs={'filter-for': 'device'}
|
||||
)
|
||||
)
|
||||
device = forms.ModelChoiceField(
|
||||
queryset=Device.objects.all(),
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/?rack_id={{rack}}',
|
||||
attrs={'filter-for': 'interface'}
|
||||
)
|
||||
)
|
||||
livesearch = forms.CharField(
|
||||
required=False,
|
||||
label='Device',
|
||||
widget=Livesearch(
|
||||
query_key='q',
|
||||
query_url='dcim-api:device_list',
|
||||
field_to_update='device'
|
||||
)
|
||||
)
|
||||
interface = forms.ModelChoiceField(
|
||||
queryset=Interface.objects.all(),
|
||||
required=False,
|
||||
label='Interface',
|
||||
widget=APISelect(
|
||||
api_url='/api/dcim/devices/{{device}}/interfaces/?type=physical',
|
||||
disabled_indicator='is_connected'
|
||||
)
|
||||
)
|
||||
comments = CommentField()
|
||||
|
||||
class Meta:
|
||||
model = Termination
|
||||
fields = [
|
||||
'tid', 'site', 'rack', 'device', 'livesearch',
|
||||
'interface','port_speed', 'upstream_speed', 'commit_rate',
|
||||
'xconnect_id', 'pp_info', 'comments'
|
||||
]
|
||||
help_texts = {
|
||||
'tid': "Termination ID",
|
||||
'port_speed': "Physical circuit speed",
|
||||
'commit_rate': "Commited rate",
|
||||
'xconnect_id': "ID of the local cross-connect",
|
||||
'pp_info': "Patch panel ID and port number(s)"
|
||||
}
|
||||
widgets = {
|
||||
'circuit': forms.HiddenInput(),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
|
||||
super(TerminationForm, self).__init__(*args, **kwargs)
|
||||
|
||||
# If this circuit has been assigned to an interface, initialize rack and device
|
||||
if self.instance.interface:
|
||||
self.initial['rack'] = self.instance.interface.device.rack
|
||||
@ -143,11 +312,11 @@ class CircuitForm(BootstrapMixin, CustomFieldForm):
|
||||
# Limit interface choices
|
||||
if self.is_bound and self.data.get('device'):
|
||||
interfaces = Interface.objects.filter(device=self.data['device'])\
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('termination', 'connected_as_a', 'connected_as_b')
|
||||
self.fields['interface'].widget.attrs['initial'] = self.data.get('interface')
|
||||
elif self.initial.get('device'):
|
||||
interfaces = Interface.objects.filter(device=self.initial['device'])\
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('termination', 'connected_as_a', 'connected_as_b')
|
||||
self.fields['interface'].widget.attrs['initial'] = self.initial.get('interface')
|
||||
else:
|
||||
interfaces = []
|
||||
@ -157,64 +326,3 @@ class CircuitForm(BootstrapMixin, CustomFieldForm):
|
||||
'disabled': iface.is_connected and iface.id != self.fields['interface'].widget.attrs.get('initial'),
|
||||
}) for iface in interfaces
|
||||
]
|
||||
|
||||
|
||||
class CircuitFromCSVForm(forms.ModelForm):
|
||||
provider = forms.ModelChoiceField(Provider.objects.all(), to_field_name='name',
|
||||
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', 'tenant', 'site', 'install_date', 'port_speed', 'upstream_speed',
|
||||
'commit_rate', 'xconnect_id', 'pp_info']
|
||||
|
||||
|
||||
class CircuitImportForm(BulkImportForm, BootstrapMixin):
|
||||
csv = CSVDataField(csv_form=CircuitFromCSVForm)
|
||||
|
||||
|
||||
class CircuitBulkEditForm(BootstrapMixin, CustomFieldBulkEditForm):
|
||||
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()
|
||||
|
||||
|
||||
def circuit_type_choices():
|
||||
type_choices = CircuitType.objects.annotate(circuit_count=Count('circuits'))
|
||||
return [(t.slug, u'{} ({})'.format(t.name, t.circuit_count)) for t in type_choices]
|
||||
|
||||
|
||||
def circuit_provider_choices():
|
||||
provider_choices = Provider.objects.annotate(circuit_count=Count('circuits'))
|
||||
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]
|
||||
|
||||
|
||||
class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
||||
model = Circuit
|
||||
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}))
|
||||
|
42
netbox/circuits/migrations/0006_auto_20160908_0213.py
Normal file
42
netbox/circuits/migrations/0006_auto_20160908_0213.py
Normal file
@ -0,0 +1,42 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-09-08 02:13
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dcim', '0018_device_add_asset_tag'),
|
||||
('circuits', '0005_circuit_add_upstream_speed'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Termination',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('created', models.DateField(auto_now_add=True)),
|
||||
('last_updated', models.DateTimeField(auto_now=True)),
|
||||
('tid', models.CharField(max_length=50, verbose_name=b'Termination ID')),
|
||||
('port_speed', models.PositiveIntegerField(verbose_name=b'Port speed (Kbps)')),
|
||||
('upstream_speed', models.PositiveIntegerField(blank=True, help_text=b'Upstream speed, if different from port speed', null=True, verbose_name=b'Upstream speed (Kbps)')),
|
||||
('commit_rate', models.PositiveIntegerField(blank=True, null=True, verbose_name=b'Commit rate (Kbps)')),
|
||||
('xconnect_id', models.CharField(blank=True, max_length=50, verbose_name=b'Cross-connect ID')),
|
||||
('pp_info', models.CharField(blank=True, max_length=100, verbose_name=b'Patch panel/port(s)')),
|
||||
('comments', models.TextField(blank=True)),
|
||||
('circuit', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='terminations', to='circuits.Circuit')),
|
||||
('interface', models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='termination', to='dcim.Interface')),
|
||||
('site', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='terminations', to='dcim.Site')),
|
||||
],
|
||||
options={
|
||||
'ordering': ['circuit', 'tid'],
|
||||
},
|
||||
),
|
||||
migrations.AlterUniqueTogether(
|
||||
name='termination',
|
||||
unique_together=set([('circuit', 'tid')]),
|
||||
),
|
||||
]
|
43
netbox/circuits/migrations/0007_auto_20160908_0220.py
Normal file
43
netbox/circuits/migrations/0007_auto_20160908_0220.py
Normal file
@ -0,0 +1,43 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-09-08 02:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0006_auto_20160908_0213'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='commit_rate',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='interface',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='port_speed',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='pp_info',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='site',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='upstream_speed',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='circuit',
|
||||
name='xconnect_id',
|
||||
),
|
||||
]
|
@ -0,0 +1,19 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-09-11 12:29
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0007_auto_20160908_0220'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RemoveField(
|
||||
model_name='termination',
|
||||
name='comments',
|
||||
),
|
||||
]
|
20
netbox/circuits/migrations/0009_termination_comments.py
Normal file
20
netbox/circuits/migrations/0009_termination_comments.py
Normal file
@ -0,0 +1,20 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10 on 2016-09-11 12:59
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('circuits', '0008_remove_termination_comments'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AddField(
|
||||
model_name='termination',
|
||||
name='comments',
|
||||
field=models.TextField(blank=True),
|
||||
),
|
||||
]
|
@ -1,15 +1,13 @@
|
||||
from django.contrib.contenttypes.fields import GenericRelation
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.db import models
|
||||
|
||||
from dcim.fields import ASNField
|
||||
from dcim.models import Site, Interface
|
||||
from extras.models import CustomFieldModel, CustomFieldValue
|
||||
from tenancy.models import Tenant
|
||||
from utilities.models import CreatedUpdatedModel
|
||||
|
||||
|
||||
class Provider(CreatedUpdatedModel, CustomFieldModel):
|
||||
class Provider(CreatedUpdatedModel):
|
||||
"""
|
||||
Each Circuit belongs to a Provider. This is usually a telecommunications company or similar organization. This model
|
||||
stores information pertinent to the user's relationship with the Provider.
|
||||
@ -22,7 +20,6 @@ class Provider(CreatedUpdatedModel, CustomFieldModel):
|
||||
noc_contact = models.TextField(blank=True, verbose_name='NOC contact')
|
||||
admin_contact = models.TextField(blank=True, verbose_name='Admin contact')
|
||||
comments = models.TextField(blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
class Meta:
|
||||
ordering = ['name']
|
||||
@ -61,7 +58,7 @@ class CircuitType(models.Model):
|
||||
return "{}?type={}".format(reverse('circuits:circuit_list'), self.slug)
|
||||
|
||||
|
||||
class Circuit(CreatedUpdatedModel, CustomFieldModel):
|
||||
class Circuit(CreatedUpdatedModel):
|
||||
"""
|
||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
||||
circuits. Each circuit is also assigned a CircuitType and a Site. A Circuit may be terminated to a specific device
|
||||
@ -71,17 +68,8 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
|
||||
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')
|
||||
port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
|
||||
upstream_speed = models.PositiveIntegerField(blank=True, null=True, verbose_name='Upstream speed (Kbps)',
|
||||
help_text='Upstream speed, if different from port speed')
|
||||
commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)')
|
||||
xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID')
|
||||
pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)')
|
||||
comments = models.TextField(blank=True)
|
||||
custom_field_values = GenericRelation(CustomFieldValue, content_type_field='obj_type', object_id_field='obj_id')
|
||||
|
||||
class Meta:
|
||||
ordering = ['provider', 'cid']
|
||||
@ -95,6 +83,42 @@ class Circuit(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
def to_csv(self):
|
||||
return ','.join([
|
||||
self.cid,
|
||||
self.provider.name,
|
||||
self.type.name,
|
||||
self.tenant.name if self.tenant else '',
|
||||
self.install_date.isoformat() if self.install_date else '',
|
||||
])
|
||||
|
||||
class Termination(CreatedUpdatedModel):
|
||||
"""
|
||||
A Termination is where a site
|
||||
"""
|
||||
circuit = models.ForeignKey('Circuit', related_name='terminations', on_delete=models.PROTECT)
|
||||
tid = models.CharField(max_length=50, verbose_name='Termination ID')
|
||||
site = models.ForeignKey(Site, related_name='terminations', on_delete=models.PROTECT)
|
||||
interface = models.OneToOneField(Interface, related_name='termination', blank=True, null=True)
|
||||
port_speed = models.PositiveIntegerField(verbose_name='Port speed (Kbps)')
|
||||
upstream_speed = models.PositiveIntegerField(blank=True, null=True, verbose_name='Upstream speed (Kbps)',
|
||||
help_text='Upstream speed, if different from port speed')
|
||||
commit_rate = models.PositiveIntegerField(blank=True, null=True, verbose_name='Commit rate (Kbps)')
|
||||
xconnect_id = models.CharField(max_length=50, blank=True, verbose_name='Cross-connect ID')
|
||||
pp_info = models.CharField(max_length=100, blank=True, verbose_name='Patch panel/port(s)')
|
||||
comments = models.TextField(blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ['circuit', 'tid']
|
||||
unique_together = ['circuit', 'tid']
|
||||
|
||||
def __unicode__(self):
|
||||
return u'{} Termination {}'.format(self.circuit.cid, self.tid)
|
||||
|
||||
def get_absolute_url(self):
|
||||
return reverse('circuits:circuit', args=[self.circuit.pk])
|
||||
|
||||
def to_csv(self):
|
||||
return ','.join([
|
||||
self.tid,
|
||||
self.cid,
|
||||
self.provider.name,
|
||||
self.type.name,
|
||||
|
@ -56,12 +56,10 @@ class CircuitTable(BaseTable):
|
||||
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 = tables.Column(accessor=Accessor('port_speed_human'), order_by=Accessor('port_speed'),
|
||||
verbose_name='Port Speed')
|
||||
commit_rate = tables.Column(accessor=Accessor('commit_rate_human'), order_by=Accessor('commit_rate'),
|
||||
verbose_name='Commit Rate')
|
||||
termination_count = tables.Column(accessor=Accessor('count_terminations'),
|
||||
verbose_name='Terminations')
|
||||
|
||||
|
||||
class Meta(BaseTable.Meta):
|
||||
model = Circuit
|
||||
fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'site', 'port_speed', 'commit_rate')
|
||||
fields = ('pk', 'cid', 'type', 'provider', 'tenant', 'termination_count')
|
||||
|
@ -31,4 +31,9 @@ urlpatterns = [
|
||||
url(r'^circuits/(?P<pk>\d+)/edit/$', views.CircuitEditView.as_view(), name='circuit_edit'),
|
||||
url(r'^circuits/(?P<pk>\d+)/delete/$', views.CircuitDeleteView.as_view(), name='circuit_delete'),
|
||||
|
||||
# Terminations
|
||||
url(r'^circuits/(?P<pk>\d+)/terminations/add/$', views.termination_add, name='termination_add'),
|
||||
url(r'^terminations/(?P<pk>\d+)/edit/$', views.TerminationEditView.as_view(), name='termination_edit'),
|
||||
url(r'^terminations/(?P<pk>\d+)/delete/$', views.termination_delete, name='termination_delete'),
|
||||
|
||||
]
|
||||
|
@ -1,14 +1,18 @@
|
||||
from django.contrib.auth.mixins import PermissionRequiredMixin
|
||||
from django.db.models import Count
|
||||
from django.shortcuts import get_object_or_404, render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.core.urlresolvers import reverse, resolve
|
||||
from django.contrib import messages
|
||||
|
||||
from extras.models import Graph, GRAPH_TYPE_PROVIDER
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.views import (
|
||||
BulkDeleteView, BulkEditView, BulkImportView, ObjectDeleteView, ObjectEditView, ObjectListView,
|
||||
)
|
||||
|
||||
from . import filters, forms, tables
|
||||
from .models import Circuit, CircuitType, Provider
|
||||
from .models import Circuit, CircuitType, Provider, Termination
|
||||
|
||||
|
||||
#
|
||||
@ -27,7 +31,7 @@ class ProviderListView(ObjectListView):
|
||||
def provider(request, slug):
|
||||
|
||||
provider = get_object_or_404(Provider, slug=slug)
|
||||
circuits = Circuit.objects.filter(provider=provider).select_related('site', 'interface__device')
|
||||
circuits = Circuit.objects.filter(provider=provider)
|
||||
show_graphs = Graph.objects.filter(type=GRAPH_TYPE_PROVIDER).exists()
|
||||
|
||||
return render(request, 'circuits/provider.html', {
|
||||
@ -103,7 +107,7 @@ class CircuitTypeBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
#
|
||||
|
||||
class CircuitListView(ObjectListView):
|
||||
queryset = Circuit.objects.select_related('provider', 'type', 'tenant', 'site')
|
||||
queryset = Circuit.objects.select_related('provider', 'type', 'tenant').annotate(count_terminations=Count('terminations'))
|
||||
filter = filters.CircuitFilter
|
||||
filter_form = forms.CircuitFilterForm
|
||||
table = tables.CircuitTable
|
||||
@ -155,3 +159,61 @@ class CircuitBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
|
||||
permission_required = 'circuits.delete_circuit'
|
||||
cls = Circuit
|
||||
default_redirect_url = 'circuits:circuit_list'
|
||||
|
||||
#
|
||||
# Terminations
|
||||
#
|
||||
@permission_required('circuits.change_circuit')
|
||||
def termination_add(request, pk):
|
||||
|
||||
circuit = get_object_or_404(Circuit, pk=pk)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = forms.TerminationForm(request.POST)
|
||||
if form.is_valid():
|
||||
if not form.errors:
|
||||
new_termination = form.save(commit=False)
|
||||
new_termination.circuit = circuit
|
||||
new_termination.save()
|
||||
if '_addanother' in request.POST:
|
||||
return redirect('circuits:termination_add', pk=circuit.pk)
|
||||
else:
|
||||
return redirect('circuits:circuit', pk=circuit.pk)
|
||||
|
||||
else:
|
||||
form = forms.TerminationForm()
|
||||
|
||||
return render(request, 'circuits/termination_edit.html', {
|
||||
'form': form,
|
||||
'obj_type': "Termination",
|
||||
'cancel_url': reverse('circuits:circuit', kwargs={'pk': circuit.pk}),
|
||||
})
|
||||
|
||||
class TerminationEditView(PermissionRequiredMixin, ObjectEditView):
|
||||
permission_required = 'circuits.change_circuit'
|
||||
model = Termination
|
||||
form_class = forms.TerminationForm
|
||||
fields_initial = ['site']
|
||||
template_name = 'circuits/termination_edit.html'
|
||||
cancel_url = 'circuits:circuit_list'
|
||||
|
||||
@permission_required('circuits.delete_circuit')
|
||||
def termination_delete(request, pk):
|
||||
|
||||
termination = get_object_or_404(Termination, pk=pk)
|
||||
|
||||
if request.method == 'POST':
|
||||
form = ConfirmationForm(request.POST)
|
||||
if form.is_valid():
|
||||
termination.delete()
|
||||
messages.success(request, "Termination {0} has been deleted from {1}".format(termination, termination.circuit))
|
||||
return redirect('circuits:circuit', pk=termination.circuit.pk)
|
||||
|
||||
else:
|
||||
form = ConfirmationForm()
|
||||
|
||||
return render(request, 'circuits/termination_delete.html', {
|
||||
'termination': termination,
|
||||
'form': form,
|
||||
'cancel_url': reverse('circuits:circuit', kwargs={'pk': termination.circuit.pk}),
|
||||
})
|
||||
|
@ -1121,7 +1121,7 @@ class InterfaceConnectionForm(forms.ModelForm, BootstrapMixin):
|
||||
|
||||
# Initialize interface A choices
|
||||
device_a_interfaces = Interface.objects.filter(device=device_a).exclude(form_factor=IFACE_FF_VIRTUAL) \
|
||||
.select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
.select_related('termination', 'connected_as_a', 'connected_as_b')
|
||||
self.fields['interface_a'].choices = [
|
||||
(iface.id, {'label': iface.name, 'disabled': iface.is_connected}) for iface in device_a_interfaces
|
||||
]
|
||||
@ -1137,10 +1137,10 @@ class InterfaceConnectionForm(forms.ModelForm, BootstrapMixin):
|
||||
# Initialize interface_b choices if device_b is set
|
||||
if self.is_bound:
|
||||
device_b_interfaces = Interface.objects.filter(device=self.data['device_b']) \
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('termination', 'connected_as_a', 'connected_as_b')
|
||||
elif self.initial.get('device_b'):
|
||||
device_b_interfaces = Interface.objects.filter(device=self.initial['device_b']) \
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('circuit', 'connected_as_a', 'connected_as_b')
|
||||
.exclude(form_factor=IFACE_FF_VIRTUAL).select_related('termination', 'connected_as_a', 'connected_as_b')
|
||||
else:
|
||||
device_b_interfaces = []
|
||||
self.fields['interface_b'].choices = [
|
||||
|
@ -269,7 +269,7 @@ class Site(CreatedUpdatedModel, CustomFieldModel):
|
||||
|
||||
@property
|
||||
def count_circuits(self):
|
||||
return self.circuits.count()
|
||||
return self.terminations.count()
|
||||
|
||||
|
||||
#
|
||||
@ -1052,7 +1052,7 @@ class Interface(models.Model):
|
||||
@property
|
||||
def is_connected(self):
|
||||
try:
|
||||
return bool(self.circuit)
|
||||
return bool(self.termination)
|
||||
except ObjectDoesNotExist:
|
||||
pass
|
||||
return bool(self.connection)
|
||||
|
@ -15,7 +15,7 @@ from django.utils.http import urlencode
|
||||
from django.views.generic import View
|
||||
|
||||
from ipam.models import Prefix, IPAddress, VLAN
|
||||
from circuits.models import Circuit
|
||||
from circuits.models import Circuit, Termination
|
||||
from extras.models import Graph, TopologyMap, GRAPH_TYPE_INTERFACE, GRAPH_TYPE_SITE
|
||||
from utilities.forms import ConfirmationForm
|
||||
from utilities.views import (
|
||||
@ -78,7 +78,7 @@ def site(request, slug):
|
||||
'device_count': Device.objects.filter(rack__site=site).count(),
|
||||
'prefix_count': Prefix.objects.filter(site=site).count(),
|
||||
'vlan_count': VLAN.objects.filter(site=site).count(),
|
||||
'circuit_count': Circuit.objects.filter(site=site).count(),
|
||||
'termination_count': Termination.objects.filter(site=site).count(),
|
||||
}
|
||||
rack_groups = RackGroup.objects.filter(site=site).annotate(rack_count=Count('racks'))
|
||||
topology_maps = TopologyMap.objects.filter(site=site)
|
||||
@ -554,9 +554,9 @@ def device(request, pk):
|
||||
PowerOutlet.objects.filter(device=device).select_related('connected_port'), key=attrgetter('name')
|
||||
)
|
||||
interfaces = Interface.objects.filter(device=device, mgmt_only=False)\
|
||||
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
||||
.select_related('connected_as_a', 'connected_as_b', 'termination')
|
||||
mgmt_interfaces = Interface.objects.filter(device=device, mgmt_only=True)\
|
||||
.select_related('connected_as_a', 'connected_as_b', 'circuit')
|
||||
.select_related('connected_as_a', 'connected_as_b', 'termination')
|
||||
device_bays = natsorted(
|
||||
DeviceBay.objects.filter(device=device).select_related('installed_device__device_type__manufacturer'),
|
||||
key=attrgetter('name')
|
||||
|
@ -42,3 +42,4 @@ urlpatterns = [
|
||||
url(r'^admin/', include(admin.site.urls)),
|
||||
|
||||
]
|
||||
|
||||
|
@ -27,6 +27,10 @@
|
||||
</div>
|
||||
<div class="pull-right">
|
||||
{% if perms.circuits.change_circuit %}
|
||||
<a href="{% url 'circuits:termination_add' pk=circuit.pk %}" class="btn btn-primary">
|
||||
<span class="glyphicon glyphicon-plus" aria-hidden="true"></span>
|
||||
Add termination
|
||||
</a>
|
||||
<a href="{% url 'circuits:circuit_edit' pk=circuit.pk %}" class="btn btn-warning">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
Edit this circuit
|
||||
@ -81,73 +85,6 @@
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Speed</td>
|
||||
<td>
|
||||
{% if circuit.upstream_speed %}
|
||||
<i class="fa fa-arrow-down" title="Downstream"></i> {{ circuit.port_speed_human }}
|
||||
<i class="fa fa-arrow-up" title="Upstream"></i> {{ circuit.upstream_speed_human }}
|
||||
{% else %}
|
||||
{{ circuit.port_speed_human }}
|
||||
{% endif %}
|
||||
</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>
|
||||
</table>
|
||||
</div>
|
||||
{% include 'inc/created_updated.html' with obj=circuit %}
|
||||
</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>
|
||||
<a href="{% url 'dcim:site' slug=circuit.site.slug %}">{{ circuit.site }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Termination</td>
|
||||
<td>
|
||||
{% if circuit.interface %}
|
||||
<span><a href="{% url 'dcim:device' pk=circuit.interface.device.pk %}">{{ circuit.interface.device }}</a> {{ circuit.interface }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Cross-Connect</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>
|
||||
{% if circuit.pp_info %}
|
||||
{{ circuit.pp_info }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
@ -162,6 +99,41 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% include 'inc/created_updated.html' with obj=circuit %}
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
{% for t in circuit.terminations.all %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<span class="pull-right">
|
||||
{% if perms.circuits.change_circuit %}
|
||||
<a href="{% url 'circuits:termination_edit' pk=t.pk %}" class="btn btn-info btn-xs" title="Edit interface">
|
||||
<i class="glyphicon glyphicon-pencil" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.circuits.delete_circuit %}
|
||||
<a href="{% url 'circuits:termination_delete' pk=t.pk %}" class="btn btn-danger btn-xs" title="Delete interface">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% endif %}
|
||||
</span>
|
||||
<strong>Termination ID {{t.tid }}</strong>
|
||||
</div>
|
||||
<table class="table table-hover panel-body">
|
||||
{% include 'circuits/inc/_termination.html' %}
|
||||
</table>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<strong>Termination</strong>
|
||||
</div>
|
||||
<div class="text-muted" colspan="7">
|
||||
None
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
@ -11,16 +11,6 @@
|
||||
{% render_field form.type %}
|
||||
{% render_field form.tenant %}
|
||||
{% render_field form.install_date %}
|
||||
{% 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.upstream_speed %}
|
||||
{% render_field form.commit_rate %}
|
||||
</div>
|
||||
</div>
|
||||
{% if form.custom_fields %}
|
||||
@ -31,26 +21,6 @@
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Termination</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.site %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
||||
<li role="presentation"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="select">
|
||||
{% render_field form.rack %}
|
||||
{% render_field form.device %}
|
||||
</div>
|
||||
<div class="tab-pane" id="search">
|
||||
{% render_field form.livesearch %}
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.interface %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Comments</strong></div>
|
||||
<div class="panel-body">
|
||||
|
49
netbox/templates/circuits/inc/_termination.html
Normal file
49
netbox/templates/circuits/inc/_termination.html
Normal file
@ -0,0 +1,49 @@
|
||||
{% load helpers %}
|
||||
<tr>
|
||||
<th>Site</th>
|
||||
<td>
|
||||
<a href="{% url 'dcim:site' slug=t.site.slug %}">{{ t.site }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Termination</th>
|
||||
<td>
|
||||
{% if t.interface %}
|
||||
<span><a href="{% url 'dcim:device' pk=t.interface.device.pk %}">{{ t.interface.device }}</a> {{ t.interface }}</span>
|
||||
{% else %}
|
||||
<span class="text-muted">Not defined</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Cross-Connect</th>
|
||||
<td>
|
||||
{% if t.xconnect_id %}
|
||||
{{ t.xconnect_id }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Patch Panel/Port</th>
|
||||
<td>
|
||||
{% if t.pp_info %}
|
||||
{{ t.pp_info }}
|
||||
{% else %}
|
||||
<span class="text-muted">N/A</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colspan="2">Comments</th>
|
||||
</tr>
|
||||
<tr>
|
||||
{% if t.comments %}
|
||||
<td colspan="2">
|
||||
{{ t.comments|gfm }}
|
||||
</td>
|
||||
{% else %}
|
||||
<td class="text-muted" colspan="2">None</td>
|
||||
{% endif %}
|
||||
</tr>
|
@ -133,15 +133,6 @@
|
||||
<td>
|
||||
<a href="{% url 'circuits:circuit' pk=c.pk %}">{{ c.cid }}</a>
|
||||
</td>
|
||||
<td>
|
||||
<a href="{% url 'dcim:site' slug=c.site.slug %}">{{ c.site }}</a>
|
||||
</td>
|
||||
<td>
|
||||
{% if c.interface %}
|
||||
<a href="{% url 'dcim:device' pk=c.interface.device.pk %}">{{ c.interface.device }}</a>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ c.port_speed_human }}</td>
|
||||
</tr>
|
||||
{% empty %}
|
||||
<tr>
|
||||
|
8
netbox/templates/circuits/termination_delete.html
Normal file
8
netbox/templates/circuits/termination_delete.html
Normal file
@ -0,0 +1,8 @@
|
||||
{% extends 'utilities/confirmation_form.html' %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block title %}Delete termination {{ termination }}?{% endblock %}
|
||||
|
||||
{% block message %}
|
||||
<p>Are you sure you want to delete this termination from <strong>{{ termination.circuit.cid }}</strong>?</p>
|
||||
{% endblock %}
|
61
netbox/templates/circuits/termination_edit.html
Normal file
61
netbox/templates/circuits/termination_edit.html
Normal file
@ -0,0 +1,61 @@
|
||||
{% extends 'utilities/obj_edit.html' %}
|
||||
{% load static from staticfiles %}
|
||||
{% load form_helpers %}
|
||||
|
||||
{% block form %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Termination</strong></div>
|
||||
<div class="panel-body">
|
||||
{{ form.circuit }}
|
||||
{% render_field form.tid %}
|
||||
{% 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.upstream_speed %}
|
||||
{% render_field form.commit_rate %}
|
||||
</div>
|
||||
</div>
|
||||
{% if form.custom_fields %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Custom Fields</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_custom_fields form %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Device & Interface</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.site %}
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li role="presentation" class="active"><a href="#select" aria-controls="home" role="tab" data-toggle="tab">Select</a></li>
|
||||
<li role="presentation"><a href="#search" aria-controls="search" role="tab" data-toggle="tab">Search</a></li>
|
||||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="select">
|
||||
{% render_field form.rack %}
|
||||
{% render_field form.device %}
|
||||
</div>
|
||||
<div class="tab-pane" id="search">
|
||||
{% render_field form.livesearch %}
|
||||
</div>
|
||||
</div>
|
||||
{% render_field form.interface %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading"><strong>Comments</strong></div>
|
||||
<div class="panel-body">
|
||||
{% render_field form.comments %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascript %}
|
||||
<script src="{% static 'js/livesearch.js' %}"></script>
|
||||
{% endblock %}
|
@ -24,9 +24,9 @@
|
||||
<span title="{{ connected_iface.get_form_factor_display }}">{{ connected_iface }}</span>
|
||||
</td>
|
||||
{% endwith %}
|
||||
{% elif iface.circuit %}
|
||||
{% elif iface.termination %}
|
||||
<td colspan="2">
|
||||
<a href="{% url 'circuits:circuit' pk=iface.circuit.pk %}">{{ iface.circuit }}</a>
|
||||
<a href="{% url 'circuits:circuit' pk=iface.termination.circuit.pk %}">{{ iface.termination.circuit }}</a>
|
||||
</td>
|
||||
{% else %}
|
||||
<td colspan="2">
|
||||
@ -35,7 +35,7 @@
|
||||
{% endif %}
|
||||
<td class="text-right">
|
||||
{% if show_graphs %}
|
||||
{% if iface.circuit or iface.connection %}
|
||||
{% if iface.termination 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>
|
||||
@ -56,8 +56,8 @@
|
||||
<a href="{% url 'dcim:interfaceconnection_delete' pk=iface.connection.pk %}?device={{ device.pk }}" class="btn btn-danger btn-xs" title="Delete connection">
|
||||
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% elif iface.circuit and perms.circuits.change_circuit %}
|
||||
<a href="{% url 'circuits:circuit_edit' pk=iface.circuit.pk %}" class="btn btn-danger btn-xs" title="Edit circuit">
|
||||
{% elif iface.termination and perms.circuits.change_circuit %}
|
||||
<a href="{% url 'circuits:circuit_edit' pk=iface.termination.circuit.pk %}" class="btn btn-danger btn-xs" title="Edit circuit">
|
||||
<i class="glyphicon glyphicon-remove" aria-hidden="true"></i>
|
||||
</a>
|
||||
{% else %}
|
||||
@ -71,7 +71,7 @@
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if perms.dcim.delete_interface %}
|
||||
{% if iface.connection or iface.circuit %}
|
||||
{% if iface.connection or iface.termination.circuit %}
|
||||
<button class="btn btn-danger btn-xs" disabled="disabled">
|
||||
<i class="glyphicon glyphicon-trash" aria-hidden="true"></i>
|
||||
</button>
|
||||
|
@ -151,8 +151,8 @@
|
||||
<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>
|
||||
<h2><a href="{% url 'circuits:circuit_list' %}?site={{ site.slug }}" class="btn {% if stats.termination_count %}btn-primary{% else %}btn-default{% endif %} btn-lg">{{ stats.termination_count }}</a></h2>
|
||||
<p>Terminations</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user