mirror of
https://github.com/netbox-community/netbox.git
synced 2025-07-24 17:38:37 -06:00
Extended Cables to connect CircuitTerminations
This commit is contained in:
parent
cbfb25f003
commit
4df74780b8
@ -3,7 +3,7 @@ from taggit_serializer.serializers import TaggitSerializer, TagListSerializerFie
|
|||||||
|
|
||||||
from circuits.constants import CIRCUIT_STATUS_CHOICES
|
from circuits.constants import CIRCUIT_STATUS_CHOICES
|
||||||
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
from circuits.models import Provider, Circuit, CircuitTermination, CircuitType
|
||||||
from dcim.api.serializers import NestedSiteSerializer, InterfaceSerializer
|
from dcim.api.serializers import NestedSiteSerializer
|
||||||
from extras.api.customfields import CustomFieldModelSerializer
|
from extras.api.customfields import CustomFieldModelSerializer
|
||||||
from tenancy.api.serializers import NestedTenantSerializer
|
from tenancy.api.serializers import NestedTenantSerializer
|
||||||
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
from utilities.api import ChoiceField, ValidatedModelSerializer, WritableNestedSerializer
|
||||||
@ -85,10 +85,18 @@ class NestedCircuitSerializer(WritableNestedSerializer):
|
|||||||
class CircuitTerminationSerializer(ValidatedModelSerializer):
|
class CircuitTerminationSerializer(ValidatedModelSerializer):
|
||||||
circuit = NestedCircuitSerializer()
|
circuit = NestedCircuitSerializer()
|
||||||
site = NestedSiteSerializer()
|
site = NestedSiteSerializer()
|
||||||
interface = InterfaceSerializer(required=False, allow_null=True)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'circuit', 'term_side', 'site', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
'id', 'circuit', 'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class NestedCircuitTerminationSerializer(WritableNestedSerializer):
|
||||||
|
url = serializers.HyperlinkedIdentityField(view_name='circuits-api:circuittermination-detail')
|
||||||
|
circuit = NestedCircuitSerializer()
|
||||||
|
|
||||||
|
class Meta:
|
||||||
|
model = CircuitTermination
|
||||||
|
fields = ['id', 'url', 'circuit', 'term_side']
|
||||||
|
@ -67,6 +67,6 @@ class CircuitViewSet(CustomFieldModelViewSet):
|
|||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTerminationViewSet(ModelViewSet):
|
class CircuitTerminationViewSet(ModelViewSet):
|
||||||
queryset = CircuitTermination.objects.select_related('circuit', 'site', 'interface__device')
|
queryset = CircuitTermination.objects.select_related('circuit', 'site')
|
||||||
serializer_class = serializers.CircuitTerminationSerializer
|
serializer_class = serializers.CircuitTerminationSerializer
|
||||||
filter_class = filters.CircuitTerminationFilter
|
filter_class = filters.CircuitTerminationFilter
|
||||||
|
@ -2,7 +2,7 @@ from django import forms
|
|||||||
from django.db.models import Count
|
from django.db.models import Count
|
||||||
from taggit.forms import TagField
|
from taggit.forms import TagField
|
||||||
|
|
||||||
from dcim.models import Site, Device, Interface, Rack
|
from dcim.models import Site, Device, Rack
|
||||||
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
from extras.forms import AddRemoveTagsForm, CustomFieldForm, CustomFieldBulkEditForm, CustomFieldFilterForm
|
||||||
from tenancy.forms import TenancyForm
|
from tenancy.forms import TenancyForm
|
||||||
from tenancy.models import Tenant
|
from tenancy.models import Tenant
|
||||||
@ -203,57 +203,12 @@ class CircuitFilterForm(BootstrapMixin, CustomFieldFilterForm):
|
|||||||
# Circuit terminations
|
# Circuit terminations
|
||||||
#
|
#
|
||||||
|
|
||||||
class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm):
|
class CircuitTerminationForm(BootstrapMixin, forms.ModelForm):
|
||||||
site = forms.ModelChoiceField(
|
|
||||||
queryset=Site.objects.all(),
|
|
||||||
widget=forms.Select(
|
|
||||||
attrs={'filter-for': 'rack'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
rack = ChainedModelChoiceField(
|
|
||||||
queryset=Rack.objects.all(),
|
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
),
|
|
||||||
required=False,
|
|
||||||
label='Rack',
|
|
||||||
widget=APISelect(
|
|
||||||
api_url='/api/dcim/racks/?site_id={{site}}',
|
|
||||||
attrs={'filter-for': 'device', 'nullable': 'true'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
device = ChainedModelChoiceField(
|
|
||||||
queryset=Device.objects.all(),
|
|
||||||
chains=(
|
|
||||||
('site', 'site'),
|
|
||||||
('rack', 'rack'),
|
|
||||||
),
|
|
||||||
required=False,
|
|
||||||
label='Device',
|
|
||||||
widget=APISelect(
|
|
||||||
api_url='/api/dcim/devices/?site_id={{site}}&rack_id={{rack}}',
|
|
||||||
display_field='display_name',
|
|
||||||
attrs={'filter-for': 'interface'}
|
|
||||||
)
|
|
||||||
)
|
|
||||||
interface = ChainedModelChoiceField(
|
|
||||||
queryset=Interface.objects.connectable().select_related('circuit_termination'),
|
|
||||||
chains=(
|
|
||||||
('device', 'device'),
|
|
||||||
),
|
|
||||||
required=False,
|
|
||||||
label='Interface',
|
|
||||||
widget=APISelect(
|
|
||||||
api_url='/api/dcim/interfaces/?device_id={{device}}&type=physical',
|
|
||||||
disabled_indicator='cable'
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = CircuitTermination
|
model = CircuitTermination
|
||||||
fields = [
|
fields = [
|
||||||
'term_side', 'site', 'rack', 'device', 'interface', 'port_speed', 'upstream_speed', 'xconnect_id',
|
'term_side', 'site', 'port_speed', 'upstream_speed', 'xconnect_id', 'pp_info',
|
||||||
'pp_info',
|
|
||||||
]
|
]
|
||||||
help_texts = {
|
help_texts = {
|
||||||
'port_speed': "Physical circuit speed",
|
'port_speed': "Physical circuit speed",
|
||||||
@ -263,25 +218,3 @@ class CircuitTerminationForm(BootstrapMixin, ChainedFieldsMixin, forms.ModelForm
|
|||||||
widgets = {
|
widgets = {
|
||||||
'term_side': forms.HiddenInput(),
|
'term_side': forms.HiddenInput(),
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
|
||||||
|
|
||||||
# Initialize helper selectors
|
|
||||||
instance = kwargs.get('instance')
|
|
||||||
if instance and instance.interface is not None:
|
|
||||||
initial = kwargs.get('initial', {}).copy()
|
|
||||||
initial['rack'] = instance.interface.device.rack
|
|
||||||
initial['device'] = instance.interface.device
|
|
||||||
kwargs['initial'] = initial
|
|
||||||
|
|
||||||
super(CircuitTerminationForm, self).__init__(*args, **kwargs)
|
|
||||||
|
|
||||||
# Mark occupied interfaces as disabled
|
|
||||||
self.fields['interface'].choices = []
|
|
||||||
for iface in self.fields['interface'].queryset:
|
|
||||||
self.fields['interface'].choices.append(
|
|
||||||
(iface.id, {
|
|
||||||
'label': iface.name,
|
|
||||||
'disabled': bool(iface.cable) and iface.pk != self.initial.get('interface'),
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
80
netbox/circuits/migrations/0013_cables.py
Normal file
80
netbox/circuits/migrations/0013_cables.py
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
from dcim.constants import CONNECTION_STATUS_CONNECTED
|
||||||
|
|
||||||
|
|
||||||
|
def circuit_terminations_to_cables(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Copy all existing CircuitTermination Interface associations as Cables
|
||||||
|
"""
|
||||||
|
ContentType = apps.get_model('contenttypes', 'ContentType')
|
||||||
|
CircuitTermination = apps.get_model('circuits', 'CircuitTermination')
|
||||||
|
Interface = apps.get_model('dcim', 'Interface')
|
||||||
|
Cable = apps.get_model('dcim', 'Cable')
|
||||||
|
|
||||||
|
# Load content types
|
||||||
|
circuittermination_type = ContentType.objects.get_for_model(CircuitTermination)
|
||||||
|
interface_type = ContentType.objects.get_for_model(Interface)
|
||||||
|
|
||||||
|
# Create a new Cable instance from each console connection
|
||||||
|
print("\n Adding circuit terminations... ", end='', flush=True)
|
||||||
|
for circuittermination in CircuitTermination.objects.filter(interface__isnull=False):
|
||||||
|
c = Cable()
|
||||||
|
|
||||||
|
# We have to assign all fields manually because we're inside a migration.
|
||||||
|
c.termination_a_type = circuittermination_type
|
||||||
|
c.termination_a_id = circuittermination.id
|
||||||
|
c.termination_b_type = interface_type
|
||||||
|
c.termination_b_id = circuittermination.interface_id
|
||||||
|
c.connection_status = CONNECTION_STATUS_CONNECTED
|
||||||
|
c.save()
|
||||||
|
|
||||||
|
# Cache the connected Cable on the CircuitTermination
|
||||||
|
circuittermination.cable = c
|
||||||
|
circuittermination.connected_endpoint = circuittermination.interface
|
||||||
|
circuittermination.connection_status = CONNECTION_STATUS_CONNECTED
|
||||||
|
circuittermination.save()
|
||||||
|
|
||||||
|
# Cache the connected Cable on the Interface
|
||||||
|
interface = circuittermination.interface
|
||||||
|
interface.cable = c
|
||||||
|
interface._connected_circuittermination = circuittermination
|
||||||
|
interface.connection_status = CONNECTION_STATUS_CONNECTED
|
||||||
|
interface.save()
|
||||||
|
|
||||||
|
cable_count = Cable.objects.filter(termination_a_type=circuittermination_type).count()
|
||||||
|
print("{} cables created".format(cable_count))
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
atomic = False
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('circuits', '0012_change_logging'),
|
||||||
|
('dcim', '0066_cables'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
|
||||||
|
# Add CircuitTermination.connected_endpoint
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='circuittermination',
|
||||||
|
name='connected_endpoint',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='circuittermination',
|
||||||
|
name='connection_status',
|
||||||
|
field=models.NullBooleanField(default=True),
|
||||||
|
),
|
||||||
|
|
||||||
|
# Copy CircuitTermination connections to Interfaces as Cables
|
||||||
|
migrations.RunPython(circuit_terminations_to_cables),
|
||||||
|
|
||||||
|
# Model changes
|
||||||
|
migrations.RemoveField(
|
||||||
|
model_name='circuittermination',
|
||||||
|
name='interface',
|
||||||
|
),
|
||||||
|
]
|
@ -3,7 +3,7 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
|
|
||||||
from dcim.constants import STATUS_CLASSES
|
from dcim.constants import CONNECTION_STATUS_CHOICES, CONNECTION_STATUS_CONNECTED, STATUS_CLASSES
|
||||||
from dcim.fields import ASNField
|
from dcim.fields import ASNField
|
||||||
from extras.models import CustomFieldModel, ObjectChange
|
from extras.models import CustomFieldModel, ObjectChange
|
||||||
from utilities.models import ChangeLoggedModel
|
from utilities.models import ChangeLoggedModel
|
||||||
@ -114,8 +114,8 @@ class CircuitType(ChangeLoggedModel):
|
|||||||
class Circuit(ChangeLoggedModel, CustomFieldModel):
|
class Circuit(ChangeLoggedModel, CustomFieldModel):
|
||||||
"""
|
"""
|
||||||
A communications circuit connects two points. Each Circuit belongs to a Provider; Providers may have multiple
|
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
|
circuits. Each circuit is also assigned a CircuitType and a Site. Circuit port speed and commit rate are measured
|
||||||
interface, but this is not required. Circuit port speed and commit rate are measured in Kbps.
|
in Kbps.
|
||||||
"""
|
"""
|
||||||
cid = models.CharField(
|
cid = models.CharField(
|
||||||
max_length=50,
|
max_length=50,
|
||||||
@ -227,13 +227,17 @@ class CircuitTermination(models.Model):
|
|||||||
on_delete=models.PROTECT,
|
on_delete=models.PROTECT,
|
||||||
related_name='circuit_terminations'
|
related_name='circuit_terminations'
|
||||||
)
|
)
|
||||||
interface = models.OneToOneField(
|
connected_endpoint = models.OneToOneField(
|
||||||
to='dcim.Interface',
|
to='dcim.Interface',
|
||||||
on_delete=models.PROTECT,
|
on_delete=models.SET_NULL,
|
||||||
related_name='circuit_termination',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
connection_status = models.NullBooleanField(
|
||||||
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
|
default=CONNECTION_STATUS_CONNECTED
|
||||||
|
)
|
||||||
port_speed = models.PositiveIntegerField(
|
port_speed = models.PositiveIntegerField(
|
||||||
verbose_name='Port speed (Kbps)'
|
verbose_name='Port speed (Kbps)'
|
||||||
)
|
)
|
||||||
|
@ -23,12 +23,6 @@ STATUS_LABEL = """
|
|||||||
class CircuitTerminationColumn(tables.Column):
|
class CircuitTerminationColumn(tables.Column):
|
||||||
|
|
||||||
def render(self, value):
|
def render(self, value):
|
||||||
if value.interface:
|
|
||||||
return mark_safe('<a href="{}" title="{}">{}</a>'.format(
|
|
||||||
value.interface.device.get_absolute_url(),
|
|
||||||
value.site,
|
|
||||||
value.interface.device
|
|
||||||
))
|
|
||||||
return mark_safe('<a href="{}">{}</a>'.format(
|
return mark_safe('<a href="{}">{}</a>'.format(
|
||||||
value.site.get_absolute_url(),
|
value.site.get_absolute_url(),
|
||||||
value.site
|
value.site
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from django.conf.urls import url
|
from django.conf.urls import url
|
||||||
|
|
||||||
|
from dcim.views import CableCreateView
|
||||||
from extras.views import ObjectChangeLogView
|
from extras.views import ObjectChangeLogView
|
||||||
from . import views
|
from . import views
|
||||||
from .models import Circuit, CircuitType, Provider
|
from .models import Circuit, CircuitTermination, CircuitType, Provider
|
||||||
|
|
||||||
app_name = 'circuits'
|
app_name = 'circuits'
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
@ -42,5 +43,6 @@ urlpatterns = [
|
|||||||
url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
|
url(r'^circuits/(?P<circuit>\d+)/terminations/add/$', views.CircuitTerminationCreateView.as_view(), name='circuittermination_add'),
|
||||||
url(r'^circuit-terminations/(?P<pk>\d+)/edit/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
|
url(r'^circuit-terminations/(?P<pk>\d+)/edit/$', views.CircuitTerminationEditView.as_view(), name='circuittermination_edit'),
|
||||||
url(r'^circuit-terminations/(?P<pk>\d+)/delete/$', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
|
url(r'^circuit-terminations/(?P<pk>\d+)/delete/$', views.CircuitTerminationDeleteView.as_view(), name='circuittermination_delete'),
|
||||||
|
url(r'^circuit-terminations/(?P<termination_a_id>\d+)/connect/$', CableCreateView.as_view(), name='circuittermination_connect', kwargs={'termination_a_type': CircuitTermination}),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
@ -132,7 +132,7 @@ class CircuitListView(ObjectListView):
|
|||||||
queryset = Circuit.objects.select_related(
|
queryset = Circuit.objects.select_related(
|
||||||
'provider', 'type', 'tenant'
|
'provider', 'type', 'tenant'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'terminations__site', 'terminations__interface__device'
|
'terminations__site'
|
||||||
)
|
)
|
||||||
filter = filters.CircuitFilter
|
filter = filters.CircuitFilter
|
||||||
filter_form = forms.CircuitFilterForm
|
filter_form = forms.CircuitFilterForm
|
||||||
@ -146,12 +146,12 @@ class CircuitView(View):
|
|||||||
|
|
||||||
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
|
circuit = get_object_or_404(Circuit.objects.select_related('provider', 'type', 'tenant__group'), pk=pk)
|
||||||
termination_a = CircuitTermination.objects.select_related(
|
termination_a = CircuitTermination.objects.select_related(
|
||||||
'site__region', 'interface__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_A
|
circuit=circuit, term_side=TERM_SIDE_A
|
||||||
).first()
|
).first()
|
||||||
termination_z = CircuitTermination.objects.select_related(
|
termination_z = CircuitTermination.objects.select_related(
|
||||||
'site__region', 'interface__device'
|
'site__region', 'connected_endpoint__device'
|
||||||
).filter(
|
).filter(
|
||||||
circuit=circuit, term_side=TERM_SIDE_Z
|
circuit=circuit, term_side=TERM_SIDE_Z
|
||||||
).first()
|
).first()
|
||||||
|
@ -696,8 +696,7 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
device = NestedDeviceSerializer()
|
device = NestedDeviceSerializer()
|
||||||
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
form_factor = ChoiceField(choices=IFACE_FF_CHOICES, required=False)
|
||||||
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
lag = NestedInterfaceSerializer(required=False, allow_null=True)
|
||||||
connected_endpoint = NestedInterfaceSerializer(read_only=True)
|
connected_endpoint = serializers.SerializerMethodField(read_only=True)
|
||||||
circuit_termination = InterfaceCircuitTerminationSerializer(read_only=True)
|
|
||||||
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
mode = ChoiceField(choices=IFACE_MODE_CHOICES, required=False, allow_null=True)
|
||||||
untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
|
untagged_vlan = InterfaceVLANSerializer(required=False, allow_null=True)
|
||||||
tagged_vlans = SerializedPKRelatedField(
|
tagged_vlans = SerializedPKRelatedField(
|
||||||
@ -713,7 +712,7 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
model = Interface
|
model = Interface
|
||||||
fields = [
|
fields = [
|
||||||
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
'id', 'device', 'name', 'form_factor', 'enabled', 'lag', 'mtu', 'mac_address', 'mgmt_only', 'description',
|
||||||
'connected_endpoint', 'circuit_termination', 'cable', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
|
'connected_endpoint', 'cable', 'mode', 'untagged_vlan', 'tagged_vlans', 'tags',
|
||||||
]
|
]
|
||||||
|
|
||||||
def validate(self, data):
|
def validate(self, data):
|
||||||
@ -735,6 +734,19 @@ class InterfaceSerializer(TaggitSerializer, ValidatedModelSerializer):
|
|||||||
|
|
||||||
return super(InterfaceSerializer, self).validate(data)
|
return super(InterfaceSerializer, self).validate(data)
|
||||||
|
|
||||||
|
def get_connected_endpoint(self, obj):
|
||||||
|
"""
|
||||||
|
Return the appropriate serializer for the type of connected object.
|
||||||
|
"""
|
||||||
|
if obj.connected_endpoint is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
serializer = get_serializer_for_model(obj.connected_endpoint, prefix='Nested')
|
||||||
|
context = {'request': self.context['request']}
|
||||||
|
data = serializer(obj.connected_endpoint, context=context).data
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
#
|
#
|
||||||
# Rear ports
|
# Rear ports
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.db.models import F
|
from django.db.models import F, Q
|
||||||
from django.http import HttpResponseForbidden
|
from django.http import HttpResponseForbidden
|
||||||
from django.shortcuts import get_object_or_404
|
from django.shortcuts import get_object_or_404
|
||||||
from drf_yasg import openapi
|
from drf_yasg import openapi
|
||||||
@ -407,7 +407,7 @@ class PowerOutletViewSet(CableTraceMixin, ModelViewSet):
|
|||||||
|
|
||||||
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
class InterfaceViewSet(CableTraceMixin, ModelViewSet):
|
||||||
queryset = Interface.objects.select_related(
|
queryset = Interface.objects.select_related(
|
||||||
'device', 'connected_endpoint__device', 'cable'
|
'device', '_connected_interface', '_connected_circuittermination', 'cable'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'tags'
|
'tags'
|
||||||
)
|
)
|
||||||
@ -483,10 +483,11 @@ class PowerConnectionViewSet(ListModelMixin, GenericViewSet):
|
|||||||
|
|
||||||
class InterfaceConnectionViewSet(ModelViewSet):
|
class InterfaceConnectionViewSet(ModelViewSet):
|
||||||
queryset = Interface.objects.select_related(
|
queryset = Interface.objects.select_related(
|
||||||
'device', 'connected_endpoint__device'
|
'device', '_connected_interface', '_connected_circuittermination'
|
||||||
).filter(
|
).filter(
|
||||||
connected_endpoint__isnull=False,
|
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||||
pk__lt=F('connected_endpoint')
|
Q(_connected_interface__isnull=False, pk__lt=F('_connected_interface')) |
|
||||||
|
Q(_connected_circuittermination__isnull=False)
|
||||||
)
|
)
|
||||||
serializer_class = serializers.InterfaceConnectionSerializer
|
serializer_class = serializers.InterfaceConnectionSerializer
|
||||||
filter_class = filters.InterfaceConnectionFilter
|
filter_class = filters.InterfaceConnectionFilter
|
||||||
|
@ -340,9 +340,10 @@ COMPATIBLE_TERMINATION_TYPES = {
|
|||||||
'consoleserverport': ['consoleport', 'frontport', 'rearport'],
|
'consoleserverport': ['consoleport', 'frontport', 'rearport'],
|
||||||
'powerport': ['poweroutlet'],
|
'powerport': ['poweroutlet'],
|
||||||
'poweroutlet': ['powerport'],
|
'poweroutlet': ['powerport'],
|
||||||
'interface': ['interface', 'frontport', 'rearport'],
|
'interface': ['interface', 'circuittermination', 'frontport', 'rearport'],
|
||||||
'frontport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport'],
|
'frontport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport'],
|
||||||
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport'],
|
'rearport': ['consoleport', 'consoleserverport', 'interface', 'frontport', 'rearport'],
|
||||||
|
'circuittermination': ['interface', 'frontport', 'rearport'],
|
||||||
}
|
}
|
||||||
|
|
||||||
LENGTH_UNIT_METER = 'm'
|
LENGTH_UNIT_METER = 'm'
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import django_filters
|
import django_filters
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import Count, Q
|
from django.db.models import Q
|
||||||
from netaddr import EUI
|
from netaddr import EUI
|
||||||
from netaddr.core import AddrFormatError
|
from netaddr.core import AddrFormatError
|
||||||
|
|
||||||
@ -876,7 +876,7 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__site__slug=value) |
|
Q(device__site__slug=value) |
|
||||||
Q(connected_endpoint__device__site__slug=value)
|
Q(_connected_interface__device__site__slug=value)
|
||||||
)
|
)
|
||||||
|
|
||||||
def filter_device(self, queryset, name, value):
|
def filter_device(self, queryset, name, value):
|
||||||
@ -884,5 +884,5 @@ class InterfaceConnectionFilter(django_filters.FilterSet):
|
|||||||
return queryset
|
return queryset
|
||||||
return queryset.filter(
|
return queryset.filter(
|
||||||
Q(device__name__icontains=value) |
|
Q(device__name__icontains=value) |
|
||||||
Q(connected_endpoint__device__name__icontains=value)
|
Q(_connected_interface__device__name__icontains=value)
|
||||||
)
|
)
|
||||||
|
@ -88,28 +88,26 @@ def interface_connections_to_cables(apps, schema_editor):
|
|||||||
for conn in InterfaceConnection.objects.all():
|
for conn in InterfaceConnection.objects.all():
|
||||||
c = Cable()
|
c = Cable()
|
||||||
|
|
||||||
# We have to assign GFK fields manually because we're inside a migration.
|
# We have to assign all fields manually because we're inside a migration.
|
||||||
c.termination_a_type = interface_type
|
c.termination_a_type = interface_type
|
||||||
c.termination_a_id = conn.interface_a_id
|
c.termination_a_id = conn.interface_a_id
|
||||||
c.termination_a = conn.interface_a
|
|
||||||
c.termination_b_type = interface_type
|
c.termination_b_type = interface_type
|
||||||
c.termination_b_id = conn.interface_b_id
|
c.termination_b_id = conn.interface_b_id
|
||||||
c.termination_b = conn.interface_b
|
|
||||||
c.connection_status = conn.connection_status
|
c.connection_status = conn.connection_status
|
||||||
c.save()
|
c.save()
|
||||||
|
|
||||||
# connected_endpoint and connection_status must be manually assigned
|
# Cache the connected Cable on each Interface
|
||||||
# since these are new fields on Interface
|
interface_a = conn.interface_a
|
||||||
Interface.objects.filter(pk=conn.interface_a_id).update(
|
interface_a._connected_interface = conn.interface_b
|
||||||
connected_endpoint=conn.interface_b_id,
|
interface_a.connection_status = conn.connection_status
|
||||||
connection_status=conn.connection_status,
|
interface_a.cable = c
|
||||||
cable=c
|
interface_a.save()
|
||||||
)
|
|
||||||
Interface.objects.filter(pk=conn.interface_b_id).update(
|
interface_b = conn.interface_b
|
||||||
connected_endpoint=conn.interface_a_id,
|
interface_b._connected_interface = conn.interface_a
|
||||||
connection_status=conn.connection_status,
|
interface_b.connection_status = conn.connection_status
|
||||||
cable=c
|
interface_b.cable = c
|
||||||
)
|
interface_b.save()
|
||||||
|
|
||||||
cable_count = Cable.objects.filter(termination_a_type=interface_type).count()
|
cable_count = Cable.objects.filter(termination_a_type=interface_type).count()
|
||||||
print("{} cables created".format(cable_count))
|
print("{} cables created".format(cable_count))
|
||||||
@ -120,6 +118,7 @@ class Migration(migrations.Migration):
|
|||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
('contenttypes', '0002_remove_content_type_name'),
|
('contenttypes', '0002_remove_content_type_name'),
|
||||||
|
('circuits', '0006_terminations'),
|
||||||
('dcim', '0065_front_rear_ports'),
|
('dcim', '0065_front_rear_ports'),
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -217,7 +216,12 @@ class Migration(migrations.Migration):
|
|||||||
# Alter the Interface model
|
# Alter the Interface model
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name='interface',
|
model_name='interface',
|
||||||
name='connected_endpoint',
|
name='_connected_circuittermination',
|
||||||
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='circuits.CircuitTermination'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='interface',
|
||||||
|
name='_connected_interface',
|
||||||
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'),
|
field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='+', to='dcim.Interface'),
|
||||||
),
|
),
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
|
@ -15,7 +15,7 @@ from mptt.models import MPTTModel, TreeForeignKey
|
|||||||
from taggit.managers import TaggableManager
|
from taggit.managers import TaggableManager
|
||||||
from timezone_field import TimeZoneField
|
from timezone_field import TimeZoneField
|
||||||
|
|
||||||
from circuits.models import Circuit
|
from circuits.models import Circuit, CircuitTermination
|
||||||
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange
|
from extras.models import ConfigContextModel, CustomFieldModel, ObjectChange
|
||||||
from utilities.fields import ColorField, NullableCharField
|
from utilities.fields import ColorField, NullableCharField
|
||||||
from utilities.managers import NaturalOrderByManager
|
from utilities.managers import NaturalOrderByManager
|
||||||
@ -1843,13 +1843,20 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
name = models.CharField(
|
name = models.CharField(
|
||||||
max_length=64
|
max_length=64
|
||||||
)
|
)
|
||||||
connected_endpoint = models.OneToOneField(
|
_connected_interface = models.OneToOneField(
|
||||||
to='self',
|
to='self',
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
related_name='+',
|
related_name='+',
|
||||||
blank=True,
|
blank=True,
|
||||||
null=True
|
null=True
|
||||||
)
|
)
|
||||||
|
_connected_circuittermination = models.OneToOneField(
|
||||||
|
to='circuits.CircuitTermination',
|
||||||
|
on_delete=models.SET_NULL,
|
||||||
|
related_name='+',
|
||||||
|
blank=True,
|
||||||
|
null=True
|
||||||
|
)
|
||||||
connection_status = models.NullBooleanField(
|
connection_status = models.NullBooleanField(
|
||||||
choices=CONNECTION_STATUS_CHOICES,
|
choices=CONNECTION_STATUS_CHOICES,
|
||||||
default=CONNECTION_STATUS_CONNECTED
|
default=CONNECTION_STATUS_CONNECTED
|
||||||
@ -2008,6 +2015,28 @@ class Interface(CableTermination, ComponentModel):
|
|||||||
object_data=serialize_object(self)
|
object_data=serialize_object(self)
|
||||||
).save()
|
).save()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def connected_endpoint(self):
|
||||||
|
if self._connected_interface:
|
||||||
|
return self._connected_interface
|
||||||
|
return self._connected_circuittermination
|
||||||
|
|
||||||
|
@connected_endpoint.setter
|
||||||
|
def connected_endpoint(self, value):
|
||||||
|
if value is None:
|
||||||
|
self._connected_interface = None
|
||||||
|
self._connected_circuittermination = None
|
||||||
|
elif isinstance(value, Interface):
|
||||||
|
self._connected_interface = value
|
||||||
|
self._connected_circuittermination = None
|
||||||
|
elif isinstance(value, CircuitTermination):
|
||||||
|
self._connected_interface = None
|
||||||
|
self._connected_circuittermination = value
|
||||||
|
else:
|
||||||
|
raise ValueError(
|
||||||
|
"Connected endpoint must be an Interface or CircuitTermination, not {}.".format(type(value))
|
||||||
|
)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def parent(self):
|
def parent(self):
|
||||||
return self.device or self.virtual_machine
|
return self.device or self.virtual_machine
|
||||||
|
@ -904,7 +904,7 @@ class DeviceView(View):
|
|||||||
interfaces = device.vc_interfaces.order_naturally(
|
interfaces = device.vc_interfaces.order_naturally(
|
||||||
device.device_type.interface_ordering
|
device.device_type.interface_ordering
|
||||||
).select_related(
|
).select_related(
|
||||||
'lag', 'connected_endpoint__device', 'circuit_termination__circuit', 'cable'
|
'lag', '_connected_interface__device', '_connected_circuittermination__circuit', 'cable'
|
||||||
).prefetch_related(
|
).prefetch_related(
|
||||||
'cable__termination_a', 'cable__termination_b', 'ip_addresses'
|
'cable__termination_a', 'cable__termination_b', 'ip_addresses'
|
||||||
)
|
)
|
||||||
@ -999,7 +999,7 @@ class DeviceLLDPNeighborsView(PermissionRequiredMixin, View):
|
|||||||
interfaces = device.vc_interfaces.order_naturally(
|
interfaces = device.vc_interfaces.order_naturally(
|
||||||
device.device_type.interface_ordering
|
device.device_type.interface_ordering
|
||||||
).connectable().select_related(
|
).connectable().select_related(
|
||||||
'connected_endpoint__device'
|
'_connected_interface__device'
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(request, 'dcim/device_lldp_neighbors.html', {
|
return render(request, 'dcim/device_lldp_neighbors.html', {
|
||||||
@ -1667,13 +1667,6 @@ class InterfaceView(View):
|
|||||||
|
|
||||||
interface = get_object_or_404(Interface, pk=pk)
|
interface = get_object_or_404(Interface, pk=pk)
|
||||||
|
|
||||||
# Get connected interface
|
|
||||||
connected_interface = interface.connected_endpoint
|
|
||||||
if connected_interface is None and hasattr(interface, 'circuit_termination'):
|
|
||||||
peer_termination = interface.circuit_termination.get_peer_termination()
|
|
||||||
if peer_termination is not None:
|
|
||||||
connected_interface = peer_termination.interface
|
|
||||||
|
|
||||||
# Get assigned IP addresses
|
# Get assigned IP addresses
|
||||||
ipaddress_table = InterfaceIPAddressTable(
|
ipaddress_table = InterfaceIPAddressTable(
|
||||||
data=interface.ip_addresses.select_related('vrf', 'tenant'),
|
data=interface.ip_addresses.select_related('vrf', 'tenant'),
|
||||||
@ -1696,7 +1689,8 @@ class InterfaceView(View):
|
|||||||
|
|
||||||
return render(request, 'dcim/interface.html', {
|
return render(request, 'dcim/interface.html', {
|
||||||
'interface': interface,
|
'interface': interface,
|
||||||
'connected_interface': connected_interface,
|
# TODO: Also handle connected CircuitTerminations
|
||||||
|
'connected_interface': interface._connected_interface,
|
||||||
'ipaddress_table': ipaddress_table,
|
'ipaddress_table': ipaddress_table,
|
||||||
'vlan_table': vlan_table,
|
'vlan_table': vlan_table,
|
||||||
})
|
})
|
||||||
@ -1736,8 +1730,8 @@ class InterfaceBulkDisconnectView(PermissionRequiredMixin, BulkDisconnectView):
|
|||||||
form = forms.InterfaceBulkDisconnectForm
|
form = forms.InterfaceBulkDisconnectForm
|
||||||
|
|
||||||
def disconnect_objects(self, interfaces):
|
def disconnect_objects(self, interfaces):
|
||||||
return Interface.objects.filter(connected_endpoint__in=interfaces).update(
|
return Interface.objects.filter(_connected_interface__in=interfaces).update(
|
||||||
connected_endpoint=None, connection_status=None
|
_connected_interface=None, connection_status=None
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -2103,10 +2097,11 @@ class PowerConnectionsListView(ObjectListView):
|
|||||||
|
|
||||||
class InterfaceConnectionsListView(ObjectListView):
|
class InterfaceConnectionsListView(ObjectListView):
|
||||||
queryset = Interface.objects.select_related(
|
queryset = Interface.objects.select_related(
|
||||||
'connected_endpoint__device',
|
'_connected_interface', '_connected_circuittermination'
|
||||||
).filter(
|
).filter(
|
||||||
connected_endpoint__isnull=False,
|
# Avoid duplicate connections by only selecting the lower PK in a connected pair
|
||||||
pk__lt=F('connected_endpoint'),
|
_connected_interface__isnull=False,
|
||||||
|
pk__lt=F('_connected_interface')
|
||||||
)
|
)
|
||||||
filter = filters.InterfaceConnectionFilter
|
filter = filters.InterfaceConnectionFilter
|
||||||
filter_form = forms.InterfaceConnectionFilterForm
|
filter_form = forms.InterfaceConnectionFilterForm
|
||||||
|
@ -508,17 +508,17 @@ class TopologyMap(models.Model):
|
|||||||
|
|
||||||
# Add all interface connections to the graph
|
# Add all interface connections to the graph
|
||||||
connected_interfaces = Interface.objects.select_related(
|
connected_interfaces = Interface.objects.select_related(
|
||||||
'connected_endpoint__device'
|
'_connected_interface__device'
|
||||||
).filter(
|
).filter(
|
||||||
Q(device__in=devices) | Q(connected_endpoint__device__in=devices),
|
Q(device__in=devices) | Q(_connected_interface__device__in=devices),
|
||||||
connected_endpoint__isnull=False,
|
_connected_interface__isnull=False,
|
||||||
)
|
)
|
||||||
for interface in connected_interfaces:
|
for interface in connected_interfaces:
|
||||||
style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
|
style = 'solid' if interface.connection_status == CONNECTION_STATUS_CONNECTED else 'dashed'
|
||||||
self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style)
|
self.graph.edge(interface.device.name, interface.connected_endpoint.device.name, style=style)
|
||||||
|
|
||||||
# Add all circuits to the graph
|
# Add all circuits to the graph
|
||||||
for termination in CircuitTermination.objects.filter(term_side='A', interface__device__in=devices):
|
for termination in CircuitTermination.objects.filter(term_side='A', connected_endpoint__device__in=devices):
|
||||||
peer_termination = termination.get_peer_termination()
|
peer_termination = termination.get_peer_termination()
|
||||||
if (peer_termination is not None and peer_termination.interface is not None and
|
if (peer_termination is not None and peer_termination.interface is not None and
|
||||||
peer_termination.interface.device in devices):
|
peer_termination.interface.device in devices):
|
||||||
|
@ -163,8 +163,8 @@ class HomeView(View):
|
|||||||
connected_endpoint__isnull=False
|
connected_endpoint__isnull=False
|
||||||
)
|
)
|
||||||
connected_interfaces = Interface.objects.filter(
|
connected_interfaces = Interface.objects.filter(
|
||||||
connected_endpoint__isnull=False,
|
_connected_interface__isnull=False,
|
||||||
pk__lt=F('connected_endpoint')
|
pk__lt=F('_connected_interface')
|
||||||
)
|
)
|
||||||
|
|
||||||
stats = {
|
stats = {
|
||||||
|
@ -41,9 +41,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% render_field form.site %}
|
{% render_field form.site %}
|
||||||
{% render_field form.rack %}
|
|
||||||
{% render_field form.device %}
|
|
||||||
{% render_field form.interface %}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel panel-default">
|
<div class="panel panel-default">
|
||||||
|
@ -39,10 +39,17 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>Termination</td>
|
<td>Termination</td>
|
||||||
<td>
|
<td>
|
||||||
{% if termination.interface %}
|
{% if termination.connected_endpoint %}
|
||||||
<a href="{% url 'dcim:device' pk=termination.interface.device.pk %}">{{ termination.interface.device }}</a>
|
<a href="{% url 'dcim:device' pk=termination.connected_endpoint.device.pk %}">{{ termination.connected_endpoint.device }}</a>
|
||||||
<i class="fa fa-angle-right"></i> {{ termination.interface }}
|
<i class="fa fa-angle-right"></i> {{ termination.connected_endpoint }}
|
||||||
{% else %}
|
{% else %}
|
||||||
|
{% if perms.circuits.change_circuittermination %}
|
||||||
|
<div class="pull-right">
|
||||||
|
<a href="{% url 'circuits:circuittermination_connect' termination_a_id=termination.pk %}?return_url={{ circuit.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
|
||||||
|
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i> Connect
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
<span class="text-muted">Not defined</span>
|
<span class="text-muted">Not defined</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
@ -61,8 +68,8 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>IP Addressing</td>
|
<td>IP Addressing</td>
|
||||||
<td>
|
<td>
|
||||||
{% if termination.interface %}
|
{% if termination.connected_endpoint %}
|
||||||
{% for ip in termination.interface.ip_addresses.all %}
|
{% for ip in termination.connected_endpoint.ip_addresses.all %}
|
||||||
{% if not forloop.first %}<br />{% endif %}
|
{% if not forloop.first %}<br />{% endif %}
|
||||||
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a> ({{ ip.vrf|default:"Global" }})
|
<a href="{% url 'ipam:ipaddress' pk=ip.pk %}">{{ ip }}</a> ({{ ip.vrf|default:"Global" }})
|
||||||
{% empty %}
|
{% empty %}
|
||||||
|
@ -29,30 +29,59 @@
|
|||||||
<strong>A Side</strong>
|
<strong>A Side</strong>
|
||||||
</div>
|
</div>
|
||||||
<div class="panel-body">
|
<div class="panel-body">
|
||||||
<div class="form-group">
|
{% if termination_a.device %}
|
||||||
<label class="col-md-3 control-label required">Site</label>
|
{# Device component #}
|
||||||
<div class="col-md-9">
|
<div class="form-group">
|
||||||
<p class="form-control-static">{{ termination_a.device.site }}</p>
|
<label class="col-md-3 control-label required">Site</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ termination_a.device.site }}</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="col-md-3 control-label required">Rack</label>
|
||||||
<label class="col-md-3 control-label required">Rack</label>
|
<div class="col-md-9">
|
||||||
<div class="col-md-9">
|
<p class="form-control-static">{{ termination_a.device.rack|default:"None" }}</p>
|
||||||
<p class="form-control-static">{{ termination_a.device.rack|default:"None" }}</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="col-md-3 control-label required">Device</label>
|
||||||
<label class="col-md-3 control-label required">Device</label>
|
<div class="col-md-9">
|
||||||
<div class="col-md-9">
|
<p class="form-control-static">{{ termination_a.device }}</p>
|
||||||
<p class="form-control-static">{{ termination_a.device }}</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
<div class="form-group">
|
<label class="col-md-3 control-label required">Name</label>
|
||||||
<label class="col-md-3 control-label required">Name</label>
|
<div class="col-md-9">
|
||||||
<div class="col-md-9">
|
<p class="form-control-static">{{ termination_a }}</p>
|
||||||
<p class="form-control-static">{{ termination_a }}</p>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% else %}
|
||||||
|
{# Circuit termination #}
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Site</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ termination_a.site }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Provider</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ termination_a.circuit.provider }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Circuit</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ termination_a.circuit.cid }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-md-3 control-label required">Side</label>
|
||||||
|
<div class="col-md-9">
|
||||||
|
<p class="form-control-static">{{ termination_a.term_side }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,12 +1,29 @@
|
|||||||
<table class="table table-hover panel-body attr-table">
|
<table class="table table-hover panel-body attr-table">
|
||||||
<tr>
|
{% if termination.device %}
|
||||||
<td>Device</td>
|
{# Device component #}
|
||||||
<td>
|
<tr>
|
||||||
<a href="{{ termination.device.get_absolute_url }}">{{ termination.device }}</a>
|
<td>Device</td>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<a href="{{ termination.device.get_absolute_url }}">{{ termination.device }}</a>
|
||||||
<tr>
|
</td>
|
||||||
<td>Component</td>
|
</tr>
|
||||||
<td>{{ termination }}</td>
|
<tr>
|
||||||
</tr>
|
<td>Component</td>
|
||||||
|
<td>{{ termination }}</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
{# Circuit termination #}
|
||||||
|
<tr>
|
||||||
|
<td>Provider</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ termination.circuit.provider.get_absolute_url }}">{{ termination.circuit.provider }}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td>Circuit</td>
|
||||||
|
<td>
|
||||||
|
<a href="{{ termination.circuit.get_absolute_url }}">{{ termination.circuit }}</a> (Side {{ termination.term_side }})
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
</table>
|
</table>
|
||||||
|
@ -50,17 +50,17 @@
|
|||||||
<td colspan="2" class="text-muted">Virtual interface</td>
|
<td colspan="2" class="text-muted">Virtual interface</td>
|
||||||
{% elif iface.is_wireless %}
|
{% elif iface.is_wireless %}
|
||||||
<td colspan="2" class="text-muted">Wireless interface</td>
|
<td colspan="2" class="text-muted">Wireless interface</td>
|
||||||
{% elif iface.connected_endpoint %}
|
{% elif iface.connected_endpoint.name %}
|
||||||
{% with connected_iface=iface.connected_endpoint %}
|
{# Connected to an Interface #}
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:device' pk=connected_iface.device.pk %}">{{ connected_iface.device }}</a>
|
<a href="{% url 'dcim:device' pk=iface.connected_endpoint.device.pk %}">{{ iface.connected_endpoint.device }}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<a href="{% url 'dcim:interface' pk=connected_iface.pk %}"><span title="{{ connected_iface.get_form_factor_display }}">{{ connected_iface }}</span></a>
|
<a href="{% url 'dcim:interface' pk=iface.connected_endpoint.pk %}"><span title="{{ iface.connected_endpoint.get_form_factor_display }}">{{ iface.connected_endpoint }}</span></a>
|
||||||
</td>
|
</td>
|
||||||
{% endwith %}
|
{% elif iface.connected_endpoint.term_side %}
|
||||||
{% elif iface.circuit_termination %}
|
{# Connected to a CircuitTermination #}
|
||||||
{% with iface.circuit_termination.get_peer_termination as peer_termination %}
|
{% with iface.connected_endpoint.get_peer_termination as peer_termination %}
|
||||||
<td colspan="2">
|
<td colspan="2">
|
||||||
<i class="fa fa-fw fa-globe" title="Circuit"></i>
|
<i class="fa fa-fw fa-globe" title="Circuit"></i>
|
||||||
{% if peer_termination %}
|
{% if peer_termination %}
|
||||||
@ -72,7 +72,7 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
via
|
via
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<a href="{% url 'circuits:circuit' pk=iface.circuit_termination.circuit_id %}">{{ iface.circuit_termination.circuit }}</a>
|
<a href="{% url 'circuits:circuit' pk=iface.connected_endpoint.circuit_id %}">{{ iface.connected_endpoint.circuit }}</a>
|
||||||
</td>
|
</td>
|
||||||
{% endwith %}
|
{% endwith %}
|
||||||
{% else %}
|
{% else %}
|
||||||
@ -84,7 +84,7 @@
|
|||||||
{# Buttons #}
|
{# Buttons #}
|
||||||
<td class="text-right text-nowrap">
|
<td class="text-right text-nowrap">
|
||||||
{% if show_graphs %}
|
{% if show_graphs %}
|
||||||
{% if iface.circuit_termination or iface.connected_endpoint %}
|
{% if iface.connected_endpoint %}
|
||||||
<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">
|
<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>
|
<i class="glyphicon glyphicon-signal" aria-hidden="true"></i>
|
||||||
</button>
|
</button>
|
||||||
@ -110,13 +110,6 @@
|
|||||||
<a href="{% url 'dcim:cable_delete' pk=iface.cable.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs" title="Remove cable">
|
<a href="{% url 'dcim:cable_delete' pk=iface.cable.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs" title="Remove cable">
|
||||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
||||||
</a>
|
</a>
|
||||||
{% elif iface.circuit_termination and perms.circuits.change_circuittermination %}
|
|
||||||
<button class="btn btn-warning btn-xs interface-toggle connected" disabled="disabled" title="Circuits cannot be marked as planned or connected">
|
|
||||||
<i class="glyphicon glyphicon-ban-circle" aria-hidden="true"></i>
|
|
||||||
</button>
|
|
||||||
<a href="{% url 'circuits:circuittermination_edit' pk=iface.circuit_termination.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-danger btn-xs" title="Edit circuit termination">
|
|
||||||
<i class="glyphicon glyphicon-resize-full" aria-hidden="true"></i>
|
|
||||||
</a>
|
|
||||||
{% else %}
|
{% else %}
|
||||||
<a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
|
<a href="{% url 'dcim:interface_connect' termination_a_id=iface.pk %}?return_url={{ device.get_absolute_url }}" class="btn btn-success btn-xs" title="Connect">
|
||||||
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
<i class="glyphicon glyphicon-resize-small" aria-hidden="true"></i>
|
||||||
|
Loading…
Reference in New Issue
Block a user