From 5e53066ba716a5caddee0a7d1900d0e2eeb5d348 Mon Sep 17 00:00:00 2001 From: Mattijs Vanhaverbeke Date: Thu, 14 May 2020 22:20:01 +0200 Subject: [PATCH] fixed auth, added save-coords --- README.md | 8 +- netbox_topology_views/__init__.py | 7 +- netbox_topology_views/api/serializers.py | 9 ++- netbox_topology_views/api/urls.py | 3 +- netbox_topology_views/api/views.py | 76 +++++++++++++++---- .../static/netbox_topology_views/js/app.js | 2 +- .../static/netbox_topology_views/js/vendor.js | 2 +- netbox_topology_views/static_dev/js/home.js | 22 ++---- .../netbox_topology_views/index.html | 2 + netbox_topology_views/views.py | 5 +- package.json | 2 +- setup.py | 2 +- 12 files changed, 99 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 4bf86e6..6812964 100644 --- a/README.md +++ b/README.md @@ -18,8 +18,11 @@ Then run `python3 manage.py collectstatic --no-input` ### Custom field: coordinates There is also support for custom fiels. + If you create a custom field "coordinates" for "dcim > device" with type "text" and name "coordinates" you will see the same layout every time. +The coordinates can then be provided as: "X;Y" + ## Configure If you want to override the default values configure the `PLUGINS_CONFIG` in your `netbox configuration.py`. @@ -37,7 +40,10 @@ PLUGINS_CONFIG = { | Setting | Default value | Description | | ------------- |-------------| -----| | device_img | 'access-switch,core-switch,firewall,router,distribution-switch,backup,storage,wan-network,wireless-ap,server,internal-switch,isp-cpe-material,non-racked-devices,power-units' | The slug of the device roles that you have a image for. | -| preselected_device_roles | 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP' | The full name of the device roles you want to pre select in the global view. | +| preselected_device_roles | 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP' | The full name of the device roles you want to pre select in the global view. Note that this is case sensitive| +| allow_coordinates_saving | False | (bool) Set to true if you use the custom coordinates fields and want to save the coordinates | +| ignore_cable_type | 'poweroutlet,powerport' | The cable types that you want to ignore in the views | + ### Custom Images diff --git a/netbox_topology_views/__init__.py b/netbox_topology_views/__init__.py index c8f6d4b..969c650 100644 --- a/netbox_topology_views/__init__.py +++ b/netbox_topology_views/__init__.py @@ -4,15 +4,16 @@ class TopologyViewsConfig(PluginConfig): name = 'netbox_topology_views' verbose_name = 'Topology views' description = 'An plugin to render toplogoy maps' - version = '0.3' + version = '0.4.0' author = 'Mattijs Vanhaverbeke' author_email = 'author@example.com' base_url = 'topology-views' required_settings = [] default_settings = { 'preselected_device_roles': 'Firewall,Router,Distribution Switch,Core Switch,Internal Switch,Access Switch,Server,Storage,Backup,Wireless AP', - 'IGNORE_CABLE_TYPE': 'dcim.poweroutlet,dcim.powerport', - 'device_img': 'access-switch,core-switch,firewall,router,distribution-switch,backup,storage,wan-network,wireless-ap,server,internal-switch,isp-cpe-material,non-racked-devices,power-units' + 'ignore_cable_type': 'poweroutlet,powerport', + 'device_img': 'access-switch,core-switch,firewall,router,distribution-switch,backup,storage,wan-network,wireless-ap,server,internal-switch,isp-cpe-material,non-racked-devices,power-units', + 'allow_coordinates_saving': False } config = TopologyViewsConfig diff --git a/netbox_topology_views/api/serializers.py b/netbox_topology_views/api/serializers.py index 76f571d..4b35828 100644 --- a/netbox_topology_views/api/serializers.py +++ b/netbox_topology_views/api/serializers.py @@ -1,10 +1,17 @@ from rest_framework.serializers import ModelSerializer -from dcim.models import DeviceRole +from dcim.models import DeviceRole, Device class PreDeviceRoleSerializer(ModelSerializer): class Meta: model = DeviceRole fields = ('id', 'name') + + +class TopologyDummySerializer(ModelSerializer): + + class Meta: + model = Device + fields = ('id', 'name') \ No newline at end of file diff --git a/netbox_topology_views/api/urls.py b/netbox_topology_views/api/urls.py index 7fac369..13ece51 100644 --- a/netbox_topology_views/api/urls.py +++ b/netbox_topology_views/api/urls.py @@ -1,9 +1,10 @@ from rest_framework import routers -from .views import PreSelectDeviceRolesViewSet, SearchViewSet +from .views import PreSelectDeviceRolesViewSet, SearchViewSet, SaveCoordsViewSet router = routers.DefaultRouter() router.register('preselectdeviceroles', PreSelectDeviceRolesViewSet) router.register('search', SearchViewSet, basename='search') +router.register('save-coords', SaveCoordsViewSet, basename='save_coords') urlpatterns = router.urls \ No newline at end of file diff --git a/netbox_topology_views/api/views.py b/netbox_topology_views/api/views.py index 781261b..5cfc51b 100644 --- a/netbox_topology_views/api/views.py +++ b/netbox_topology_views/api/views.py @@ -1,24 +1,68 @@ -from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet, ViewSet, ReadOnlyModelViewSet, GenericViewSet from rest_framework.decorators import action from rest_framework.response import Response -from .serializers import PreDeviceRoleSerializer +from .serializers import PreDeviceRoleSerializer, TopologyDummySerializer from django.conf import settings from utilities.api import IsAuthenticatedOrLoginNotRequired from dcim.models import DeviceRole, Device, Cable +ignore_cable_type_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["ignore_cable_type"] +ignore_cable_type = ignore_cable_type_raw.split(",") + +preselected_device_roles_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"] +preselected_device_roles = preselected_device_roles_raw.split(",") + class PreSelectDeviceRolesViewSet(ReadOnlyModelViewSet): - preselected_device_roles_raw = settings.PLUGINS_CONFIG["netbox_topology_views"]["preselected_device_roles"] - preselected_device_roles = preselected_device_roles_raw.split(",") queryset = DeviceRole.objects.filter(name__in=preselected_device_roles) serializer_class = PreDeviceRoleSerializer -class SearchViewSet(ViewSet): - _ignore_model_permissions = True - permission_classes = [IsAuthenticatedOrLoginNotRequired] +class SaveCoordsViewSet(GenericViewSet): + queryset = Device.objects.all() + serializer_class = TopologyDummySerializer + + @action(detail=False, methods=['post']) + def save_coords(self, request): + if settings.PLUGINS_CONFIG["netbox_topology_views"]["allow_coordinates_saving"]: + device_id = None + x_coord = None + y_coord = None + if "node_id" in request.data: + if request.data["node_id"]: + device_id = request.data["node_id"] + if "x" in request.data: + if request.data["x"]: + x_coord = request.data["x"] + if "y" in request.data: + if request.data["y"]: + y_coord = request.data["y"] + + actual_device= Device.objects.get(id=device_id) + for device_custom_field in actual_device.custom_field_values.all(): + if device_custom_field.field.name == "coordinates": + old_cords = device_custom_field.serialized_value.split(";") + device_custom_field.value = "%s;%s" % (x_coord,y_coord) + device_custom_field.save() + actual_device.save() + results = {} + results["status"] = "ok" + return Response(results) + + print('notok') + #TODO + return Response(status=500) + else: + return Response(status=500) + +class SearchViewSet(GenericViewSet): + #_ignore_model_permissions = True + #permission_classes = [IsAuthenticatedOrLoginNotRequired] + + queryset = Device.objects.all() + serializer_class = TopologyDummySerializer def _filter(self, site, role, name): filter_devices = Device.objects.all() @@ -47,6 +91,7 @@ class SearchViewSet(ViewSet): name = request.data["name"] devices = self._filter(sites, devicerole, name) + nodes = [] edges = [] edge_ids = 0 @@ -55,14 +100,15 @@ class SearchViewSet(ViewSet): cables = device.get_cables() for cable in cables: if cable.id not in cable_ids: - cable_ids.append(cable.id) - edge_ids += 1 - edge = {} - edge["id"] = edge_ids - edge["from"] = cable.termination_a.device.id - edge["to"] = cable.termination_b.device.id - edge["title"] = "Connection between
" + cable.termination_a.device.name + " [" + cable.termination_a.name + "]
" + cable.termination_b.device.name + " [" + cable.termination_b.name + "]" - edges.append(edge) + if cable.termination_a_type.name not in ignore_cable_type: + cable_ids.append(cable.id) + edge_ids += 1 + edge = {} + edge["id"] = edge_ids + edge["from"] = cable.termination_a.device.id + edge["to"] = cable.termination_b.device.id + edge["title"] = "Connection between
" + cable.termination_a.device.name + " [" + cable.termination_a.name + "]
" + cable.termination_b.device.name + " [" + cable.termination_b.name + "]" + edges.append(edge) node = {} node["id"] = device.id node["name"] = device.name diff --git a/netbox_topology_views/static/netbox_topology_views/js/app.js b/netbox_topology_views/static/netbox_topology_views/js/app.js index c5aef68..1c22c70 100644 --- a/netbox_topology_views/static/netbox_topology_views/js/app.js +++ b/netbox_topology_views/static/netbox_topology_views/js/app.js @@ -1 +1 @@ -var graph=null,container=null,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!0},nodes:{shape:"image",brokenImage:"/static/netbox_topology_views/img/role-unknown.png",size:35,font:{multi:"md",face:"helvetica"}},edges:{length:100,width:2,font:{face:"helvetica"}},physics:{solver:"forceAtlas2Based"}};function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),csrftoken=jQuery("[name=csrfmiddlewaretoken]").val(),startLoadSearchBar(),handleButtonPress()},!1)}function startLoadSearchBar(){$("#device-roles").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/dcim/device-roles/?brief=true",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}}),$("#sites").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"/api/dcim/sites/?brief=true",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}});var n=$("#device-roles");$.ajax({type:"GET",url:"/api/plugins/topology-views/preselectdeviceroles/"}).then(function(e){$.each(e.results,function(e,t){var a=new Option(t.name,t.id,!0,!0);n.append(a).trigger("change"),n.trigger({type:"select2:select",params:{data:t}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('Loading data'),e.preventDefault();var t=$("#name").val(),a=$("#device-roles").val(),n=$("#sites").val();$.ajax({type:"POST",url:"/api/plugins/topology-views/search/search/",data:JSON.stringify({name:t,devicerole:a,sites:n}),headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",dataType:"json",success:function(e){$("#status").html('Drawing network'),graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),$.each(e.nodes,function(e,t){nodes.add(t)}),$.each(e.edges,function(e,t){edges.add(t)}),graph.fit(),graph.on("afterDrawing",function(){$("#status").html('Ready')}),graph.on("dragEnd",function(e){dragged=this.getPositions(e.nodes),$.each(dragged,function(e,t){$("#coordstatus").html(""),$("#checkSaveCoordinates").is(":checked")&&(nodes.update({id:e,physics:!1}),$.ajax({url:"/api/save_coords",type:"POST",dataType:"json",headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",data:JSON.stringify({node_id:e,x:t.x,y:t.y}),error:function(e){$("#coordstatus").html('Failed to update coordinates')},success:function(e){$("#coordstatus").html('Updated coordinates'),$.ajax({url:"/api/reload_devices",type:"GET",headers:{"X-CSRFToken":csrftoken},success:function(e){}})}}))})})},error:function(e){$("#status").html('Something went wrong')}})})} \ No newline at end of file +var graph=null,container=null,csrftoken=null,nodes=new vis.DataSet,edges=new vis.DataSet,options={interaction:{hover:!0,hoverConnectedEdges:!0,multiselect:!0},nodes:{shape:"image",brokenImage:"../../static/netbox_topology_views/img/role-unknown.png",size:35,font:{multi:"md",face:"helvetica"}},edges:{length:100,width:2,font:{face:"helvetica"}},physics:{solver:"forceAtlas2Based"}};function iniPlotboxIndex(){document.addEventListener("DOMContentLoaded",function(){container=document.getElementById("visgraph"),csrftoken=jQuery("[name=csrfmiddlewaretoken]").val(),startLoadSearchBar(),handleButtonPress()},!1)}function startLoadSearchBar(){$("#device-roles").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"../../api/dcim/device-roles/?brief=true",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}}),$("#sites").select2({allowClear:!0,placeholder:"---------",theme:"bootstrap",multiple:!0,ajax:{url:"../../api/dcim/sites/?brief=true",dataType:"json",type:"GET",data:function(e){return{term:e.term}},processResults:function(e){return{results:$.map(e.results,function(e){return{text:e.name,id:e.id}})}}}});var n=$("#device-roles");$.ajax({type:"GET",url:"../../api/plugins/topology-views/preselectdeviceroles/"}).then(function(e){$.each(e.results,function(e,t){var a=new Option(t.name,t.id,!0,!0);n.append(a).trigger("change"),n.trigger({type:"select2:select",params:{data:t}})})})}function handleButtonPress(){$("#search-form").submit(function(e){$("#status").html('Loading data'),e.preventDefault();var t=$("#name").val(),a=$("#device-roles").val(),n=$("#sites").val();$.ajax({type:"POST",url:"../../api/plugins/topology-views/search/search/",data:JSON.stringify({name:t,devicerole:a,sites:n}),headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",dataType:"json",success:function(e){$("#status").html('Drawing network'),graph=null,nodes=new vis.DataSet,edges=new vis.DataSet,graph=new vis.Network(container,{nodes:nodes,edges:edges},options),$.each(e.nodes,function(e,t){nodes.add(t)}),$.each(e.edges,function(e,t){edges.add(t)}),graph.fit(),graph.on("afterDrawing",function(){$("#status").html('Ready')}),graph.on("dragEnd",function(e){dragged=this.getPositions(e.nodes),$.each(dragged,function(e,t){$("#coordstatus").html(""),$("#checkSaveCoordinates").is(":checked")&&(nodes.update({id:e,physics:!1}),$.ajax({url:"../../api/plugins/topology-views/save-coords/save_coords/",type:"POST",dataType:"json",headers:{"X-CSRFToken":csrftoken},contentType:"application/json; charset=utf-8",data:JSON.stringify({node_id:e,x:t.x,y:t.y}),error:function(e){$("#coordstatus").html('Failed to update coordinates')},success:function(e){$("#coordstatus").html('Updated coordinates')}}))})})},error:function(e){$("#status").html('Something went wrong')}})})} \ No newline at end of file diff --git a/netbox_topology_views/static/netbox_topology_views/js/vendor.js b/netbox_topology_views/static/netbox_topology_views/js/vendor.js index 3421d57..8fdb0d3 100644 --- a/netbox_topology_views/static/netbox_topology_views/js/vendor.js +++ b/netbox_topology_views/static/netbox_topology_views/js/vendor.js @@ -1 +1 @@ -!function(t,g){"object"==typeof exports&&"undefined"!=typeof module?g(exports):"function"==typeof define&&define.amd?define(["exports"],g):g((t=t||self).vis=t.vis||{})}(this,function(t){"use strict";var g="undefined"!=typeof globalThis?globalThis:"undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof self?self:{};function e(t,g){return t(g={exports:{}},g.exports),g.exports}function A(t){return t&&t.default||t}function I(t){return t&&t.Math==Math&&t}function a(t){try{return!!t()}catch(t){return!0}}function y(t,g){return{enumerable:!(1&t),configurable:!(2&t),writable:!(4&t),value:g}}function C(t){return k.call(t).slice(8,-1)}function o(t){if(null==t)throw TypeError("Can't call method on "+t);return t}function r(t){return O(o(t))}function d(t){return"object"==typeof t?null!==t:"function"==typeof t}function i(t,g){if(!d(t))return t;var e,A;if(g&&"function"==typeof(e=t.toString)&&!d(A=e.call(t)))return A;if("function"==typeof(e=t.valueOf)&&!d(A=e.call(t)))return A;if(!g&&"function"==typeof(e=t.toString)&&!d(A=e.call(t)))return A;throw TypeError("Can't convert object to primitive value")}function f(t,g){return N.call(t,g)}function n(t){return Z?E.createElement(t):{}}function s(t,g){var e=S[R(t)];return e==L||e!=F&&("function"==typeof g?a(g):!!g)}function l(t){if("function"!=typeof t)throw TypeError(String(t)+" is not a function");return t}function m(A,I,t){if(l(A),void 0===I)return A;switch(t){case 0:return function(){return A.call(I)};case 1:return function(t){return A.call(I,t)};case 2:return function(t,g){return A.call(I,t,g)};case 3:return function(t,g,e){return A.call(I,t,g,e)}}return function(){return A.apply(I,arguments)}}function c(t){if(!d(t))throw TypeError(String(t)+" is not an object");return t}function p(A){function t(t,g,e){if(this instanceof A){switch(arguments.length){case 0:return new A;case 1:return new A(t);case 2:return new A(t,g)}return new A(t,g,e)}return A.apply(this,arguments)}return t.prototype=A.prototype,t}function b(t,g){var e,A,I,C,i,n,o,r,s=t.target,a=t.global,d=t.stat,l=t.proto,c=a?v:d?v[s]:(v[s]||{}).prototype,h=a?Y:Y[s]||(Y[s]={}),u=h.prototype;for(I in g)e=!Q(a?I:s+(d?".":"#")+I,t.forced)&&c&&f(c,I),i=h[I],e&&(n=t.noTargetGet?(r=P(c,I))&&r.value:c[I]),C=e&&n?n:g[I],e&&typeof i==typeof C||(o=t.bind&&e?m(C,v):t.wrap&&e?p(C):l&&"function"==typeof C?m(Function.call,C):C,(t.sham||C&&C.sham||i&&i.sham)&&j(o,"sham",!0),h[I]=o,l&&(f(Y,A=s+"Prototype")||j(Y,A,{}),Y[A][I]=C,t.real&&u&&!u[I]&&j(u,I,C)))}var v=I("object"==typeof globalThis&&globalThis)||I("object"==typeof window&&window)||I("object"==typeof self&&self)||I("object"==typeof g&&g)||Function("return this")(),h=!a(function(){return 7!=Object.defineProperty({},1,{get:function(){return 7}})[1]}),u={}.propertyIsEnumerable,w=Object.getOwnPropertyDescriptor,x={f:w&&!u.call({1:2},1)?function(t){var g=w(this,t);return!!g&&g.enumerable}:u},k={}.toString,D="".split,O=a(function(){return!Object("z").propertyIsEnumerable(0)})?function(t){return"String"==C(t)?D.call(t,""):Object(t)}:Object,N={}.hasOwnProperty,E=v.document,Z=d(E)&&d(E.createElement),M=!h&&!a(function(){return 7!=Object.defineProperty(n("div"),"a",{get:function(){return 7}}).a}),T=Object.getOwnPropertyDescriptor,G={f:h?T:function(t,g){if(t=r(t),g=i(g,!0),M)try{return T(t,g)}catch(t){}if(f(t,g))return y(!x.f.call(t,g),t[g])}},B=/#|\.prototype\./,R=s.normalize=function(t){return String(t).replace(B,".").toLowerCase()},S=s.data={},F=s.NATIVE="N",L=s.POLYFILL="P",Q=s,Y={},W=Object.defineProperty,z={f:h?W:function(t,g,e){if(c(t),g=i(g,!0),c(e),M)try{return W(t,g,e)}catch(t){}if("get"in e||"set"in e)throw TypeError("Accessors not supported");return"value"in e&&(t[g]=e.value),t}},j=h?function(t,g,e){return z.f(t,g,y(1,e))}:function(t,g,e){return t[g]=e,t},P=G.f,V=[].slice,X={},U=Function.bind||function(g){var e=l(this),A=V.call(arguments,1),I=function(){var t=A.concat(V.call(arguments));return this instanceof I?function(t,g,e){if(!(g in X)){for(var A=[],I=0;Is&&(c=s),l=Math.sqrt(c*c/(1+r*r)),g+=l=n<0?-l:l,e+=r*l,!0===d?t.lineTo(g,e):t.moveTo(g,e),s-=c,d=!d}var At={circle:q,dashedLine:et,database:gt,diamond:function(t,g,e,A){t.beginPath(),t.lineTo(g,e+A),t.lineTo(g+A,e),t.lineTo(g,e-A),t.lineTo(g-A,e),t.closePath()},ellipse:tt,ellipse_vis:tt,hexagon:function(t,g,e,A){t.beginPath();var I=2*Math.PI/6;t.moveTo(g+A,e);for(var C=1;C<6;C++)t.lineTo(g+A*Math.cos(I*C),e+A*Math.sin(I*C));t.closePath()},roundRect:$,square:function(t,g,e,A){t.beginPath(),t.rect(g-A,e-A,2*A,2*A),t.closePath()},star:function(t,g,e,A){t.beginPath(),e+=.1*(A*=.82);for(var I=0;I<10;I++){var C=I%2==0?1.3*A:.5*A;t.lineTo(g+C*Math.sin(2*I*Math.PI/10),e-C*Math.cos(2*I*Math.PI/10))}t.closePath()},triangle:function(t,g,e,A){t.beginPath(),e+=.275*(A*=1.15);var I=2*A,C=I/2,i=Math.sqrt(3)/6*I,n=Math.sqrt(I*I-C*C);t.moveTo(g,e-(n-i)),t.lineTo(g+C,e+i),t.lineTo(g-C,e+i),t.lineTo(g,e-(n-i)),t.closePath()},triangleDown:function(t,g,e,A){t.beginPath(),e-=.275*(A*=1.15);var I=2*A,C=I/2,i=Math.sqrt(3)/6*I,n=Math.sqrt(I*I-C*C);t.moveTo(g,e+(n-i)),t.lineTo(g+C,e-i),t.lineTo(g-C,e-i),t.lineTo(g,e+(n-i)),t.closePath()}},It=e(function(t){function e(t){if(t)return function(t){for(var g in e.prototype)t[g]=e.prototype[g];return t}(t)}(t.exports=e).prototype.on=e.prototype.addEventListener=function(t,g){return this._callbacks=this._callbacks||{},(this._callbacks["$"+t]=this._callbacks["$"+t]||[]).push(g),this},e.prototype.once=function(t,g){function e(){this.off(t,e),g.apply(this,arguments)}return e.fn=g,this.on(t,e),this},e.prototype.off=e.prototype.removeListener=e.prototype.removeAllListeners=e.prototype.removeEventListener=function(t,g){if(this._callbacks=this._callbacks||{},0==arguments.length)return this._callbacks={},this;var e,A=this._callbacks["$"+t];if(!A)return this;if(1==arguments.length)return delete this._callbacks["$"+t],this;for(var I=0;II;)ht(A,e=g[I++])&&(~ng(C,e)||C.push(e));return C}var qt=it(function(t){var A=zt.Object,g=t.exports=function(t,g,e){return A.defineProperty(t,g,e)};A.defineProperty.sham&&(g.sham=!0)}),$t=qt,tg=Math.ceil,gg=Math.floor,eg=Math.min,Ag=Math.max,Ig=Math.min,Cg={includes:Jt(!0),indexOf:Jt(!1)},ig={},ng=Cg.indexOf,og=["constructor","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","toLocaleString","toString","valueOf"],rg=Object.keys||function(t){return Kt(t,og)},sg=wt?Object.defineProperties:function(t,g){vt(t);for(var e,A=rg(g),I=A.length,C=0;Cn;)void 0!==(e=I(A,g=C[n++]))&&lg(i,g,e);return i}});var vg=zt.Object.getOwnPropertyDescriptors,yg=Rt.f,mg=ot(function(){yg(1)});mt({target:"Object",stat:!0,forced:!wt||mg,sham:!wt},{getOwnPropertyDescriptor:function(t,g){return yg(dt(t),g)}});function bg(t){return Object(at(t))}function wg(t){return"Symbol("+String(void 0===t?"":t)+")_"+(++Rg+Sg).toString(36)}function xg(t){return Fg[t]||(Fg[t]=wg(t))}function kg(){}function Dg(t){return"