Răsfoiți Sursa

Closes #971: Implement VLANGroup VLANs view to show available VLANs within a group

Vas Sadvariy 7 ani în urmă
părinte
comite
33e45a5292

+ 1 - 1
netbox/ipam/models.py

@@ -528,7 +528,7 @@ class VLANGroup(models.Model):
         return self.name
 
     def get_absolute_url(self):
-        return "{}?group_id={}".format(reverse('ipam:vlan_list'), self.pk)
+        return reverse('ipam:vlangroup_vlans', args=[self.pk])
 
     def to_csv(self):
         return (

+ 15 - 2
netbox/ipam/tables.py

@@ -110,6 +110,16 @@ STATUS_LABEL = """
 {% endif %}
 """
 
+VLAN_LINK = """
+{% if record.pk %}
+    <a href="{{ record.get_absolute_url }}">{{ record.vid }}</a>
+{% elif perms.ipam.add_vlan %}
+    <a href="{% url 'ipam:vlan_add' %}?vid={{ record.vid }}&group={{ vlan_group.pk }}{% if vlan_group.site %}&site={{ vlan_group.site.pk }}{% endif %}" class="btn btn-xs btn-success">{{ record.available }} VLAN{{ record.available|pluralize }} available</a>
+{% else %}
+    {{ record.available }} VLAN{{ record.available|pluralize }} available
+{% endif %}
+"""
+
 VLAN_PREFIXES = """
 {% for prefix in record.prefixes.all %}
     <a href="{% url 'ipam:prefix' pk=prefix.pk %}">{{ prefix }}</a>{% if not forloop.last %}<br />{% endif %}
@@ -375,9 +385,9 @@ class VLANGroupTable(BaseTable):
 
 class VLANTable(BaseTable):
     pk = ToggleColumn()
-    vid = tables.LinkColumn('ipam:vlan', args=[Accessor('pk')], verbose_name='ID')
+    vid = tables.TemplateColumn(VLAN_LINK, verbose_name='ID')
     site = tables.LinkColumn('dcim:site', args=[Accessor('site.slug')])
-    group = tables.Column(accessor=Accessor('group.name'), verbose_name='Group')
+    group = tables.LinkColumn('ipam:vlangroup_vlans', args=[Accessor('group.pk')], verbose_name='Group')
     tenant = tables.TemplateColumn(template_code=COL_TENANT)
     status = tables.TemplateColumn(STATUS_LABEL)
     role = tables.TemplateColumn(VLAN_ROLE_LINK)
@@ -385,6 +395,9 @@ class VLANTable(BaseTable):
     class Meta(BaseTable.Meta):
         model = VLAN
         fields = ('pk', 'vid', 'site', 'group', 'name', 'tenant', 'status', 'role', 'description')
+        row_attrs = {
+            'class': lambda record: 'success' if not isinstance(record, VLAN) else '',
+        }
 
 
 class VLANDetailTable(VLANTable):

+ 1 - 0
netbox/ipam/urls.py

@@ -72,6 +72,7 @@ urlpatterns = [
     url(r'^vlan-groups/import/$', views.VLANGroupBulkImportView.as_view(), name='vlangroup_import'),
     url(r'^vlan-groups/delete/$', views.VLANGroupBulkDeleteView.as_view(), name='vlangroup_bulk_delete'),
     url(r'^vlan-groups/(?P<pk>\d+)/edit/$', views.VLANGroupEditView.as_view(), name='vlangroup_edit'),
+    url(r'^vlan-groups/(?P<pk>\d+)/vlans/$', views.VLANGroupVLANsView.as_view(), name='vlangroup_vlans'),
 
     # VLANs
     url(r'^vlans/$', views.VLANListView.as_view(), name='vlan_list'),

+ 63 - 0
netbox/ipam/views.py

@@ -84,6 +84,34 @@ def add_available_ipaddresses(prefix, ipaddress_list, is_pool=False):
     return output
 
 
+def add_available_vlans(vlan_group, vlans):
+    """
+    Create fake records for all gaps between used VLANs
+    """
+    MIN_VLAN = 1
+    MAX_VLAN = 4094
+
+    if not vlans:
+        return [{'vid': MIN_VLAN, 'available': MAX_VLAN - MIN_VLAN + 1}]
+
+    prev_vid = MAX_VLAN
+    new_vlans = []
+    for vlan in vlans:
+        if vlan.vid - prev_vid > 1:
+            new_vlans.append({'vid': prev_vid + 1, 'available': vlan.vid - prev_vid - 1})
+        prev_vid = vlan.vid
+
+    if vlans[0].vid > MIN_VLAN:
+        new_vlans.append({'vid': MIN_VLAN, 'available': vlans[0].vid - MIN_VLAN})
+    if prev_vid < MAX_VLAN:
+        new_vlans.append({'vid': prev_vid + 1, 'available': MAX_VLAN - prev_vid})
+
+    vlans = list(vlans) + new_vlans
+    vlans.sort(key=lambda v: v.vid if type(v) == VLAN else v['vid'])
+
+    return vlans
+
+
 #
 # VRFs
 #
@@ -814,6 +842,41 @@ class VLANGroupBulkDeleteView(PermissionRequiredMixin, BulkDeleteView):
     default_return_url = 'ipam:vlangroup_list'
 
 
+class VLANGroupVLANsView(View):
+    def get(self, request, pk):
+
+        vlan_group = get_object_or_404(VLANGroup.objects.all(), pk=pk)
+
+        vlans = VLAN.objects.filter(group_id=pk)
+        vlans = add_available_vlans(vlan_group, vlans)
+
+        vlan_table = tables.VLANDetailTable(vlans)
+        if request.user.has_perm('ipam.change_vlan') or request.user.has_perm('ipam.delete_vlan'):
+            vlan_table.columns.show('pk')
+        vlan_table.columns.hide('site')
+        vlan_table.columns.hide('group')
+
+        paginate = {
+            'klass': EnhancedPaginator,
+            'per_page': request.GET.get('per_page', settings.PAGINATE_COUNT)
+        }
+        RequestConfig(request, paginate).configure(vlan_table)
+
+        # Compile permissions list for rendering the object table
+        permissions = {
+            'add': request.user.has_perm('ipam.add_vlan'),
+            'change': request.user.has_perm('ipam.change_vlan'),
+            'delete': request.user.has_perm('ipam.delete_vlan'),
+        }
+
+        return render(request, 'ipam/vlangroup_vlans.html', {
+            'vlan_group': vlan_group,
+            'first_available_vlan': vlan_group.get_next_available_vid(),
+            'vlan_table': vlan_table,
+            'permissions': permissions,
+        })
+
+
 #
 # VLANs
 #

+ 14 - 0
netbox/templates/ipam/inc/vlangroup_header.html

@@ -0,0 +1,14 @@
+<div class="pull-right">
+    {% if perms.ipam.add_vlan and first_available_vlan %}
+        <a href="{% url 'ipam:vlan_add' %}?vid={{ first_available_vlan }}&group={{ vlan_group.pk }}{% if vlan_group.site %}&site={{ vlan_group.site.pk }}{% endif %}" class="btn btn-success">
+            <i class="fa fa-plus" aria-hidden="true"></i> Add a VLAN
+        </a>
+    {% endif %}
+    {% if perms.ipam.change_vlangroup %}
+               <a href="{% url 'ipam:vlangroup_edit' pk=vlan_group.pk %}" class="btn btn-warning">
+                       <span class="fa fa-pencil" aria-hidden="true"></span>
+                       Edit this VLAN Group
+               </a>
+    {% endif %}
+</div>
+<h1>{{ vlan_group }}</h1>

+ 24 - 0
netbox/templates/ipam/vlangroup_vlans.html

@@ -0,0 +1,24 @@
+{% extends '_base.html' %}
+
+{% block title %}{{ vlan_group }} - VLANs{% endblock %}
+
+{% block content %}
+<div class="row">
+    <div class="col-sm-12 col-md-12">
+        <ol class="breadcrumb">
+            <li><a href="{% url 'ipam:vlangroup_list' %}">VLAN Groups</a></li>
+            {% if vlan_group.site %}
+                <li><a href="{% url 'dcim:site' slug=vlan_group.site.slug %}">{{ vlan_group.site }}</a></li>
+            {% endif %}
+            <li>{{ vlan_group }}</li>
+        </ol>
+    </div>
+</div>
+    {% include 'ipam/inc/vlangroup_header.html' %}
+    <div class="row">
+        <div class="col-md-12">
+            {% include 'utilities/obj_table.html' with table=vlan_table table_template='panel_table.html' heading='VLANs' bulk_edit_url='ipam:vlan_bulk_edit' bulk_delete_url='ipam:vlan_bulk_delete' %}
+        </div>
+    </div>
+{% endblock %}
+