From 077d4ba092bdbed7e23ed9bc58fa1dc4ba179269 Mon Sep 17 00:00:00 2001 From: M87NET Date: Thu, 23 Mar 2023 10:18:49 +0800 Subject: [PATCH 01/10] =?UTF-8?q?rest=20api=E5=8F=96=E6=B6=88=E8=AE=A4?= =?UTF-8?q?=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/apps/api/tools/custom_viewset_base.py | 1 + netaxe/apps/api/views.py | 3 +- netaxe/apps/asset/views.py | 94 +++----- netaxe/apps/automation/views.py | 6 +- netaxe/apps/config_center/views.py | 24 +- netaxe/apps/int_utilization/views.py | 5 +- netaxe/apps/open_ipam/urls.py | 6 - netaxe/apps/open_ipam/views.py | 16 +- netaxe/apps/route_backend/views.py | 26 ++- netaxe/apps/topology/views.py | 7 +- netaxe/utils/tools/custom_viewset_base.py | 1 + web/src/assets/rack_bkg.jpeg | Bin 0 -> 48792 bytes web/src/types/d3Types.ts | 25 +++ web/src/views/net_topology/show.vue | 7 +- web/src/views/net_topology/test.vue | 113 ++++++++++ web/src/views/net_topology/test2.vue | 224 +++++++++++++++++++ 16 files changed, 452 insertions(+), 106 deletions(-) create mode 100644 web/src/assets/rack_bkg.jpeg create mode 100644 web/src/types/d3Types.ts create mode 100644 web/src/views/net_topology/test.vue create mode 100644 web/src/views/net_topology/test2.vue diff --git a/netaxe/apps/api/tools/custom_viewset_base.py b/netaxe/apps/api/tools/custom_viewset_base.py index 9bc02e0..087c2ab 100644 --- a/netaxe/apps/api/tools/custom_viewset_base.py +++ b/netaxe/apps/api/tools/custom_viewset_base.py @@ -17,6 +17,7 @@ class CustomViewBase(viewsets.ModelViewSet): queryset = '' serializer_class = '' permission_classes = () + authentication_classes = () filter_fields = () search_fields = () filter_backends = (rest_framework.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) diff --git a/netaxe/apps/api/views.py b/netaxe/apps/api/views.py index 18bb133..6b5b684 100644 --- a/netaxe/apps/api/views.py +++ b/netaxe/apps/api/views.py @@ -13,7 +13,7 @@ from .tools.custom_viewset_base import CustomViewBase # from rest_framework_extensions.cache.mixins import CacheResponseMixin from rest_framework_extensions.cache.decorators import cache_response from rest_framework_extensions.cache.mixins import BaseCacheResponseMixin, CacheResponseMixin -from rest_framework_tracking.mixins import LoggingMixin +# from rest_framework_tracking.mixins import LoggingMixin from .tools.custom_pagination import LargeResultsSetPagination from apps.api.serializers import * from rest_framework_extensions.key_constructor import bits @@ -193,7 +193,6 @@ class LimitSet(pagination.LimitOffsetPagination): # # print('after', self.log['data']) # # Do some stuff after saving. - # 任务列表 class PeriodicTaskViewSet(viewsets.ModelViewSet): # queryset = PeriodicTask.objects.all().order_by('id') diff --git a/netaxe/apps/asset/views.py b/netaxe/apps/asset/views.py index bb55bb6..050fe1c 100644 --- a/netaxe/apps/asset/views.py +++ b/netaxe/apps/asset/views.py @@ -101,8 +101,8 @@ class IdcViewSet(viewsets.ModelViewSet): """ queryset = Idc.objects.all().order_by('-id') serializer_class = IdcSerializer - # permission_classes = (permissions.IsAuthenticated,) - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () + authentication_classes = () pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) @@ -111,45 +111,22 @@ class IdcViewSet(viewsets.ModelViewSet): search_fields = '__all__' -class NetZoneFilter(django_filters.FilterSet): - """模糊字段过滤""" - - # vendor = django_filters.CharFilter(lookup_expr='icontains') - # memo = django_filters.CharFilter(lookup_expr='icontains') - # name = django_filters.CharFilter(lookup_expr='icontains') - - class Meta: - model = NetZone - fields = '__all__' - - class CmdbNetzoneModelViewSet(viewsets.ModelViewSet): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ - queryset = NetZone.objects.all().order_by('id') - # queryset = NetZoneSerializer.setup_eager_loading(queryset) + queryset = NetZone.objects.all().order_by('-id') serializer_class = NetZoneSerializer - # permission_classes = (permissions.IsAuthenticated,) - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () + authentication_classes = () pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - filterset_class = NetZoneFilter - # filter_fields = '__all__' - # list_cache_key_func = QueryParamsKeyConstructor() - + filter_fields = '__all__' + # 设置搜索的关键字 + search_fields = '__all__' -class RackFilter(django_filters.FilterSet): - """模糊字段过滤""" - # vendor = django_filters.CharFilter(lookup_expr='icontains') - # memo = django_filters.CharFilter(lookup_expr='icontains') - # name = django_filters.CharFilter(lookup_expr='icontains') - - class Meta: - model = Rack - fields = '__all__' class CmdbRackModelViewSet(viewsets.ModelViewSet): @@ -160,25 +137,11 @@ class CmdbRackModelViewSet(viewsets.ModelViewSet): # queryset = NetZoneSerializer.setup_eager_loading(queryset) serializer_class = CmdbRackSerializer # permission_classes = (permissions.IsAuthenticated,) - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - filterset_class = RackFilter - # filter_fields = '__all__' - # list_cache_key_func = QueryParamsKeyConstructor() - - -class IdcModelFilter(django_filters.FilterSet): - """模糊字段过滤""" - - # vendor = django_filters.CharFilter(lookup_expr='icontains') - # memo = django_filters.CharFilter(lookup_expr='icontains') - # name = django_filters.CharFilter(lookup_expr='icontains') - - class Meta: - model = IdcModel - fields = '__all__' + filter_fields = '__all__' class CmdbIdcModelViewSet(viewsets.ModelViewSet): @@ -189,13 +152,11 @@ class CmdbIdcModelViewSet(viewsets.ModelViewSet): queryset = IdcModelSerializer.setup_eager_loading(queryset) serializer_class = IdcModelSerializer # permission_classes = (permissions.IsAuthenticated,) - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - filterset_class = IdcModelFilter - # filter_fields = '__all__' - # list_cache_key_func = QueryParamsKeyConstructor() + filter_fields = '__all__' # asset account @@ -206,6 +167,7 @@ class AccountList(viewsets.ModelViewSet): queryset = AssetAccount.objects.all().order_by('id') serializer_class = AssetAccountSerializer pagination_class = LimitSet + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 @@ -222,7 +184,7 @@ class VendorViewSet(viewsets.ModelViewSet): """ queryset = Vendor.objects.all().order_by('id') serializer_class = AssetVendorSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 @@ -239,7 +201,7 @@ class AssetRoleViewSet(viewsets.ModelViewSet): """ queryset = Role.objects.all().order_by('id') serializer_class = RoleSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 @@ -255,7 +217,7 @@ class CategoryViewSet(viewsets.ModelViewSet): """ queryset = Category.objects.all().order_by('id') serializer_class = CategorySerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 @@ -272,7 +234,7 @@ class ModelViewSet(viewsets.ModelViewSet): queryset = Model.objects.all().order_by('id') queryset = ModelSerializer.setup_eager_loading(queryset) serializer_class = ModelSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) filter_fields = ('vendor', 'name') @@ -285,7 +247,7 @@ class AttributelViewSet(viewsets.ModelViewSet): """ queryset = Attribute.objects.all().order_by('id') serializer_class = AttributeSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) filter_fields = '__all__' @@ -298,7 +260,7 @@ class FrameworkViewSet(viewsets.ModelViewSet): """ queryset = Framework.objects.all().order_by('id') serializer_class = FrameworkSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) filter_fields = '__all__' @@ -324,12 +286,16 @@ class NetworkDeviceViewSet(viewsets.ModelViewSet): queryset = NetworkDevice.objects.all().order_by('-id') queryset = NetworkDeviceSerializer.setup_eager_loading(queryset) serializer_class = NetworkDeviceSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () + authentication_classes = () # authentication_classes = (authentication.JWTAuthentication,) # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - filterset_class = NetworkDeviceFilter - # filter_fields = ('asset', 'asset__idc__name', 'asset__idc_model__name', 'asset__rack__name') + # filterset_class = NetworkDeviceFilter + filter_fields = ('serial_num', 'manage_ip', 'bridge_mac', 'category__name', 'role__name', + 'name', 'vendor__name', 'idc__name', 'patch_version', 'idc_model__name', 'soft_version', + 'model__name', 'netzone__name', 'attribute__name', 'framework__name', 'rack__name', + 'u_location', 'memo', 'status', 'ha_status') # pagination_class = LimitSet pagination_class = LargeResultsSetPagination # 设置搜索的关键字 @@ -338,8 +304,6 @@ class NetworkDeviceViewSet(viewsets.ModelViewSet): 'model__name', 'netzone__name', 'attribute__name', 'framework__name', 'rack__name', 'u_location', 'memo', 'status', 'ha_status') - # list_cache_key_func = QueryParamsKeyConstructor() - def get_queryset(self): """ expires 比 expire多一个s ,用来筛选已过期的设备数据 lt 小于 gt 大于 lte小于等于 gte 大于等于 @@ -361,6 +325,6 @@ class NetworkDeviceViewSet(viewsets.ModelViewSet): return self.queryset # 重新update方法主要用来捕获更改前的字段值并赋值给self.log - def update(self, request, *args, **kwargs): - print('更新', super().update(request, *args, **kwargs)) - return super().update(request, *args, **kwargs) + # def update(self, request, *args, **kwargs): + # print('更新', super().update(request, *args, **kwargs)) + # return super().update(request, *args, **kwargs) diff --git a/netaxe/apps/automation/views.py b/netaxe/apps/automation/views.py index fd3df25..8793adc 100644 --- a/netaxe/apps/automation/views.py +++ b/netaxe/apps/automation/views.py @@ -29,10 +29,10 @@ class CollectionPlanViewSet(CustomViewBase): queryset = CollectionPlan.objects.all().order_by('-id') queryset = CollectionPlanSerializer.setup_eager_loading(queryset) serializer_class = CollectionPlanSerializer - permission_classes = (permissions.IsAuthenticated,) + # permission_classes = (permissions.IsAuthenticated,) # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) - # filter_fields = ('vendor', 'name',) - filterset_class = CollectionPlanFilter + filter_fields = ('vendor', 'name',) + # filterset_class = CollectionPlanFilter search_fields = ('vendor', 'name',) pagination_class = LimitSet diff --git a/netaxe/apps/config_center/views.py b/netaxe/apps/config_center/views.py index 4a54cc1..989c44b 100644 --- a/netaxe/apps/config_center/views.py +++ b/netaxe/apps/config_center/views.py @@ -43,7 +43,7 @@ def jinja_render(data, template): class ConfigComplianceViewSet(CustomViewBase): queryset = ConfigCompliance.objects.all().order_by('-id') serializer_class = ConfigComplianceSerializer - permission_classes = (permissions.IsAuthenticated,) + # permission_classes = (permissions.IsAuthenticated,) pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) @@ -58,7 +58,7 @@ class ConfigTemplateViewSet(CustomViewBase): """ queryset = ConfigTemplate.objects.all().order_by('-id') serializer_class = ConfigTemplateSerializer - permission_classes = (permissions.IsAuthenticated,) + # permission_classes = (permissions.IsAuthenticated,) # pagination_class = LimitSet pagination_class = LargeResultsSetPagination # 配置搜索功能 @@ -76,7 +76,7 @@ class TTPTemplateViewSet(CustomViewBase): """ queryset = TTPTemplate.objects.all().order_by('-id') serializer_class = TTPTemplateSerializer - permission_classes = (permissions.IsAuthenticated,) + # permission_classes = (permissions.IsAuthenticated,) # pagination_class = LimitSet pagination_class = LargeResultsSetPagination # 配置搜索功能 @@ -98,7 +98,9 @@ class DateEncoder(json.JSONEncoder): class GitConfig(APIView): - permission_classes = (IsAuthenticated,) + permission_classes = () + authentication_classes = () + # permission_classes = (IsAuthenticated,) # authentication_classes = (JWTAuthentication, SessionAuthentication) @@ -170,6 +172,9 @@ class GitConfig(APIView): class ComplianceResults(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_param = request.GET.dict() if 'get_results' in get_param.keys(): @@ -191,6 +196,9 @@ class ComplianceResults(APIView): class RegexTest(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_param = request.GET.dict() data = { @@ -222,6 +230,9 @@ class RegexTest(APIView): # TTP 前端页面接口 class TTPParse(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): pass @@ -251,6 +262,9 @@ class TTPParse(APIView): # TextFSM 前端页面接口 class TextFSMParse(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_param = request.GET.dict() if 'get_tree' in get_param.keys(): @@ -324,6 +338,8 @@ class TextFSMParse(APIView): class Jinja2View(APIView): + permission_classes = () + authentication_classes = () def get(self, request): get_param = request.GET.dict() diff --git a/netaxe/apps/int_utilization/views.py b/netaxe/apps/int_utilization/views.py index 532cf22..ed2e843 100644 --- a/netaxe/apps/int_utilization/views.py +++ b/netaxe/apps/int_utilization/views.py @@ -26,13 +26,14 @@ class InterfaceUsedNewViewSet(CustomViewBase): queryset = InterfaceUsedNew.objects.all().order_by('-log_time') # queryset = InterfaceUsedNewSerializer.setup_eager_loading(queryset) serializer_class = InterfaceUsedNewSerializer - permission_classes = (permissions.IsAuthenticated,) + # permission_classes = (permissions.IsAuthenticated,) pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 - filterset_class = InterfaceUsedFilter + # filterset_class = InterfaceUsedFilter search_fields = ('host_ip', 'host') + filter_fields = ('host_ip', 'host') ordering_fields = ('log_time', 'id') def get_queryset(self): diff --git a/netaxe/apps/open_ipam/urls.py b/netaxe/apps/open_ipam/urls.py index 903243f..46ea617 100644 --- a/netaxe/apps/open_ipam/urls.py +++ b/netaxe/apps/open_ipam/urls.py @@ -13,16 +13,10 @@ router.register(r'periodic_task', PeriodicTaskViewSet) router.register(r'interval_schedule', IntervalScheduleViewSet) urlpatterns = [ path(r'', include(router.urls)), - # path(r'api/', include(router.urls)), - # path('jobCenter/', JobCenterView.as_view(), name='jobCenter'), path('subnet_tree/', IpAmSubnetTreeView.as_view(), name='subnet_tree'), path('address_handel/', csrf_exempt(IpAmHandelView.as_view()), name='address_handel'), - path('subnet//ip_address/', SubnetAddressView.as_view(), name='subnet_ip_address'), - - # 供admin后台展示,暂未修改过多逻辑 path('subnet//hosts/', SubnetHostsView.as_view(), name='hosts'), - # path('subnet//hosts/', SubnetHostsView.as_view(), name='hosts'), path( 'subnet//get-next-available-ip/', AvailableIpView.as_view(), diff --git a/netaxe/apps/open_ipam/views.py b/netaxe/apps/open_ipam/views.py index 512799f..bee1294 100644 --- a/netaxe/apps/open_ipam/views.py +++ b/netaxe/apps/open_ipam/views.py @@ -1,16 +1,11 @@ import json from collections import OrderedDict import ipaddr -import netaddr -from celery import current_app -from django.db.models import Sum -from django.http import JsonResponse, HttpResponse -from django.shortcuts import render +from django.http import JsonResponse # Create your views here. -from django_celery_beat.models import CrontabSchedule, PeriodicTask, IntervalSchedule +from django_celery_beat.models import PeriodicTask, IntervalSchedule from django_filters.rest_framework import DjangoFilterBackend -from kombu.utils.json import loads from netaddr import iter_iprange from rest_framework import serializers, pagination, viewsets, permissions, filters from django.utils.translation import gettext_lazy as _ @@ -20,16 +15,12 @@ from rest_framework.permissions import DjangoModelPermissions from rest_framework.response import Response from rest_framework.utils.urls import replace_query_param, remove_query_param from rest_framework.views import APIView - -from netboost.celery import app -# from .tasks import get_all_tasks from .models import Subnet, IpAddress, TagsModel from .serializers import HostsResponseSerializer, SubnetSerializer, IpAddressSerializer, \ PeriodicTaskSerializer, IntervalScheduleSerializer, TagsModelSerializer from utils.custom.pagination import LargeResultsSetPagination from utils.custom.viewset import CustomViewBase from utils.ipam_utils import IpAmForNetwork -# from ..route_backend.tasks import get_tasks class HostsResponse(object): @@ -216,14 +207,12 @@ class AvailableIpView(RetrieveAPIView): def get(self, request, *args, **kwargs): subnet = get_object_or_404(self.subnet_model, pk=self.kwargs['subnet_id']) - print(subnet) return Response(subnet.get_next_available_ip()) # 子网网段API class SubnetApiViewSet(CustomViewBase): # subnet_model = Subnet - permission_classes = (permissions.IsAuthenticated,) queryset = Subnet.objects.all().order_by('-id') serializer_class = SubnetSerializer pagination_class = LargeResultsSetPagination @@ -259,7 +248,6 @@ class LimitSet(pagination.LimitOffsetPagination): # 任务列表 class PeriodicTaskViewSet(viewsets.ModelViewSet): - # queryset = PeriodicTask.objects.all().order_by('id') queryset = PeriodicTask.objects.exclude(task__startswith='celery').order_by('id') serializer_class = PeriodicTaskSerializer permission_classes = (permissions.IsAuthenticated,) diff --git a/netaxe/apps/route_backend/views.py b/netaxe/apps/route_backend/views.py index f0044d6..0e92bdf 100644 --- a/netaxe/apps/route_backend/views.py +++ b/netaxe/apps/route_backend/views.py @@ -33,6 +33,7 @@ from .serializers import CrontabSerializer, IntervalSerializer class QueryParamsKeyConstructor(DefaultKeyConstructor): all_query_params = bits.QueryParamsKeyBit() + class LimitSet(pagination.LimitOffsetPagination): # 每页默认几条 default_limit = 10 @@ -45,7 +46,11 @@ class LimitSet(pagination.LimitOffsetPagination): # 最大每页显示条数 max_limit = None + class DashboardChart(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_params = request.GET.dict() if "device_idc_dimension" in get_params: @@ -61,6 +66,9 @@ class DashboardChart(APIView): class WebSshView(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_param = request.GET.dict() server_obj = NetworkDevice.objects.get(id=get_param.get('pk')) @@ -101,6 +109,9 @@ class WebSshView(APIView): # 设备采集方案 class DeviceCollectView(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_param = request.GET.dict() if all(k in get_param for k in ("vendor", "netconf_class")): @@ -131,6 +142,9 @@ class DeviceCollectView(APIView): # 自动化chart class AutomationChart(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): get_params = request.GET.dict() @@ -205,6 +219,9 @@ class DispatchManageView(View): # 作业中心taskList class JobCenterView(APIView): + permission_classes = () + authentication_classes = () + def get(self, request): # Operationinfo = 'None' get_current_tasks = request.GET.get('current_tasks') @@ -262,7 +279,8 @@ class PeriodicTaskViewSet(viewsets.ModelViewSet): # queryset = PeriodicTask.objects.all().order_by('id') queryset = PeriodicTask.objects.exclude(task__startswith='celery').order_by('id') serializer_class = PeriodicTaskSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () + authentication_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 @@ -273,10 +291,11 @@ class PeriodicTaskViewSet(viewsets.ModelViewSet): # list_cache_key_func = QueryParamsKeyConstructor() -class IntervalScheduleViewSet(CacheResponseMixin, viewsets.ModelViewSet): +class IntervalScheduleViewSet(viewsets.ModelViewSet): queryset = IntervalSchedule.objects.all().order_by('id') serializer_class = IntervalScheduleSerializer - permission_classes = (permissions.IsAuthenticated,) + permission_classes = () + authentication_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 @@ -284,4 +303,3 @@ class IntervalScheduleViewSet(CacheResponseMixin, viewsets.ModelViewSet): pagination_class = LimitSet # 设置搜索的关键字 search_fields = '__all__' - list_cache_key_func = QueryParamsKeyConstructor() \ No newline at end of file diff --git a/netaxe/apps/topology/views.py b/netaxe/apps/topology/views.py index d27e823..14dd730 100644 --- a/netaxe/apps/topology/views.py +++ b/netaxe/apps/topology/views.py @@ -51,7 +51,9 @@ class TopologyViewSet(CustomViewBase): # 拓扑显示 class TopologyShow(APIView): - permission_classes = (IsAuthenticated,) + # permission_classes = (IsAuthenticated,) + permission_classes = () + authentication_classes = () def get(self, request): get_param = request.GET.dict() @@ -195,7 +197,8 @@ class TopologyShow(APIView): # 图标库 class IconView(APIView): - permission_classes = (IsAuthenticated,) + permission_classes = () + authentication_classes = () def get(self, request): get_param = request.GET.dict() diff --git a/netaxe/utils/tools/custom_viewset_base.py b/netaxe/utils/tools/custom_viewset_base.py index 9bc02e0..087c2ab 100644 --- a/netaxe/utils/tools/custom_viewset_base.py +++ b/netaxe/utils/tools/custom_viewset_base.py @@ -17,6 +17,7 @@ class CustomViewBase(viewsets.ModelViewSet): queryset = '' serializer_class = '' permission_classes = () + authentication_classes = () filter_fields = () search_fields = () filter_backends = (rest_framework.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) diff --git a/web/src/assets/rack_bkg.jpeg b/web/src/assets/rack_bkg.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8999e59f876fd3362c5fe658fc309efb1e8325d1 GIT binary patch literal 48792 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

OG>3Ymi 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 p9>+|XTR4`EFz&gprY|jOB(SaT;T(null) const zoom = ref(null) - const group = ref(null) + const group = ref(null) var svg_node = null var svg_link = null var svg_brush = null @@ -585,7 +585,6 @@ interface: [], }) const topology_value = ref(null) - // const topology_data = shallowReactive([]) as Array const topology_options = shallowReactive([]) as Array const showAddNodeButton = ref(false) function node_click(node) { @@ -1010,7 +1009,7 @@ // .force('collision', d3.forceCollide().radius(25)) svg.value = d3 - .select('svg#primary-svg') + .select(toppolgoy_svg.value) // .attr('viewBox', '0 0 3000 3000') .classed('svg-content', true) group.value = svg.value.append('g') diff --git a/web/src/views/net_topology/test.vue b/web/src/views/net_topology/test.vue new file mode 100644 index 0000000..88bf518 --- /dev/null +++ b/web/src/views/net_topology/test.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/web/src/views/net_topology/test2.vue b/web/src/views/net_topology/test2.vue new file mode 100644 index 0000000..7cc2884 --- /dev/null +++ b/web/src/views/net_topology/test2.vue @@ -0,0 +1,224 @@ + + + + + -- Gitee From bb70d962e8c5da6dc2f9c2097586bac2e6ab865a Mon Sep 17 00:00:00 2001 From: M87NET Date: Thu, 23 Mar 2023 10:25:25 +0800 Subject: [PATCH 02/10] =?UTF-8?q?=E5=8F=96=E6=B6=88=E8=AE=A4=E8=AF=81?= =?UTF-8?q?=EF=BC=8C=E7=BB=9F=E4=B8=80API=20=E7=9A=84view=E7=BB=A7?= =?UTF-8?q?=E6=89=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/apps/api/views.py | 164 +-------------------- netaxe/apps/asset/views.py | 37 ++--- netaxe/apps/automation/views.py | 3 +- netaxe/apps/int_utilization/views.py | 4 +- netaxe/apps/route_backend/views.py | 10 +- netaxe/apps/topology/views.py | 4 +- netaxe/utils/tools/custom_exception.py | 69 --------- netaxe/utils/tools/custom_json_response.py | 47 ------ netaxe/utils/tools/custom_pagination.py | 47 ------ netaxe/utils/tools/custom_viewset_base.py | 67 --------- 10 files changed, 24 insertions(+), 428 deletions(-) delete mode 100644 netaxe/utils/tools/custom_exception.py delete mode 100644 netaxe/utils/tools/custom_json_response.py delete mode 100644 netaxe/utils/tools/custom_pagination.py delete mode 100644 netaxe/utils/tools/custom_viewset_base.py diff --git a/netaxe/apps/api/views.py b/netaxe/apps/api/views.py index 6b5b684..3b1ca6f 100644 --- a/netaxe/apps/api/views.py +++ b/netaxe/apps/api/views.py @@ -8,22 +8,11 @@ from django_filters.rest_framework import DjangoFilterBackend import django_filters from rest_framework import viewsets, permissions, filters, pagination -from .serializers import PeriodicTaskSerializer -from .tools.custom_viewset_base import CustomViewBase -# from rest_framework_extensions.cache.mixins import CacheResponseMixin -from rest_framework_extensions.cache.decorators import cache_response -from rest_framework_extensions.cache.mixins import BaseCacheResponseMixin, CacheResponseMixin -# from rest_framework_tracking.mixins import LoggingMixin -from .tools.custom_pagination import LargeResultsSetPagination from apps.api.serializers import * from rest_framework_extensions.key_constructor import bits from rest_framework_extensions.key_constructor.constructors import ( DefaultKeyConstructor ) -from datetime import date - -from apps.automation.models import CollectionPlan -from apps.int_utilization.models import InterfaceUsedNew class QueryParamsKeyConstructor(DefaultKeyConstructor): @@ -43,156 +32,6 @@ class LimitSet(pagination.LimitOffsetPagination): max_limit = None -# class CategoryViewSet(viewsets.ModelViewSet): -# """ -# 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE -# """ -# queryset = Category.objects.all().order_by('id') -# serializer_class = CategorySerializer -# permission_classes = (permissions.IsAuthenticated,) -# # 配置搜索功能 -# filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) -# # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 -# filter_fields = '__all__' -# pagination_class = LimitSet -# # 设置搜索的关键字 -# search_fields = '__all__' -# -# -# -# -# -# class ModelViewSet(viewsets.ModelViewSet): -# """ -# 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE -# """ -# queryset = Model.objects.all().order_by('id') -# queryset = ModelSerializer.setup_eager_loading(queryset) -# serializer_class = ModelSerializer -# permission_classes = (permissions.IsAuthenticated,) -# # 配置搜索功能 -# filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) -# filter_fields = ('vendor', 'name') -# pagination_class = LimitSet -# -# -# class AttributelViewSet(viewsets.ModelViewSet): -# """ -# 处理 设备网络属性 GET POST , 处理 /api/post// GET PUT PATCH DELETE -# """ -# queryset = Attribute.objects.all().order_by('id') -# serializer_class = AttributeSerializer -# permission_classes = (permissions.IsAuthenticated,) -# # 配置搜索功能 -# filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) -# filter_fields = '__all__' -# pagination_class = LimitSet -# -# -# class FrameworkViewSet(viewsets.ModelViewSet): -# """ -# 处理 设备网络架构 GET POST , 处理 /api/post// GET PUT PATCH DELETE -# """ -# queryset = Framework.objects.all().order_by('id') -# serializer_class = FrameworkSerializer -# permission_classes = (permissions.IsAuthenticated,) -# # 配置搜索功能 -# filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) -# filter_fields = '__all__' -# pagination_class = LimitSet -# -# -# class NetworkDeviceFilter(django_filters.FilterSet): -# """模糊字段过滤""" -# -# serial_num = django_filters.CharFilter(lookup_expr='icontains') -# manage_ip = django_filters.CharFilter(lookup_expr='icontains') -# name = django_filters.CharFilter(lookup_expr='icontains') -# -# class Meta: -# model = NetworkDevice -# fields = '__all__' -# -# -# class NetworkDeviceViewSet(LoggingMixin, viewsets.ModelViewSet): -# """ -# 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE -# """ -# logging_methods = ['POST', 'PUT', 'PATCH', 'DELETE'] -# queryset = NetworkDevice.objects.all().order_by('-id') -# queryset = NetworkDeviceSerializer.setup_eager_loading(queryset) -# serializer_class = NetworkDeviceSerializer -# permission_classes = (permissions.IsAuthenticated,) -# # authentication_classes = (authentication.JWTAuthentication,) -# # 配置搜索功能 -# filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) -# filterset_class = NetworkDeviceFilter -# # filter_fields = ('asset', 'asset__idc__name', 'asset__idc_model__name', 'asset__rack__name') -# # pagination_class = LimitSet -# pagination_class = LargeResultsSetPagination -# # 设置搜索的关键字 -# search_fields = ('serial_num', 'manage_ip', 'bridge_mac', 'category__name', 'role__name', -# 'name', 'vendor__name', 'idc__name', 'patch_version', 'idc_model__name', 'soft_version', -# 'model__name', 'netzone__name', 'attribute__name', 'framework__name', 'rack__name', -# 'u_location', 'memo', 'status', 'ha_status') -# -# # list_cache_key_func = QueryParamsKeyConstructor() -# -# def get_queryset(self): -# """ -# expires 比 expire多一个s ,用来筛选已过期的设备数据 lt 小于 gt 大于 lte小于等于 gte 大于等于 -# :return: -# """ -# expires = self.request.query_params.get('expires', None) -# search_host_list = self.request.query_params.get('search_host_list', None) -# if search_host_list: -# if search_host_list.find('-') != -1: -# return self.queryset.filter(manage_ip__in=search_host_list.split('-')) -# else: -# return self.queryset.filter(manage_ip__in=[search_host_list]) -# # return self.queryset.filter(manage_ip__in=search_host_list) -# if expires == '1': -# return self.queryset.filter(expire__lt=date.today()) -# elif expires == '0': -# return self.queryset.filter(expire__gt=date.today()) -# else: -# return self.queryset -# -# # 重新update方法主要用来捕获更改前的字段值并赋值给self.log -# def update(self, request, *args, **kwargs): -# return super().update(request, *args, **kwargs) -# -# # 拼接log记录中data字段前后变化 -# def handle_log(self): -# # Do some stuff before saving. -# # print('before', self.log['data']) -# # print(self.request) -# if self.request.POST.get('serial_num'): -# # print('PUT记录写入') -# for key in self.request.POST.keys(): -# if key == 'serial_num': -# continue -# self.log['data'][key] += " => " + str(self.request.POST[key]) -# self.log['data'].pop('serial_num') -# if self.log['data'].get('id'): -# self.log['data'].pop('id') -# if self.log['view_method'] == 'create': -# tmp = json.loads(self.log['response']) -# if isinstance(tmp['data'], dict): -# if 'id' in tmp['data'].keys(): -# self.log['path'] += str(tmp['data']['id']) + '/' -# elif self.request.data.get('serial_num'): -# # print('PATCH记录写入') -# for key in self.request.data.keys(): -# if key == 'serial_num': -# continue -# self.log['data'][key] += " => " + str(self.request.data[key]) -# self.log['data'].pop('serial_num') -# # print(self.log) -# super(NetworkDeviceViewSet, self).handle_log() -# # print('after', self.log['data']) -# # Do some stuff after saving. - # 任务列表 class PeriodicTaskViewSet(viewsets.ModelViewSet): # queryset = PeriodicTask.objects.all().order_by('id') @@ -209,7 +48,7 @@ class PeriodicTaskViewSet(viewsets.ModelViewSet): # list_cache_key_func = QueryParamsKeyConstructor() -class IntervalScheduleViewSet(CacheResponseMixin, viewsets.ModelViewSet): +class IntervalScheduleViewSet(viewsets.ModelViewSet): queryset = IntervalSchedule.objects.all().order_by('id') serializer_class = IntervalScheduleSerializer permission_classes = (permissions.IsAuthenticated,) @@ -220,4 +59,3 @@ class IntervalScheduleViewSet(CacheResponseMixin, viewsets.ModelViewSet): pagination_class = LimitSet # 设置搜索的关键字 search_fields = '__all__' - list_cache_key_func = QueryParamsKeyConstructor() diff --git a/netaxe/apps/asset/views.py b/netaxe/apps/asset/views.py index 050fe1c..e18f975 100644 --- a/netaxe/apps/asset/views.py +++ b/netaxe/apps/asset/views.py @@ -1,22 +1,17 @@ import json import os from datetime import date - import django_filters from django.views import View from django.http import JsonResponse, FileResponse, Http404 from django_filters.rest_framework import DjangoFilterBackend from rest_framework.views import APIView -from rest_framework import viewsets, permissions, filters - +from rest_framework import viewsets, filters from netboost.settings import MEDIA_ROOT from apps.route_backend.views import LimitSet from utils.crypt_pwd import CryptPwd -# from scripts.crypt_pwd import CryptPwd -# from utils.excel2list import excel2list -# asset import export excel -# from utils.netops_api import netOpsApi -from utils.tools.custom_pagination import LargeResultsSetPagination +from apps.api.tools.custom_pagination import LargeResultsSetPagination +from apps.api.tools.custom_viewset_base import CustomViewBase from apps.asset.models import Idc, AssetAccount, Vendor, Role, Category, Model, Attribute, Framework, NetworkDevice, \ IdcModel, NetZone, Rack from apps.asset.serializers import IdcSerializer, AssetAccountSerializer, AssetVendorSerializer, RoleSerializer, \ @@ -95,7 +90,7 @@ class DeviceAccountView(APIView): # asset IDC -class IdcViewSet(viewsets.ModelViewSet): +class IdcViewSet(CustomViewBase): """ IDC 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -111,7 +106,7 @@ class IdcViewSet(viewsets.ModelViewSet): search_fields = '__all__' -class CmdbNetzoneModelViewSet(viewsets.ModelViewSet): +class CmdbNetzoneModelViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -127,9 +122,7 @@ class CmdbNetzoneModelViewSet(viewsets.ModelViewSet): search_fields = '__all__' - - -class CmdbRackModelViewSet(viewsets.ModelViewSet): +class CmdbRackModelViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -144,7 +137,7 @@ class CmdbRackModelViewSet(viewsets.ModelViewSet): filter_fields = '__all__' -class CmdbIdcModelViewSet(viewsets.ModelViewSet): +class CmdbIdcModelViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -160,7 +153,7 @@ class CmdbIdcModelViewSet(viewsets.ModelViewSet): # asset account -class AccountList(viewsets.ModelViewSet): +class AccountList(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -178,7 +171,7 @@ class AccountList(viewsets.ModelViewSet): # asset vendor -class VendorViewSet(viewsets.ModelViewSet): +class VendorViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -195,7 +188,7 @@ class VendorViewSet(viewsets.ModelViewSet): # asset role -class AssetRoleViewSet(viewsets.ModelViewSet): +class AssetRoleViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -211,7 +204,7 @@ class AssetRoleViewSet(viewsets.ModelViewSet): search_fields = '__all__' -class CategoryViewSet(viewsets.ModelViewSet): +class CategoryViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -227,7 +220,7 @@ class CategoryViewSet(viewsets.ModelViewSet): search_fields = '__all__' -class ModelViewSet(viewsets.ModelViewSet): +class ModelViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -241,7 +234,7 @@ class ModelViewSet(viewsets.ModelViewSet): pagination_class = LimitSet -class AttributelViewSet(viewsets.ModelViewSet): +class AttributelViewSet(CustomViewBase): """ 处理 设备网络属性 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -254,7 +247,7 @@ class AttributelViewSet(viewsets.ModelViewSet): pagination_class = LimitSet -class FrameworkViewSet(viewsets.ModelViewSet): +class FrameworkViewSet(CustomViewBase): """ 处理 设备网络架构 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ @@ -279,7 +272,7 @@ class NetworkDeviceFilter(django_filters.FilterSet): fields = '__all__' -class NetworkDeviceViewSet(viewsets.ModelViewSet): +class NetworkDeviceViewSet(CustomViewBase): """ 处理 GET POST , 处理 /api/post// GET PUT PATCH DELETE """ diff --git a/netaxe/apps/automation/views.py b/netaxe/apps/automation/views.py index 8793adc..994e324 100644 --- a/netaxe/apps/automation/views.py +++ b/netaxe/apps/automation/views.py @@ -6,8 +6,7 @@ from rest_framework import viewsets, permissions, filters, pagination from apps.route_backend.views import LimitSet from apps.automation.models import CollectionPlan from apps.automation.serializers import CollectionPlanSerializer - -from utils.tools.custom_viewset_base import CustomViewBase +from apps.api.tools.custom_viewset_base import CustomViewBase class CollectionPlanFilter(django_filters.FilterSet): diff --git a/netaxe/apps/int_utilization/views.py b/netaxe/apps/int_utilization/views.py index ed2e843..19aaa03 100644 --- a/netaxe/apps/int_utilization/views.py +++ b/netaxe/apps/int_utilization/views.py @@ -5,8 +5,8 @@ from rest_framework import viewsets, permissions, filters, pagination from .models import InterfaceUsedNew from .serializers import InterfaceUsedNewSerializer -from utils.tools.custom_viewset_base import CustomViewBase -from utils.tools.custom_pagination import LargeResultsSetPagination +from apps.api.tools.custom_viewset_base import CustomViewBase +from apps.api.tools.custom_pagination import LargeResultsSetPagination class InterfaceUsedFilter(django_filters.FilterSet): diff --git a/netaxe/apps/route_backend/views.py b/netaxe/apps/route_backend/views.py index 0e92bdf..ba4ed64 100644 --- a/netaxe/apps/route_backend/views.py +++ b/netaxe/apps/route_backend/views.py @@ -5,9 +5,6 @@ from django.views import View from django.db.models import Count from django.http import JsonResponse from django_filters.rest_framework import DjangoFilterBackend -from rest_framework_extensions.cache.mixins import BaseCacheResponseMixin, CacheResponseMixin -from django_celery_beat.models import PeriodicTask, PeriodicTasks, CrontabSchedule, IntervalSchedule - # 根据角色的菜单组件 from celery import current_app from kombu.utils.json import loads @@ -15,11 +12,10 @@ from rest_framework.views import APIView from rest_framework_extensions.key_constructor import bits from rest_framework import viewsets, permissions, filters, pagination from rest_framework_extensions.key_constructor.constructors import DefaultKeyConstructor - from apps.route_backend.serializers import * from apps.asset.models import NetworkDevice from apps.automation.models import CollectionPlan - +from apps.api.tools.custom_viewset_base import CustomViewBase from .tasks import get_tasks from netboost import settings from netboost.celery import app @@ -275,7 +271,7 @@ class JobCenterView(APIView): return JsonResponse({'code': 200, 'data': str(task_ids[0])}, safe=False) # 任务列表 -class PeriodicTaskViewSet(viewsets.ModelViewSet): +class PeriodicTaskViewSet(CustomViewBase): # queryset = PeriodicTask.objects.all().order_by('id') queryset = PeriodicTask.objects.exclude(task__startswith='celery').order_by('id') serializer_class = PeriodicTaskSerializer @@ -291,7 +287,7 @@ class PeriodicTaskViewSet(viewsets.ModelViewSet): # list_cache_key_func = QueryParamsKeyConstructor() -class IntervalScheduleViewSet(viewsets.ModelViewSet): +class IntervalScheduleViewSet(CustomViewBase): queryset = IntervalSchedule.objects.all().order_by('id') serializer_class = IntervalScheduleSerializer permission_classes = () diff --git a/netaxe/apps/topology/views.py b/netaxe/apps/topology/views.py index 14dd730..7fc6ae5 100644 --- a/netaxe/apps/topology/views.py +++ b/netaxe/apps/topology/views.py @@ -14,8 +14,8 @@ from apps.topology.tasks import TopologyTask from apps.topology.models import Topology from .serializers import TopologySerializer from utils.db.mongo_ops import MongoOps, MongoNetOps -from utils.tools.custom_pagination import LargeResultsSetPagination -from utils.tools.custom_viewset_base import CustomViewBase +from apps.api.tools.custom_viewset_base import CustomViewBase +from apps.api.tools.custom_pagination import LargeResultsSetPagination # Create your views here. # 设备二层接口表 diff --git a/netaxe/utils/tools/custom_exception.py b/netaxe/utils/tools/custom_exception.py deleted file mode 100644 index 463d594..0000000 --- a/netaxe/utils/tools/custom_exception.py +++ /dev/null @@ -1,69 +0,0 @@ -# -*- coding: utf-8 -*- -""" -------------------------------------------------- - File Name: custom_exception - Description: - Author: Lijiamin - date: 2022/7/29 11:05 -------------------------------------------------- - Change Activity: - 2022/7/29 11:05 -------------------------------------------------- -""" -from rest_framework.views import exception_handler - - -def custom_exception_handler(exc, context): - # Call REST framework's default exception handler first, - # to get the standard error response. - response = exception_handler(exc, context) - # 这个循环是取第一个错误的提示用于渲染 - # print(response.data) - message = '' - if response is None: - return response - for index, value in enumerate(response.data): - if index == 0: - key = value - value = response.data[key] - # print('value', value, type(value[0])) - if isinstance(value, str): - message = value - else: - message = key + value[0] - # Now add the HTTP status code to the response. - if response is not None: - # print("response.data", response.data, type(response.data)) - # print("response", response, type(response)) - # print(str(response.data)) - response.data.clear() - if not response.status_code: - response.status_code = 200 - response.data['code'] = response.status_code - response.data['data'] = [] - if response.status_code == 404: - try: - # response.data['message'] = response.data.pop('detail') - response.data['message'] = "Not found" - except KeyError: - response.data['message'] = "Not found" - - if response.status_code == 400: - - # response.data['message'] = 'Input error' - response.data['message'] = message - - elif response.status_code == 401: - response.data['message'] = "Auth failed" - - elif response.status_code >= 500: - response.data['message'] = "Internal service errors" - - elif response.status_code == 403: - response.data['message'] = "Access denied" - - elif response.status_code == 405: - response.data['message'] = 'Request method error' - response.code = response.status_code - response.status_code = 200 - return response \ No newline at end of file diff --git a/netaxe/utils/tools/custom_json_response.py b/netaxe/utils/tools/custom_json_response.py deleted file mode 100644 index 4beb6a2..0000000 --- a/netaxe/utils/tools/custom_json_response.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2020/11/12 18:24 -# @Author : LiJiaMin -# @Site : -# @File : custom_json_response.py -# @Software: PyCharm - -import six -from rest_framework.response import Response -from rest_framework.serializers import Serializer - - -class JsonResponse(Response): - """ - An HttpResponse that allows its data to be rendered into - arbitrary media types. - """ - - def __init__(self, data=None, code=None, msg=None, - status=None, - template_name=None, headers=None, - exception=False, content_type=None): - """ - Alters the init arguments slightly. - For example, drop 'template_name', and instead use 'data'. - Setting 'renderer' and 'media_type' will typically be deferred, - For example being set automatically by the `APIView`. - """ - super(Response, self).__init__(None, status=status) - - if isinstance(data, Serializer): - msg = ( - 'You passed a Serializer instance as data, but ' - 'probably meant to pass serialized `.data` or ' - '`.error`. representation.' - ) - raise AssertionError(msg) - - self.data = {"code": code, "message": msg, "data": data} - # print(self.data) - self.template_name = template_name - self.exception = exception - self.content_type = content_type - - if headers: - for name, value in six.iteritems(headers): - self[name] = value \ No newline at end of file diff --git a/netaxe/utils/tools/custom_pagination.py b/netaxe/utils/tools/custom_pagination.py deleted file mode 100644 index 6ba60b6..0000000 --- a/netaxe/utils/tools/custom_pagination.py +++ /dev/null @@ -1,47 +0,0 @@ -# -*- coding: utf-8 -*- -""" -------------------------------------------------- - File Name: custom_pagination - Description: - Author: Lijiamin - date: 2022/7/29 10:54 -------------------------------------------------- - Change Activity: - 2022/7/29 10:54 -------------------------------------------------- -""" -from rest_framework.pagination import LimitOffsetPagination -from collections import OrderedDict -from rest_framework.response import Response - - -class LargeResultsSetPagination(LimitOffsetPagination): - # 每页默认几条 - default_limit = 10 - # 设置传入页码数参数名 - page_query_param = "page" - # 设置传入条数参数名 - limit_query_param = 'limit' - # 设置传入位置参数名 - offset_query_param = 'start' - # 最大每页显示条数 - max_limit = None - page_size = 10 - page_size_query_param = 'page_size' - max_page_size = 100000 - - def get_paginated_response(self, data): - code = 200 - msg = 'success' - if not data: - code = 404 - msg = "Data Not Found" - - return Response(OrderedDict([ - ('code', code), - ('msg', msg), - ('count', self.count), - ('next', self.get_next_link()), - ('previous', self.get_previous_link()), - ('results', data), - ])) diff --git a/netaxe/utils/tools/custom_viewset_base.py b/netaxe/utils/tools/custom_viewset_base.py deleted file mode 100644 index 087c2ab..0000000 --- a/netaxe/utils/tools/custom_viewset_base.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -# @Time : 2020/11/12 18:25 -# @Author : LiJiaMin -# @Site : -# @File : custom_viewset_base.py -# @Software: PyCharm - -from django_filters import rest_framework -from rest_framework import filters -from rest_framework import status -from rest_framework import viewsets - -from .custom_json_response import JsonResponse - - -class CustomViewBase(viewsets.ModelViewSet): - queryset = '' - serializer_class = '' - permission_classes = () - authentication_classes = () - filter_fields = () - search_fields = () - filter_backends = (rest_framework.DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter,) - - def create(self, request, *args, **kwargs): - serializer = self.get_serializer(data=request.data) - print(request.data) - serializer.is_valid(raise_exception=True) - self.perform_create(serializer) - headers = self.get_success_headers(serializer.data) - return JsonResponse(data=serializer.data, msg="success", code=201, - status=status.HTTP_201_CREATED, headers=headers) - - def list(self, request, *args, **kwargs): - queryset = self.filter_queryset(self.get_queryset()) - page = self.paginate_queryset(queryset) - if page is not None: - serializer = self.get_serializer(page, many=True) - return self.get_paginated_response(data=serializer.data) - - serializer = self.get_serializer(queryset, many=True) - return JsonResponse(data=serializer.data, code=200, msg="success", status=status.HTTP_200_OK) - - def retrieve(self, request, *args, **kwargs): - instance = self.get_object() - serializer = self.get_serializer(instance) - return JsonResponse(data=serializer.data, code=200, msg="success", status=status.HTTP_200_OK) - - def update(self, request, *args, **kwargs): - partial = kwargs.pop('partial', False) - instance = self.get_object() - serializer = self.get_serializer(instance, data=request.data, partial=partial) - serializer.is_valid(raise_exception=True) - self.perform_update(serializer) - - if getattr(instance, '_prefetched_objects_cache', None): - # If 'prefetch_related' has been applied to a queryset, we need to - # forcibly invalidate the prefetch cache on the instance. - instance._prefetched_objects_cache = {} - - return JsonResponse(data=serializer.data, msg="success", code=200, status=status.HTTP_200_OK) - - def destroy(self, request, *args, **kwargs): - instance = self.get_object() - self.perform_destroy(instance) - # return JsonResponse(data=[], code=204, msg="delete resource success", status=status.HTTP_204_NO_CONTENT) - return JsonResponse(data=[], code=204, msg="delete resource success", status=status.HTTP_200_OK) -- Gitee From 827deaa82848f8e0aaa2e7444bf3b4351b67d6e4 Mon Sep 17 00:00:00 2001 From: M87NET Date: Thu, 23 Mar 2023 10:28:41 +0800 Subject: [PATCH 03/10] =?UTF-8?q?=E5=8F=96=E6=B6=88=E6=8E=A5=E5=8F=A3?= =?UTF-8?q?=E8=AE=A4=E8=AF=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/apps/users/views/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/netaxe/apps/users/views/user.py b/netaxe/apps/users/views/user.py index 5f79600..e1aeada 100644 --- a/netaxe/apps/users/views/user.py +++ b/netaxe/apps/users/views/user.py @@ -4,7 +4,7 @@ from rest_framework import serializers, permissions, filters, viewsets from rest_framework.decorators import action from rest_framework.permissions import IsAuthenticated from django_filters.rest_framework import DjangoFilterBackend -from utils.tools.custom_pagination import LargeResultsSetPagination +from apps.api.tools.custom_pagination import LargeResultsSetPagination from apps.users.models import UserProfile, BgBu from apps.users.serializers import BgBuSerializer from apps.system.views.role import RoleSerializer -- Gitee From 8d4b04c1afbcbeff28d4f759d7d5946334a37baf Mon Sep 17 00:00:00 2001 From: xuehaoweng <13721113750@163.com> Date: Thu, 23 Mar 2023 10:38:41 +0800 Subject: [PATCH 04/10] 'ipamupdate' --- netaxe/apps/open_ipam/serializers.py | 12 -- netaxe/apps/open_ipam/urls.py | 22 +-- netaxe/apps/open_ipam/views.py | 142 ++---------------- .../ipam/netaxe_ipam_ip_fail-2023-02-16.log | 3 + .../ipam/netaxe_ipam_ip_fail-2023-03-13.log | 1 + netaxe/utils/custom/pagination.py | 18 --- web/.env.development | 4 +- 7 files changed, 23 insertions(+), 179 deletions(-) create mode 100644 netaxe/media/ipam/netaxe_ipam_ip_fail-2023-02-16.log create mode 100644 netaxe/media/ipam/netaxe_ipam_ip_fail-2023-03-13.log diff --git a/netaxe/apps/open_ipam/serializers.py b/netaxe/apps/open_ipam/serializers.py index 8243347..bd81de3 100644 --- a/netaxe/apps/open_ipam/serializers.py +++ b/netaxe/apps/open_ipam/serializers.py @@ -50,15 +50,3 @@ class HostsResponseSerializer(serializers.Serializer): # bgbu = serializers.CharField() description = serializers.CharField() lastOnlineTime = serializers.DateField() - - -class IntervalScheduleSerializer(serializers.ModelSerializer): - class Meta: - model = IntervalSchedule - fields = '__all__' - - -class PeriodicTaskSerializer(serializers.ModelSerializer): - class Meta: - model = PeriodicTask - fields = '__all__' diff --git a/netaxe/apps/open_ipam/urls.py b/netaxe/apps/open_ipam/urls.py index 46ea617..ed482d8 100644 --- a/netaxe/apps/open_ipam/urls.py +++ b/netaxe/apps/open_ipam/urls.py @@ -1,25 +1,15 @@ from django.urls import path, include -from django.views.decorators.csrf import csrf_exempt from rest_framework.routers import DefaultRouter -from .views import SubnetHostsView, AvailableIpView, SubnetApiViewSet, IpAddressApiViewSet, SubnetAddressView, \ - IpAmSubnetTreeView, PeriodicTaskViewSet, IpAmHandelView, IntervalScheduleViewSet +from .views import SubnetHostsView, AvailableIpView, SubnetAddressView, IpAmSubnetTreeView, IpAmHandleView router = DefaultRouter() -# -router.register(r'subnet_list', SubnetApiViewSet) # 获取子网段列表 -router.register(r'ip_address_list', IpAddressApiViewSet) -router.register(r'periodic_task', PeriodicTaskViewSet) -router.register(r'interval_schedule', IntervalScheduleViewSet) + urlpatterns = [ path(r'', include(router.urls)), - path('subnet_tree/', IpAmSubnetTreeView.as_view(), name='subnet_tree'), - path('address_handel/', csrf_exempt(IpAmHandelView.as_view()), name='address_handel'), + path('subnet_tree/', IpAmSubnetTreeView.as_view(), name='subnet_tree'), # get_subnet_tree + path('address_handel/', IpAmHandleView.as_view(), name='address_handle'), # ip_addr_handle path('subnet//ip_address/', SubnetAddressView.as_view(), name='subnet_ip_address'), - path('subnet//hosts/', SubnetHostsView.as_view(), name='hosts'), - path( - 'subnet//get-next-available-ip/', - AvailableIpView.as_view(), - name='get_next_available_ip', - ), + path('subnet//hosts/', SubnetHostsView.as_view(), name='hosts'), # admin- ip_addr_by_subnet_id + path('subnet//get-next-available-ip/', AvailableIpView.as_view(), name='get_next_available_ip'), ] diff --git a/netaxe/apps/open_ipam/views.py b/netaxe/apps/open_ipam/views.py index bee1294..b96c479 100644 --- a/netaxe/apps/open_ipam/views.py +++ b/netaxe/apps/open_ipam/views.py @@ -3,8 +3,6 @@ from collections import OrderedDict import ipaddr from django.http import JsonResponse -# Create your views here. -from django_celery_beat.models import PeriodicTask, IntervalSchedule from django_filters.rest_framework import DjangoFilterBackend from netaddr import iter_iprange from rest_framework import serializers, pagination, viewsets, permissions, filters @@ -15,24 +13,21 @@ from rest_framework.permissions import DjangoModelPermissions from rest_framework.response import Response from rest_framework.utils.urls import replace_query_param, remove_query_param from rest_framework.views import APIView +from utils.tools.custom_pagination import LargeResultsSetPagination +from utils.tools.custom_viewset_base import CustomViewBase from .models import Subnet, IpAddress, TagsModel -from .serializers import HostsResponseSerializer, SubnetSerializer, IpAddressSerializer, \ - PeriodicTaskSerializer, IntervalScheduleSerializer, TagsModelSerializer -from utils.custom.pagination import LargeResultsSetPagination -from utils.custom.viewset import CustomViewBase +from .serializers import HostsResponseSerializer, SubnetSerializer, IpAddressSerializer, TagsModelSerializer from utils.ipam_utils import IpAmForNetwork class HostsResponse(object): def __init__(self, address, used, tag, subnet, lastOnlineTime, description): - # def __init__(self, address, used, tag, subnet): self.address = address self.used = used self.tag = tag self.subnet = subnet self.lastOnlineTime = lastOnlineTime self.description = description - # self.bgbu = bgbu class HostsSet: @@ -108,6 +103,7 @@ class HostsSet: return index +# 地址列表分页及数据格式化方法 class HostsListPagination(pagination.BasePagination): limit = 256 start_query_param = 'start' @@ -187,6 +183,7 @@ class ProtectedAPIMixin(object): throttle_scope = 'ipam' +# 给admin后台使用,根据子网id获取地址信息 class SubnetHostsView(ProtectedAPIMixin, ListAPIView): subnet_model = Subnet queryset = Subnet.objects.none() @@ -200,6 +197,7 @@ class SubnetHostsView(ProtectedAPIMixin, ListAPIView): return qs +# 获取可用地址 class AvailableIpView(RetrieveAPIView): subnet_model = Subnet queryset = IpAddress.objects.none() @@ -210,71 +208,7 @@ class AvailableIpView(RetrieveAPIView): return Response(subnet.get_next_available_ip()) -# 子网网段API -class SubnetApiViewSet(CustomViewBase): - # subnet_model = Subnet - queryset = Subnet.objects.all().order_by('-id') - serializer_class = SubnetSerializer - pagination_class = LargeResultsSetPagination - # 配置搜索功能 - filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - filter_fields = ('mask', 'name') - - -# IP地址Api -class IpAddressApiViewSet(CustomViewBase): - # subnet_model = Subnet - permission_classes = (permissions.IsAuthenticated,) - queryset = IpAddress.objects.all().order_by('-id') - serializer_class = IpAddressSerializer - pagination_class = LargeResultsSetPagination - # 配置搜索功能 - filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - filter_fields = '__all__' - - -class LimitSet(pagination.LimitOffsetPagination): - # 每页默认几条 - default_limit = 10 - # 设置传入页码数参数名 - page_query_param = "page" - # 设置传入条数参数名 - limit_query_param = 'limit' - # 设置传入位置参数名 - offset_query_param = 'start' - # 最大每页显示条数 - max_limit = None - - -# 任务列表 -class PeriodicTaskViewSet(viewsets.ModelViewSet): - queryset = PeriodicTask.objects.exclude(task__startswith='celery').order_by('id') - serializer_class = PeriodicTaskSerializer - permission_classes = (permissions.IsAuthenticated,) - # 配置搜索功能 - filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 - filter_fields = '__all__' - pagination_class = LimitSet - # 设置搜索的关键字 - search_fields = '__all__' - # list_cache_key_func = QueryParamsKeyConstructor() - - -class IntervalScheduleViewSet(viewsets.ModelViewSet): - queryset = IntervalSchedule.objects.all().order_by('id') - serializer_class = IntervalScheduleSerializer - permission_classes = (permissions.IsAuthenticated,) - # 配置搜索功能 - filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) - # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 - filter_fields = '__all__' - pagination_class = LimitSet - # 设置搜索的关键字 - search_fields = '__all__' - # list_cache_key_func = QueryParamsKeyConstructor() - - +# 根据网段ID获取对应下面IP地址信息 class SubnetAddressView(ListAPIView): subnet_model = Subnet queryset = Subnet.objects.none() @@ -307,7 +241,9 @@ class IpAmSubnetTreeView(APIView): # 地址操作 -class IpAmHandelView(APIView): +class IpAmHandleView(APIView): + permission_classes = () + authentication_classes = () def post(self, request): update = request.POST.get('update') range_update = request.POST.get('range_update') @@ -351,12 +287,10 @@ class IpAmHandelView(APIView): IpAddress.objects.filter(ip_address=delete_info['ipaddr']).delete() res = {'message': '地址回收成功', 'code': 200, 'delete_ip_list': [j['ipaddr'] for j in delete_ip_list]} return JsonResponse(res, safe=True) - if description: Subnet.objects.filter(id=subnet_id).update(description=description) res = {'message': '网段描述更新成功', 'code': 200, } return JsonResponse(res, safe=True) - if add_subnet: try: # print(add_master_id, type(add_master_id)) @@ -407,58 +341,4 @@ class IpAmHandelView(APIView): return JsonResponse(res, safe=True) -# # 作业中心taskList -# class JobCenterView(APIView): -# def get(self, request): -# # Operationinfo = 'None' -# get_current_tasks = request.GET.get('current_tasks') -# get_crontab_schedules = request.GET.get('crontab_schedules') -# get_queues = request.GET.get('get_queues') -# celery_app = current_app -# if get_current_tasks: -# res = get_tasks() -# # print(res.get()) -# # while True: -# # if res.ready(): -# # result = res.get() -# # break -# result = json.loads(res) -# return JsonResponse( -# {'code': 200, 'data': result['result'], 'count': len(result['result'])}) -# if get_crontab_schedules: -# from django.core import serializers -# crontab_schedules = serializers.serialize("json", CrontabSchedule.objects.all()) -# return JsonResponse( -# {'code': 200, 'data': json.loads(crontab_schedules), 'count': len(json.loads(crontab_schedules))}) -# if get_queues: -# queues_list = [queue.name for queue in app.conf.task_queues] -# return JsonResponse({'code': 200, 'data': queues_list, 'count': len(queues_list)}) -# -# def post(self, request): -# """ run tasks""" -# celery_app = current_app -# f = request.POST -# f = json.loads(f['data']) -# taskname = f['task'] -# args = f['args'] -# kwargs = f['kwargs'] -# queue = f['queue'] -# celery_app.loader.import_default_modules() -# tasks = [(celery_app.tasks.get(taskname), -# loads(args), -# loads(kwargs), -# queue)] -# if any(t[0] is None for t in tasks): -# for i, t in enumerate(tasks): -# if t[0] is None: -# break -# return JsonResponse({'code': 400, 'data': None}, safe=False) -# task_ids = [task.apply_async(args=args, kwargs=kwargs, queue=queue) -# if queue and len(queue) -# else task.apply_async(args=args, kwargs=kwargs) -# for task, args, kwargs, queue in tasks] -# if task_ids[0] == None: -# return JsonResponse({'code': 400, 'data': '执行失败'}, safe=False) -# # list: -# # print(task_ids[0],str(task_ids[0])) -# return JsonResponse({'code': 200, 'data': str(task_ids[0])}, safe=False) + diff --git a/netaxe/media/ipam/netaxe_ipam_ip_fail-2023-02-16.log b/netaxe/media/ipam/netaxe_ipam_ip_fail-2023-02-16.log new file mode 100644 index 0000000..7f7b8a9 --- /dev/null +++ b/netaxe/media/ipam/netaxe_ipam_ip_fail-2023-02-16.log @@ -0,0 +1,3 @@ +10.254.15.241 +100.80.0.13 +10.255.253.6 diff --git a/netaxe/media/ipam/netaxe_ipam_ip_fail-2023-03-13.log b/netaxe/media/ipam/netaxe_ipam_ip_fail-2023-03-13.log new file mode 100644 index 0000000..bdae263 --- /dev/null +++ b/netaxe/media/ipam/netaxe_ipam_ip_fail-2023-03-13.log @@ -0,0 +1 @@ +192.168.1.10 diff --git a/netaxe/utils/custom/pagination.py b/netaxe/utils/custom/pagination.py index 9ad1712..646bdc9 100644 --- a/netaxe/utils/custom/pagination.py +++ b/netaxe/utils/custom/pagination.py @@ -118,21 +118,3 @@ class SubnetAddressPagination(pagination.BasePagination): return replace_query_param( url, self.start_query_param, self.queryset[offset].address ) -# class LargeResultsSetPagination(PageNumberPagination): -# """ -# -# """ -# def get_paginated_response(self, data): -# code = 200 -# msg = 'success' -# if not data: -# code = 404 -# msg = "Data Not Found" -# return Response(OrderedDict([ -# ('code', code), -# ('msg', msg), -# ('count', self.page.paginator.count), -# ('next', self.get_next_link()), -# ('previous', self.get_previous_link()), -# ('results', data) -# ])) diff --git a/web/.env.development b/web/.env.development index e100b3d..0a07d43 100644 --- a/web/.env.development +++ b/web/.env.development @@ -2,7 +2,7 @@ //VITE_BASIC_URL = http://127.0.0.1:8001/ //VITE_BASIC_URL = http://10.254.0.110:10080/ //VITE_BASIC_URL = http://10.254.0.111:9999/ -//VITE_BASIC_URL = http://10.254.0.110:10089/ +VITE_BASIC_URL = http://10.254.0.110:10089/ //VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.2.188:8000/ -VITE_BASIC_URL = http://10.254.0.111:9080/ +//VITE_BASIC_URL = http://10.254.0.111:9080/ -- Gitee From 87d1b2495ab6dfdaf5a748593d5337afcbc2dd56 Mon Sep 17 00:00:00 2001 From: M87NET Date: Thu, 23 Mar 2023 10:42:04 +0800 Subject: [PATCH 05/10] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=8B=93=E6=89=91?= =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/apps/topology/tasks.py | 1 - netaxe/apps/topology/views.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/netaxe/apps/topology/tasks.py b/netaxe/apps/topology/tasks.py index ee66956..60a4d33 100644 --- a/netaxe/apps/topology/tasks.py +++ b/netaxe/apps/topology/tasks.py @@ -44,7 +44,6 @@ class TopologyTask: return MongoNetOps.get_topology(self.topology.name) # 删除拓扑 - @property def del_graph(self): self.topology.delete() MongoNetOps.del_topology(self.topology.name) diff --git a/netaxe/apps/topology/views.py b/netaxe/apps/topology/views.py index 7fc6ae5..f5d1938 100644 --- a/netaxe/apps/topology/views.py +++ b/netaxe/apps/topology/views.py @@ -136,7 +136,7 @@ class TopologyShow(APIView): # 删除拓扑图 if all(k in post_param for k in ("name", "del_graph")): _TopologyTask = TopologyTask(post_param['name']) - _TopologyTask.del_graph + _TopologyTask.del_graph() data = { "code": 200, "data": [], -- Gitee From 4cc60e0849641e88026fa90eae66c12ed78d7c44 Mon Sep 17 00:00:00 2001 From: xuehaoweng <13721113750@163.com> Date: Thu, 23 Mar 2023 10:44:53 +0800 Subject: [PATCH 06/10] 'ipam-permission' --- netaxe/apps/open_ipam/views.py | 10 ++-------- web/.env.development | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/netaxe/apps/open_ipam/views.py b/netaxe/apps/open_ipam/views.py index b96c479..6e257fa 100644 --- a/netaxe/apps/open_ipam/views.py +++ b/netaxe/apps/open_ipam/views.py @@ -3,9 +3,8 @@ from collections import OrderedDict import ipaddr from django.http import JsonResponse -from django_filters.rest_framework import DjangoFilterBackend from netaddr import iter_iprange -from rest_framework import serializers, pagination, viewsets, permissions, filters +from rest_framework import serializers, pagination from django.utils.translation import gettext_lazy as _ from rest_framework.authentication import SessionAuthentication from rest_framework.generics import ListAPIView, get_object_or_404, RetrieveAPIView @@ -13,10 +12,8 @@ from rest_framework.permissions import DjangoModelPermissions from rest_framework.response import Response from rest_framework.utils.urls import replace_query_param, remove_query_param from rest_framework.views import APIView -from utils.tools.custom_pagination import LargeResultsSetPagination -from utils.tools.custom_viewset_base import CustomViewBase from .models import Subnet, IpAddress, TagsModel -from .serializers import HostsResponseSerializer, SubnetSerializer, IpAddressSerializer, TagsModelSerializer +from .serializers import HostsResponseSerializer, TagsModelSerializer from utils.ipam_utils import IpAmForNetwork @@ -339,6 +336,3 @@ class IpAmHandleView(APIView): except Exception as e: res = {'message': e, 'code': 400, } return JsonResponse(res, safe=True) - - - diff --git a/web/.env.development b/web/.env.development index 0a07d43..e100b3d 100644 --- a/web/.env.development +++ b/web/.env.development @@ -2,7 +2,7 @@ //VITE_BASIC_URL = http://127.0.0.1:8001/ //VITE_BASIC_URL = http://10.254.0.110:10080/ //VITE_BASIC_URL = http://10.254.0.111:9999/ -VITE_BASIC_URL = http://10.254.0.110:10089/ +//VITE_BASIC_URL = http://10.254.0.110:10089/ //VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.2.188:8000/ -//VITE_BASIC_URL = http://10.254.0.111:9080/ +VITE_BASIC_URL = http://10.254.0.111:9080/ -- Gitee From f1eb369586eea4390bde958c6900f2014f73def1 Mon Sep 17 00:00:00 2001 From: xuehaoweng <13721113750@163.com> Date: Wed, 12 Apr 2023 16:09:36 +0800 Subject: [PATCH 07/10] =?UTF-8?q?cmdb=E5=AF=B9=E6=8E=A5=E5=BE=AE=E6=9C=8D?= =?UTF-8?q?=E5=8A=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/apps/asset/views.py | 1 + netaxe/apps/int_utilization/views.py | 3 +- netaxe/apps/open_ipam/models.py | 19 - netaxe/apps/open_ipam/views.py | 2 + netaxe/netboost/settings.py | 36 +- web/.env.development | 7 +- web/nginx.conf | 27 +- web/src/api/axios.config.ts | 2 +- web/src/api/http.ts | 20 +- web/src/api/url.ts | 3 +- web/src/assets/img_login_fg.9c0e0a4c.jpeg | Bin 0 -> 32824 bytes web/src/components/avatar/VAWAvatar.vue | 189 +- web/src/components/logo/index.vue | 2 +- web/src/components/navbar/NavBar.vue | 2 +- web/src/hooks/useMenuWidth.ts | 15 +- web/src/router/index.ts | 32 +- web/src/setting/index.ts | 32 +- web/src/store/index.ts | 3 +- web/src/store/keys.ts | 32 +- web/src/store/modules/user.ts | 6 +- web/src/store/modules/visited-view.ts | 30 +- web/src/utils/router.ts | 186 +- web/src/utils/router_bk.ts | 163 + web/src/views/cmdb/interfaceused.vue | 652 +-- web/src/views/cmdb/network_device.vue | 6402 ++++++++++----------- web/src/views/index/work-place.vue | 411 +- web/src/views/login/LoginComponent.vue | 137 +- web/vite.config.ts | 149 +- 28 files changed, 4175 insertions(+), 4388 deletions(-) create mode 100644 web/src/assets/img_login_fg.9c0e0a4c.jpeg create mode 100644 web/src/utils/router_bk.ts diff --git a/netaxe/apps/asset/views.py b/netaxe/apps/asset/views.py index e18f975..d73ea73 100644 --- a/netaxe/apps/asset/views.py +++ b/netaxe/apps/asset/views.py @@ -195,6 +195,7 @@ class AssetRoleViewSet(CustomViewBase): queryset = Role.objects.all().order_by('id') serializer_class = RoleSerializer permission_classes = () + authentication_classes = () # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) # 如果要允许对某些字段进行过滤,可以使用filter_fields属性。 diff --git a/netaxe/apps/int_utilization/views.py b/netaxe/apps/int_utilization/views.py index 19aaa03..ca8fcd8 100644 --- a/netaxe/apps/int_utilization/views.py +++ b/netaxe/apps/int_utilization/views.py @@ -26,7 +26,8 @@ class InterfaceUsedNewViewSet(CustomViewBase): queryset = InterfaceUsedNew.objects.all().order_by('-log_time') # queryset = InterfaceUsedNewSerializer.setup_eager_loading(queryset) serializer_class = InterfaceUsedNewSerializer - # permission_classes = (permissions.IsAuthenticated,) + # permission_classes = () + # authentication_classes = () pagination_class = LargeResultsSetPagination # 配置搜索功能 filter_backends = (DjangoFilterBackend, filters.SearchFilter, filters.OrderingFilter) diff --git a/netaxe/apps/open_ipam/models.py b/netaxe/apps/open_ipam/models.py index 296c3bf..17335ad 100644 --- a/netaxe/apps/open_ipam/models.py +++ b/netaxe/apps/open_ipam/models.py @@ -14,8 +14,6 @@ from django.db.models import Q from django.utils.translation import gettext_lazy as _ from rest_framework.exceptions import ValidationError -# from users.models import OpLogs - class CsvImportException(Exception): pass @@ -268,23 +266,6 @@ class IpAddress(models.Model): instance.save() -# class BgBu(models.Model): -# """ """ -# name = models.CharField(verbose_name='业务线名称', max_length=20, null=False, unique=True) -# -# # def user_list(self): -# # return ','.join([i.username for i in self.authusers_set.all()]) -# -# def __str__(self): -# return self.name -# -# class Meta: -# verbose_name = '业务线表' -# verbose_name_plural = '业务线表' -# db_table = 'ipam_bgbu' # 通过db_table自定义数据表名 -# indexes = [models.Index(fields=['name', ]), ] - - class TagsModel(models.Model): bg_color = models.CharField(max_length=100, blank=True, verbose_name='bg_color') compress = models.CharField(max_length=100, blank=True, verbose_name='compress') diff --git a/netaxe/apps/open_ipam/views.py b/netaxe/apps/open_ipam/views.py index 6e257fa..fa0f583 100644 --- a/netaxe/apps/open_ipam/views.py +++ b/netaxe/apps/open_ipam/views.py @@ -221,6 +221,8 @@ class SubnetAddressView(ListAPIView): # 获取subnet_tree class IpAmSubnetTreeView(APIView): + permission_classes = () + authentication_classes = () def get(self, request): get_params = request.GET.dict() if 'subnet' in get_params: diff --git a/netaxe/netboost/settings.py b/netaxe/netboost/settings.py index 7d4e789..e84c6b3 100644 --- a/netaxe/netboost/settings.py +++ b/netaxe/netboost/settings.py @@ -57,6 +57,7 @@ INSTALLED_APPS = [ # "rest_framework_tracking", # "rest_framework.authtoken", # "rest_framework.apps.RestFrameworkConfig", + "corsheaders", "apps.users.apps.UsersConfig", "apps.system.apps.SystemConfig", "apps.topology.apps.TopologyConfig", @@ -74,6 +75,7 @@ INSTALLED_APPS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", + "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", # "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", @@ -81,6 +83,36 @@ MIDDLEWARE = [ "django.middleware.clickjacking.XFrameOptionsMiddleware", # "utils.custom.middleware.ApiLoggingMiddleware", ] +# 跨域增加忽略 +CORS_ALLOW_CREDENTIALS = True +CORS_ORIGIN_ALLOW_ALL = True +CORS_ORIGIN_WHITELIST = ( + ['http://10.254.2.188:*'] +) + +CORS_ALLOW_METHODS = ( + 'DELETE', + 'GET', + 'OPTIONS', + 'PATCH', + 'POST', + 'PUT', + 'VIEW', +) + +CORS_ALLOW_HEADERS = ( + 'XMLHttpRequest', + 'X_FILENAME', + 'accept-encoding', + 'authorization', + 'content-type', + 'dnt', + 'origin', + 'user-agent', + 'x-csrftoken', + 'x-requested-with', + 'Pragma', +) ROOT_URLCONF = "netboost.urls" @@ -292,7 +324,7 @@ REST_FRAMEWORK = { "django_filters.rest_framework.DjangoFilterBackend", ), "DEFAULT_PERMISSION_CLASSES": ( - "rest_framework.permissions.IsAuthenticated", + # "rest_framework.permissions.IsAuthenticated", # "rest_framework.permissions.DjangoModelPermissions", # "rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly" ), @@ -300,7 +332,7 @@ REST_FRAMEWORK = { # "rest_framework.authentication.BasicAuthentication", # "rest_framework.authentication.SessionAuthentication", # "rest_framework.authentication.TokenAuthentication", - "rest_framework_simplejwt.authentication.JWTAuthentication", + # "rest_framework_simplejwt.authentication.JWTAuthentication", # "apps.api.authentication.ExpiringTokenAuthentication", ), "EXCEPTION_HANDLER": "utils.custom.exception.CustomExceptionHandler", # 自定义的异常处理 diff --git a/web/.env.development b/web/.env.development index e100b3d..9b5fb2a 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,8 +1,9 @@ // 开发环境配置 //VITE_BASIC_URL = http://127.0.0.1:8001/ -//VITE_BASIC_URL = http://10.254.0.110:10080/ +VITE_BASIC_URL = http://10.254.0.110:10089/ +//VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.0.111:9999/ -//VITE_BASIC_URL = http://10.254.0.110:10089/ //VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.2.188:8000/ -VITE_BASIC_URL = http://10.254.0.111:9080/ +//VITE_BASIC_URL = http://10.254.0.111:9080/ +VITE_BASIC_RBAC = http://10.254.2.188:8000/ \ No newline at end of file diff --git a/web/nginx.conf b/web/nginx.conf index d5665eb..acf4e21 100644 --- a/web/nginx.conf +++ b/web/nginx.conf @@ -27,30 +27,9 @@ server { location /media { proxy_pass http://apisix:9080/media; } - # location /api { - # proxy_pass http://netaxe-nginx:9999/api; - # } - # location /net_backend { - # proxy_pass http://netaxe-nginx:9999/backend; - # } - # location /topology { - # proxy_pass http://netaxe-nginx:9999/topology; - # } - # location /jobcenter { - # proxy_pass http://netaxe-nginx:9999/jobcenter; - # } - # location /automation { - # proxy_pass http://netaxe-nginx:9999/automation; - # } - # location /resources_manage { - # proxy_pass http://netaxe-nginx:9999/resources_manage; - # } - # location /int_utilization { - # proxy_pass http://netaxe-nginx:9999/int_utilization; - # } - # location /config_center { - # proxy_pass http://netaxe-nginx:9999/config_center; - # } + location /rbac { + proxy_pass http://apisix:9080/rbac; + } location /ws { proxy_pass http://netaxe-nginx:9999/ws; proxy_connect_timeout 30000s; diff --git a/web/src/api/axios.config.ts b/web/src/api/axios.config.ts index dc7ef03..7cb44ec 100644 --- a/web/src/api/axios.config.ts +++ b/web/src/api/axios.config.ts @@ -18,7 +18,7 @@ export const TEXT_PLAIN = 'text/plain; charset=UTF-8' const service = Axios.create({ // baseURL, timeout: 10 * 60 * 1000, - withCredentials: true, // 跨域请求时发送cookie + withCredentials: false, }) // 在正式发送请求之前进行拦截配置 service.interceptors.request.use( diff --git a/web/src/api/http.ts b/web/src/api/http.ts index 992fde3..2712238 100644 --- a/web/src/api/http.ts +++ b/web/src/api/http.ts @@ -41,7 +41,7 @@ function http({ url, data, method, headers, beforeRequest, afterRequest }: HttpO return res.data } if (res.data.code === 401) { - router.push('/login') + // router.push('/login') throw new Error('认证失败请重新登录一下') // throw new Error('认证失败请重新登录一下') @@ -65,6 +65,7 @@ function http({ url, data, method, headers, beforeRequest, afterRequest }: HttpO throw new Error(res.data.message + '请求失败,未知异常') // return res.data } + // eslint-disable-next-line @typescript-eslint/no-unused-vars const failHandler = (error: Response) => { afterRequest && afterRequest() // console.log(error) @@ -76,12 +77,12 @@ function http({ url, data, method, headers, beforeRequest, afterRequest }: HttpO return method === 'GET' ? service.get(url, { params }).then(successHandler, failHandler) : method === 'POST' - ? service.post(url, params, { headers: headers }).then(successHandler, failHandler) - : method === 'PUT' - ? service.put(url, params, { headers: headers }).then(successHandler, failHandler) - : method === 'PATCH' - ? service.patch(url, params, { headers: headers }).then(successHandler, failHandler) - : service.delete(url, params).then(successHandler, failHandler) + ? service.post(url, params, { headers: headers }).then(successHandler, failHandler) + : method === 'PUT' + ? service.put(url, params, { headers: headers }).then(successHandler, failHandler) + : method === 'PATCH' + ? service.patch(url, params, { headers: headers }).then(successHandler, failHandler) + : service.delete(url, params).then(successHandler, failHandler) } export function get({ @@ -91,6 +92,7 @@ export function get({ beforeRequest, afterRequest, }: HttpOption): Promise { + // @ts-ignore return http({ url, method, @@ -108,6 +110,7 @@ export function post({ beforeRequest, afterRequest, }: HttpOption): Promise { + // @ts-ignore return http({ url, method, @@ -126,6 +129,7 @@ export function put({ beforeRequest, afterRequest, }: HttpOption): Promise { + // @ts-ignore return http({ url, method, @@ -144,6 +148,7 @@ export function patch({ beforeRequest, afterRequest, }: HttpOption): Promise { + // @ts-ignore return http({ url, method, @@ -162,6 +167,7 @@ export function delete_fun({ beforeRequest, afterRequest, }: HttpOption): Promise { + // @ts-ignore return http({ url, method, diff --git a/web/src/api/url.ts b/web/src/api/url.ts index 36db3d8..1929e9b 100644 --- a/web/src/api/url.ts +++ b/web/src/api/url.ts @@ -10,7 +10,8 @@ export const getRoleList = '/api/system/role/' export const getMenuList = '/api/system/menu/' export const getDepartmentList = '/api/system/dept/' export const getMenuListByRole = '/api/system/menu/web_router/' - +export const WebRouter = '/rbac/system/menu/web_router/' +export const WebPermission = '/rbac/system/menu/web_permission/' // 调度管理 export const getdispach = '/api/backend/dispatch_page/' // 任务列表 diff --git a/web/src/assets/img_login_fg.9c0e0a4c.jpeg b/web/src/assets/img_login_fg.9c0e0a4c.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..8e390cfb1d9954e3e8fa1593fcf2030d8ef4a80e GIT binary patch literal 32824 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 MP()!x?LTw>2R`dpN&o-= literal 0 HcmV?d00001 diff --git a/web/src/components/avatar/VAWAvatar.vue b/web/src/components/avatar/VAWAvatar.vue index e5ba1a6..938d747 100644 --- a/web/src/components/avatar/VAWAvatar.vue +++ b/web/src/components/avatar/VAWAvatar.vue @@ -3,7 +3,7 @@

- +
{{ userStore.nickName }} @@ -17,107 +17,114 @@ diff --git a/web/src/components/logo/index.vue b/web/src/components/logo/index.vue index e524c56..f05c4dd 100644 --- a/web/src/components/logo/index.vue +++ b/web/src/components/logo/index.vue @@ -2,7 +2,7 @@
- {{ projectName }} + CMDB
diff --git a/web/src/components/navbar/NavBar.vue b/web/src/components/navbar/NavBar.vue index 433eab1..2e3a992 100644 --- a/web/src/components/navbar/NavBar.vue +++ b/web/src/components/navbar/NavBar.vue @@ -4,7 +4,7 @@
- +
diff --git a/web/src/hooks/useMenuWidth.ts b/web/src/hooks/useMenuWidth.ts index 700a31f..81a6f7e 100644 --- a/web/src/hooks/useMenuWidth.ts +++ b/web/src/hooks/useMenuWidth.ts @@ -1,16 +1,19 @@ import Setting from '../setting' +import { ADMIN_WORK_SETTING_INFO, ADMIN_WORK_S_TENANT } from '@/store/keys' export function useMenuWidth() { - const r = document.querySelector(':root') as HTMLElement - const styles = getComputedStyle(r) - const menuWith = styles.getPropertyValue('--menu-width') + // const r = document.querySelector(':root') as HTMLElement + // const styles = getComputedStyle(r) + // const menuWith = styles.getPropertyValue('--menu-width') + // console.log(menuWith) + const menuWith = '210px' return parseInt(menuWith) } export function useChangeMenuWidth(width: Number) { - const r = document.querySelector(':root') as HTMLElement - r.style.setProperty('--menu-width', width + 'px') + // const r = document.querySelector(':root') as HTMLElement + // r.style.setProperty('--menu-width', width + 'px') localStorage.setItem( - 'setting-info', + ADMIN_WORK_SETTING_INFO, JSON.stringify( Object.assign(Setting, { sideWidth: width, diff --git a/web/src/router/index.ts b/web/src/router/index.ts index b0297a3..f1b73ae 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -1,5 +1,5 @@ -import {mapTwoLevelRouter} from '@/utils' -import {createRouter, createWebHistory} from 'vue-router' +import { mapTwoLevelRouter } from '@/utils' +import { createRouter, createWebHistory } from 'vue-router' const Layout = () => import('@/components/Layout.vue') @@ -51,11 +51,37 @@ export const constantRoutes = [ title: '工作台', affix: true, iconPrefix: 'iconfont', - icon: 'infomation', + icon: 'index', }, }, ], }, + + // { + // path: '/ip_manage', + // component: Layout, + // name: 'Ip_manage', + // meta: { + // title: '地址管理', + // iconPrefix: 'iconfont', + // icon: 'dashboard', + // }, + // children: [ + // { + // path: 'ipam', + // name: 'ipam', + // component: (): any => import('@/views/ip_manage/ipam.vue'), + // meta: { + // title: 'IPAM', + // affix: true, + // iconPrefix: 'iconfont', + // icon: 'infomation', + // }, + // }, + // ], + // }, + + { path: '/redirect', component: Layout, diff --git a/web/src/setting/index.ts b/web/src/setting/index.ts index cdd240d..5fd54ef 100644 --- a/web/src/setting/index.ts +++ b/web/src/setting/index.ts @@ -1,4 +1,5 @@ -const settingInfo = JSON.parse(localStorage.getItem('setting-info') || '{}') +import { ADMIN_WORK_SETTING_INFO, ADMIN_WORK_S_TENANT } from '@/store/keys' +const settingInfo = JSON.parse(localStorage.getItem(ADMIN_WORK_SETTING_INFO) || '{}') interface Setting { projectName: string theme: 'light' | 'dark' @@ -15,15 +16,14 @@ interface Setting { isShowFullScreen: boolean } } - -export const projectName = '' +export const projectName = 'NET-AXE' export default Object.assign( { theme: 'light', - sideTheme: 'dark', + sideTheme: 'white', themeColor: 'cyan@#18a058', - layoutMode: 'ltr', + layoutMode: 'ttb', sideWidth: 210, pageAnim: 'opacity', isFixedNavBar: true, @@ -34,5 +34,25 @@ export default Object.assign( isShowFullScreen: true, }, }, - settingInfo + settingInfo, ) as Setting +// export const projectName = '' +// +// export default Object.assign( +// { +// theme: 'light', +// sideTheme: 'dark', +// themeColor: 'cyan@#18a058', +// layoutMode: 'ltr', +// sideWidth: 210, +// pageAnim: 'opacity', +// isFixedNavBar: true, +// actionBar: { +// isShowSearch: true, +// isShowMessage: true, +// isShowRefresh: true, +// isShowFullScreen: true, +// }, +// }, +// settingInfo +// ) as Setting diff --git a/web/src/store/index.ts b/web/src/store/index.ts index d96d9cd..65a21f4 100644 --- a/web/src/store/index.ts +++ b/web/src/store/index.ts @@ -1,6 +1,7 @@ import { StoreType, SideTheme, RouteRecordRawWithHidden } from './../types/store' import { reactive } from 'vue' import { DeviceType, LayoutMode, StateType, ThemeMode } from '../types/store' +import { ADMIN_WORK_SETTING_INFO, ADMIN_WORK_S_TENANT } from '@/store/keys' import { transfromRoutes } from '../utils' import CachedViewAction from './modules/cached-view' import VisitedViewAction from './modules/visited-view' @@ -11,7 +12,7 @@ const layoutModes = ['ltr', 'lcr', 'ttb'] useChangeMenuWidth(Setting.sideWidth) function presistSettingInfo(setting: any) { - localStorage.setItem('setting-info', JSON.stringify(setting)) + localStorage.setItem(ADMIN_WORK_SETTING_INFO, JSON.stringify(setting)) } const primaryColor = Setting.themeColor.split('@')[1] diff --git a/web/src/store/keys.ts b/web/src/store/keys.ts index 70ee511..ee62242 100644 --- a/web/src/store/keys.ts +++ b/web/src/store/keys.ts @@ -1,15 +1,17 @@ -export const ADMIN_WORK_USER_INFO_KEY = 'admin-work-user-info' - -export const ADMIN_WORK_TOkEN_KEY = 'admin-work-token' - -export const NETOPS_TOKEN = 'netops-token' - -export const PUBLIC_KEY ="-----BEGIN PUBLIC KEY-----\n" + - "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqVozgu4PP91Nl+KgG7P0\n" + - "0ZHwGDvkfjx4htMTVBwM4soz6oC+UxROPIY7sIgEC9WO6nju6vLJdHepwm6pBWMf\n" + - "M+w1CFjfN6OypqND4L4pTdTWdlhkwMMBL2a2gDxceryC1PjswCjiqWu1sqXuYOkD\n" + - "DoHgork9QZkHtVL0pHvupYQ0VFynpQFdOc8gnr/hPUUVk1cp9WSeRFQGXhqfI/xB\n" + - "aaZ5yBMLNK6EAZep+1Xsj2u/3NI2tlwljlZZAAeqEu2hOeyGKBaB5Sa9duvMTR83\n" + - "lE+kogzZbdi/PXeGApuOosITXnZeLvFKrpIiLbzq+TzhLAHC7Obre2TzmldinBQn\n" + - "QQIDAQAB\n" + - "-----END PUBLIC KEY-----" \ No newline at end of file +export const ADMIN_TOKEN = 'netops-token' +export const ADMIN_WORK_S_TENANT = 'netops-work-tenant' +export const ADMIN_WORK_TOkEN_KEY = 'netops-work-token' +export const ADMIN_WORK_VISITED_KEY = 'netops-work-visited' +export const ADMIN_WORK_BUTTON_AUTH = 'netops-work-button-auth' +export const ADMIN_WORK_USER_INFO_KEY = 'netops-work-user-info' +export const ADMIN_WORK_SETTING_INFO = 'netops-work-setting-info' +export const PUBLIC_KEY = + '-----BEGIN PUBLIC KEY-----\n' + + 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqVozgu4PP91Nl+KgG7P0\n' + + '0ZHwGDvkfjx4htMTVBwM4soz6oC+UxROPIY7sIgEC9WO6nju6vLJdHepwm6pBWMf\n' + + 'M+w1CFjfN6OypqND4L4pTdTWdlhkwMMBL2a2gDxceryC1PjswCjiqWu1sqXuYOkD\n' + + 'DoHgork9QZkHtVL0pHvupYQ0VFynpQFdOc8gnr/hPUUVk1cp9WSeRFQGXhqfI/xB\n' + + 'aaZ5yBMLNK6EAZep+1Xsj2u/3NI2tlwljlZZAAeqEu2hOeyGKBaB5Sa9duvMTR83\n' + + 'lE+kogzZbdi/PXeGApuOosITXnZeLvFKrpIiLbzq+TzhLAHC7Obre2TzmldinBQn\n' + + 'QQIDAQAB\n' + + '-----END PUBLIC KEY-----' diff --git a/web/src/store/modules/user.ts b/web/src/store/modules/user.ts index f8f262f..7aeaf1e 100644 --- a/web/src/store/modules/user.ts +++ b/web/src/store/modules/user.ts @@ -1,7 +1,7 @@ import { defineStore } from 'pinia' import { UserState } from '../types' import layoutStore from '../index' -import { ADMIN_WORK_USER_INFO_KEY, ADMIN_WORK_TOkEN_KEY, NETOPS_TOKEN } from '../keys' +import { ADMIN_WORK_USER_INFO_KEY, ADMIN_WORK_TOkEN_KEY, ADMIN_TOKEN } from '../keys' import Avatar from '@/assets/img_avatar.gif' import Cookies from 'js-cookie' @@ -29,7 +29,7 @@ const useUserStore = defineStore('user', { this.userName = userInfo.userName this.nickName = userInfo.nickName this.image = userInfo.image || defaultAvatar - Cookies.set(NETOPS_TOKEN, 'Bearer ' + userInfo.token) + Cookies.set(ADMIN_TOKEN, 'Bearer ' + userInfo.token) // Cookies.set('csrftoken', userInfo['csrf_token']) localStorage.setItem('is_superuser', userInfo.isSuperuser + '') res() @@ -46,7 +46,7 @@ const useUserStore = defineStore('user', { this.userName = '' this.nickName = '' localStorage.clear() - Cookies.remove(NETOPS_TOKEN) + Cookies.remove(ADMIN_TOKEN) layoutStore.reset() resolve() }) diff --git a/web/src/store/modules/visited-view.ts b/web/src/store/modules/visited-view.ts index 3a5cf72..515d36b 100644 --- a/web/src/store/modules/visited-view.ts +++ b/web/src/store/modules/visited-view.ts @@ -8,21 +8,21 @@ export default { addVisitedView(route) { return new Promise((resolve) => { if (!(this as StoreType).state.visitedView.find((it) => it.fullPath === route.fullPath)) { - ;(this as StoreType).state.visitedView.push(route) + ; (this as StoreType).state.visitedView.push(route) this.persistentVisitedView() } - ;(this as StoreType).addCachedView && (this as StoreType).addCachedView(route) + ; (this as StoreType).addCachedView && (this as StoreType).addCachedView(route) resolve(route) }) }, removeVisitedView(route) { return new Promise((resolve) => { - ;(this as StoreType).state.visitedView.splice( + ; (this as StoreType).state.visitedView.splice( (this as StoreType).state.visitedView.indexOf(route), 1 ) - ;(this as StoreType).persistentVisitedView() - ;(this as StoreType).removeCachedView && (this as StoreType).removeCachedView(route) + ; (this as StoreType).persistentVisitedView() + ; (this as StoreType).removeCachedView && (this as StoreType).removeCachedView(route) resolve(route) }) }, @@ -30,14 +30,14 @@ export default { return new Promise((resolve) => { const selectIndex = (this as StoreType).state.visitedView.indexOf(selectRoute) if (selectIndex !== -1) { - ;(this as StoreType).state.visitedView = (this as StoreType).state.visitedView.filter( + ; (this as StoreType).state.visitedView = (this as StoreType).state.visitedView.filter( (it, index) => { return (it.meta && it.meta.affix) || index >= selectIndex } ) this.persistentVisitedView() } - ;(this as StoreType).resetCachedView && (this as StoreType).resetCachedView() + ; (this as StoreType).resetCachedView && (this as StoreType).resetCachedView() resolve(selectRoute) }) }, @@ -45,26 +45,26 @@ export default { return new Promise((resolve) => { const selectIndex = (this as StoreType).state.visitedView.indexOf(selectRoute) if (selectIndex !== -1) { - ;(this as StoreType).state.visitedView = (this as StoreType).state.visitedView.filter( + ; (this as StoreType).state.visitedView = (this as StoreType).state.visitedView.filter( (it, index) => { return (it.meta && it.meta.affix) || index <= selectIndex } ) this.persistentVisitedView() } - ;(this as StoreType).resetCachedView && (this as StoreType).resetCachedView() + ; (this as StoreType).resetCachedView && (this as StoreType).resetCachedView() resolve(selectRoute) }) }, closeAllVisitedView() { return new Promise((resolve) => { - ;(this as StoreType).state.visitedView = (this as StoreType).state.visitedView.filter( + ; (this as StoreType).state.visitedView = (this as StoreType).state.visitedView.filter( (it) => { return it.meta && it.meta.affix } ) - ;(this as StoreType).persistentVisitedView() - ;(this as StoreType).resetCachedView && (this as StoreType).resetCachedView() + ; (this as StoreType).persistentVisitedView() + ; (this as StoreType).resetCachedView && (this as StoreType).resetCachedView() resolve() }) }, @@ -79,10 +79,10 @@ export default { query: it.query, } }) - localStorage.setItem(LOCAL_STOREAGE_VISITED_KEY, JSON.stringify(tempPersistendRoutes)) + // localStorage.setItem(LOCAL_STOREAGE_VISITED_KEY, JSON.stringify(tempPersistendRoutes)) }, restoreVisitedView() { - ;(this as StoreType).state.visitedView = [...(this as StoreType).state.visitedView] + ; (this as StoreType).state.visitedView = [...(this as StoreType).state.visitedView] const originRouteString = localStorage.getItem(LOCAL_STOREAGE_VISITED_KEY) const persistentVisitedRoutes = JSON.parse(originRouteString || '[]') persistentVisitedRoutes.forEach((originRoute: RouteRecordRawWithHidden) => { @@ -91,7 +91,7 @@ export default { (it) => it.fullPath === originRoute.fullPath && it.name === originRoute.name ) ) { - ;(this as StoreType).state.visitedView.push(originRoute) + ; (this as StoreType).state.visitedView.push(originRoute) } }) }, diff --git a/web/src/utils/router.ts b/web/src/utils/router.ts index d484cd2..d9ebb17 100644 --- a/web/src/utils/router.ts +++ b/web/src/utils/router.ts @@ -1,21 +1,23 @@ -import router, { constantRoutes } from '../router' import Cookies from 'js-cookie' import { get } from '@/api/http' -import { baseAddress, getMenuListByRole } from '@/api/url' -import { RouteRecordRaw } from 'vue-router' -import { isExternal, mapTwoLevelRouter, toHump } from '.' -import { Layout } from '@/components' import layoutStore from '@/store' +import { Layout } from '@/components' +import { UserState } from '@/store/types' import { defineAsyncComponent } from 'vue' +import { RouteRecordRaw } from 'vue-router' +import useUserStore from '@/store/modules/user' +import router, { constantRoutes } from '../router' +import { isExternal, mapTwoLevelRouter, toHump } from '.' import LoadingComponent from '../components/loading/index.vue' +import { baseAddress, WebRouter, WebPermission } from '@/api/url' +import { ADMIN_WORK_USER_INFO_KEY, ADMIN_WORK_BUTTON_AUTH, ADMIN_WORK_S_TENANT } from '@/store/keys' interface OriginRoute { - // menuUrl: string - // menuName?: string + key: any name: string web_path: string + link_path?: string hidden?: boolean - outLink?: string affix?: boolean cacheable?: boolean iconPrefix?: string @@ -31,17 +33,31 @@ function loadComponents() { } const asynComponents = loadComponents() -// 获取路由 +const navigateID = localStorage.getItem(ADMIN_WORK_S_TENANT) + +// 获取web权限 function getRoutes() { + // console.log(layoutStore.state) return get({ - url: baseAddress + getMenuListByRole, + url: baseAddress + WebRouter, method: 'GET', - data: { parent__isnull: true }, + data: { parent__isnull: true, navigate__id: navigateID } }).then((res: any) => { return generatorRoutes(res.results) }) } +// 获取menu权限 +function getPermission() { + return get({ + url: baseAddress + WebPermission, + method: 'GET', + data: { navigate__id: navigateID } + }).then((res: any) => { + localStorage.setItem(ADMIN_WORK_BUTTON_AUTH, JSON.stringify(res.results)) + }) +} + function getComponent(it: OriginRoute) { return defineAsyncComponent({ loader: asynComponents['../views' + it.web_path + '.vue'], @@ -57,34 +73,37 @@ function getCharCount(str: string, char: string) { } function isMenu(path: string) { - return getCharCount(path, '/') === 1 + return getCharCount(path, '\/') === 1 } -function getNameByUrl(web_path: string) { - const temp = web_path.split('/') +function getNameByUrl(path: string) { + const temp = path.split('/') return toHump(temp[temp.length - 1]) } function generatorRoutes(res: Array) { const tempRoutes: Array = [] res.forEach((it) => { - const route: RouteRecordRawWithHidden = { - path: it.outLink && isExternal(it.outLink) ? it.outLink : it.web_path, - name: getNameByUrl(it.web_path), - hidden: !!it.hidden, - component: isMenu(it.web_path) ? Layout : getComponent(it), - meta: { - title: it.name, - affix: !!it.affix, - cacheable: !!it.cacheable, - icon: it.icon || 'menu', - iconPrefix: it.iconPrefix || 'iconfont', - }, - } - if (it.children) { - route.children = generatorRoutes(it.children) + if (!it.key) { + const path = it.link_path && isExternal(it.link_path) ? it.link_path : it.web_path + const route: RouteRecordRawWithHidden = { + path: path, + name: getNameByUrl(path), + hidden: !!it.hidden, + component: it.web_path && isMenu(it.web_path) ? Layout : getComponent(it), + meta: { + title: it.name, + affix: !!it.affix, + cacheable: !!it.cacheable, + icon: it.icon || 'menu', + iconPrefix: it.iconPrefix || 'iconfont', + }, + } + if (it.children) { + route.children = generatorRoutes(it.children) + } + tempRoutes.push(route) } - tempRoutes.push(route) }) return tempRoutes } @@ -97,6 +116,7 @@ function isTokenExpired(): boolean { } router.beforeEach(async (to) => { + console.log(to.path) if (whiteRoutes.includes(to.path)) { return true } else { @@ -106,85 +126,35 @@ router.beforeEach(async (to) => { query: { redirect: to.fullPath }, } } else { + // 获取租户信息 + const userInfo: UserState = JSON.parse(localStorage.getItem(ADMIN_WORK_USER_INFO_KEY) || '{}') + + const isEmptyRoute = layoutStore.isEmptyPermissionRoute() - if (isEmptyRoute) { - try { - // 加载路由 - const accessRoutes: Array = [] - const tempRoutes = await getRoutes() - accessRoutes.push(...tempRoutes) - if (localStorage.getItem('is_superuser') === 'true') { - const system_data = { - path: '/system', - name: 'System', - component: Layout, - meta: { - title: '系统配置', - iconPrefix: 'iconfont', - icon: 'setting', - }, - children: [ - { - path: 'user', - name: 'User', - component: () => import('@/views/system/user.vue'), - meta: { - title: '用户配置', - iconPrefix: 'iconfont', - icon: 'user', - }, - }, - { - path: 'department', - name: 'Department', - component: () => import('@/views/system/department.vue'), - meta: { - title: '部门配置', - iconPrefix: 'iconfont', - icon: 'apartment', - }, - }, - { - path: 'role', - name: 'Role', - component: () => import('@/views/system/role.vue'), - meta: { - title: '角色配置', - iconPrefix: 'iconfont', - icon: 'control', - }, - }, - { - path: 'menu', - name: 'Menu', - component: () => import('@/views/system/menu.vue'), - meta: { - title: '菜单配置', - iconPrefix: 'iconfont', - icon: 'menu', - }, - }, - ], - } - accessRoutes.push(system_data) - } - const mapRoutes = mapTwoLevelRouter(accessRoutes) - mapRoutes.forEach((it: any) => { - router.addRoute(it) - }) - router.addRoute({ - path: '/:pathMatch(.*)*', - redirect: '/404', - hidden: true, - } as RouteRecordRaw) - layoutStore.initPermissionRoute([...constantRoutes, ...accessRoutes]) - return { ...to, replace: true } - } catch { - return { - path: '/login', - query: { redirect: to.fullPath }, - } - } + console.log(isEmptyRoute) + if (isEmptyRoute && to.path!='/ssh') { + + // 加载路由和按钮 + const webRoutes = await getRoutes() + // console.log(webRoutes) + // const webPermission = await getPermission() + const accessRoutes: Array = [] + accessRoutes.push(...webRoutes) + + + const mapRoutes = mapTwoLevelRouter(accessRoutes) + mapRoutes.forEach((it: any) => { + router.addRoute(it) + }) + router.addRoute({ + path: '/:pathMatch(.*)*', + redirect: '/404', + hidden: true, + } as RouteRecordRaw) + // console.log('constantRoutes',JSON.stringify(constantRoutes)) + // console.log('accessRoutes',JSON.stringify(accessRoutes)) + layoutStore.initPermissionRoute([...constantRoutes, ...accessRoutes]) + return { ...to, replace: true } } else { return true } diff --git a/web/src/utils/router_bk.ts b/web/src/utils/router_bk.ts new file mode 100644 index 0000000..dad67eb --- /dev/null +++ b/web/src/utils/router_bk.ts @@ -0,0 +1,163 @@ +import Cookies from 'js-cookie' +import {get} from '@/api/http' +import layoutStore from '@/store' +import {Layout} from '@/components' +import {UserState} from '@/store/types' +import {defineAsyncComponent} from 'vue' +import {RouteRecordRaw} from 'vue-router' +import router, {constantRoutes} from '../router' +import {isExternal, mapTwoLevelRouter, toHump} from '.' +import LoadingComponent from '../components/loading/index.vue' +import {baseAddress, WebPermission, WebRouter} from '@/api/url' +import {ADMIN_WORK_BUTTON_AUTH, ADMIN_WORK_S_TENANT, ADMIN_WORK_USER_INFO_KEY} from '@/store/keys' + +const navigateID = localStorage.getItem(ADMIN_WORK_S_TENANT) + +interface OriginRoute { + key: any + name: string + web_path: string + link_path?: string + hidden?: boolean + affix?: boolean + cacheable?: boolean + iconPrefix?: string + icon?: string + badge?: string | number + children: Array +} + +type RouteRecordRawWithHidden = RouteRecordRaw & { hidden: boolean } + +function loadComponents() { + return import.meta.glob('../views/**/*.vue') +} + +const asynComponents = loadComponents() + +// 获取路由 +function getRoutes() { + console.log(layoutStore.state) + return get({ + url: baseAddress + WebRouter, + method: 'GET', + data: { parent__isnull: true, navigate__id: navigateID } + }).then((res: any) => { + console.log(res) + return generatorRoutes(res.results) + }) +} + +// 获取路由 +// eslint-disable-next-line @typescript-eslint/no-unused-vars +function getPermission() { + return get({ + url: baseAddress + WebPermission, + method: 'GET', + data: { navigate__id: layoutStore.state.navigateID } + }).then((res: any) => { + localStorage.setItem(ADMIN_WORK_BUTTON_AUTH, JSON.stringify(res.results)) + }) +} + +function getComponent(it: OriginRoute) { + return defineAsyncComponent({ + loader: asynComponents['../views' + it.web_path + '.vue'], + loadingComponent: LoadingComponent, + }) +} + +function getCharCount(str: string, char: string) { + const regex = new RegExp(char, 'g') + const result = str.match(regex) + return !result ? 0 : result.length +} + +function isMenu(path: string) { + return getCharCount(path, '/') === 1 +} + +function getNameByUrl(path: string) { + const temp = path.split('/') + return toHump(temp[temp.length - 1]) +} + +function generatorRoutes(res: Array) { + const tempRoutes: Array = [] + res.forEach((it) => { + if (!it.key){ + const path = it.link_path && isExternal(it.link_path) ? it.link_path : it.web_path + const route: RouteRecordRawWithHidden = { + path: path, + name: getNameByUrl(path), + hidden: !!it.hidden, + component: it.web_path && isMenu(it.web_path) ? Layout:getComponent(it), + meta: { + title: it.name, + affix: !!it.affix, + cacheable: !!it.cacheable, + icon: it.icon || 'menu', + iconPrefix: it.iconPrefix || 'iconfont', + }, + } + if (it.children) { + route.children = generatorRoutes(it.children) + } + tempRoutes.push(route) + } + }) + return tempRoutes +} + +const whiteRoutes: string[] = ['/login', '/404', '/403', '/500'] + +function isTokenExpired(): boolean { + const token = Cookies.get('netops-token') + return !!token +} + +router.beforeEach(async (to) => { + if (whiteRoutes.includes(to.path)) { + return true + } else { + if (!isTokenExpired()) { + return { + path: '/login', + query: { redirect: to.fullPath }, + } + } else { + // 获取租户信息 + const userInfo: UserState = JSON.parse(localStorage.getItem(ADMIN_WORK_USER_INFO_KEY) || '{}') + // layoutStore.changeNavigateID(userInfo.tenantUser[0] as any) + + // 配置租户是否显示 + // layoutStore.changeIsNavigate(false) + // if (to.matched[0]?.name === "Permissions"){ + // layoutStore.changeIsNavigate(true) + // } + + const isEmptyRoute = layoutStore.isEmptyPermissionRoute() + if (isEmptyRoute) { + // 加载路由和按钮 + const webRoutes = await getRoutes() + // const webPermission = await getPermission() + const accessRoutes: Array = [] + accessRoutes.push(...webRoutes) + + const mapRoutes = mapTwoLevelRouter(accessRoutes) + mapRoutes.forEach((it: any) => { + router.addRoute(it) + }) + router.addRoute({ + path: '/:pathMatch(.*)*', + redirect: '/404', + hidden: true, + } as RouteRecordRaw) + layoutStore.initPermissionRoute([...constantRoutes, ...accessRoutes]) + return { ...to, replace: true } + } else { + return true + } + } + } +}) diff --git a/web/src/views/cmdb/interfaceused.vue b/web/src/views/cmdb/interfaceused.vue index 0afe002..0b66175 100644 --- a/web/src/views/cmdb/interfaceused.vue +++ b/web/src/views/cmdb/interfaceused.vue @@ -2,396 +2,364 @@
- +
diff --git a/web/src/views/cmdb/network_device.vue b/web/src/views/cmdb/network_device.vue index fedf1e5..bb3a571 100644 --- a/web/src/views/cmdb/network_device.vue +++ b/web/src/views/cmdb/network_device.vue @@ -1,292 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {{ checkedRowKeysRef.length }} + + + + + + + + + + + + + 下载导入模板 + + +
+ + 取消 + + +
+
+
diff --git a/web/src/views/index/work-place.vue b/web/src/views/index/work-place.vue index 810d384..7a1df85 100644 --- a/web/src/views/index/work-place.vue +++ b/web/src/views/index/work-place.vue @@ -7,18 +7,18 @@
-
早上好,Andy,青春只有一次,别让自己过得不精彩
+
CMDB工作台
今日有小雨,出门别忘记带伞哦~
-
项目数
+
应用资源
12
-
待办项
-
3/20
+
基础设施
+
2250
当前日期
@@ -27,317 +27,146 @@ - +
- + {{ item.title }}
-
diff --git a/web/src/views/login/LoginComponent.vue b/web/src/views/login/LoginComponent.vue index cd7ba96..c20aa42 100644 --- a/web/src/views/login/LoginComponent.vue +++ b/web/src/views/login/LoginComponent.vue @@ -2,15 +2,15 @@ - - - - - - - - - - - - - - +
- - - - - - - - - - - - - - @@ -121,6 +94,7 @@ import { computed, defineComponent, ref } from 'vue' import { useRoute, useRouter } from 'vue-router' import ImageBg1 from '@/assets/img_login_bg.png' + import ImageBg2 from '@/assets/img_login_fg.9c0e0a4c.jpeg' import { post, Response } from '@/api/http' import { login } from '@/api/url' import { UserState } from '@/store/types' @@ -130,9 +104,9 @@ import { PhonePortraitOutline as PhoneIcon, LockClosedOutline as PasswordIcon, - LogoGithub, - LogoAlipay, - LogoWechat, + // LogoGithub, + // LogoAlipay, + // LogoWechat, } from '@vicons/ionicons5' import useAppInfo from '@/hooks/useAppInfo' import useUserStore from '@/store/modules/user' @@ -140,7 +114,7 @@ export default defineComponent({ name: 'Login', - components: { PhoneIcon, PasswordIcon, LogoGithub, LogoAlipay, LogoWechat }, + components: { PhoneIcon, PasswordIcon }, setup() { const { version } = useAppInfo() const username = ref('') @@ -162,7 +136,7 @@ //加密 encryptStr.setKey(PUBLIC_KEY) formdata.append('username', username.value) - formdata.append('password', encryptStr.encrypt(password.value)) + formdata.append('password', encryptStr.encrypt(password.value).toString()) post({ url: login, data: formdata, @@ -191,6 +165,7 @@ loading, onLogin, ImageBg1, + ImageBg2, version, } }, @@ -222,9 +197,10 @@ display: block; position: relative; min-width: 450px; - width: 450px; + /*width: 450px;*/ & > img { + width: 100%; height: 100%; } @@ -274,12 +250,12 @@ font-weight: 500; font-size: 30px; // text-shadow: 1px 1px 2px #f5f5f5; - animation: left-to-right 1s cubic-bezier(0.175, 0.885, 0.32, 1.275); - text-shadow: 0 0 5px var(--primary-color), 0 0 15px var(--primary-color), - 0 0 50px var(--primary-color), 0 0 150px var(--primary-color); + /*animation: left-to-right 1s cubic-bezier(0.175, 0.885, 0.32, 1.275);*/ + /*text-shadow: 0 0 5px var(--primary-color), 0 0 15px var(--primary-color),*/ + /* 0 0 50px var(--primary-color), 0 0 150px var(--primary-color);*/ } - .bottom-wrapper { + .version { margin-bottom: 5%; color: #c0c0c0; font-size: 16px; @@ -293,14 +269,15 @@ justify-content: center; flex-direction: column; align-items: center; - background: linear-gradient(to bottom, var(--primary-color)); + width: 45%; + /*background: linear-gradient(to bottom, var(--primary-color));*/ .form-wrapper { width: 35%; border-radius: 5px; border: 1px solid #f0f0f0; padding: 20px; - box-shadow: 0px 0px 7px #dddddd; + /*box-shadow: 0px 0px 7px #dddddd;*/ .form-title { font-size: 26px; @@ -322,78 +299,4 @@ } } } - - .m-login-container { - position: relative; - height: 100vh; - max-height: 100vh; - overflow: hidden; - background: linear-gradient(#7a9ad7, #3b5a94, #133064); - // background-image: url(../../assets/img_login_mobile_bg_01.jpg); - .header { - height: 25vh; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - - .the-p { - width: 100px; - height: 100px; - background: rgba(255, 255, 255, 0.2); - border: 1px solid #f5f5f5; - border-radius: 50%; - display: flex; - justify-content: center; - align-items: center; - font-size: 56px; - font-weight: bold; - } - } - - .top-line { - background-image: linear-gradient( - to right, - rgba(117, 117, 117, 0.9) 25%, - rgba(255, 255, 255, 0.3) 50%, - rgba(117, 117, 117, 0.9) 75% - ); - height: 1px; - background-color: #ffffff; - } - - .content { - height: 40vh; - margin: 5% 10%; - border-radius: 10px; - - :deep(.n-input) { - background-color: rgba(183, 183, 183, 0); - } - - :deep(.n-input .n-input__input-el, .n-input .n-input__textarea-el) { - color: #fff; - } - - :deep(.n-checkbox .n-checkbox__label) { - color: #fff; - } - } - - .footer { - position: absolute; - left: 10%; - right: 10%; - bottom: 10%; - - :deep(.n-divider .n-divider__title) { - color: #c3c3c3; - font-size: 14px; - } - - :deep(.n-divider:not(.n-divider--dashed) .n-divider__line) { - background-color: rgba(117, 117, 117); - } - } - } diff --git a/web/vite.config.ts b/web/vite.config.ts index 66fce2a..6f97f06 100644 --- a/web/vite.config.ts +++ b/web/vite.config.ts @@ -6,77 +6,86 @@ import ViteComponents from 'unplugin-vue-components/vite' import { NaiveUiResolver } from 'unplugin-vue-components/resolvers' import { loadEnv } from 'vite' import vueJsx from '@vitejs/plugin-vue-jsx' + export default ({ mode }) => { - const env = loadEnv(mode, './') - const config = { - plugins: [ - vue(), - viteSvgIcons({ - iconDirs: [path.resolve(process.cwd(), 'src/icons')], - symbolId: 'icon-[dir]-[name]', - }), - vitePluginCompression({ - threshold: 1024 * 10, - }), - ViteComponents({ - resolvers: [NaiveUiResolver()], - }), - vueJsx(), - ], - resolve: { - alias: [ - { - find: '@/', - replacement: path.resolve(process.cwd(), 'src') + '/', + const env = loadEnv(mode, './') + const config = { + plugins: [ + vue(), + viteSvgIcons({ + iconDirs: [path.resolve(process.cwd(), 'src/icons')], + symbolId: 'icon-[dir]-[name]', + }), + vitePluginCompression({ + threshold: 1024 * 10, + }), + ViteComponents({ + resolvers: [NaiveUiResolver()], + }), + vueJsx(), + ], + resolve: { + alias: [ + { + find: '@/', + replacement: path.resolve(process.cwd(), 'src') + '/', + }, + ], }, - ], - }, - server: { - open: true, - port: 1005, - host: '0.0.0.0', - proxy: { - '/api': { - target: env.VITE_BASIC_URL, - ws: true, //代理websockets - changeOrigin: true, // 虚拟的站点需要更管origin - rewrite: (path: string) => path.replace(/^\/api/, '/api'), + server: { + open: true, + port: 8890, + host: '0.0.0.0', + hmr: { overlay: false }, + cors: true, + proxy: { + '/api': { + target: env.VITE_BASIC_URL, + // ws: true, //代理websockets + changeOrigin: true, // 虚拟的站点需要更管origin + rewrite: (path: string) => path.replace(/^\/api/, '/api'), + }, + '/rbac': { + target: env.VITE_BASIC_RBAC, + // ws: true, //代理websockets + changeOrigin: true, // 虚拟的站点需要更管origin + rewrite: (path: string) => path.replace(/^\/rbac/, '/rbac'), + }, + '/ipam': { + target: env.VITE_BASIC_URL, + // ws: true, //代理websockets + changeOrigin: true, // 虚拟的站点需要更管origin + rewrite: (path: string) => path.replace(/^\/ipam/, '/ipam'), + }, + '/media': { + target: env.VITE_BASIC_URL, + // ws: true, //代理websockets + changeOrigin: true, // 虚拟的站点需要更管origin + rewrite: (path: string) => path.replace(/^\/media/, '/media'), + }, + '/ws': { + target: env.VITE_BASIC_URL, + timeout: 60000, + ws: true, //代理websockets + changeOrigin: true, // 虚拟的站点需要更管origin + rewrite: (path: string) => path.replace(/^\/ws/, '/ws'), + }, + }, }, - '/ipam': { - target: env.VITE_BASIC_URL, - ws: true, //代理websockets - changeOrigin: true, // 虚拟的站点需要更管origin - rewrite: (path: string) => path.replace(/^\/ipam/, '/ipam'), - }, - '/media': { - target: env.VITE_BASIC_URL, - ws: true, //代理websockets - changeOrigin: true, // 虚拟的站点需要更管origin - rewrite: (path: string) => path.replace(/^\/media/, '/media'), - }, - '/ws': { - target: env.VITE_BASIC_URL, - timeout: 60000, - ws: true, //代理websockets - changeOrigin: true, // 虚拟的站点需要更管origin - rewrite: (path: string) => path.replace(/^\/ws/, '/ws'), - }, - }, - }, - } - if (mode === 'staging') { - return Object.assign( - { - base: '/admin-work/', - }, - config - ) - } else { - return Object.assign( - { - base: '/', - }, - config - ) - } + } + if (mode === 'staging') { + return Object.assign( + { + base: '/admin-work/', + }, + config + ) + } else { + return Object.assign( + { + base: '/', + }, + config + ) + } } -- Gitee From 89764c20158bbc148afa87554a4c6264354f53ad Mon Sep 17 00:00:00 2001 From: xuehaoweng <13721113750@163.com> Date: Wed, 19 Apr 2023 11:45:24 +0800 Subject: [PATCH 08/10] =?UTF-8?q?'=E6=9B=B4=E6=96=B0=E5=89=8D=E7=AB=AF'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/Dockerfile | 19 ++++++++++++++++++ netaxe/netboost/__init__.py | 13 +++++++++++- netaxe/netboost/settings.py | 33 ++----------------------------- netaxe/utils/custom/middleware.py | 12 +++++++++++ web/nginx.conf | 9 --------- web/src/api/url.ts | 2 +- 6 files changed, 46 insertions(+), 42 deletions(-) diff --git a/netaxe/Dockerfile b/netaxe/Dockerfile index e69de29..9085d5f 100644 --- a/netaxe/Dockerfile +++ b/netaxe/Dockerfile @@ -0,0 +1,19 @@ +FROM registry.cn-hangzhou.aliyuncs.com/netaxe/netaxe-backend:1.0.13 + +# 更新pip版本 +RUN pip3 install -i https://pypi.doubanio.com/simple/ uwsgi --upgrade pip +COPY . /home/netaxe +# 再次切换工作目录为Django主目录 +WORKDIR /home/netaxe + + +# 安装项目所需python第三方库 +# 指定setuptools的版本,必须指定,新版本有兼容问题 +RUN set -ex \ + && /usr/local/python3/bin/pip3 install setuptools_scm -i https://mirrors.aliyun.com/pypi/simple/ \ + # && /usr/local/python3/bin/pip3 install --upgrade pip setuptools==45.2.0 -i https://mirrors.aliyun.com/pypi/simple/ \ + &&/usr/local/python3/bin/pip3 install -r requirements.txt -i https://mirrors.aliyun.com/pypi/simple/ \ + && rm -rf /var/cache/yum/* +EXPOSE 8001 +EXPOSE 5555 +CMD ["sh", "start.sh"] \ No newline at end of file diff --git a/netaxe/netboost/__init__.py b/netaxe/netboost/__init__.py index 1a84238..f819806 100644 --- a/netaxe/netboost/__init__.py +++ b/netaxe/netboost/__init__.py @@ -1,5 +1,11 @@ from __future__ import absolute_import, unicode_literals + +import sys + import pymysql + +from utils.custom.nacos import nacos +from . import settings from .celery import app as celery_app # Fake PyMySQL's version and install as MySQLdb @@ -7,4 +13,9 @@ from .celery import app as celery_app pymysql.version_info = (1, 4, 2, "final", 0) pymysql.install_as_MySQLdb() -__all__ = ['celery_app'] \ No newline at end of file +__all__ = ['celery_app'] +if sys.argv[1] not in ["makemigrations", "migrate"]: + # 注册服务 + nacosServer = nacos(ip=settings.SERVERIP, port=8848) + nacosServer.registerService(serviceIp=settings.SERVERIP, servicePort=settings.SERVERPORT, serviceName="auth", groupName="default") + nacosServer.healthyCheck() \ No newline at end of file diff --git a/netaxe/netboost/settings.py b/netaxe/netboost/settings.py index e84c6b3..c8dd949 100644 --- a/netaxe/netboost/settings.py +++ b/netaxe/netboost/settings.py @@ -57,7 +57,6 @@ INSTALLED_APPS = [ # "rest_framework_tracking", # "rest_framework.authtoken", # "rest_framework.apps.RestFrameworkConfig", - "corsheaders", "apps.users.apps.UsersConfig", "apps.system.apps.SystemConfig", "apps.topology.apps.TopologyConfig", @@ -81,38 +80,10 @@ MIDDLEWARE = [ "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", - # "utils.custom.middleware.ApiLoggingMiddleware", + "utils.custom.middleware.CorsMiddleWare", ] -# 跨域增加忽略 -CORS_ALLOW_CREDENTIALS = True -CORS_ORIGIN_ALLOW_ALL = True -CORS_ORIGIN_WHITELIST = ( - ['http://10.254.2.188:*'] -) - -CORS_ALLOW_METHODS = ( - 'DELETE', - 'GET', - 'OPTIONS', - 'PATCH', - 'POST', - 'PUT', - 'VIEW', -) +# MIDDLEWARE +=[''] -CORS_ALLOW_HEADERS = ( - 'XMLHttpRequest', - 'X_FILENAME', - 'accept-encoding', - 'authorization', - 'content-type', - 'dnt', - 'origin', - 'user-agent', - 'x-csrftoken', - 'x-requested-with', - 'Pragma', -) ROOT_URLCONF = "netboost.urls" diff --git a/netaxe/utils/custom/middleware.py b/netaxe/utils/custom/middleware.py index d26272c..d609f71 100644 --- a/netaxe/utils/custom/middleware.py +++ b/netaxe/utils/custom/middleware.py @@ -87,3 +87,15 @@ class ApiLoggingMiddleware(MiddlewareMixin): if self.methods == 'ALL' or request.method in self.methods: self.__handle_response(request, response) return response + + +class CorsMiddleWare(MiddlewareMixin): + def process_response(self, request, response): + if request.META.get("HTTP_REFERER") != None: + response["Access-Control-Allow-Methods"] = "*" + response["Access-Control-Allow-Credentials"] = True + response['Access-Control-Allow-Headers'] = "Authorization" + response["Access-Control-Allow-Origin"] = "/".join(request.META.get("HTTP_REFERER").split("/")[0:3]) + return response + else: + return response diff --git a/web/nginx.conf b/web/nginx.conf index acf4e21..ca342fe 100644 --- a/web/nginx.conf +++ b/web/nginx.conf @@ -1,15 +1,6 @@ server { listen 80; server_name netaxe.com; - # keepalive_timeout 3600; - # client_max_body_size 5120M; - # access_log /var/log/nginx/netaxe.log main; - # gzip on; - # gzip_min_length 1k; - # gzip_comp_level 9; - # gzip_types text/plain application/javascript application/x-javascript text/css application/xml text/javascript application/x-httpd-php image/jpeg image/gif image/png; - # gzip_vary on; - # gzip_disable "MSIE [1-6]\."; location / { root /home; diff --git a/web/src/api/url.ts b/web/src/api/url.ts index 1929e9b..4f0e105 100644 --- a/web/src/api/url.ts +++ b/web/src/api/url.ts @@ -9,7 +9,7 @@ export const getTableList = '/api/users/user/' export const getRoleList = '/api/system/role/' export const getMenuList = '/api/system/menu/' export const getDepartmentList = '/api/system/dept/' -export const getMenuListByRole = '/api/system/menu/web_router/' +// export const getMenuListByRole = '/api/system/menu/web_router/' export const WebRouter = '/rbac/system/menu/web_router/' export const WebPermission = '/rbac/system/menu/web_permission/' // 调度管理 -- Gitee From 79ddc67128839260d9839761d3bf8e8f05c4a6a0 Mon Sep 17 00:00:00 2001 From: xuehaoweng <13721113750@163.com> Date: Thu, 20 Apr 2023 10:12:53 +0800 Subject: [PATCH 09/10] =?UTF-8?q?'cmdb-web=E4=BC=98=E5=8C=96'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- netaxe/netboost/settings.py | 1 - netaxe/nginx.conf | 47 ++++++++++++++++++++++++++++++ web/.env.development | 2 +- web/nginx.conf | 57 +++++++++++++++++++++++++++++++++++-- web/src/api/axios.config.ts | 6 ++-- web/src/router/index.ts | 25 ---------------- web/src/setting/index.ts | 20 ------------- 7 files changed, 106 insertions(+), 52 deletions(-) create mode 100644 netaxe/nginx.conf diff --git a/netaxe/netboost/settings.py b/netaxe/netboost/settings.py index c8dd949..e2d421a 100644 --- a/netaxe/netboost/settings.py +++ b/netaxe/netboost/settings.py @@ -74,7 +74,6 @@ INSTALLED_APPS = [ MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", - "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", # "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", diff --git a/netaxe/nginx.conf b/netaxe/nginx.conf new file mode 100644 index 0000000..262e180 --- /dev/null +++ b/netaxe/nginx.conf @@ -0,0 +1,47 @@ +upstream wsbackend { + server cmdb-server:8001; + } +server { + listen 9999; + server_name 0.0.0.0; + + keepalive_timeout 3600; + client_max_body_size 5120M; + + location / { + include uwsgi_params; + proxy_pass http://cmdb-server:8001; + proxy_redirect off; + proxy_connect_timeout 3800s; + proxy_read_timeout 3600s; + proxy_http_version 1.1; + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + + } + location /ws/{ + proxy_pass http://wsbackend; + proxy_redirect off; + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header Connection "upgrade"; + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Host $server_name; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + location /static/ { + expires 2d; + autoindex off; + add_header Cache-Control private; + alias /home/netaxe/static/; + } +} \ No newline at end of file diff --git a/web/.env.development b/web/.env.development index 9b5fb2a..75489bd 100644 --- a/web/.env.development +++ b/web/.env.development @@ -6,4 +6,4 @@ VITE_BASIC_URL = http://10.254.0.110:10089/ //VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.2.188:8000/ //VITE_BASIC_URL = http://10.254.0.111:9080/ -VITE_BASIC_RBAC = http://10.254.2.188:8000/ \ No newline at end of file +VITE_BASIC_RBAC = http://10.254.2.219:9080/ \ No newline at end of file diff --git a/web/nginx.conf b/web/nginx.conf index ca342fe..e40c0d7 100644 --- a/web/nginx.conf +++ b/web/nginx.conf @@ -6,23 +6,76 @@ server { root /home; index index.html index.htm; try_files $uri $uri/ /index.html; + #允许跨域请求的域,* 代表所有 + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; } - + location /api { + if ($request_method = 'OPTIONS') { + #允许跨域请求的域,* 代表所有 + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + return 204; + } proxy_pass http://apisix:9080/api; + + } location /ipam { + if ($request_method = 'OPTIONS') { + #允许跨域请求的域,* 代表所有 + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + return 204; + } proxy_pass http://apisix:9080/ipam; } location /media { + if ($request_method = 'OPTIONS') { + #允许跨域请求的域,* 代表所有 + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + return 204; + } proxy_pass http://apisix:9080/media; } location /rbac { + if ($request_method = 'OPTIONS') { + #允许跨域请求的域,* 代表所有 + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + return 204; + } proxy_pass http://apisix:9080/rbac; } location /ws { - proxy_pass http://netaxe-nginx:9999/ws; + if ($request_method = 'OPTIONS') { + #允许跨域请求的域,* 代表所有 + add_header 'Access-Control-Allow-Origin' *; + #允许请求的header + add_header 'Access-Control-Allow-Headers' *; + #允许请求的方法,比如 GET,POST,PUT,DELETE + add_header 'Access-Control-Allow-Methods' *; + return 204; + } + proxy_pass http://cmdb-nginx:9999/ws; proxy_connect_timeout 30000s; proxy_read_timeout 36000s; proxy_send_timeout 86000s; diff --git a/web/src/api/axios.config.ts b/web/src/api/axios.config.ts index 7cb44ec..2970776 100644 --- a/web/src/api/axios.config.ts +++ b/web/src/api/axios.config.ts @@ -22,7 +22,7 @@ const service = Axios.create({ }) // 在正式发送请求之前进行拦截配置 service.interceptors.request.use( - (config) => { + (config: { headers: { [x: string]: string; Authorization?: any }; data: any }) => { !config.headers && (config.headers = {}) if (!config.headers.Authorization && Cookies.get('netops-token')) { config.headers.Authorization = Cookies.get('netops-token') ? Cookies.get('netops-token') : '' @@ -35,7 +35,7 @@ service.interceptors.request.use( } return config }, - (error) => { + (error: { response: any }) => { return Promise.reject(error.response) } ) @@ -55,7 +55,7 @@ service.interceptors.response.use( throw new Error(response.status.toString()) } }, - (error) => { + (error: any) => { if (import.meta.env.MODE === 'development') { console.log(error) } diff --git a/web/src/router/index.ts b/web/src/router/index.ts index f1b73ae..fb32a29 100644 --- a/web/src/router/index.ts +++ b/web/src/router/index.ts @@ -57,31 +57,6 @@ export const constantRoutes = [ ], }, - // { - // path: '/ip_manage', - // component: Layout, - // name: 'Ip_manage', - // meta: { - // title: '地址管理', - // iconPrefix: 'iconfont', - // icon: 'dashboard', - // }, - // children: [ - // { - // path: 'ipam', - // name: 'ipam', - // component: (): any => import('@/views/ip_manage/ipam.vue'), - // meta: { - // title: 'IPAM', - // affix: true, - // iconPrefix: 'iconfont', - // icon: 'infomation', - // }, - // }, - // ], - // }, - - { path: '/redirect', component: Layout, diff --git a/web/src/setting/index.ts b/web/src/setting/index.ts index 5fd54ef..669557a 100644 --- a/web/src/setting/index.ts +++ b/web/src/setting/index.ts @@ -36,23 +36,3 @@ export default Object.assign( }, settingInfo, ) as Setting -// export const projectName = '' -// -// export default Object.assign( -// { -// theme: 'light', -// sideTheme: 'dark', -// themeColor: 'cyan@#18a058', -// layoutMode: 'ltr', -// sideWidth: 210, -// pageAnim: 'opacity', -// isFixedNavBar: true, -// actionBar: { -// isShowSearch: true, -// isShowMessage: true, -// isShowRefresh: true, -// isShowFullScreen: true, -// }, -// }, -// settingInfo -// ) as Setting -- Gitee From c991e2554bb66fbf2faf3367e4c5481164cf5584 Mon Sep 17 00:00:00 2001 From: xuehaoweng <13721113750@163.com> Date: Thu, 20 Apr 2023 10:52:51 +0800 Subject: [PATCH 10/10] =?UTF-8?q?'=E6=9B=B4=E6=96=B0cmdb-edit'?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/.env.development | 2 +- web/package.json | 2 +- web/src/views/cmdb/network_device.vue | 616 +++++++++++++------------- 3 files changed, 316 insertions(+), 304 deletions(-) diff --git a/web/.env.development b/web/.env.development index 75489bd..1c2e374 100644 --- a/web/.env.development +++ b/web/.env.development @@ -1,6 +1,6 @@ // 开发环境配置 //VITE_BASIC_URL = http://127.0.0.1:8001/ -VITE_BASIC_URL = http://10.254.0.110:10089/ +VITE_BASIC_URL = http://10.254.2.219:9999/ //VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.0.111:9999/ //VITE_BASIC_URL = http://10.254.0.111:9999/ diff --git a/web/package.json b/web/package.json index a32fbe1..5319289 100644 --- a/web/package.json +++ b/web/package.json @@ -1,5 +1,5 @@ { - "name": "netaxe-web", + "name": "netaxe-cmdb-web", "version": "1.0.0", "scripts": { "dev": "vite", diff --git a/web/src/views/cmdb/network_device.vue b/web/src/views/cmdb/network_device.vue index bb3a571..6d8769e 100644 --- a/web/src/views/cmdb/network_device.vue +++ b/web/src/views/cmdb/network_device.vue @@ -240,303 +240,317 @@ export default defineComponent({ const selectValues = ref(null) const selectCollectValues = ref('') const EditFormOptions = [ - { - key: 'manage_ip', - label: '管理地址', - value: ref(null), - // optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NInput, { - value: formItem.value.value, - onUpdateValue: (newVal: any) => { - formItem.value.value = newVal - }, - maxlength: 50, - placeholder: '', - }) - }, + { + key: 'manage_ip', + label: '管理地址', + value: ref(null), + // optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NInput, { + value: formItem.value.value, + onUpdateValue: (newVal: any) => { + formItem.value.value = newVal }, - { - key: 'framework', - label: '网络架构', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择架构', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + maxlength: 50, + placeholder: '', + }) + }, + }, + { + key: 'framework', + label: '网络架构', + value: ref(null), + optionItems: [ + { value: 0, label: '' }, + { value: 2, label: '二层' }, + { value: 4, label: '三层' }, + { value: 6, label: '大二层' }, + ], + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + placeholder: '请选择架构', + filterable: true, + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'vendor', - label: '供应商', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择供应商', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'vendor', + label: '供应商', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择供应商', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'role', - label: '设备角色', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择角色', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'role', + label: '设备角色', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择角色', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'category', - label: '类型', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择角色', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'category', + label: '类型', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择角色', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'idc_model', - label: '模块', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择角色', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'idc_model', + label: '模块', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择角色', + onUpdateValue: (val) => { + formItem.value.value = val }, - - { - label: 'SN号', - key: 'serial_num', - value: ref(null), - render: (formItem) => { - return h(NInput, { - value: formItem.value.value, - onUpdateValue: (val: any) => { - formItem.value.value = val - }, - placeholder: '请输入部门编号', - disabled: true, - }) - }, + }) + }, + }, + + { + label: 'SN号', + key: 'serial_num', + value: ref(null), + render: (formItem) => { + return h(NInput, { + value: formItem.value.value, + onUpdateValue: (val: any) => { + formItem.value.value = val }, - { - key: 'rack', - label: '机柜', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择角色', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + placeholder: '请输入部门编号', + disabled: true, + }) + }, + }, + { + key: 'rack', + label: '机柜', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择角色', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'idc', - label: '所属机房', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择角色', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'idc', + label: '所属机房', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + placeholder: '请选择角色', + filterable: true, + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'u_location_value', - label: 'U位置', - value: ref(''), - render: (formItem) => { - return h(NInputGroup, {}, [ - h(NInput, { - // value: ref(''), - value: formItem.value.value, - onUpdateValue: (newVal: any) => { - // rowData.u_location_start = newVal - //console.log('formItem.value', newVal) - formItem.value.value = newVal - }, - maxlength: 50, - placeholder: 'U位起始', - }), - h(NInput, { - // value: ref(''), - value: formItem.value.value, - onUpdateValue: (newVal: any) => { - formItem.value.value = newVal - // rowData.u_location_start = newVal - }, - maxlength: 50, - placeholder: 'U位结束', - }), - ]) - }, - }, - { - key: 'netzone', - label: '网络区域', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择区域', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'u_location_start', + label: 'U位起始', + value: ref(''), + render: (formItem) => { + return h(NInputGroup, {}, [ + h(NInput, { + value: formItem.value.value, + onUpdateValue: (newVal: any) => { + formItem.value.value = newVal + }, + maxlength: 50, + placeholder: 'U位起始', + }), + + ]) + }, + }, + { + key: 'u_location_end', + label: 'U位结束', + value: ref(''), + render: (formItem) => { + return h(NInputGroup, {}, [ + h(NInput, { + value: formItem.value.value, + onUpdateValue: (newVal: any) => { + formItem.value.value = newVal + }, + maxlength: 50, + placeholder: 'U位结束', + }), + + ]) + }, + }, + { + key: 'netzone', + label: '网络区域', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择区域', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'memo', - label: '备注', - value: ref(null), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NInput, { - value: formItem.value.value, - onUpdateValue: (newVal: any) => { - // rowData.u_location_start = newVal - // //console.log('formItem.value', rowData) - formItem.value.value = newVal - }, - }) - }, + }) + }, + }, + { + key: 'memo', + label: '备注', + value: ref(null), + optionItems: shallowReactive([] as Array), + render: (formItem) => { + return h(NInput, { + value: formItem.value.value, + onUpdateValue: (newVal: any) => { + // rowData.u_location_start = newVal + // //console.log('formItem.value', rowData) + formItem.value.value = newVal }, - { - key: 'attribute', - label: '网络属性', - value: ref(''), - optionItems: shallowReactive([] as Array), - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择属性', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'attribute', + label: '网络属性', + value: ref(''), + optionItems: [ + { value: '', label: '' }, + { value: 2, label: '生产网络' }, + { value: 4, label: '研发网络' }, + { value: 6, label: '研测网络' }, + { value: 8, label: '骨干网络' }, + { value: 10, label: '公网网络' }, + { value: 12, label: '测试网络' }, + ], + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择属性', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'status', - label: '设备状态', - value: ref(0), - optionItems: [ - { value: '', label: '' }, - { value: 0, label: '在线' }, - { value: 1, label: '下线' }, - { value: 2, label: '挂牌' }, - { value: 3, label: '备用' }, - ], - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择角色', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'status', + label: '设备状态', + value: ref(0), + optionItems: [ + { value: '', label: '' }, + { value: 0, label: '在线' }, + { value: 1, label: '下线' }, + { value: 2, label: '挂牌' }, + { value: 3, label: '备用' }, + ], + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + filterable: true, + placeholder: '请选择角色', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - key: 'auto_enable', - label: '数据采集', - value: ref(null), - optionItems: [ - { value: '', label: '' }, - { value: true, label: '是' }, - { value: false, label: '否' }, - ], - render: (formItem) => { - return h(NSelect, { - options: formItem.optionItems as Array, - value: formItem.value.value, - placeholder: '请选择', - onUpdateValue: (val) => { - formItem.value.value = val - }, - }) - }, + }) + }, + }, + { + key: 'auto_enable', + label: '数据采集', + value: ref(null), + optionItems: [ + { value: '', label: '' }, + { value: true, label: '是' }, + { value: false, label: '否' }, + ], + render: (formItem) => { + return h(NSelect, { + options: formItem.optionItems as Array, + value: formItem.value.value, + placeholder: '请选择', + onUpdateValue: (val) => { + formItem.value.value = val }, - { - label: 'id', - key: 'id', - value: ref(''), - render: (formItem) => { - return h(NInput, { - value: formItem.value.value, - disabled: true, - onUpdateValue: (newVal: any) => { - // rowData.u_location_start = newVal - //console.log('formItem.value', rowData) - formItem.value.value = newVal - }, - }) - }, + }) + }, + }, + { + label: 'id', + key: 'id', + value: ref(''), + render: (formItem) => { + return h(NInput, { + value: formItem.value.value, + disabled: true, + onUpdateValue: (newVal: any) => { + // rowData.u_location_start = newVal + //console.log('formItem.value', rowData) + formItem.value.value = newVal }, - // { - // key: 'plan', - // label: '采集方案', - // value: ref(null), - // optionItems: shallowReactive([] as Array), - // render: (formItem) => { - // return h(NSelect, { - // options: formItem.optionItems as Array, - // value: formItem.value.value, - // placeholder: '请选择采集方案', - // onUpdateValue: (val) => { - // formItem.value.value = val - // }, - // }) - // }, - // }, - ] as Array + }) + }, + }, + + ] as Array const connect_account_FormOptions = [ { key: 'account', @@ -2476,8 +2490,8 @@ export default defineComponent({ edit_formdata.append('idc_model', device_info['idc_model']) edit_formdata.append('rack', device_info['rack']) edit_formdata.append('idc', device_info['idc']) - edit_formdata.append('u_location_start', device_info['u_location_value']) - edit_formdata.append('u_location_end', device_info['u_location_value']) + edit_formdata.append('u_location_start', device_info['u_location_start']) + edit_formdata.append('u_location_end', device_info['u_location_end']) edit_formdata.append('memo', device_info['memo']) edit_formdata.append('attribute', device_info['attribute']) edit_formdata.append('status', device_info['status']) @@ -2564,8 +2578,8 @@ export default defineComponent({ get({ url: getCmdbNetzoneList, }).then((res) => { - if (EditFormOptions[10].optionItems !== undefined) { - EditFormOptions[10].optionItems.length = 0 + if (EditFormOptions[11].optionItems !== undefined) { + EditFormOptions[11].optionItems.length = 0 let netzone_list = [] res.results.forEach((ele) => { let dict = { @@ -2574,7 +2588,7 @@ export default defineComponent({ } netzone_list.push(dict) }) - EditFormOptions[10].optionItems.push(...netzone_list) + EditFormOptions[11].optionItems.push(...netzone_list) } }) @@ -2634,17 +2648,15 @@ export default defineComponent({ // 根据供应商查询型号 EditFormOptions.forEach((it) => { - const key = it.key - const propName = item[key] - if (key === 'u_location') { - it.value.value = item['u_location_start'] + '-' + item['u_location_end'] - } - if (key === 'id') { - it.value.value = JSON.stringify(propName) - } else { - it.value.value = propName - } - }) + const key = it.key + const propName = item[key] + + if (key === 'id') { + it.value.value = JSON.stringify(propName) + } else { + it.value.value = propName + } + }) }) } @@ -3101,8 +3113,8 @@ export default defineComponent({ if (conditionItems[15].optionItems != undefined) { conditionItems[15].optionItems.push(dict) } - if (EditFormOptions[15].optionItems != undefined) { - EditFormOptions[15].optionItems.push(dict) + if (EditFormOptions[16].optionItems != undefined) { + EditFormOptions[16].optionItems.push(dict) } } nextTick(() => { @@ -3164,8 +3176,8 @@ export default defineComponent({ conditionItems[13].optionItems.push(dict) } - if (EditFormOptions[12].optionItems != undefined) { - EditFormOptions[12].optionItems.push(dict) + if (EditFormOptions[13].optionItems != undefined) { + EditFormOptions[13].optionItems.push(dict) } if (importFormOptions[12].optionItems != undefined) { importFormOptions[12].optionItems.push(dict) -- Gitee