From 602edea8f8ba05784e0edc44e30f4d44a1f77ddc Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Mon, 26 Nov 2018 21:10:43 +0800 Subject: [PATCH 01/13] deployment setting --- .idea/deployment.xml | 15 ++ .idea/misc.xml | 2 +- .idea/sandboxMP.iml | 2 +- .idea/webServers.xml | 15 ++ .idea/workspace.xml | 173 +++++++++++++----- apps/__pycache__/custom.cpython-36.pyc | Bin 2800 -> 2800 bytes .../__pycache__/middleware.cpython-36.pyc | Bin 3537 -> 3537 bytes apps/system/__pycache__/models.cpython-36.pyc | Bin 3751 -> 3751 bytes apps/system/__pycache__/urls.cpython-36.pyc | Bin 2294 -> 2294 bytes apps/system/__pycache__/views.cpython-36.pyc | Bin 488 -> 488 bytes .../__pycache__/views_menu.cpython-36.pyc | Bin 1410 -> 1410 bytes .../__pycache__/views_role.cpython-36.pyc | Bin 4690 -> 4690 bytes .../views_structure.cpython-36.pyc | Bin 3678 -> 3678 bytes .../__pycache__/views_user.cpython-36.pyc | Bin 8063 -> 8063 bytes .../0002_auto_20181115_2124.cpython-36.pyc | Bin 704 -> 704 bytes requirements/{dev => dev.txt} | 0 requirements/{pro => pro.txt} | 0 sandboxMP/__pycache__/settings.cpython-36.pyc | Bin 2803 -> 2803 bytes sandboxMP/settings.py | 2 +- 19 files changed, 165 insertions(+), 44 deletions(-) create mode 100644 .idea/deployment.xml create mode 100644 .idea/webServers.xml rename requirements/{dev => dev.txt} (100%) rename requirements/{pro => pro.txt} (100%) diff --git a/.idea/deployment.xml b/.idea/deployment.xml new file mode 100644 index 0000000..6161149 --- /dev/null +++ b/.idea/deployment.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index eb73b7d..d7674bd 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,4 @@ - + \ No newline at end of file diff --git a/.idea/sandboxMP.iml b/.idea/sandboxMP.iml index 6b57a36..84613af 100644 --- a/.idea/sandboxMP.iml +++ b/.idea/sandboxMP.iml @@ -15,7 +15,7 @@ - + diff --git a/.idea/webServers.xml b/.idea/webServers.xml new file mode 100644 index 0000000..91e714a --- /dev/null +++ b/.idea/webServers.xml @@ -0,0 +1,15 @@ + + + + + + \ No newline at end of file diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 450747f..b517ad2 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -2,8 +2,11 @@ + + - + + @@ -47,6 +60,7 @@ + @@ -105,7 +119,7 @@ true DEFINITION_ORDER - + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1539589405377 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file From ba936d7f9e48a41de0df2391591da7a9f4895a24 Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Mon, 10 Dec 2018 20:32:45 +0800 Subject: [PATCH 04/13] create cmdb app --- apps/cmdb/__init__.py | 0 apps/cmdb/admin.py | 3 +++ apps/cmdb/apps.py | 5 +++++ apps/cmdb/migrations/__init__.py | 0 apps/cmdb/models.py | 22 ++++++++++++++++++++++ apps/cmdb/tests.py | 3 +++ apps/cmdb/urls.py | 9 +++++++++ apps/cmdb/views.py | 9 +++++++++ sandboxMP/settings.py | 1 + sandboxMP/urls.py | 1 + templates/cmdb/cmdb_index.html | 20 ++++++++++++++++++++ 11 files changed, 73 insertions(+) create mode 100644 apps/cmdb/__init__.py create mode 100644 apps/cmdb/admin.py create mode 100644 apps/cmdb/apps.py create mode 100644 apps/cmdb/migrations/__init__.py create mode 100644 apps/cmdb/models.py create mode 100644 apps/cmdb/tests.py create mode 100644 apps/cmdb/urls.py create mode 100644 apps/cmdb/views.py create mode 100644 templates/cmdb/cmdb_index.html diff --git a/apps/cmdb/__init__.py b/apps/cmdb/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cmdb/admin.py b/apps/cmdb/admin.py new file mode 100644 index 0000000..8c38f3f --- /dev/null +++ b/apps/cmdb/admin.py @@ -0,0 +1,3 @@ +from django.contrib import admin + +# Register your models here. diff --git a/apps/cmdb/apps.py b/apps/cmdb/apps.py new file mode 100644 index 0000000..9e8f468 --- /dev/null +++ b/apps/cmdb/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class CmdbConfig(AppConfig): + name = 'cmdb' diff --git a/apps/cmdb/migrations/__init__.py b/apps/cmdb/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/apps/cmdb/models.py b/apps/cmdb/models.py new file mode 100644 index 0000000..f231cf1 --- /dev/null +++ b/apps/cmdb/models.py @@ -0,0 +1,22 @@ +from django.db import models + +# Create your models here. + + +class AbstractMode(models.Model): + parent = models.ForeignKey( + 'self', blank=True, null=True, on_delete=models.SET_NULL, related_name='child' + ) + + class Meta: + abstract = True + + +class Code(AbstractMode): + key = models.CharField(max_length=80, verbose_name='键') + value = models.CharField(max_length=80, verbose_name='值') + desc = models.BooleanField(default=True, verbose_name='备注') + + class Meta: + verbose_name = '字典' + verbose_name_plural = verbose_name diff --git a/apps/cmdb/tests.py b/apps/cmdb/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/apps/cmdb/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/apps/cmdb/urls.py b/apps/cmdb/urls.py new file mode 100644 index 0000000..7cbc875 --- /dev/null +++ b/apps/cmdb/urls.py @@ -0,0 +1,9 @@ +from django.urls import path + +from .views import CmdbView + +app_name = 'cmdb' + +urlpatterns = [ + path('', CmdbView.as_view(), name='index'), +] diff --git a/apps/cmdb/views.py b/apps/cmdb/views.py new file mode 100644 index 0000000..ac3e598 --- /dev/null +++ b/apps/cmdb/views.py @@ -0,0 +1,9 @@ +from django.views.generic import TemplateView + +from system.mixin import LoginRequiredMixin +from custom import BreadcrumbMixin + + +class CmdbView(LoginRequiredMixin, BreadcrumbMixin, TemplateView): + + template_name = 'cmdb/cmdb_index.html' diff --git a/sandboxMP/settings.py b/sandboxMP/settings.py index 7810c11..a4f734f 100644 --- a/sandboxMP/settings.py +++ b/sandboxMP/settings.py @@ -40,6 +40,7 @@ INSTALLED_APPS = [ 'django.contrib.messages', 'django.contrib.staticfiles', 'system', + 'cmdb', ] MIDDLEWARE = [ diff --git a/sandboxMP/urls.py b/sandboxMP/urls.py index d3ae5f7..8386877 100644 --- a/sandboxMP/urls.py +++ b/sandboxMP/urls.py @@ -28,6 +28,7 @@ urlpatterns = [ path('login/', LoginView.as_view(), name='login'), path('logout/', LogoutView.as_view(), name='logout'), path('system/', include('system.urls', namespace='system')), + path('cmdb/', include('cmdb.urls', namespace='cmdb')), ] diff --git a/templates/cmdb/cmdb_index.html b/templates/cmdb/cmdb_index.html new file mode 100644 index 0000000..b83f7f9 --- /dev/null +++ b/templates/cmdb/cmdb_index.html @@ -0,0 +1,20 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %}{% endblock %} +{% block content %} + + +
+ 这里是配置管理首页(临时内容) + +
+ + + +{% endblock %} + + +{% block javascripts %} + +{% endblock %} \ No newline at end of file From 00f7112b67722ddbcf6af268666c9b01fcaa41c0 Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Sat, 15 Dec 2018 15:34:27 +0800 Subject: [PATCH 05/13] logging --- apps/cmdb/tests.py | 19 +++++++++++++++ sandboxMP/settings.py | 55 ++++++++++++++++++++++++++++++++++++++++++- sandboxMP/urls.py | 6 +++-- 3 files changed, 77 insertions(+), 3 deletions(-) diff --git a/apps/cmdb/tests.py b/apps/cmdb/tests.py index 7ce503c..f8783cd 100644 --- a/apps/cmdb/tests.py +++ b/apps/cmdb/tests.py @@ -1,3 +1,22 @@ from django.test import TestCase # Create your tests here. +from django.views.generic.base import View +from django.shortcuts import HttpResponse +import logging +from .models import Code + +info_logger = logging.getLogger('sandbox_info') +error_logger = logging.getLogger('sandbox_error') + + +class TestLoggingView(View): + + def get(self, request): + print('a') + info_logger.info('The system print a letter "a" ') + try: + Code.objects.get(id=100) + except Exception as e: + error_logger.error(e) + return HttpResponse("OK!") \ No newline at end of file diff --git a/sandboxMP/settings.py b/sandboxMP/settings.py index a4f734f..8eecb8b 100644 --- a/sandboxMP/settings.py +++ b/sandboxMP/settings.py @@ -154,10 +154,63 @@ SAFE_URL = [r'^/$', '/media/', '/admin/', '/ckeditor/', + '/test/', ] # session timeout SESSION_COOKIE_AGE = 60 * 20 SESSION_EXPIRE_AT_BROWSER_CLOSE = True -SESSION_SAVE_EVERY_REQUEST = True \ No newline at end of file +SESSION_SAVE_EVERY_REQUEST = True + + +# logging config + +BASE_LOG_DIR = os.path.join(BASE_DIR, 'slogs') + +LOGGING = { + 'version': 1, + 'disable_existing_loggers': False, + 'formatters': { + 'standard': { + 'format': '[%(asctime)s][task_id:%(name)s][%(levelname)s]' + '[%(filename)s:%(lineno)d][%(message)s]' + }, + 'simple': { + 'format': '[%(levelname)s][%(asctime)s][%(filename)s:%(lineno)d]%(message)s' + }, + + }, + 'handlers': { + 'default': { + 'level': 'INFO', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(BASE_LOG_DIR, "sandbox_info.log"), + 'maxBytes': 1024 * 1024 * 50, + 'backupCount': 3, + 'formatter': 'simple', + 'encoding': 'utf-8', + }, + 'error': { + 'level': 'ERROR', + 'class': 'logging.handlers.RotatingFileHandler', + 'filename': os.path.join(BASE_LOG_DIR, "sandbox_err.log"), + 'backupCount': 5, + 'formatter': 'standard', + 'encoding': 'utf-8', + } + + }, + 'loggers': { + 'sandbox_info': { + 'handlers': ['default'], + 'level': 'INFO', + 'propagate': True, + }, + 'sandbox_error': { + 'handlers': ['error'], + 'level': 'ERROR', + } + } + +} \ No newline at end of file diff --git a/sandboxMP/urls.py b/sandboxMP/urls.py index 8386877..9422131 100644 --- a/sandboxMP/urls.py +++ b/sandboxMP/urls.py @@ -20,7 +20,7 @@ from django.urls import re_path from django.views.static import serve from system.views_user import IndexView, LoginView, LogoutView - +from cmdb.tests import TestLoggingView urlpatterns = [ path('admin/', admin.site.urls), @@ -30,10 +30,12 @@ urlpatterns = [ path('system/', include('system.urls', namespace='system')), path('cmdb/', include('cmdb.urls', namespace='cmdb')), + path('test/', TestLoggingView.as_view()), + ] if settings.DEBUG: urlpatterns += [ re_path(r'^media/(?P.*)$', serve, {"document_root": settings.MEDIA_ROOT}), - ] \ No newline at end of file + ] From 860ae14d4c725d5542f3ba984e65f8877df17628 Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Wed, 19 Dec 2018 21:41:50 +0800 Subject: [PATCH 06/13] code management --- apps/cmdb/forms.py | 46 +++ apps/cmdb/urls.py | 6 + apps/cmdb/views_code.py | 53 ++++ apps/custom.py | 99 ++++++- requirements/dev.txt | 3 +- requirements/pro.txt | 14 +- .../plugins/datatables/dataTables.const-1.js | 2 +- templates/cmdb/code.html | 274 ++++++++++++++++++ templates/cmdb/code_create.html | 97 +++++++ templates/cmdb/code_update.html | 99 +++++++ 10 files changed, 687 insertions(+), 6 deletions(-) create mode 100644 apps/cmdb/forms.py create mode 100644 apps/cmdb/views_code.py create mode 100644 templates/cmdb/code.html create mode 100644 templates/cmdb/code_create.html create mode 100644 templates/cmdb/code_update.html diff --git a/apps/cmdb/forms.py b/apps/cmdb/forms.py new file mode 100644 index 0000000..4f44c9b --- /dev/null +++ b/apps/cmdb/forms.py @@ -0,0 +1,46 @@ +# @Time : 2018/12/19 16:13 +# @Author : RobbieHan +# @File : forms.py + +from django import forms + +from .models import Code + + +class CodeCreateForm(forms.ModelForm): + class Meta: + model = Code + fields = '__all__' + + error_messages = { + 'key': {'required': 'key不能为空'}, + 'value': {'required': 'value不能为空'} + } + + def clean(self): + cleaned_data = super(CodeCreateForm, self).clean() + key = cleaned_data.get('key') + value = cleaned_data.get('value') + + if Code.objects.filter(key=key).count(): + raise forms.ValidationError('key:{}已存在'.format(key)) + + if Code.objects.filter(value=value).count(): + raise forms.ValidationError('value: {}已存在'.format(value)) + + +class CodeUpdateForm(CodeCreateForm): + + def clean(self): + cleaned_data = self.cleaned_data + key = cleaned_data.get('key') + value = cleaned_data.get('value') + + if self.instance: + matching_code = Code.objects.exclude(pk=self.instance.pk) + if matching_code.filter(key=key).exists(): + msg = 'key:{} 已经存在'.format(key) + raise forms.ValidationError(msg) + if matching_code.filter(value=value).exists(): + msg = 'value:{} 已经存在'.format(value) + raise forms.ValidationError(msg) \ No newline at end of file diff --git a/apps/cmdb/urls.py b/apps/cmdb/urls.py index 7cbc875..2e2503c 100644 --- a/apps/cmdb/urls.py +++ b/apps/cmdb/urls.py @@ -1,9 +1,15 @@ from django.urls import path from .views import CmdbView +from . import views_code app_name = 'cmdb' urlpatterns = [ path('', CmdbView.as_view(), name='index'), + path('portal/code/', views_code.CodeView.as_view(), name='portal-code'), + path('portal/code/create/', views_code.CodeCreateView.as_view(), name='portal-code-create'), + path('portal/code/list/', views_code.CodeListView.as_view(), name='portal-code-list'), + path('portal/code/update/', views_code.CodeUpdateView.as_view(), name='portal-code-update'), + path('portal/code/delete/', views_code.CodeDeleteView.as_view(), name='portal-code-delete'), ] diff --git a/apps/cmdb/views_code.py b/apps/cmdb/views_code.py new file mode 100644 index 0000000..af63d20 --- /dev/null +++ b/apps/cmdb/views_code.py @@ -0,0 +1,53 @@ +# @Time : 2018/12/19 13:31 +# @Author : RobbieHan +# @File : views_code.py.py + +from django.views.generic import TemplateView + +from system.mixin import LoginRequiredMixin +from custom import (BreadcrumbMixin, SandboxCreateView, + SandboxListView, SandboxUpdateView, SandboxDeleteView) +from .models import Code +from .forms import CodeCreateForm, CodeUpdateForm + + +class CodeView(LoginRequiredMixin, BreadcrumbMixin, TemplateView): + template_name = 'cmdb/code.html' + + def get_context_data(self): + context = dict(code_parent=Code.objects.filter(parent=None)) + return context + + +class CodeCreateView(SandboxCreateView): + model = Code + form_class = CodeCreateForm + template_name_suffix = '_create' + + def get_context_data(self, **kwargs): + kwargs['code_parent'] = Code.objects.filter(parent=None) + return super().get_context_data(**kwargs) + + +class CodeListView(SandboxListView): + model = Code + fields = ['id', 'key', 'value', 'parent__value'] + + def get(self, request): + if 'parent' in request.GET and request.GET['parent']: + self.filters = dict(parent__key=request.GET['parent']) + return super().get(request) + + +class CodeUpdateView(SandboxUpdateView): + model = Code + form_class = CodeUpdateForm + template_name_suffix = '_update' + + def get_context_data(self, **kwargs): + kwargs['code_parent'] = Code.objects.filter(parent=None) + return super().get_context_data(**kwargs) + + +class CodeDeleteView(SandboxDeleteView): + model = Code diff --git a/apps/custom.py b/apps/custom.py index 6c2e497..0315e2c 100644 --- a/apps/custom.py +++ b/apps/custom.py @@ -3,10 +3,13 @@ # @File : custom.py import json +import re -from django.views.generic import CreateView, UpdateView +from django.views.generic import CreateView, UpdateView, View from django.shortcuts import HttpResponse -from django.http import Http404 +from django.http import Http404, JsonResponse +from django.db.models.query import QuerySet +from django.core.exceptions import ImproperlyConfigured from system.mixin import LoginRequiredMixin from system.models import Menu @@ -42,6 +45,69 @@ class SandboxGetObjectMixin: return obj +class SandboxMultipleObjectMixin: + + filters = {} + fields = [] + queryset = None + model = None + + def get_queryset(self): + if self.queryset is not None: + queryset = self.queryset + if isinstance(queryset, QuerySet): + queryset = queryset.all() + elif self.model is not None: + queryset = self.model._default_manager.all() + else: + raise ImproperlyConfigured( + "%(cls)s is missing a QuerySet. Define " + "%(cls)s.model, %(cls)s.queryset." + % {'cls': self.__class__.__name__} + ) + return queryset + + def get_datatables_paginator(self, request): + datatables = request.GET + draw = int(datatables.get('draw')) + start = int(datatables.get('start')) + length = int(datatables.get('length')) + order_column = datatables.get('order[0][column]') + order_dir = datatables.get('order[0][dir]') + order_field = datatables.get('columns[{}][data]'.format(order_column)) + queryset = self.get_queryset() + if order_dir == 'asc': + queryset = queryset.order_by(order_field) + else: + queryset = queryset.order_by('-{0}'.format(order_field)) + record_total_count = queryset.count() + filters = self.get_filters() + fields = self.get_fields() + if filters: + queryset = queryset.filter(**self.filters) + if fields: + queryset = queryset.values(*self.fields) + + record_filter_count = queryset.count() + + object_list = queryset[start:(start + length)] + + data = list(object_list) + + return { + 'draw': draw, + 'recordsTotal': record_total_count, + 'recordsFiltered': record_filter_count, + 'data': data, + } + + def get_filters(self): + return self.filters + + def get_fields(self): + return self.fields + + class SandboxEditViewMixin: def post(self, request, *args, **kwargs): @@ -50,6 +116,11 @@ class SandboxEditViewMixin: if form.is_valid(): form.save() res['result'] = True + else: + pattern = '
  • .*?
    • (.*?)
    • ' + form_errors = str(form.errors) + errors = re.findall(pattern, form_errors) + res['error'] = errors[0] return HttpResponse(json.dumps(res), content_type='application/json') @@ -65,3 +136,27 @@ class SandboxUpdateView(LoginRequiredMixin, SandboxEditViewMixin, SandboxGetObje def post(self, request, *args, **kwargs): self.object = self.get_object() return super().post(request, *args, **kwargs) + + +class SandboxListView(LoginRequiredMixin, SandboxMultipleObjectMixin, View): + """ + JsonResponse some json of objects, set by `self.model` or `self.queryset`. + """ + def get(self, request): + context = self.get_datatables_paginator(request) + return JsonResponse(context) + + +class SandboxDeleteView(LoginRequiredMixin, SandboxMultipleObjectMixin, View): + + def post(self, request): + context = dict(result=False) + queryset = self.get_queryset() + if 'id' in request.POST and request.POST['id']: + id_list = map(int, request.POST['id'].split(',')) + queryset.filter(id__in=id_list).delete() + context['result'] = True + else: + raise AttributeError("Sandbox delete view %s must be called with id. " + % self.__class__.__name__) + return JsonResponse(context) \ No newline at end of file diff --git a/requirements/dev.txt b/requirements/dev.txt index 4643bc9..694772d 100644 --- a/requirements/dev.txt +++ b/requirements/dev.txt @@ -1,2 +1 @@ -django==2.1.2 -pillow==5.3.0 \ No newline at end of file +-r pro.txt \ No newline at end of file diff --git a/requirements/pro.txt b/requirements/pro.txt index 4643bc9..fff54da 100644 --- a/requirements/pro.txt +++ b/requirements/pro.txt @@ -1,2 +1,14 @@ django==2.1.2 -pillow==5.3.0 \ No newline at end of file +pillow==5.3.0 +mysqlclient==1.3.13 +ipython==7.1.1 +pyyaml==3.13 +ruamel.yaml==0.15.80 +python-nmap==0.6.1 +redis==3.0.1 +pymongo==3.7.1 +paramiko==2.4.2 +pycrypto==2.6.1 +celery==4.2.1 +celery-once==2.0.0 +flower==0.9.2 diff --git a/static/plugins/datatables/dataTables.const-1.js b/static/plugins/datatables/dataTables.const-1.js index 3e5df8a..8c13eaa 100644 --- a/static/plugins/datatables/dataTables.const-1.js +++ b/static/plugins/datatables/dataTables.const-1.js @@ -2,7 +2,7 @@ var DATATABLES_CONSTANT = { // datatables常量 DATA_TABLES : { - DEFAULT_OPTION : { // DataTables初始化选项 + SERVER_SIDE_OPTION : { // DataTables初始化选项 oLanguage : { sProcessing : "处理中...", sLengthMenu : "每页 _MENU_ 项",//"显示 _MENU_ 项结果,", diff --git a/templates/cmdb/code.html b/templates/cmdb/code.html new file mode 100644 index 0000000..b36f4d6 --- /dev/null +++ b/templates/cmdb/code.html @@ -0,0 +1,274 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %} + + + +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +
      + +
      +
       
      +
      + + +
      +
       
      +
      + +
      +
      +
      +
      + + +
      +
      +
      +
      + +
      + + + + + + + + + + + + + +
      IDKEYVALUE所属操作
      +

      +
      +
      +
      + +
      + + + +{% endblock %} + + +{% block javascripts %} + + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/cmdb/code_create.html b/templates/cmdb/code_create.html new file mode 100644 index 0000000..001f57d --- /dev/null +++ b/templates/cmdb/code_create.html @@ -0,0 +1,97 @@ +{% extends 'base-layer.html' %} +{% load staticfiles %} +{% block css %} + + +{% endblock %} +{% block main %} +
      +
      + {% csrf_token %} +
      +
      + +

      新建字典

      +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      +
      + +
      +
      + + +
      +
      + +{% endblock %} + +{% block javascripts %} + + + +{% endblock %} \ No newline at end of file diff --git a/templates/cmdb/code_update.html b/templates/cmdb/code_update.html new file mode 100644 index 0000000..aa28a99 --- /dev/null +++ b/templates/cmdb/code_update.html @@ -0,0 +1,99 @@ +{% extends 'base-layer.html' %} +{% load staticfiles %} +{% block css %} + + +{% endblock %} +{% block main %} +
      +
      + + {% csrf_token %} +
      +
      + +

      修改字典

      +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      +
      + +
      +
      + + +
      +
      + +{% endblock %} + +{% block javascripts %} + + + +{% endblock %} \ No newline at end of file From 4fbdc88743159baa956a5a48d37ecb539bfd740c Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Sat, 29 Dec 2018 20:19:34 +0800 Subject: [PATCH 07/13] scan config --- apps/cmdb/urls.py | 4 +- apps/cmdb/views_scan.py | 54 ++++++++++ apps/utils/__init__.py | 3 + apps/utils/sandbox_utils.py | 74 ++++++++++++++ config/scanhosts.yml | 18 ++++ templates/cmdb/scan_config.html | 176 ++++++++++++++++++++++++++++++++ 6 files changed, 328 insertions(+), 1 deletion(-) create mode 100644 apps/cmdb/views_scan.py create mode 100644 apps/utils/__init__.py create mode 100644 apps/utils/sandbox_utils.py create mode 100644 config/scanhosts.yml create mode 100644 templates/cmdb/scan_config.html diff --git a/apps/cmdb/urls.py b/apps/cmdb/urls.py index 2e2503c..c64f485 100644 --- a/apps/cmdb/urls.py +++ b/apps/cmdb/urls.py @@ -1,7 +1,7 @@ from django.urls import path from .views import CmdbView -from . import views_code +from . import views_code, views_scan app_name = 'cmdb' @@ -12,4 +12,6 @@ urlpatterns = [ path('portal/code/list/', views_code.CodeListView.as_view(), name='portal-code-list'), path('portal/code/update/', views_code.CodeUpdateView.as_view(), name='portal-code-update'), path('portal/code/delete/', views_code.CodeDeleteView.as_view(), name='portal-code-delete'), + + path('portal/scan_config/', views_scan.ScanConfigView.as_view(), name='portal-scan_config'), ] diff --git a/apps/cmdb/views_scan.py b/apps/cmdb/views_scan.py new file mode 100644 index 0000000..bffd7c5 --- /dev/null +++ b/apps/cmdb/views_scan.py @@ -0,0 +1,54 @@ +# @Time : 2018/12/29 19:25 +# @Author : RobbieHan +# @File : views_scan.py + +import ast +import logging +from ruamel import yaml + +from django.views.generic import View +from django.http import JsonResponse +from django.shortcuts import render + + +from system.mixin import LoginRequiredMixin +from custom import BreadcrumbMixin +from utils.sandbox_utils import ConfigFileMixin +from system.models import Menu + +error_logger = logging.getLogger('sandbox_error') + + +class ScanConfigView(LoginRequiredMixin, BreadcrumbMixin, ConfigFileMixin, View): + + def get(self, request): + menu = Menu.get_menu_by_request_url(request.path_info) + template_name = 'cmdb/scan_config.html' + context = self.get_conf_content() + context.update(menu) + return render(request, template_name, context) + + def post(self, request): + ret = dict(result=False) + config = dict() + hosts = request.POST + try: + config['net_address'] = ast.literal_eval(hosts['net_address']) + config['ssh_username'] = hosts['ssh_username'] + config['ssh_port'] = hosts['ssh_port'] + config['ssh_password'] = hosts['ssh_password'] + config['ssh_private_key'] = hosts['ssh_private_key'] + config['commands'] = ast.literal_eval(hosts['commands']) + config['auth_type'] = hosts['auth_type'] + config['scan_type'] = hosts['scan_type'] + config['email'] = hosts['email'] + config['send_email'] = hosts['send_email'] + data = dict(hosts=config) + config_file = self.get_config_file() + with open(config_file, 'w', encoding='utf-8') as f: + yaml.dump(data, f, Dumper=yaml.RoundTripDumper, indent=4) + ret['result'] = True + except Exception as e: + error_logger.error(e) + + return JsonResponse(ret) diff --git a/apps/utils/__init__.py b/apps/utils/__init__.py new file mode 100644 index 0000000..1caca67 --- /dev/null +++ b/apps/utils/__init__.py @@ -0,0 +1,3 @@ +# @Time : 2018/12/29 16:26 +# @Author : RobbieHan +# @File : __init__.py.py \ No newline at end of file diff --git a/apps/utils/sandbox_utils.py b/apps/utils/sandbox_utils.py new file mode 100644 index 0000000..44c3e4b --- /dev/null +++ b/apps/utils/sandbox_utils.py @@ -0,0 +1,74 @@ +# @Time : 2018/12/29 19:22 +# @Author : RobbieHan +# @File : sandbox_utils.py + +import os + +from django.conf import settings + +import yaml +import logging + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sandboxMP.settings') +error_logger = logging.getLogger('sandbox_error') + + +class ConfigFileMixin: + config_file = None + + def get_config_file(self): + """ + Return 'config_file' that will be used to look up the scan hosts IP, + network, range of IP, or other config settings. + This method is called by the default implementation of get_hosts(), + """ + + if self.config_file is None: + config_file = os.path.join(os.path.join(settings.BASE_DIR, 'config'), 'scanhosts.yml') + if os.path.exists(config_file): + return config_file + else: + msg = ' %(cls)s is missing a config file. Define %(cls)s.config_file, ' \ + 'or override %(cls)s.get_config_file().' % {'cls': self.__class__.__name__} + error_logger.error(msg) + raise ValueError(msg) + + return self.config_file + + def get_conf_content(self, *key): + """ + Get the configuration content from config file . + Example ssh_password, commands, email which is in the config file. + """ + _config = self.get_config_file() + with open(_config) as f: + content = yaml.load(f) + if key is not None: + try: + num = 0 + while num < len(key): + content = content[key[num]] + num += 1 + except Exception as e: + msg = '%(exc)s is not in %(config)s.' % { + 'exc': e, + 'config': _config + } + error_logger.error(msg) + raise ValueError(msg) + return content + + def get_commands(self): + """ + Get the commands from config file. + """ + key = ['hosts', 'commands'] + return self.get_conf_content(*key) + + def get_net_address(self): + """ + Return the hosts that will be used to scan. + Subclasses can override this to return any hosts. + """ + key = ['hosts', 'net_address'] + return self.get_conf_content(*key) diff --git a/config/scanhosts.yml b/config/scanhosts.yml new file mode 100644 index 0000000..10d2bd8 --- /dev/null +++ b/config/scanhosts.yml @@ -0,0 +1,18 @@ +hosts: + net_address: + - '172.16.3.0/24' + - '172.16.2.100-105' + ssh_username: 'root' + ssh_port: '22' + ssh_password: '1234@abcd.com' + ssh_private_key: '/root/.ssh/id_rsa' + commands: + sys_hostname: 'hostname' + mac_address: 'cat /sys/class/net/[^tsbvl]*/address' + sn_number: 'dmidecode -s system-serial-number' + os_type: 'cat /etc/redhat-release' + device_type: 'echo `dmidecode -s system-manufacturer && dmidecode -s system-product-name`' + email: 'robbie_han@outlook.com' + send_email: 'false' + scan_type: 'basic_scan' + auth_type: 'private_key' diff --git a/templates/cmdb/scan_config.html b/templates/cmdb/scan_config.html new file mode 100644 index 0000000..e833fab --- /dev/null +++ b/templates/cmdb/scan_config.html @@ -0,0 +1,176 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %} + +{% endblock %} + +{% block content %} + + +
      +
      +
      + +
      +
      +

      扫面参数配置

      +
      + + +
      + {% csrf_token %} +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      +
      +
      + +
      + +
      + +
      + +
      +
      +
      + + +
      + +
      + +
      +
      + +
      + + +
      +
      +
      + + + +
      +
      +
      +
      +
      +
      +

      参数配置说明

      +
      +
      + 扫描网段必填参数 +

      网络扫描地址列表,列表中可以包含一个或多个网段,也可以是一个或多个地址区间,例如['192.168.100.0/24', '192.168.100.10-20']

      + 登陆用户必填参数 +

      系统登陆测试使用的用户名,默认root。

      + 登陆密码可选参数 +

      系统登陆测试使用的密码,如过认证方式为密码认证,必须填写密码信息。

      + 密钥路径可选参数 +

      系统登陆测试使用的密钥存放路径,如过认证方式为私钥认证,必须填写密钥路径。

      + 运行命令预定义参数 +

      登陆系统后运行的基本命令,用来获取系统基本信息,当扫描方式为加强扫描时,才会执行运行命令,运行命令为预定义,禁止修改。

      + 认证方式可选参数 +

      系统登陆认证方式,包括密码认证和私钥认证两种方式,当扫描方式为加强扫描时,需要设定认证方式。

      + 扫描方式可选参数 +

      系统扫描方式,包括基本扫描和加强扫描,当设置为加强扫描时会登陆系统执行运行命令获取系统信息。

      + 收件邮箱可选参数 +

      用于接收扫描结果邮件,可以通过发送邮件选项设置是否发送通知邮件。

      +
      +
      +
      + +
      +
      + + + +{% endblock %} + +{% block javascripts %} + + +{% endblock %} \ No newline at end of file From 9d0bd95b69a528880d481a8f84e310d469c2fa75 Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Thu, 3 Jan 2019 20:54:17 +0800 Subject: [PATCH 08/13] device models --- apps/cmdb/models.py | 100 +++++++++++++++++++++++++++++++++++++++- apps/cmdb/views_code.py | 4 +- 2 files changed, 100 insertions(+), 4 deletions(-) diff --git a/apps/cmdb/models.py b/apps/cmdb/models.py index f231cf1..6927c8d 100644 --- a/apps/cmdb/models.py +++ b/apps/cmdb/models.py @@ -1,6 +1,11 @@ -from django.db import models +import os +from datetime import datetime -# Create your models here. +from django.db import models +from django.contrib.auth import get_user_model +from django.dispatch import receiver + +User = get_user_model() class AbstractMode(models.Model): @@ -20,3 +25,94 @@ class Code(AbstractMode): class Meta: verbose_name = '字典' verbose_name_plural = verbose_name + + +class TimeAbstract(models.Model): + add_time = models.DateTimeField(auto_now_add=True, verbose_name="添加时间") + modify_time = models.DateTimeField(auto_now=True, verbose_name="更新时间") + + class Meta: + abstract = True + + +class ConnectionAbstract(models.Model): + auth_method_choices = ( + ('private_key', '密钥认证'), + ('password', '密码认证') + ) + hostname = models.CharField(max_length=50, verbose_name='设备地址(IP或域名)') + port = models.IntegerField(default=22, verbose_name='SSH端口') + username = models.CharField(max_length=15, blank=True, default='', verbose_name='SSH用户名') + password = models.CharField(max_length=80, blank=True, default='', verbose_name='SSH密码') + private_key = models.CharField(max_length=100, blank=True, default='', verbose_name='密钥路径') + auth_type = models.CharField(max_length=30, choices=auth_method_choices, default='') + status = models.CharField(max_length=10, blank=True, default='') + + class Meta: + abstract = True + + +class DeviceAbstract(models.Model): + sys_hostname = models.CharField(max_length=50, blank=True, default='', verbose_name='主机名') + mac_address = models.CharField(max_length=50, blank=True, default='', verbose_name='MAC地址') + sn_number = models.CharField(max_length=50, blank=True, default='', verbose_name='SN号码') + os_type = models.CharField(max_length=50, blank=True, default='', verbose_name='系统类型') + device_type = models.CharField(max_length=50, blank=True, default='', verbose_name='设备类型') + + class Meta: + abstract = True + + +class DeviceScanInfo(ConnectionAbstract, DeviceAbstract, TimeAbstract): + error_message = models.CharField(max_length=80, blank=True, default='', verbose_name='错误信息') + + class Meta: + verbose_name = '扫描信息' + verbose_name_plural = verbose_name + + +class ConnectionInfo(ConnectionAbstract, TimeAbstract): + + class Meta: + verbose_name = 'SSH连接信息' + verbose_name_plural = verbose_name + + +class Cabinet(models.Model): + number = models.CharField(max_length=50, verbose_name='机柜编号') + position = models.CharField(max_length=80, verbose_name='机柜位置') + desc = models.TextField(blank=True, default='', verbose_name='备注信息') + + class Meta: + verbose_name = '机柜信息' + verbose_name_plural = verbose_name + + +class DeviceInfo(AbstractMode, DeviceAbstract, TimeAbstract): + hostname = models.CharField(max_length=50, verbose_name='设备地址(IP或域名)') + network_type = models.IntegerField(blank=True, null=True, verbose_name='网络类型') + service_type = models.IntegerField(blank=True, null=True, verbose_name='服务类型') + operation_type = models.IntegerField(blank=True, null=True, verbose_name='业务类型') + leader = models.IntegerField(blank=True, null=True, verbose_name='责任人') + dev_cabinet = models.IntegerField(blank=True, null=True, verbose_name='机柜信息') + dev_connection = models.IntegerField(blank=True, null=True, verbose_name='连接信息') + buyDate = models.DateField(default=datetime.now, verbose_name="购买日期") + warrantyDate = models.DateField(default=datetime.now, verbose_name="到保日期") + desc = models.TextField(blank=True, default='', verbose_name='备注信息') + + class Meta: + verbose_name = '设备信息' + verbose_name_plural = verbose_name + + +class DeviceFile(TimeAbstract): + device = models.ForeignKey('DeviceInfo', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='设备') + file_content = models.FileField(upload_to="asset_file/%Y/%m", null=True, blank=True, verbose_name="资产文件") + upload_user = models.CharField(max_length=20, verbose_name="上传人") + + +@receiver(models.signals.post_delete, sender=DeviceFile) +def auto_delete_file(sender, instance, **kwargs): + if instance.file_content: + if os.path.isfile(instance.file_content.path): + os.remove(instance.file_content.path) diff --git a/apps/cmdb/views_code.py b/apps/cmdb/views_code.py index af63d20..ca45aa4 100644 --- a/apps/cmdb/views_code.py +++ b/apps/cmdb/views_code.py @@ -15,8 +15,8 @@ class CodeView(LoginRequiredMixin, BreadcrumbMixin, TemplateView): template_name = 'cmdb/code.html' def get_context_data(self): - context = dict(code_parent=Code.objects.filter(parent=None)) - return context + self.kwargs['code_parent'] = Code.objects.filter(parent=None) + return super().get_context_data(**self.kwargs) class CodeCreateView(SandboxCreateView): From 371b1ebbe363bacb74bbbc5d32fbfef4289f792b Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Fri, 4 Jan 2019 19:54:50 +0800 Subject: [PATCH 09/13] signals --- apps/cmdb/apps.py | 2 +- apps/system/__init__.py | 1 + apps/system/apps.py | 8 ++++++- apps/system/signals.py | 50 +++++++++++++++++++++++++++++++++++++++ apps/system/views_user.py | 1 + 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 apps/system/signals.py diff --git a/apps/cmdb/apps.py b/apps/cmdb/apps.py index 9e8f468..f555ae7 100644 --- a/apps/cmdb/apps.py +++ b/apps/cmdb/apps.py @@ -2,4 +2,4 @@ from django.apps import AppConfig class CmdbConfig(AppConfig): - name = 'cmdb' + name = 'cmdb' \ No newline at end of file diff --git a/apps/system/__init__.py b/apps/system/__init__.py index e69de29..ff8389f 100644 --- a/apps/system/__init__.py +++ b/apps/system/__init__.py @@ -0,0 +1 @@ +default_app_config = 'system.apps.SystemConfig' diff --git a/apps/system/apps.py b/apps/system/apps.py index 19fc95d..22886ad 100644 --- a/apps/system/apps.py +++ b/apps/system/apps.py @@ -2,4 +2,10 @@ from django.apps import AppConfig class SystemConfig(AppConfig): - name = 'apps.system' + name = 'system' + + def ready(self): + from .signals import create_menu + from .signals import user_logged_in_handler + from .signals import user_logged_out_handler + from .signals import user_login_failed_handler diff --git a/apps/system/signals.py b/apps/system/signals.py new file mode 100644 index 0000000..a2ea99f --- /dev/null +++ b/apps/system/signals.py @@ -0,0 +1,50 @@ +import logging + +from django.dispatch import receiver +from django.db.models.signals import post_save +from django.contrib.auth.signals import user_logged_in, user_logged_out, user_login_failed + +from .models import Role, Menu + +error_logger = logging.getLogger('sandbox_error') +info_logger = logging.getLogger('sandbox_info') + + +@receiver(post_save, sender=Menu) +def create_menu(sender, instance, **kwargs): + queryset = Role.objects.filter(id=1) + try: + admin_role = queryset.get() + admin_role.permissions.add(instance) + except queryset.model.DoesNotExist as e: + error_logger.error(e) + + +@receiver(user_logged_in) +def user_logged_in_handler(sender, request, user, **kwargs): + ip = request.META.get('REMOTE_ADDR') + msg = 'login user: {user}, remote ip: {ip}, action: login, status: successed'.format( + user=user.username, + ip=ip, + ) + info_logger.info(msg) + + +@receiver(user_logged_out) +def user_logged_out_handler(sender, request, user, **kwargs): + ip = request.META.get('REMOTE_ADDR') + msg = 'login user: {user}, remote ip: {ip}, action: logout, status: successed'.format( + user=user.username, + ip=ip, + ) + info_logger.info(msg) + + +@receiver(user_login_failed) +def user_login_failed_handler(sender, credentials, request, **kwargs): + msg = 'logout failed for: {credentials}'.format( + credentials=credentials, + ) + + info_logger.info(msg) + diff --git a/apps/system/views_user.py b/apps/system/views_user.py index 2066631..30274e2 100644 --- a/apps/system/views_user.py +++ b/apps/system/views_user.py @@ -40,6 +40,7 @@ class LoginView(View): redirect_to = request.GET.get('next', '/') login_form = LoginForm(request.POST) ret = dict(login_form=login_form) + print(request.META.get('REMOTE_ADDR')) if login_form.is_valid(): user_name = request.POST['username'] pass_word = request.POST['password'] From 9c204933e810dcb89c8fde51436952832d8b2b52 Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Fri, 11 Jan 2019 17:53:32 +0800 Subject: [PATCH 10/13] scan&login execution --- apps/cmdb/__init__.py | 1 + apps/cmdb/apps.py | 5 +- apps/cmdb/models.py | 20 +++--- apps/cmdb/signals.py | 13 ++++ apps/utils/sandbox_utils.py | 135 ++++++++++++++++++++++++++++++++++++ requirements/pro.txt | 2 +- sandboxMP/settings.py | 2 + 7 files changed, 168 insertions(+), 10 deletions(-) create mode 100644 apps/cmdb/signals.py diff --git a/apps/cmdb/__init__.py b/apps/cmdb/__init__.py index e69de29..4490fa6 100644 --- a/apps/cmdb/__init__.py +++ b/apps/cmdb/__init__.py @@ -0,0 +1 @@ +default_app_config = 'cmdb.apps.CmdbConfig' \ No newline at end of file diff --git a/apps/cmdb/apps.py b/apps/cmdb/apps.py index f555ae7..a9135fa 100644 --- a/apps/cmdb/apps.py +++ b/apps/cmdb/apps.py @@ -2,4 +2,7 @@ from django.apps import AppConfig class CmdbConfig(AppConfig): - name = 'cmdb' \ No newline at end of file + name = 'cmdb' + + def ready(self): + from .signals import auto_delete_file \ No newline at end of file diff --git a/apps/cmdb/models.py b/apps/cmdb/models.py index 6927c8d..1151a50 100644 --- a/apps/cmdb/models.py +++ b/apps/cmdb/models.py @@ -1,9 +1,9 @@ -import os from datetime import datetime from django.db import models from django.contrib.auth import get_user_model -from django.dispatch import receiver + +from simple_history.models import HistoricalRecords User = get_user_model() @@ -99,20 +99,24 @@ class DeviceInfo(AbstractMode, DeviceAbstract, TimeAbstract): buyDate = models.DateField(default=datetime.now, verbose_name="购买日期") warrantyDate = models.DateField(default=datetime.now, verbose_name="到保日期") desc = models.TextField(blank=True, default='', verbose_name='备注信息') + changed_by = models.ForeignKey(User, null=True, blank=True, on_delete=models.SET_NULL) + history = HistoricalRecords(excluded_fields=['add_time', 'modify_time']) class Meta: verbose_name = '设备信息' verbose_name_plural = verbose_name + @property + def _history_user(self): + return self.changed_by + + @_history_user.setter + def _history_user(self, value): + self.changed_by = value + class DeviceFile(TimeAbstract): device = models.ForeignKey('DeviceInfo', blank=True, null=True, on_delete=models.SET_NULL, verbose_name='设备') file_content = models.FileField(upload_to="asset_file/%Y/%m", null=True, blank=True, verbose_name="资产文件") upload_user = models.CharField(max_length=20, verbose_name="上传人") - -@receiver(models.signals.post_delete, sender=DeviceFile) -def auto_delete_file(sender, instance, **kwargs): - if instance.file_content: - if os.path.isfile(instance.file_content.path): - os.remove(instance.file_content.path) diff --git a/apps/cmdb/signals.py b/apps/cmdb/signals.py new file mode 100644 index 0000000..d66e53a --- /dev/null +++ b/apps/cmdb/signals.py @@ -0,0 +1,13 @@ +import os + +from django.dispatch import receiver +from django.db.models.signals import post_delete + +from .models import DeviceFile + + +@receiver(post_delete, sender=DeviceFile) +def auto_delete_file(sender, instance, **kwargs): + if instance.file_content: + if os.path.isfile(instance.file_content.path): + os.remove(instance.file_content.path) \ No newline at end of file diff --git a/apps/utils/sandbox_utils.py b/apps/utils/sandbox_utils.py index 44c3e4b..1afcf92 100644 --- a/apps/utils/sandbox_utils.py +++ b/apps/utils/sandbox_utils.py @@ -8,6 +8,8 @@ from django.conf import settings import yaml import logging +import nmap +import paramiko os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sandboxMP.settings') error_logger = logging.getLogger('sandbox_error') @@ -72,3 +74,136 @@ class ConfigFileMixin: """ key = ['hosts', 'net_address'] return self.get_conf_content(*key) + + +class SandboxScan(ConfigFileMixin): + + def basic_scan(self): + """ + Use ICMP discovery online hosts and return online hosts. + """ + hosts = self.get_net_address() + nm = nmap.PortScanner() + nm.scan(hosts=hosts, arguments='-n -sP -PE') + online_hosts = nm.all_hosts() + return online_hosts + + def os_scan(self): + """ + Get the system type by nmap scan and return hosts list with os type. + """ + hosts = self.get_net_address() + nm = nmap.PortScanner() + nm.scan(hosts=hosts, arguments='-n sS -O') + online_hosts = [] + for host in nm.all_hosts(): + try: + os_type = nm[host]['osmatch'][0]['osclass'][0]['osfamily'] + except Exception: + os_type = 'unknown' + host_dict = {'host': host, 'os': os_type} + online_hosts.append(host_dict) + return online_hosts + + def get_net_address(self): + """ + Return the hosts that will be used to scan. + Subclasses can override this to return any hosts.` + """ + hosts_list = super().get_net_address() + hosts = ' '.join(str(i) for i in hosts_list) + return hosts + + +class LoginExecution(ConfigFileMixin): + + def login_execution(self, auth_type='password', **kwargs): + """ + Support two authentication modes: password or private_key, and auth_type default is password. + """ + ssh = paramiko.SSHClient() + ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy()) + try: + if auth_type == 'password': + ssh.connect( + kwargs['hostname'], + kwargs['port'], + kwargs['username'], + kwargs['password'], + timeout=3, + ) + kwargs['auth_type'] = 'password' + elif auth_type == 'private_key': + kwargs['auth_type'] = 'private_key' + private_key = paramiko.RSAKey.from_private_key_file(kwargs['private_key']) + ssh.connect( + kwargs['hostname'], + kwargs['port'], + kwargs['username'], + private_key, + timeout=3, + ) + kwargs['status'] = 'succeed' + kwargs['error_message'] = '' + commands = self.get_commands() + for key, value in commands.items(): + stdin, stdout, stderr = ssh.exec_command(value, timeout=5) + result = str(stdout.read()).strip('b').strip("'").strip('\\n') + kwargs[key] = result + except Exception as e: + msg = '%(exc)s hostname %(hostname)s' % { + 'exc': e, + 'hostname': kwargs['hostname'] + } + error_logger.error(msg) + kwargs['status'] = 'failed' + kwargs['error_message'] = str(e) + return kwargs + + def password_login_execution(self, **kwargs): + """ + Login to the remote system with a password. + Kwargs is a dict containing hostname, port, username and password. + Example: kwargs = {'hostname': '172.16.3.101', 'port': 22, 'username': 'root', 'password': 'paw123'} + """ + return self.login_execution(**kwargs) + + def private_key_login_execution(self, **kwargs): + """ + Login to the remote system with a private_key. + Kwargs is a dict containing hostname, port, username and private key. + Example:kwargs = {'hostname': '172.16.3.101', 'port': 22, 'username': 'root', 'private_key': '/root/.ssh/id_rsa'} + """ + return self.login_execution(auth_type='private_key', **kwargs) + + def get_auth_type(self): + key = ['hosts', 'auth_type'] + return self.get_conf_content(*key) + + def get_ssh_username(self): + key = ['hosts', 'ssh_username'] + return self.get_conf_content(*key) + + def get_ssh_port(self): + key = ['hosts', 'ssh_port'] + return self.get_conf_content(*key) + + def get_ssh_password(self): + key = ['hosts', 'ssh_password'] + return self.get_conf_content(*key) + + def get_ssh_private_key(self): + key = ['hosts', 'ssh_private_key'] + return self.get_conf_content(*key) + + def get_email(self): + key = ['hosts', 'email'] + return self.get_conf_content(*key) + + def get_send_email(self): + key = ['hosts', 'send_email'] + return self.get_conf_content(*key) + + def get_scan_type(self): + key = ['hosts', 'scan_type'] + return self.get_conf_content(*key) \ No newline at end of file diff --git a/requirements/pro.txt b/requirements/pro.txt index fff54da..bc887e7 100644 --- a/requirements/pro.txt +++ b/requirements/pro.txt @@ -8,7 +8,7 @@ python-nmap==0.6.1 redis==3.0.1 pymongo==3.7.1 paramiko==2.4.2 -pycrypto==2.6.1 +django-simple-history==2.6.0 celery==4.2.1 celery-once==2.0.0 flower==0.9.2 diff --git a/sandboxMP/settings.py b/sandboxMP/settings.py index 8eecb8b..c97f46c 100644 --- a/sandboxMP/settings.py +++ b/sandboxMP/settings.py @@ -39,6 +39,7 @@ INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'simple_history', 'system', 'cmdb', ] @@ -53,6 +54,7 @@ MIDDLEWARE = [ 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'apps.system.middleware.MenuCollection', 'apps.system.middleware.RbacMiddleware', + 'simple_history.middleware.HistoryRequestMiddleware', ] ROOT_URLCONF = 'sandboxMP.urls' From eb10ffe9bede0ba6e0d7736636df53e86694e85e Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Mon, 14 Jan 2019 02:12:08 +0800 Subject: [PATCH 11/13] device scan --- apps/cmdb/urls.py | 6 + apps/cmdb/views_scan.py | 82 ++++++- templates/cmdb/device_scan.html | 298 +++++++++++++++++++++++++ templates/cmdb/device_scan_detail.html | 113 ++++++++++ 4 files changed, 496 insertions(+), 3 deletions(-) create mode 100644 templates/cmdb/device_scan.html create mode 100644 templates/cmdb/device_scan_detail.html diff --git a/apps/cmdb/urls.py b/apps/cmdb/urls.py index c64f485..f39f734 100644 --- a/apps/cmdb/urls.py +++ b/apps/cmdb/urls.py @@ -14,4 +14,10 @@ urlpatterns = [ path('portal/code/delete/', views_code.CodeDeleteView.as_view(), name='portal-code-delete'), path('portal/scan_config/', views_scan.ScanConfigView.as_view(), name='portal-scan_config'), + path('portal/device_scan/', views_scan.DeviceScanView.as_view(), name='portal-device_scan'), + path('portal/device_scan/list/', views_scan.DeviceScanListView.as_view(), name='portal-device_scan-list'), + path('portal/device_scan/detail/', views_scan.DeviceScanDetailView.as_view(), name='portal-device_scan-detail'), + path('portal/device_scan/delete/', views_scan.DeviceScanDeleteView.as_view(), name='portal-device_scan-delete'), + path('portal/device_scan/exec/', views_scan.DeviceScanExecView.as_view(), name='portal-device_scan-exec'), + ] diff --git a/apps/cmdb/views_scan.py b/apps/cmdb/views_scan.py index bffd7c5..205ccf2 100644 --- a/apps/cmdb/views_scan.py +++ b/apps/cmdb/views_scan.py @@ -6,15 +6,16 @@ import ast import logging from ruamel import yaml -from django.views.generic import View +from django.views.generic import View, TemplateView from django.http import JsonResponse -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from system.mixin import LoginRequiredMixin -from custom import BreadcrumbMixin +from custom import BreadcrumbMixin, SandboxListView, SandboxDeleteView from utils.sandbox_utils import ConfigFileMixin from system.models import Menu +from .models import DeviceScanInfo error_logger = logging.getLogger('sandbox_error') @@ -52,3 +53,78 @@ class ScanConfigView(LoginRequiredMixin, BreadcrumbMixin, ConfigFileMixin, View) error_logger.error(e) return JsonResponse(ret) + + +class DeviceScanView(LoginRequiredMixin, BreadcrumbMixin, TemplateView): + template_name = 'cmdb/device_scan.html' + + +class DeviceScanListView(SandboxListView): + model = DeviceScanInfo + fields = ['id', 'sys_hostname', 'hostname', 'mac_address', 'auth_type', 'status', 'os_type', 'device_type'] + + +class DeviceScanDetailView(LoginRequiredMixin, View): + + def get(self, request): + ret = Menu.get_menu_by_request_url(request.path_info) + if 'id' in request.GET and request.GET['id']: + device = get_object_or_404(DeviceScanInfo, pk=int(request.GET['id'])) + ret['device'] = device + return render(request, 'cmdb/device_scan_detail.html', ret) + + +class DeviceScanDeleteView(SandboxDeleteView): + model = DeviceScanInfo + + +class DeviceScanExecView(LoginRequiredMixin, View): + + def get(self, request): + import time + from utils.sandbox_utils import SandboxScan, LoginExecution + info_logger = logging.getLogger('sandbox_info') + ret = dict(result=False) + scan = SandboxScan() + execution = LoginExecution() + scan_type = execution.get_scan_type() + auth_type = execution.get_auth_type() + start_time = time.time() + if scan_type == 'basic_scan': + hosts = scan.basic_scan() + for host in hosts: + DeviceScanInfo.objects.update_or_create( + hostname=host, + ) + else: + hosts = scan.os_scan() + login_hosts = [host for host in hosts if host['os'] in ['Linux', 'embedded']] + nologin_hosts = [host for host in hosts if host not in login_hosts] + for host in nologin_hosts: + DeviceScanInfo.objects.update_or_create( + hostname=host['host'], + defaults={ + 'os_type': host['os'] + } + ) + for host in login_hosts: + kwargs = { + 'hostname': host['host'], + 'username': execution.get_ssh_username(), + 'port': execution.get_ssh_port(), + 'password': execution.get_ssh_password(), + 'private_key': execution.get_ssh_private_key() + } + defaults = execution.login_execution(auth_type=auth_type, **kwargs) + DeviceScanInfo.objects.update_or_create( + hostname=host['host'], + defaults=defaults + ) + end_time = time.time() + msg = 'Scan task has been completed, execution time: %(time)s, %(num)s hosts are up.' % { + 'time': end_time - start_time, + 'num': len(hosts) + } + info_logger.info(msg) + ret['result'] = True + return JsonResponse(ret) \ No newline at end of file diff --git a/templates/cmdb/device_scan.html b/templates/cmdb/device_scan.html new file mode 100644 index 0000000..da37af9 --- /dev/null +++ b/templates/cmdb/device_scan.html @@ -0,0 +1,298 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %} + + + +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +
      + +
      +
       
      +
      + +
      +
       
      +
      + +
      +
       
      +
      + +
      +
      + +
      + + + + + + + + + + + + + + + + + +
      ID主机名IP地址MAC地址认证类型登陆状态系统类型设备类型操作
      +
      + 点击【执行入库】可将扫描结果中,登陆状态为:成功(succeed)的设备数据导入正式设备管理数据库。 +
      +
      +
      + +
      + + + +{% endblock %} + + +{% block javascripts %} + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/cmdb/device_scan_detail.html b/templates/cmdb/device_scan_detail.html new file mode 100644 index 0000000..3c73a77 --- /dev/null +++ b/templates/cmdb/device_scan_detail.html @@ -0,0 +1,113 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %} + +{% endblock %} + +{% block content %} + + + +
      +
      +
      +
      +
      +

      设备详情

      + +
      + +
      +
      +
      +
      + +
      +
      +
      + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
      主机名{{ device.sys_hostname }}SN编号{{ device.sn_number }}
      SSH用户名{{ device.username }}SSH端口{{ device.port }}
      认证类型{{ device.auth_type }}登陆状态{{ device.status }}
      IP地址{{ device.hostname }}MAC地址{{ device.mac_address }}
      系统类型{{ device.os_type }}设备类型{{ device.device_type }}
      入库时间{{ device.add_time }}变更时间{{ device.modify_time }}
      错误信息{{ device.error_message }}
      +
      +
      + + +
      + +
      +
      + + + +
      + + + +{% endblock %} + +{% block javascripts %} + + + + + +{% endblock %} \ No newline at end of file From cea6fa7cbaed43e4764c397b95203c330c6cb39a Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Fri, 18 Jan 2019 16:50:05 +0800 Subject: [PATCH 12/13] celery&flower&supervisor --- apps/cmdb/tasks.py | 56 +++++++++++++++++++++++++++++++++ apps/cmdb/views_scan.py | 54 +++++-------------------------- sandboxMP/__init__.py | 5 +++ sandboxMP/celery.py | 38 ++++++++++++++++++++++ sandboxMP/settings.py | 2 ++ templates/cmdb/device_scan.html | 8 +++-- 6 files changed, 115 insertions(+), 48 deletions(-) create mode 100644 apps/cmdb/tasks.py create mode 100644 sandboxMP/celery.py diff --git a/apps/cmdb/tasks.py b/apps/cmdb/tasks.py new file mode 100644 index 0000000..febdee3 --- /dev/null +++ b/apps/cmdb/tasks.py @@ -0,0 +1,56 @@ +import time +import logging + +from celery import shared_task +from celery_once import QueueOnce + +from utils.sandbox_utils import SandboxScan, LoginExecution +from .models import DeviceScanInfo + +info_logger = logging.getLogger('sandbox_info') + + +@shared_task(base=QueueOnce) +def scan_execution(): + scan = SandboxScan() + execution = LoginExecution() + scan_type = execution.get_scan_type() + auth_type = execution.get_auth_type() + start_time = time.time() + if scan_type == 'basic_scan': + hosts = scan.basic_scan() + for host in hosts: + DeviceScanInfo.objects.update_or_create( + hostname=host, + ) + else: + hosts = scan.os_scan() + login_hosts = [host for host in hosts if host['os'] in ['Linux', 'embedded']] + nologin_hosts = [host for host in hosts if host not in login_hosts] + for host in nologin_hosts: + DeviceScanInfo.objects.update_or_create( + hostname=host['host'], + defaults={ + 'os_type': host['os'] + } + ) + for host in login_hosts: + kwargs = { + 'hostname': host['host'], + 'username': execution.get_ssh_username(), + 'port': execution.get_ssh_port(), + 'password': execution.get_ssh_password(), + 'private_key': execution.get_ssh_private_key() + } + defaults = execution.login_execution(auth_type=auth_type, **kwargs) + DeviceScanInfo.objects.update_or_create( + hostname=host['host'], + defaults=defaults + ) + end_time = time.time() + msg = 'Scan task has been completed, execution time: %(time)s, %(num)s hosts are up.' % { + 'time': end_time - start_time, + 'num': len(hosts) + } + info_logger.info(msg) + return msg \ No newline at end of file diff --git a/apps/cmdb/views_scan.py b/apps/cmdb/views_scan.py index 205ccf2..2791bb8 100644 --- a/apps/cmdb/views_scan.py +++ b/apps/cmdb/views_scan.py @@ -10,12 +10,14 @@ from django.views.generic import View, TemplateView from django.http import JsonResponse from django.shortcuts import render, get_object_or_404 +from celery_once import AlreadyQueued from system.mixin import LoginRequiredMixin from custom import BreadcrumbMixin, SandboxListView, SandboxDeleteView from utils.sandbox_utils import ConfigFileMixin from system.models import Menu from .models import DeviceScanInfo +from .tasks import scan_execution error_logger = logging.getLogger('sandbox_error') @@ -81,50 +83,10 @@ class DeviceScanDeleteView(SandboxDeleteView): class DeviceScanExecView(LoginRequiredMixin, View): def get(self, request): - import time - from utils.sandbox_utils import SandboxScan, LoginExecution - info_logger = logging.getLogger('sandbox_info') - ret = dict(result=False) - scan = SandboxScan() - execution = LoginExecution() - scan_type = execution.get_scan_type() - auth_type = execution.get_auth_type() - start_time = time.time() - if scan_type == 'basic_scan': - hosts = scan.basic_scan() - for host in hosts: - DeviceScanInfo.objects.update_or_create( - hostname=host, - ) - else: - hosts = scan.os_scan() - login_hosts = [host for host in hosts if host['os'] in ['Linux', 'embedded']] - nologin_hosts = [host for host in hosts if host not in login_hosts] - for host in nologin_hosts: - DeviceScanInfo.objects.update_or_create( - hostname=host['host'], - defaults={ - 'os_type': host['os'] - } - ) - for host in login_hosts: - kwargs = { - 'hostname': host['host'], - 'username': execution.get_ssh_username(), - 'port': execution.get_ssh_port(), - 'password': execution.get_ssh_password(), - 'private_key': execution.get_ssh_private_key() - } - defaults = execution.login_execution(auth_type=auth_type, **kwargs) - DeviceScanInfo.objects.update_or_create( - hostname=host['host'], - defaults=defaults - ) - end_time = time.time() - msg = 'Scan task has been completed, execution time: %(time)s, %(num)s hosts are up.' % { - 'time': end_time - start_time, - 'num': len(hosts) - } - info_logger.info(msg) - ret['result'] = True + ret = dict(status='fail') + try: + scan_execution.delay() + ret['status'] = 'success' + except AlreadyQueued: + ret['status'] = 'already_queued' return JsonResponse(ret) \ No newline at end of file diff --git a/sandboxMP/__init__.py b/sandboxMP/__init__.py index e69de29..297b1b5 100644 --- a/sandboxMP/__init__.py +++ b/sandboxMP/__init__.py @@ -0,0 +1,5 @@ +from __future__ import absolute_import, unicode_literals + +from .celery import app as celery_app + +__all__ = ('celery_app') \ No newline at end of file diff --git a/sandboxMP/celery.py b/sandboxMP/celery.py new file mode 100644 index 0000000..a5a91e6 --- /dev/null +++ b/sandboxMP/celery.py @@ -0,0 +1,38 @@ +from __future__ import absolute_import, unicode_literals +import os +from celery import Celery + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'sandboxMP.settings') + +app = Celery('sandbox') + +app.config_from_object('django.conf:settings') + +app.autodiscover_tasks() + +BROKER_URL = 'redis://localhost:6379/0' + +CELERY_RESULT_BACKEND = 'redis://localhost:6379/1' + +CELERY_TIMEZONE = 'Asia/Shanghai' + +CELERY_ENABLE_UTC = False + +CELERYD_FORCE_EXECV = True + +CELERYD_CONCURRENCY = 5 + +CELERY_ACKS_LATE = True + +CELERYD_MAX_TASKS_PER_CHILD = 100 + +CELERYD_TASK_TIME_LIMIT = 60 * 5 + + +app.conf.ONCE = { + 'backend': 'celery_once.backends.Redis', + 'settings': { + 'url': 'redis://localhost:6379/2', + 'default_timeout': 60 * 5 + } +} \ No newline at end of file diff --git a/sandboxMP/settings.py b/sandboxMP/settings.py index c97f46c..741f587 100644 --- a/sandboxMP/settings.py +++ b/sandboxMP/settings.py @@ -13,6 +13,8 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ import os import sys +from .celery import * + # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) diff --git a/templates/cmdb/device_scan.html b/templates/cmdb/device_scan.html index da37af9..b753b05 100644 --- a/templates/cmdb/device_scan.html +++ b/templates/cmdb/device_scan.html @@ -279,8 +279,12 @@ }, success: function (msg) { layer.closeAll('loading'); - if (msg.result) { - layer.alert('扫描已完成', {icon: 1}); + if (msg.status == 'success') { + layer.alert('扫描任务已下发', {icon: 1}); + oDataTable.ajax.reload(); + } + else if (msg.status == 'already_queued') { + layer.alert('当前已有扫描任务正在执行', {icon: 4}); oDataTable.ajax.reload(); } else { From a36f8d74f6a50e117bff20ac870217675275b14b Mon Sep 17 00:00:00 2001 From: RobbieHan Date: Fri, 25 Jan 2019 14:34:42 +0800 Subject: [PATCH 13/13] cabinet --- apps/cmdb/urls.py | 9 +- apps/cmdb/views_eam.py | 38 +++++ apps/cmdb/views_scan.py | 30 +++- apps/custom.py | 4 +- templates/cmdb/cabinet.html | 276 +++++++++++++++++++++++++++++++ templates/cmdb/cabinet_form.html | 99 +++++++++++ templates/cmdb/device_scan.html | 27 +++ 7 files changed, 478 insertions(+), 5 deletions(-) create mode 100644 apps/cmdb/views_eam.py create mode 100644 templates/cmdb/cabinet.html create mode 100644 templates/cmdb/cabinet_form.html diff --git a/apps/cmdb/urls.py b/apps/cmdb/urls.py index f39f734..f58f0a7 100644 --- a/apps/cmdb/urls.py +++ b/apps/cmdb/urls.py @@ -1,7 +1,7 @@ from django.urls import path from .views import CmdbView -from . import views_code, views_scan +from . import views_code, views_scan, views_eam app_name = 'cmdb' @@ -19,5 +19,12 @@ urlpatterns = [ path('portal/device_scan/detail/', views_scan.DeviceScanDetailView.as_view(), name='portal-device_scan-detail'), path('portal/device_scan/delete/', views_scan.DeviceScanDeleteView.as_view(), name='portal-device_scan-delete'), path('portal/device_scan/exec/', views_scan.DeviceScanExecView.as_view(), name='portal-device_scan-exec'), + path('portal/device_scan/inbound/', views_scan.DeviceScanInboundView.as_view(), name='portal-device_scan-inbound'), + + path('eam/cabinet/', views_eam.CabinetView.as_view(), name='eam-cabinet'), + path('eam/cabinet/create/', views_eam.CabinetCreateView.as_view(), name='eam-cabinet-create'), + path('eam/cabinet/update/', views_eam.CabinetUpdateView.as_view(), name='eam-cabinet-update'), + path('eam/cabinet/list/', views_eam.CabinetListView.as_view(), name='eam-cabinet-list'), + path('eam/cabinet/delete/', views_eam.CabinetDeleteView.as_view(), name='eam-cabinet-delete'), ] diff --git a/apps/cmdb/views_eam.py b/apps/cmdb/views_eam.py new file mode 100644 index 0000000..68d9018 --- /dev/null +++ b/apps/cmdb/views_eam.py @@ -0,0 +1,38 @@ +from django.views.generic import TemplateView + +from system.mixin import LoginRequiredMixin +from custom import (BreadcrumbMixin, SandboxDeleteView, + SandboxListView, SandboxUpdateView, SandboxCreateView) +from .models import Cabinet + + +class CabinetView(LoginRequiredMixin, BreadcrumbMixin, TemplateView): + template_name = 'cmdb/cabinet.html' + + +class CabinetCreateView(SandboxCreateView): + model = Cabinet + fields = '__all__' + + +class CabinetUpdateView(SandboxUpdateView): + model = Cabinet + fields = '__all__' + + +class CabinetListView(SandboxListView): + model = Cabinet + fields = ['id', 'number', 'position', 'desc'] + + def get_filters(self): + data = self.request.GET + filters = {} + if 'number' in data and data['number']: + filters['number__icontains'] = data['number'] + if 'position' in data and data['position']: + filters['position__icontains'] = data['position'] + return filters + + +class CabinetDeleteView(SandboxDeleteView): + model = Cabinet diff --git a/apps/cmdb/views_scan.py b/apps/cmdb/views_scan.py index 2791bb8..543dd06 100644 --- a/apps/cmdb/views_scan.py +++ b/apps/cmdb/views_scan.py @@ -16,7 +16,8 @@ from system.mixin import LoginRequiredMixin from custom import BreadcrumbMixin, SandboxListView, SandboxDeleteView from utils.sandbox_utils import ConfigFileMixin from system.models import Menu -from .models import DeviceScanInfo +from .models import (DeviceScanInfo, ConnectionInfo, DeviceInfo, + ConnectionAbstract, DeviceAbstract) from .tasks import scan_execution error_logger = logging.getLogger('sandbox_error') @@ -89,4 +90,29 @@ class DeviceScanExecView(LoginRequiredMixin, View): ret['status'] = 'success' except AlreadyQueued: ret['status'] = 'already_queued' - return JsonResponse(ret) \ No newline at end of file + return JsonResponse(ret) + + +class DeviceScanInboundView(LoginRequiredMixin, View): + def post(self, request): + ret = dict(result=False) + login_succeed = list(DeviceScanInfo.objects.filter(status='succeed').values()) + connection_fields = [field.name for field in ConnectionAbstract._meta.fields if field.name is not 'id'] + device_fields = [field.name for field in DeviceAbstract._meta.fields if field.name is not 'id'] + device_fields.append('hostname') + for host in login_succeed: + connection_defaults = {key: host[key] for key in host.keys() & connection_fields} + device_defaults = {key: host[key] for key in host.keys() & device_fields} + connection_info, _ = ConnectionInfo.objects.update_or_create( + hostname=host['hostname'], + defaults=connection_defaults + ) + connection_id = int(getattr(connection_info, 'id')) + device_defaults['dev_connection'] = connection_id + device_defaults['changed_by_id'] = request.user.id + DeviceInfo.objects.update_or_create( + hostname=host['hostname'], + defaults=device_defaults + ) + ret['result'] = True + return JsonResponse(ret) diff --git a/apps/custom.py b/apps/custom.py index 0315e2c..4490fd5 100644 --- a/apps/custom.py +++ b/apps/custom.py @@ -84,9 +84,9 @@ class SandboxMultipleObjectMixin: filters = self.get_filters() fields = self.get_fields() if filters: - queryset = queryset.filter(**self.filters) + queryset = queryset.filter(**filters) if fields: - queryset = queryset.values(*self.fields) + queryset = queryset.values(*fields) record_filter_count = queryset.count() diff --git a/templates/cmdb/cabinet.html b/templates/cmdb/cabinet.html new file mode 100644 index 0000000..219a504 --- /dev/null +++ b/templates/cmdb/cabinet.html @@ -0,0 +1,276 @@ +{% extends "base-left.html" %} +{% load staticfiles %} + +{% block css %} + + + +{% endblock %} + +{% block content %} + + +
      +
      +
      +
      +
      + +
      +
       
      +
      + + +
      +
       
      +
      + +
      +
      +
      +
      + + +
      +
      + + +
      + +
      +
      +
      + +
      + + + + + + + + + + + + + +
      ID机柜编号机柜位置备注信息操作
      +

      +
      +
      +
      + +
      + + + +{% endblock %} + + +{% block javascripts %} + + + + + + + +{% endblock %} \ No newline at end of file diff --git a/templates/cmdb/cabinet_form.html b/templates/cmdb/cabinet_form.html new file mode 100644 index 0000000..6541ed9 --- /dev/null +++ b/templates/cmdb/cabinet_form.html @@ -0,0 +1,99 @@ +{% extends 'base-layer.html' %} +{% load staticfiles %} +{% block css %} + + +{% endblock %} +{% block main %} +
      +
      + + {% csrf_token %} +
      +
      + +

      机柜信息

      +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      + +
      +
      + +
      +
      + + +
      +
      + +{% endblock %} + +{% block javascripts %} + + + +{% endblock %} \ No newline at end of file diff --git a/templates/cmdb/device_scan.html b/templates/cmdb/device_scan.html index b753b05..55d751d 100644 --- a/templates/cmdb/device_scan.html +++ b/templates/cmdb/device_scan.html @@ -298,5 +298,32 @@ }); } + function doInbound() { + layer.alert('确定将扫描结果导入设备管理库吗?', { + title: '提示' + , icon: 3 + , time: 0 + , btn: ['YES', 'NO'] + , yes: function (index) { + layer.close(index); + $.ajax({ + type: "POST", + url: "{% url 'cmdb:portal-device_scan-inbound' %}", + data: {csrfmiddlewaretoken: '{{ csrf_token }}'}, + cache: false, + success: function (msg) { + if (msg.result) { + layer.alert('设备已入库', {icon: 1}); + } + else { + //alert(msg.message); + layer.alert('设备入库失败', {icon: 2}); + } + return; + } + }); + } + }); + } {% endblock %} \ No newline at end of file