from collections import OrderedDict from django.conf import settings from django.db.models import Count, F from django.shortcuts import render from django.views.generic import View from packaging import version from rest_framework.response import Response from rest_framework.reverse import reverse from rest_framework.views import APIView from circuits.filters import CircuitFilterSet, ProviderFilterSet from circuits.models import Circuit, Provider from circuits.tables import CircuitTable, ProviderTable from dcim.filters import ( CableFilterSet, DeviceFilterSet, DeviceTypeFilterSet, PowerFeedFilterSet, RackFilterSet, RackGroupFilterSet, SiteFilterSet, VirtualChassisFilterSet, ) from dcim.models import ( Cable, ConsolePort, Device, DeviceType, Interface, PowerPanel, PowerFeed, PowerPort, Rack, RackGroup, Site, VirtualChassis ) from dcim.tables import ( CableTable, DeviceTable, DeviceTypeTable, PowerFeedTable, RackTable, RackGroupTable, SiteTable, VirtualChassisTable, ) from extras.models import ObjectChange, ReportResult from ipam.filters import AggregateFilterSet, IPAddressFilterSet, PrefixFilterSet, VLANFilterSet, VRFFilterSet from ipam.models import Aggregate, IPAddress, Prefix, VLAN, VRF from ipam.tables import AggregateTable, IPAddressTable, PrefixTable, VLANTable, VRFTable from netbox.releases import get_latest_release from secrets.filters import SecretFilterSet from secrets.models import Secret from secrets.tables import SecretTable from tenancy.filters import TenantFilterSet from tenancy.models import Tenant from tenancy.tables import TenantTable from virtualization.filters import ClusterFilterSet, VirtualMachineFilterSet from virtualization.models import Cluster, VirtualMachine from virtualization.tables import ClusterTable, VirtualMachineDetailTable from .forms import SearchForm SEARCH_MAX_RESULTS = 15 SEARCH_TYPES = OrderedDict(( # Circuits ('provider', { 'permission': 'circuits.view_provider', 'queryset': Provider.objects.annotate(count_circuits=Count('circuits')), 'filterset': ProviderFilterSet, 'table': ProviderTable, 'url': 'circuits:provider_list', }), ('circuit', { 'permission': 'circuits.view_circuit', 'queryset': Circuit.objects.prefetch_related( 'type', 'provider', 'tenant', 'terminations__site' ).annotate_sites(), 'filterset': CircuitFilterSet, 'table': CircuitTable, 'url': 'circuits:circuit_list', }), # DCIM ('site', { 'permission': 'dcim.view_site', 'queryset': Site.objects.prefetch_related('region', 'tenant'), 'filterset': SiteFilterSet, 'table': SiteTable, 'url': 'dcim:site_list', }), ('rack', { 'permission': 'dcim.view_rack', 'queryset': Rack.objects.prefetch_related('site', 'group', 'tenant', 'role'), 'filterset': RackFilterSet, 'table': RackTable, 'url': 'dcim:rack_list', }), ('rackgroup', { 'permission': 'dcim.view_rackgroup', 'queryset': RackGroup.objects.prefetch_related('site').annotate(rack_count=Count('racks')), 'filterset': RackGroupFilterSet, 'table': RackGroupTable, 'url': 'dcim:rackgroup_list', }), ('devicetype', { 'permission': 'dcim.view_devicetype', 'queryset': DeviceType.objects.prefetch_related('manufacturer').annotate(instance_count=Count('instances')), 'filterset': DeviceTypeFilterSet, 'table': DeviceTypeTable, 'url': 'dcim:devicetype_list', }), ('device', { 'permission': 'dcim.view_device', 'queryset': Device.objects.prefetch_related( 'device_type__manufacturer', 'device_role', 'tenant', 'site', 'rack', 'primary_ip4', 'primary_ip6', ), 'filterset': DeviceFilterSet, 'table': DeviceTable, 'url': 'dcim:device_list', }), ('virtualchassis', { 'permission': 'dcim.view_virtualchassis', 'queryset': VirtualChassis.objects.prefetch_related('master').annotate(member_count=Count('members')), 'filterset': VirtualChassisFilterSet, 'table': VirtualChassisTable, 'url': 'dcim:virtualchassis_list', }), ('cable', { 'permission': 'dcim.view_cable', 'queryset': Cable.objects.all(), 'filterset': CableFilterSet, 'table': CableTable, 'url': 'dcim:cable_list', }), ('powerfeed', { 'permission': 'dcim.view_powerfeed', 'queryset': PowerFeed.objects.all(), 'filterset': PowerFeedFilterSet, 'table': PowerFeedTable, 'url': 'dcim:powerfeed_list', }), # Virtualization ('cluster', { 'permission': 'virtualization.view_cluster', 'queryset': Cluster.objects.prefetch_related('type', 'group'), 'filterset': ClusterFilterSet, 'table': ClusterTable, 'url': 'virtualization:cluster_list', }), ('virtualmachine', { 'permission': 'virtualization.view_virtualmachine', 'queryset': VirtualMachine.objects.prefetch_related( 'cluster', 'tenant', 'platform', 'primary_ip4', 'primary_ip6', ), 'filterset': VirtualMachineFilterSet, 'table': VirtualMachineDetailTable, 'url': 'virtualization:virtualmachine_list', }), # IPAM ('vrf', { 'permission': 'ipam.view_vrf', 'queryset': VRF.objects.prefetch_related('tenant'), 'filterset': VRFFilterSet, 'table': VRFTable, 'url': 'ipam:vrf_list', }), ('aggregate', { 'permission': 'ipam.view_aggregate', 'queryset': Aggregate.objects.prefetch_related('rir'), 'filterset': AggregateFilterSet, 'table': AggregateTable, 'url': 'ipam:aggregate_list', }), ('prefix', { 'permission': 'ipam.view_prefix', 'queryset': Prefix.objects.prefetch_related('site', 'vrf__tenant', 'tenant', 'vlan', 'role'), 'filterset': PrefixFilterSet, 'table': PrefixTable, 'url': 'ipam:prefix_list', }), ('ipaddress', { 'permission': 'ipam.view_ipaddress', 'queryset': IPAddress.objects.prefetch_related('vrf__tenant', 'tenant'), 'filterset': IPAddressFilterSet, 'table': IPAddressTable, 'url': 'ipam:ipaddress_list', }), ('vlan', { 'permission': 'ipam.view_vlan', 'queryset': VLAN.objects.prefetch_related('site', 'group', 'tenant', 'role'), 'filterset': VLANFilterSet, 'table': VLANTable, 'url': 'ipam:vlan_list', }), # Secrets ('secret', { 'permission': 'secrets.view_secret', 'queryset': Secret.objects.prefetch_related('role', 'device'), 'filterset': SecretFilterSet, 'table': SecretTable, 'url': 'secrets:secret_list', }), # Tenancy ('tenant', { 'permission': 'tenancy.view_tenant', 'queryset': Tenant.objects.prefetch_related('group'), 'filterset': TenantFilterSet, 'table': TenantTable, 'url': 'tenancy:tenant_list', }), )) class HomeView(View): template_name = 'home.html' def get(self, request): connected_consoleports = ConsolePort.objects.filter( connected_endpoint__isnull=False ) connected_powerports = PowerPort.objects.filter( _connected_poweroutlet__isnull=False ) connected_interfaces = Interface.objects.filter( _connected_interface__isnull=False, pk__lt=F('_connected_interface') ) cables = Cable.objects.all() stats = { # Organization 'site_count': Site.objects.count(), 'tenant_count': Tenant.objects.count(), # DCIM 'rack_count': Rack.objects.count(), 'devicetype_count': DeviceType.objects.count(), 'device_count': Device.objects.count(), 'interface_connections_count': connected_interfaces.count(), 'cable_count': cables.count(), 'console_connections_count': connected_consoleports.count(), 'power_connections_count': connected_powerports.count(), 'powerpanel_count': PowerPanel.objects.count(), 'powerfeed_count': PowerFeed.objects.count(), # IPAM 'vrf_count': VRF.objects.count(), 'aggregate_count': Aggregate.objects.count(), 'prefix_count': Prefix.objects.count(), 'ipaddress_count': IPAddress.objects.count(), 'vlan_count': VLAN.objects.count(), # Circuits 'provider_count': Provider.objects.count(), 'circuit_count': Circuit.objects.count(), # Secrets 'secret_count': Secret.objects.count(), # Virtualization 'cluster_count': Cluster.objects.count(), 'virtualmachine_count': VirtualMachine.objects.count(), } # Check whether a new release is available. (Only for staff/superusers.) new_release = None if request.user.is_staff or request.user.is_superuser: latest_release, release_url = get_latest_release() if isinstance(latest_release, version.Version): current_version = version.parse(settings.VERSION) if latest_release > current_version: new_release = { 'version': str(latest_release), 'url': release_url, } return render(request, self.template_name, { 'search_form': SearchForm(), 'stats': stats, 'report_results': ReportResult.objects.order_by('-created')[:10], 'changelog': ObjectChange.objects.prefetch_related('user', 'changed_object_type')[:15], 'new_release': new_release, }) class SearchView(View): def get(self, request): # No query if 'q' not in request.GET: return render(request, 'search.html', { 'form': SearchForm(), }) form = SearchForm(request.GET) results = [] if form.is_valid(): # Searching for a single type of object obj_types = [] if form.cleaned_data['obj_type']: obj_type = form.cleaned_data['obj_type'] if request.user.has_perm(SEARCH_TYPES[obj_type]['permission']): obj_types.append(form.cleaned_data['obj_type']) # Searching all object types else: for obj_type in SEARCH_TYPES.keys(): if request.user.has_perm(SEARCH_TYPES[obj_type]['permission']): obj_types.append(obj_type) for obj_type in obj_types: queryset = SEARCH_TYPES[obj_type]['queryset'] filterset = SEARCH_TYPES[obj_type]['filterset'] table = SEARCH_TYPES[obj_type]['table'] url = SEARCH_TYPES[obj_type]['url'] # Construct the results table for this object type filtered_queryset = filterset({'q': form.cleaned_data['q']}, queryset=queryset).qs table = table(filtered_queryset, orderable=False) table.paginate(per_page=SEARCH_MAX_RESULTS) if table.page: results.append({ 'name': queryset.model._meta.verbose_name_plural, 'table': table, 'url': '{}?q={}'.format(reverse(url), form.cleaned_data['q']) }) return render(request, 'search.html', { 'form': form, 'results': results, }) class StaticMediaFailureView(View): """ Display a user-friendly error message with troubleshooting tips when a static media file fails to load. """ def get(self, request): return render(request, 'media_failure.html', { 'filename': request.GET.get('filename') }) class APIRootView(APIView): _ignore_model_permissions = True exclude_from_schema = True swagger_schema = None def get_view_name(self): return "API Root" def get(self, request, format=None): return Response(OrderedDict(( ('circuits', reverse('circuits-api:api-root', request=request, format=format)), ('dcim', reverse('dcim-api:api-root', request=request, format=format)), ('extras', reverse('extras-api:api-root', request=request, format=format)), ('ipam', reverse('ipam-api:api-root', request=request, format=format)), ('plugins', reverse('plugins-api:api-root', request=request, format=format)), ('secrets', reverse('secrets-api:api-root', request=request, format=format)), ('tenancy', reverse('tenancy-api:api-root', request=request, format=format)), ('virtualization', reverse('virtualization-api:api-root', request=request, format=format)), )))