diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..3ab78cc
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,3 @@
+*.js linguist-language=python
+*.css linguist-language=python
+*.html linguist-language=python
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..723ef36
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+.idea
\ No newline at end of file
diff --git a/.idea/dictionaries/RobbieHan.xml b/.idea/dictionaries/RobbieHan.xml
deleted file mode 100644
index 2b2e610..0000000
--- a/.idea/dictionaries/RobbieHan.xml
+++ /dev/null
@@ -1,3 +0,0 @@
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index eb73b7d..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 1d4b809..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/sandboxMP.iml b/.idea/sandboxMP.iml
deleted file mode 100644
index 6b57a36..0000000
--- a/.idea/sandboxMP.iml
+++ /dev/null
@@ -1,32 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/apps/__pycache__/custom.cpython-36.pyc b/apps/__pycache__/custom.cpython-36.pyc
index 458ff71..65e77f7 100644
Binary files a/apps/__pycache__/custom.cpython-36.pyc and b/apps/__pycache__/custom.cpython-36.pyc differ
diff --git a/apps/cmdb/__init__.py b/apps/cmdb/__init__.py
new file mode 100644
index 0000000..4490fa6
--- /dev/null
+++ 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/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..a9135fa
--- /dev/null
+++ b/apps/cmdb/apps.py
@@ -0,0 +1,8 @@
+from django.apps import AppConfig
+
+
+class CmdbConfig(AppConfig):
+ name = 'cmdb'
+
+ def ready(self):
+ from .signals import auto_delete_file
\ No newline at end of file
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/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..1151a50
--- /dev/null
+++ b/apps/cmdb/models.py
@@ -0,0 +1,122 @@
+from datetime import datetime
+
+from django.db import models
+from django.contrib.auth import get_user_model
+
+from simple_history.models import HistoricalRecords
+
+User = get_user_model()
+
+
+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
+
+
+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='备注信息')
+ 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="上传人")
+
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/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/tests.py b/apps/cmdb/tests.py
new file mode 100644
index 0000000..f8783cd
--- /dev/null
+++ b/apps/cmdb/tests.py
@@ -0,0 +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/apps/cmdb/urls.py b/apps/cmdb/urls.py
new file mode 100644
index 0000000..f58f0a7
--- /dev/null
+++ b/apps/cmdb/urls.py
@@ -0,0 +1,30 @@
+from django.urls import path
+
+from .views import CmdbView
+from . import views_code, views_scan, views_eam
+
+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'),
+
+ 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'),
+ 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.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/apps/cmdb/views_code.py b/apps/cmdb/views_code.py
new file mode 100644
index 0000000..ca45aa4
--- /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):
+ self.kwargs['code_parent'] = Code.objects.filter(parent=None)
+ return super().get_context_data(**self.kwargs)
+
+
+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/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
new file mode 100644
index 0000000..543dd06
--- /dev/null
+++ b/apps/cmdb/views_scan.py
@@ -0,0 +1,118 @@
+# @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, 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, ConnectionInfo, DeviceInfo,
+ ConnectionAbstract, DeviceAbstract)
+from .tasks import scan_execution
+
+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)
+
+
+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):
+ ret = dict(status='fail')
+ try:
+ scan_execution.delay()
+ ret['status'] = 'success'
+ except AlreadyQueued:
+ ret['status'] = 'already_queued'
+ 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 6c2e497..4490fd5 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(**filters)
+ if fields:
+ queryset = queryset.values(*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/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/__pycache__/middleware.cpython-36.pyc b/apps/system/__pycache__/middleware.cpython-36.pyc
index 33eb240..c7ca069 100644
Binary files a/apps/system/__pycache__/middleware.cpython-36.pyc and b/apps/system/__pycache__/middleware.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/models.cpython-36.pyc b/apps/system/__pycache__/models.cpython-36.pyc
index 3a07064..a001f8a 100644
Binary files a/apps/system/__pycache__/models.cpython-36.pyc and b/apps/system/__pycache__/models.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/urls.cpython-36.pyc b/apps/system/__pycache__/urls.cpython-36.pyc
index f15392e..65e6ff1 100644
Binary files a/apps/system/__pycache__/urls.cpython-36.pyc and b/apps/system/__pycache__/urls.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views.cpython-36.pyc b/apps/system/__pycache__/views.cpython-36.pyc
index f3fd174..82175cd 100644
Binary files a/apps/system/__pycache__/views.cpython-36.pyc and b/apps/system/__pycache__/views.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_menu.cpython-36.pyc b/apps/system/__pycache__/views_menu.cpython-36.pyc
index da598de..58f85e1 100644
Binary files a/apps/system/__pycache__/views_menu.cpython-36.pyc and b/apps/system/__pycache__/views_menu.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_role.cpython-36.pyc b/apps/system/__pycache__/views_role.cpython-36.pyc
index 905c786..3a4f2e0 100644
Binary files a/apps/system/__pycache__/views_role.cpython-36.pyc and b/apps/system/__pycache__/views_role.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_structure.cpython-36.pyc b/apps/system/__pycache__/views_structure.cpython-36.pyc
index 352da33..2834d98 100644
Binary files a/apps/system/__pycache__/views_structure.cpython-36.pyc and b/apps/system/__pycache__/views_structure.cpython-36.pyc differ
diff --git a/apps/system/__pycache__/views_user.cpython-36.pyc b/apps/system/__pycache__/views_user.cpython-36.pyc
index 3603868..79dd501 100644
Binary files a/apps/system/__pycache__/views_user.cpython-36.pyc and b/apps/system/__pycache__/views_user.cpython-36.pyc differ
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/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc b/apps/system/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc
index 2f6d4f7..781ef2a 100644
Binary files a/apps/system/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc and b/apps/system/migrations/__pycache__/0002_auto_20181115_2124.cpython-36.pyc differ
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']
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..1afcf92
--- /dev/null
+++ b/apps/utils/sandbox_utils.py
@@ -0,0 +1,209 @@
+# @Time : 2018/12/29 19:22
+# @Author : RobbieHan
+# @File : sandbox_utils.py
+
+import os
+
+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')
+
+
+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)
+
+
+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/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/requirements/dev b/requirements/dev
deleted file mode 100644
index 4643bc9..0000000
--- a/requirements/dev
+++ /dev/null
@@ -1,2 +0,0 @@
-django==2.1.2
-pillow==5.3.0
\ No newline at end of file
diff --git a/requirements/dev.txt b/requirements/dev.txt
new file mode 100644
index 0000000..694772d
--- /dev/null
+++ b/requirements/dev.txt
@@ -0,0 +1 @@
+-r pro.txt
\ No newline at end of file
diff --git a/requirements/pro b/requirements/pro
deleted file mode 100644
index 4643bc9..0000000
--- a/requirements/pro
+++ /dev/null
@@ -1,2 +0,0 @@
-django==2.1.2
-pillow==5.3.0
\ No newline at end of file
diff --git a/requirements/pro.txt b/requirements/pro.txt
new file mode 100644
index 0000000..bc887e7
--- /dev/null
+++ b/requirements/pro.txt
@@ -0,0 +1,14 @@
+django==2.1.2
+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
+django-simple-history==2.6.0
+celery==4.2.1
+celery-once==2.0.0
+flower==0.9.2
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/__pycache__/settings.cpython-36.pyc b/sandboxMP/__pycache__/settings.cpython-36.pyc
index cf9d0a2..34e68f7 100644
Binary files a/sandboxMP/__pycache__/settings.cpython-36.pyc and b/sandboxMP/__pycache__/settings.cpython-36.pyc differ
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 7520930..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__)))
@@ -27,7 +29,7 @@ SECRET_KEY = 'o6ijylqj@xxpvxzybcv2khtu5zk@y56nt4ptsb4dbgmdz8t%q='
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
-ALLOWED_HOSTS = []
+ALLOWED_HOSTS = ['*']
# Application definition
@@ -39,7 +41,9 @@ INSTALLED_APPS = [
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
+ 'simple_history',
'system',
+ 'cmdb',
]
MIDDLEWARE = [
@@ -52,6 +56,7 @@ MIDDLEWARE = [
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'apps.system.middleware.MenuCollection',
'apps.system.middleware.RbacMiddleware',
+ 'simple_history.middleware.HistoryRequestMiddleware',
]
ROOT_URLCONF = 'sandboxMP.urls'
@@ -81,14 +86,24 @@ WSGI_APPLICATION = 'sandboxMP.wsgi.application'
# Database
# https://docs.djangoproject.com/en/2.1/ref/settings/#databases
+# DATABASES = {
+# 'default': {
+# 'ENGINE': 'django.db.backends.sqlite3',
+# 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+# }
+# }
+
DATABASES = {
'default': {
- 'ENGINE': 'django.db.backends.sqlite3',
- 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'),
+ 'ENGINE': 'django.db.backends.mysql',
+ 'NAME': 'sandboxMP',
+ 'HOST': '127.0.0.1',
+ 'USER': 'ddadmin',
+ 'PASSWORD': '1234@abcd.com',
+ 'PORT': '3306'
}
}
-
# Password validation
# https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators
@@ -143,10 +158,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 d3ae5f7..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),
@@ -28,6 +28,9 @@ 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')),
+
+ path('test/', TestLoggingView.as_view()),
]
@@ -35,4 +38,4 @@ if settings.DEBUG:
urlpatterns += [
re_path(r'^media/(?P.*)$', serve, {"document_root": settings.MEDIA_ROOT}),
- ]
\ No newline at end of file
+ ]
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/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 %}
+
+
+
+
+
+
+{% 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 %}
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+
+
+{% endblock %}
\ No newline at end of file
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
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 %}
+
+
+
+
+
+
+{% 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 %}
+
+
+{% 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 %}
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+
+
+{% endblock %}
\ 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..55d751d
--- /dev/null
+++ b/templates/cmdb/device_scan.html
@@ -0,0 +1,329 @@
+{% extends "base-left.html" %}
+{% load staticfiles %}
+
+{% block css %}
+
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+
+
+
+
+
点击【执行入库】可将扫描结果中,登陆状态为:成功(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
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 %}
+
+
+
+
+
+
+
+
+
+
扫描网段必填参数
+
网络扫描地址列表,列表中可以包含一个或多个网段,也可以是一个或多个地址区间,例如['192.168.100.0/24', '192.168.100.10-20']
+
登陆用户必填参数
+
系统登陆测试使用的用户名,默认root。
+
登陆密码可选参数
+
系统登陆测试使用的密码,如过认证方式为密码认证,必须填写密码信息。
+
密钥路径可选参数
+
系统登陆测试使用的密钥存放路径,如过认证方式为私钥认证,必须填写密钥路径。
+
运行命令预定义参数
+
登陆系统后运行的基本命令,用来获取系统基本信息,当扫描方式为加强扫描时,才会执行运行命令,运行命令为预定义,禁止修改。
+
认证方式可选参数
+
系统登陆认证方式,包括密码认证和私钥认证两种方式,当扫描方式为加强扫描时,需要设定认证方式。
+
扫描方式可选参数
+
系统扫描方式,包括基本扫描和加强扫描,当设置为加强扫描时会登陆系统执行运行命令获取系统信息。
+
收件邮箱可选参数
+
用于接收扫描结果邮件,可以通过发送邮件选项设置是否发送通知邮件。
+
+
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block javascripts %}
+
+
+{% endblock %}
\ No newline at end of file