diff --git a/docs/release-notes/version-3.1.md b/docs/release-notes/version-3.1.md index b117c4b1e40..a09b43400e2 100644 --- a/docs/release-notes/version-3.1.md +++ b/docs/release-notes/version-3.1.md @@ -2,6 +2,11 @@ ## v3.1.3 (FUTURE) +### Bug Fixes + +* [#7962](https://github.com/netbox-community/netbox/issues/7962) - Fix user menu under report/script result view +* [#8131](https://github.com/netbox-community/netbox/issues/8131) - Restore annotation of available IPs under prefix IPs view + --- ## v3.1.2 (2021-12-20) diff --git a/netbox/dcim/models/__init__.py b/netbox/dcim/models/__init__.py index 8d4b1dce699..d74f3482888 100644 --- a/netbox/dcim/models/__init__.py +++ b/netbox/dcim/models/__init__.py @@ -5,46 +5,3 @@ from .devices import * from .power import * from .racks import * from .sites import * - -__all__ = ( - 'BaseInterface', - 'Cable', - 'CablePath', - 'LinkTermination', - 'ConsolePort', - 'ConsolePortTemplate', - 'ConsoleServerPort', - 'ConsoleServerPortTemplate', - 'Device', - 'DeviceBay', - 'DeviceBayTemplate', - 'DeviceRole', - 'DeviceType', - 'FrontPort', - 'FrontPortTemplate', - 'Interface', - 'InterfaceTemplate', - 'InventoryItem', - 'Location', - 'Manufacturer', - 'Module', - 'ModuleBay', - 'ModuleBayTemplate', - 'ModuleType', - 'Platform', - 'PowerFeed', - 'PowerOutlet', - 'PowerOutletTemplate', - 'PowerPanel', - 'PowerPort', - 'PowerPortTemplate', - 'Rack', - 'RackReservation', - 'RackRole', - 'RearPort', - 'RearPortTemplate', - 'Region', - 'Site', - 'SiteGroup', - 'VirtualChassis', -) diff --git a/netbox/dcim/tables/devicetypes.py b/netbox/dcim/tables/devicetypes.py index 38a33c652b6..ad4c4d84412 100644 --- a/netbox/dcim/tables/devicetypes.py +++ b/netbox/dcim/tables/devicetypes.py @@ -112,8 +112,7 @@ class ComponentTemplateTable(BaseTable): class ConsolePortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=ConsolePortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_consoleports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -125,8 +124,7 @@ class ConsolePortTemplateTable(ComponentTemplateTable): class ConsoleServerPortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=ConsoleServerPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_consoleserverports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -138,8 +136,7 @@ class ConsoleServerPortTemplateTable(ComponentTemplateTable): class PowerPortTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=PowerPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_powerports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -151,8 +148,7 @@ class PowerPortTemplateTable(ComponentTemplateTable): class PowerOutletTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=PowerOutletTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_poweroutlets' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -167,8 +163,7 @@ class InterfaceTemplateTable(ComponentTemplateTable): ) actions = ButtonsColumn( model=InterfaceTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_interfaces' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -184,8 +179,7 @@ class FrontPortTemplateTable(ComponentTemplateTable): color = ColorColumn() actions = ButtonsColumn( model=FrontPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_frontports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -198,8 +192,7 @@ class RearPortTemplateTable(ComponentTemplateTable): color = ColorColumn() actions = ButtonsColumn( model=RearPortTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_rearports' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -211,8 +204,7 @@ class RearPortTemplateTable(ComponentTemplateTable): class ModuleBayTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=ModuleBayTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_modulebays' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): @@ -224,8 +216,7 @@ class ModuleBayTemplateTable(ComponentTemplateTable): class DeviceBayTemplateTable(ComponentTemplateTable): actions = ButtonsColumn( model=DeviceBayTemplate, - buttons=('edit', 'delete'), - return_url_extra='%23tab_devicebays' + buttons=('edit', 'delete') ) class Meta(ComponentTemplateTable.Meta): diff --git a/netbox/dcim/views.py b/netbox/dcim/views.py index a7f1e2bcaff..5aff57a4e7f 100644 --- a/netbox/dcim/views.py +++ b/netbox/dcim/views.py @@ -27,13 +27,7 @@ from virtualization.models import VirtualMachine from . import filtersets, forms, tables from .choices import DeviceFaceChoices from .constants import NONCONNECTABLE_IFACE_TYPES -from .models import ( - Cable, CablePath, ConsolePort, ConsolePortTemplate, ConsoleServerPort, ConsoleServerPortTemplate, Device, DeviceBay, - DeviceBayTemplate, DeviceRole, DeviceType, FrontPort, FrontPortTemplate, Interface, InterfaceTemplate, - InventoryItem, Manufacturer, Module, ModuleBay, ModuleBayTemplate, ModuleType, PathEndpoint, Platform, PowerFeed, - PowerOutlet, PowerOutletTemplate, PowerPanel, PowerPort, PowerPortTemplate, Rack, Location, RackReservation, - RackRole, RearPort, RearPortTemplate, Region, Site, SiteGroup, VirtualChassis, -) +from .models import * class DeviceComponentsView(generic.ObjectChildrenView): @@ -51,10 +45,21 @@ class DeviceComponentsView(generic.ObjectChildrenView): class DeviceTypeComponentsView(DeviceComponentsView): queryset = DeviceType.objects.all() template_name = 'dcim/devicetype/component_templates.html' + viewname = None # Used for return_url resolution def get_children(self, request, parent): return self.child_model.objects.restrict(request.user, 'view').filter(device_type=parent) + def get_extra_context(self, request, instance): + if self.viewname: + return_url = reverse(self.viewname, kwargs={'pk': instance.pk}) + else: + return_url = instance.get_absolute_url() + return { + 'active_tab': f"{self.child_model._meta.verbose_name_plural.replace(' ', '-')}", + 'return_url': return_url, + } + class ModuleTypeComponentsView(DeviceComponentsView): queryset = ModuleType.objects.all() @@ -806,42 +811,49 @@ class DeviceTypeConsolePortsView(DeviceTypeComponentsView): child_model = ConsolePortTemplate table = tables.ConsolePortTemplateTable filterset = filtersets.ConsolePortTemplateFilterSet + viewname = 'dcim:devicetype_consoleports' class DeviceTypeConsoleServerPortsView(DeviceTypeComponentsView): child_model = ConsoleServerPortTemplate table = tables.ConsoleServerPortTemplateTable filterset = filtersets.ConsoleServerPortTemplateFilterSet + viewname = 'dcim:devicetype_consoleserverports' class DeviceTypePowerPortsView(DeviceTypeComponentsView): child_model = PowerPortTemplate table = tables.PowerPortTemplateTable filterset = filtersets.PowerPortTemplateFilterSet + viewname = 'dcim:devicetype_powerports' class DeviceTypePowerOutletsView(DeviceTypeComponentsView): child_model = PowerOutletTemplate table = tables.PowerOutletTemplateTable filterset = filtersets.PowerOutletTemplateFilterSet + viewname = 'dcim:devicetype_poweroutlets' class DeviceTypeInterfacesView(DeviceTypeComponentsView): child_model = InterfaceTemplate table = tables.InterfaceTemplateTable filterset = filtersets.InterfaceTemplateFilterSet + viewname = 'dcim:devicetype_interfaces' class DeviceTypeFrontPortsView(DeviceTypeComponentsView): child_model = FrontPortTemplate table = tables.FrontPortTemplateTable filterset = filtersets.FrontPortTemplateFilterSet + viewname = 'dcim:devicetype_frontports' class DeviceTypeRearPortsView(DeviceTypeComponentsView): child_model = RearPortTemplate table = tables.RearPortTemplateTable filterset = filtersets.RearPortTemplateFilterSet + viewname = 'dcim:devicetype_rearports' class DeviceTypeModuleBaysView(DeviceTypeComponentsView): @@ -854,6 +866,7 @@ class DeviceTypeDeviceBaysView(DeviceTypeComponentsView): child_model = DeviceBayTemplate table = tables.DeviceBayTemplateTable filterset = filtersets.DeviceBayTemplateFilterSet + viewname = 'dcim:devicetype_devicebays' class DeviceTypeEditView(generic.ObjectEditView): diff --git a/netbox/extras/api/serializers.py b/netbox/extras/api/serializers.py index 1be18759694..9e4665cc292 100644 --- a/netbox/extras/api/serializers.py +++ b/netbox/extras/api/serializers.py @@ -5,10 +5,10 @@ from drf_yasg.utils import swagger_serializer_method from rest_framework import serializers from dcim.api.nested_serializers import ( - NestedDeviceSerializer, NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer, - NestedRackSerializer, NestedRegionSerializer, NestedSiteSerializer, NestedSiteGroupSerializer, + NestedDeviceRoleSerializer, NestedDeviceTypeSerializer, NestedPlatformSerializer, NestedRegionSerializer, + NestedSiteSerializer, NestedSiteGroupSerializer, ) -from dcim.models import Device, DeviceRole, DeviceType, Platform, Rack, Region, Site, SiteGroup +from dcim.models import DeviceRole, DeviceType, Platform, Region, Site, SiteGroup from extras.choices import * from extras.models import * from extras.utils import FeatureQuery diff --git a/netbox/extras/views.py b/netbox/extras/views.py index 4382d1fbbe5..a2bc92f88e0 100644 --- a/netbox/extras/views.py +++ b/netbox/extras/views.py @@ -10,6 +10,7 @@ from rq import Worker from netbox.views import generic from utilities.forms import ConfirmationForm +from utilities.htmx import is_htmx from utilities.tables import paginate_table from utilities.utils import copy_safe_request, count_related, normalize_querydict, shallow_compare_dict from utilities.views import ContentTypePermissionRequiredMixin @@ -693,16 +694,26 @@ class ReportResultView(ContentTypePermissionRequiredMixin, View): def get(self, request, job_result_pk): report_content_type = ContentType.objects.get(app_label='extras', model='report') - jobresult = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type) + result = get_object_or_404(JobResult.objects.all(), pk=job_result_pk, obj_type=report_content_type) # Retrieve the Report and attach the JobResult to it - module, report_name = jobresult.name.split('.') + module, report_name = result.name.split('.') report = get_report(module, report_name) - report.result = jobresult + report.result = result + + # If this is an HTMX request, return only the result HTML + if is_htmx(request): + response = render(request, 'extras/htmx/report_result.html', { + 'report': report, + 'result': result, + }) + if result.completed: + response.status_code = 286 + return response return render(request, 'extras/report_result.html', { 'report': report, - 'result': jobresult, + 'result': result, }) @@ -820,6 +831,16 @@ class ScriptResultView(ContentTypePermissionRequiredMixin, GetScriptMixin, View) script = self._get_script(result.name) + # If this is an HTMX request, return only the result HTML + if is_htmx(request): + response = render(request, 'extras/htmx/script_result.html', { + 'script': script, + 'result': result, + }) + if result.completed: + response.status_code = 286 + return response + return render(request, 'extras/script_result.html', { 'script': script, 'result': result, diff --git a/netbox/ipam/views.py b/netbox/ipam/views.py index ab1cdec5be5..4c93ee982dd 100644 --- a/netbox/ipam/views.py +++ b/netbox/ipam/views.py @@ -5,18 +5,18 @@ from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from dcim.filtersets import InterfaceFilterSet -from dcim.models import Device, Interface, Site +from dcim.models import Interface, Site from dcim.tables import SiteTable from netbox.views import generic from utilities.tables import paginate_table from utilities.utils import count_related from virtualization.filtersets import VMInterfaceFilterSet -from virtualization.models import VirtualMachine, VMInterface +from virtualization.models import VMInterface from . import filtersets, forms, tables from .constants import * from .models import * from .models import ASN -from .utils import add_requested_prefixes, add_available_vlans +from .utils import add_requested_prefixes, add_available_ipaddresses, add_available_vlans # @@ -502,6 +502,13 @@ class PrefixIPAddressesView(generic.ObjectChildrenView): def get_children(self, request, parent): return parent.get_child_ips().restrict(request.user, 'view') + def prep_table_data(self, request, queryset, parent): + show_available = bool(request.GET.get('show_available', 'true') == 'true') + if show_available: + return add_available_ipaddresses(parent.prefix, queryset, parent.is_pool) + + return queryset + def get_extra_context(self, request, instance): return { 'bulk_querystring': f"vrf_id={instance.vrf.pk if instance.vrf else '0'}&parent={instance.prefix}", diff --git a/netbox/project-static/bundle.js b/netbox/project-static/bundle.js index 100b70ac88b..76a1581ad2e 100644 --- a/netbox/project-static/bundle.js +++ b/netbox/project-static/bundle.js @@ -40,7 +40,6 @@ async function bundleGraphIQL() { async function bundleNetBox() { const entryPoints = { netbox: 'src/index.ts', - jobs: 'src/jobs.ts', lldp: 'src/device/lldp.ts', config: 'src/device/config.ts', status: 'src/device/status.ts', diff --git a/netbox/project-static/dist/jobs.js b/netbox/project-static/dist/jobs.js deleted file mode 100644 index 2aedf121948..00000000000 --- a/netbox/project-static/dist/jobs.js +++ /dev/null @@ -1,30 +0,0 @@ -(()=>{var mr=Object.create;var le=Object.defineProperty,gr=Object.defineProperties,_r=Object.getOwnPropertyDescriptor,Er=Object.getOwnPropertyDescriptors,vr=Object.getOwnPropertyNames,pn=Object.getOwnPropertySymbols,yr=Object.getPrototypeOf,hn=Object.prototype.hasOwnProperty,br=Object.prototype.propertyIsEnumerable;var mn=(i,t,e)=>t in i?le(i,t,{enumerable:!0,configurable:!0,writable:!0,value:e}):i[t]=e,O=(i,t)=>{for(var e in t||(t={}))hn.call(t,e)&&mn(i,e,t[e]);if(pn)for(var e of pn(t))br.call(t,e)&&mn(i,e,t[e]);return i},je=(i,t)=>gr(i,Er(t)),gn=i=>le(i,"__esModule",{value:!0});var mt=(i,t)=>()=>(t||i((t={exports:{}}).exports,t),t.exports),Tr=(i,t)=>{gn(i);for(var e in t)le(i,e,{get:t[e],enumerable:!0})},Ar=(i,t,e)=>{if(t&&typeof t=="object"||typeof t=="function")for(let n of vr(t))!hn.call(i,n)&&n!=="default"&&le(i,n,{get:()=>t[n],enumerable:!(e=_r(t,n))||e.enumerable});return i},_n=i=>Ar(gn(le(i!=null?mr(yr(i)):{},"default",i&&i.__esModule&&"default"in i?{get:()=>i.default,enumerable:!0}:{value:i,enumerable:!0})),i);var ce=(i,t,e)=>new Promise((n,o)=>{var r=u=>{try{l(e.next(u))}catch(h){o(h)}},s=u=>{try{l(e.throw(u))}catch(h){o(h)}},l=u=>u.done?n(u.value):Promise.resolve(u.value).then(r,s);l((e=e.apply(i,t)).next())});var en=mt((Zo,li)=>{(function(i,t){typeof define=="function"&&define.amd?define(t):typeof li=="object"&&li.exports?li.exports=t():i.EvEmitter=t()})(typeof window!="undefined"?window:Zo,function(){"use strict";function i(){}var t=i.prototype;return t.on=function(e,n){if(!(!e||!n)){var o=this._events=this._events||{},r=o[e]=o[e]||[];return r.indexOf(n)==-1&&r.push(n),this}},t.once=function(e,n){if(!(!e||!n)){this.on(e,n);var o=this._onceEvents=this._onceEvents||{},r=o[e]=o[e]||{};return r[n]=!0,this}},t.off=function(e,n){var o=this._events&&this._events[e];if(!(!o||!o.length)){var r=o.indexOf(n);return r!=-1&&o.splice(r,1),this}},t.emitEvent=function(e,n){var o=this._events&&this._events[e];if(!(!o||!o.length)){o=o.slice(0),n=n||[];for(var r=this._onceEvents&&this._onceEvents[e],s=0;s{(function(i,t){typeof define=="function"&&define.amd?define(t):typeof ci=="object"&&ci.exports?ci.exports=t():i.getSize=t()})(window,function(){"use strict";function t(d){var y=parseFloat(d),E=d.indexOf("%")==-1&&!isNaN(y);return E&&y}function e(){}var n=typeof console=="undefined"?e:function(d){console.error(d)},o=["paddingLeft","paddingRight","paddingTop","paddingBottom","marginLeft","marginRight","marginTop","marginBottom","borderLeftWidth","borderRightWidth","borderTopWidth","borderBottomWidth"],r=o.length;function s(){for(var d={width:0,height:0,innerWidth:0,innerHeight:0,outerWidth:0,outerHeight:0},y=0;y