mirror of
https://github.com/RobbieHan/sandboxMP.git
synced 2026-02-11 22:54:55 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cea6fa7cba | ||
|
|
eb10ffe9be | ||
|
|
9c204933e8 | ||
|
|
371b1ebbe3 | ||
|
|
9d0bd95b69 | ||
|
|
4fbdc88743 | ||
|
|
860ae14d4c |
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'cmdb.apps.CmdbConfig'
|
||||||
@@ -3,3 +3,6 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
class CmdbConfig(AppConfig):
|
class CmdbConfig(AppConfig):
|
||||||
name = 'cmdb'
|
name = 'cmdb'
|
||||||
|
|
||||||
|
def ready(self):
|
||||||
|
from .signals import auto_delete_file
|
||||||
46
apps/cmdb/forms.py
Normal file
46
apps/cmdb/forms.py
Normal file
@@ -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)
|
||||||
@@ -1,6 +1,11 @@
|
|||||||
from django.db import models
|
from datetime import datetime
|
||||||
|
|
||||||
# Create your models here.
|
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):
|
class AbstractMode(models.Model):
|
||||||
@@ -20,3 +25,98 @@ class Code(AbstractMode):
|
|||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = '字典'
|
verbose_name = '字典'
|
||||||
verbose_name_plural = 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="上传人")
|
||||||
|
|
||||||
|
|||||||
13
apps/cmdb/signals.py
Normal file
13
apps/cmdb/signals.py
Normal file
@@ -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)
|
||||||
56
apps/cmdb/tasks.py
Normal file
56
apps/cmdb/tasks.py
Normal file
@@ -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
|
||||||
@@ -1,9 +1,23 @@
|
|||||||
from django.urls import path
|
from django.urls import path
|
||||||
|
|
||||||
from .views import CmdbView
|
from .views import CmdbView
|
||||||
|
from . import views_code, views_scan
|
||||||
|
|
||||||
app_name = 'cmdb'
|
app_name = 'cmdb'
|
||||||
|
|
||||||
urlpatterns = [
|
urlpatterns = [
|
||||||
path('', CmdbView.as_view(), name='index'),
|
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'),
|
||||||
|
|
||||||
]
|
]
|
||||||
|
|||||||
53
apps/cmdb/views_code.py
Normal file
53
apps/cmdb/views_code.py
Normal file
@@ -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
|
||||||
92
apps/cmdb/views_scan.py
Normal file
92
apps/cmdb/views_scan.py
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
# @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
|
||||||
|
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)
|
||||||
@@ -3,10 +3,13 @@
|
|||||||
# @File : custom.py
|
# @File : custom.py
|
||||||
|
|
||||||
import json
|
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.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.mixin import LoginRequiredMixin
|
||||||
from system.models import Menu
|
from system.models import Menu
|
||||||
@@ -42,6 +45,69 @@ class SandboxGetObjectMixin:
|
|||||||
return obj
|
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:
|
class SandboxEditViewMixin:
|
||||||
|
|
||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
@@ -50,6 +116,11 @@ class SandboxEditViewMixin:
|
|||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
form.save()
|
form.save()
|
||||||
res['result'] = True
|
res['result'] = True
|
||||||
|
else:
|
||||||
|
pattern = '<li>.*?<ul class=.*?><li>(.*?)</li>'
|
||||||
|
form_errors = str(form.errors)
|
||||||
|
errors = re.findall(pattern, form_errors)
|
||||||
|
res['error'] = errors[0]
|
||||||
return HttpResponse(json.dumps(res), content_type='application/json')
|
return HttpResponse(json.dumps(res), content_type='application/json')
|
||||||
|
|
||||||
|
|
||||||
@@ -65,3 +136,27 @@ class SandboxUpdateView(LoginRequiredMixin, SandboxEditViewMixin, SandboxGetObje
|
|||||||
def post(self, request, *args, **kwargs):
|
def post(self, request, *args, **kwargs):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
return super().post(request, *args, **kwargs)
|
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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
default_app_config = 'system.apps.SystemConfig'
|
||||||
|
|||||||
@@ -2,4 +2,10 @@ from django.apps import AppConfig
|
|||||||
|
|
||||||
|
|
||||||
class SystemConfig(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
|
||||||
|
|||||||
50
apps/system/signals.py
Normal file
50
apps/system/signals.py
Normal file
@@ -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)
|
||||||
|
|
||||||
@@ -40,6 +40,7 @@ class LoginView(View):
|
|||||||
redirect_to = request.GET.get('next', '/')
|
redirect_to = request.GET.get('next', '/')
|
||||||
login_form = LoginForm(request.POST)
|
login_form = LoginForm(request.POST)
|
||||||
ret = dict(login_form=login_form)
|
ret = dict(login_form=login_form)
|
||||||
|
print(request.META.get('REMOTE_ADDR'))
|
||||||
if login_form.is_valid():
|
if login_form.is_valid():
|
||||||
user_name = request.POST['username']
|
user_name = request.POST['username']
|
||||||
pass_word = request.POST['password']
|
pass_word = request.POST['password']
|
||||||
|
|||||||
3
apps/utils/__init__.py
Normal file
3
apps/utils/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# @Time : 2018/12/29 16:26
|
||||||
|
# @Author : RobbieHan
|
||||||
|
# @File : __init__.py.py
|
||||||
209
apps/utils/sandbox_utils.py
Normal file
209
apps/utils/sandbox_utils.py
Normal file
@@ -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)
|
||||||
18
config/scanhosts.yml
Normal file
18
config/scanhosts.yml
Normal file
@@ -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'
|
||||||
@@ -1,2 +1 @@
|
|||||||
django==2.1.2
|
-r pro.txt
|
||||||
pillow==5.3.0
|
|
||||||
@@ -1,2 +1,14 @@
|
|||||||
django==2.1.2
|
django==2.1.2
|
||||||
pillow==5.3.0
|
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
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
from __future__ import absolute_import, unicode_literals
|
||||||
|
|
||||||
|
from .celery import app as celery_app
|
||||||
|
|
||||||
|
__all__ = ('celery_app')
|
||||||
38
sandboxMP/celery.py
Normal file
38
sandboxMP/celery.py
Normal file
@@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -13,6 +13,8 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
|
|||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
from .celery import *
|
||||||
|
|
||||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||||
|
|
||||||
@@ -39,6 +41,7 @@ INSTALLED_APPS = [
|
|||||||
'django.contrib.sessions',
|
'django.contrib.sessions',
|
||||||
'django.contrib.messages',
|
'django.contrib.messages',
|
||||||
'django.contrib.staticfiles',
|
'django.contrib.staticfiles',
|
||||||
|
'simple_history',
|
||||||
'system',
|
'system',
|
||||||
'cmdb',
|
'cmdb',
|
||||||
]
|
]
|
||||||
@@ -53,6 +56,7 @@ MIDDLEWARE = [
|
|||||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||||
'apps.system.middleware.MenuCollection',
|
'apps.system.middleware.MenuCollection',
|
||||||
'apps.system.middleware.RbacMiddleware',
|
'apps.system.middleware.RbacMiddleware',
|
||||||
|
'simple_history.middleware.HistoryRequestMiddleware',
|
||||||
]
|
]
|
||||||
|
|
||||||
ROOT_URLCONF = 'sandboxMP.urls'
|
ROOT_URLCONF = 'sandboxMP.urls'
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ var DATATABLES_CONSTANT = {
|
|||||||
|
|
||||||
// datatables常量
|
// datatables常量
|
||||||
DATA_TABLES : {
|
DATA_TABLES : {
|
||||||
DEFAULT_OPTION : { // DataTables初始化选项
|
SERVER_SIDE_OPTION : { // DataTables初始化选项
|
||||||
oLanguage : {
|
oLanguage : {
|
||||||
sProcessing : "处理中...",
|
sProcessing : "处理中...",
|
||||||
sLengthMenu : "每页 _MENU_ 项",//"显示 _MENU_ 项结果,",
|
sLengthMenu : "每页 _MENU_ 项",//"显示 _MENU_ 项结果,",
|
||||||
|
|||||||
274
templates/cmdb/code.html
Normal file
274
templates/cmdb/code.html
Normal file
@@ -0,0 +1,274 @@
|
|||||||
|
{% extends "base-left.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<section class="content">
|
||||||
|
<div id="devlist">
|
||||||
|
<div class="box box-primary" id="liebiao">
|
||||||
|
<div class="box-header">
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnRefresh" class="btn btn-default">
|
||||||
|
<i class="glyphicon glyphicon-repeat"></i>刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group pull-left"> </div>
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnCreate" class="btn btn-default">
|
||||||
|
<i class="glyphicon glyphicon-plus"></i>新增
|
||||||
|
</button>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="btn-group pull-left"> </div>
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnDelete" class="btn btn-default">
|
||||||
|
<i class="glyphicon glyphicon-trash"></i>删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="pull-right">
|
||||||
|
<form class="form-inline" id="queryForm">
|
||||||
|
<div class="form-group searchArea margin-r-5 margin-top-5">
|
||||||
|
<label>字典分类:</label>
|
||||||
|
<select class="form-control inputText select2" name="parent" id="parent">
|
||||||
|
<option style='text-align:center' value="">---所有---</option>
|
||||||
|
{% for code in code_parent %}
|
||||||
|
<option value={{ code.key}}>{{ code.value }}</option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box-body">
|
||||||
|
<table id="dtbList" class="display" cellspacing="0" width="100%">
|
||||||
|
<thead>
|
||||||
|
<tr valign="middle">
|
||||||
|
<th><input type="checkbox" id="checkAll"></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>KEY</th>
|
||||||
|
<th>VALUE</th>
|
||||||
|
<th>所属</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br> <br>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- /.content -->
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
|
||||||
|
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
|
||||||
|
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
|
||||||
|
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
|
||||||
|
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// 菜单选中高亮
|
||||||
|
$(function () {
|
||||||
|
$('#CMDB-PORTAL').addClass('active');
|
||||||
|
$('#CDMB-PORTAL-CODE').addClass('active');
|
||||||
|
});
|
||||||
|
|
||||||
|
// datatables 初始化配置
|
||||||
|
var oDataTable = null;
|
||||||
|
$(function () {
|
||||||
|
oDataTable = initTable();
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
|
var oTable = $('#dtbList').DataTable($.extend(true, {},
|
||||||
|
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
|
||||||
|
|
||||||
|
{
|
||||||
|
ajax: {
|
||||||
|
"url": "{% url 'cmdb:portal-code-list' %}",
|
||||||
|
"data": function (d) {
|
||||||
|
d.parent = $("#parent").val();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
columns: [
|
||||||
|
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
|
||||||
|
{
|
||||||
|
data: "id",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "key",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "value",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "parent__value",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "id",
|
||||||
|
width: "10%",
|
||||||
|
bSortable: "false",
|
||||||
|
render: function (data, type, row, meta) {
|
||||||
|
var ret = "";
|
||||||
|
var ret = "<button title='详情' onclick='doUpdate("
|
||||||
|
+ data + ")'><i class='glyphicon glyphicon-pencil'></i></button>";
|
||||||
|
ret = ret + "<button title='删除' onclick='doDelete("
|
||||||
|
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
return oTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
//select2
|
||||||
|
$(function () {
|
||||||
|
//Initialize Select2 Elements
|
||||||
|
$(".select2").select2();
|
||||||
|
});
|
||||||
|
|
||||||
|
//过滤刷新接口获取新的数据
|
||||||
|
$("#parent").change(function () {
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
$("#btnRefresh").click(function () {
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
});
|
||||||
|
//新建字典
|
||||||
|
$("#btnCreate").click(function () {
|
||||||
|
layer.open({
|
||||||
|
type: 2,
|
||||||
|
title: '新增',
|
||||||
|
shadeClose: false,
|
||||||
|
maxmin: true,
|
||||||
|
area: ['800px', '400px'],
|
||||||
|
content: "{% url 'cmdb:portal-code-create' %}",
|
||||||
|
end: function () {
|
||||||
|
//关闭时做的事情
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//修改字典
|
||||||
|
function doUpdate(id) {
|
||||||
|
layer.open({
|
||||||
|
type: 2,
|
||||||
|
title: '编辑',
|
||||||
|
shadeClose: false,
|
||||||
|
maxmin: true,
|
||||||
|
area: ['800px', '400px'],
|
||||||
|
content: ["{% url 'cmdb:portal-code-update' %}" + '?id=' + id, 'no'],
|
||||||
|
end: function () {
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//checkbox全选
|
||||||
|
$("#checkAll").on("click", function () {
|
||||||
|
if ($(this).prop("checked") === true) {
|
||||||
|
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
|
||||||
|
$('#example tbody tr').addClass('selected');
|
||||||
|
} else {
|
||||||
|
$("input[name='checkList']").prop("checked", false);
|
||||||
|
$('#example tbody tr').removeClass('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//批量删除
|
||||||
|
$("#btnDelete").click(function () {
|
||||||
|
if ($("input[name='checkList']:checked").length == 0) {
|
||||||
|
layer.msg("请选择要删除的记录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrId = new Array();
|
||||||
|
$("input[name='checkList']:checked").each(function () {
|
||||||
|
//alert($(this).val());
|
||||||
|
arrId.push($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
sId = arrId.join(',');
|
||||||
|
|
||||||
|
layer.alert('确定删除吗?', {
|
||||||
|
title: '提示'
|
||||||
|
, icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸
|
||||||
|
, time: 0 //不自动关闭
|
||||||
|
, btn: ['YES', 'NO']
|
||||||
|
, yes: function (index) {
|
||||||
|
layer.close(index);
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "{% url 'cmdb:portal-code-delete' %}",
|
||||||
|
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||||
|
cache: false,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert("操作成功", {icon: 1});
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
} else {
|
||||||
|
//alert(msg.message);
|
||||||
|
layer.alert("操作失败", {icon: 2});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//删除单个数据
|
||||||
|
function doDelete(id) {
|
||||||
|
layer.alert('确定删除吗?', {
|
||||||
|
title: '提示'
|
||||||
|
, icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸
|
||||||
|
, time: 0 //不自动关闭
|
||||||
|
, btn: ['YES', 'NO']
|
||||||
|
, yes: function (index) {
|
||||||
|
layer.close(index);
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "{% url 'cmdb:portal-code-delete' %}",
|
||||||
|
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||||
|
cache: false,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert('删除成功', {icon: 1});
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
} else {
|
||||||
|
//alert(msg.message);
|
||||||
|
layer.alert('删除失败', {icon: 2});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
97
templates/cmdb/code_create.html
Normal file
97
templates/cmdb/code_create.html
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
{% extends 'base-layer.html' %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
|
||||||
|
<!-- iCheck for checkboxes and radio inputs -->
|
||||||
|
{% endblock %}
|
||||||
|
{% block main %}
|
||||||
|
<div class="box box-danger">
|
||||||
|
<form class="form-horizontal" id="addForm" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="box-body">
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
<h4>新建字典</h4>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<label class="col-sm-2 control-label">KEY</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input class="form-control" name="key" type="text"/>
|
||||||
|
</div>
|
||||||
|
<label class="col-sm-2 control-label">VALUE</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input class="form-control" name="value" type="text" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<label class="col-sm-2 control-label">父菜单</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-control select2" name="parent">
|
||||||
|
<option value=""></option>
|
||||||
|
{% for parent in code_parent %}
|
||||||
|
<option value={{ parent.id }}> {{ parent.value }} </option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="col-sm-2 control-label">描述信息</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input class="form-control" id="desc" name="desc" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer ">
|
||||||
|
<div class="row span7 text-center ">
|
||||||
|
<button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button>
|
||||||
|
<button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$("#btnSave").click(function () {
|
||||||
|
var data = $("#addForm").serialize();
|
||||||
|
$.ajax({
|
||||||
|
type: $("#addForm").attr('method'),
|
||||||
|
url: "{% url 'cmdb:portal-code-create' %}",
|
||||||
|
data: data,
|
||||||
|
cache: false,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert('数据保存成功!', {icon: 1}, function (index) {
|
||||||
|
parent.layer.closeAll(); //关闭所有弹窗
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
layer.alert(msg.error, {icon: 5});
|
||||||
|
//$('errorMessage').html(msg.message)
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*点取消刷新新页面*/
|
||||||
|
$("#btnCancel").click(function () {
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
//Initialize Select2 Elements
|
||||||
|
$(".select2").select2();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
99
templates/cmdb/code_update.html
Normal file
99
templates/cmdb/code_update.html
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{% extends 'base-layer.html' %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
|
||||||
|
<!-- iCheck for checkboxes and radio inputs -->
|
||||||
|
{% endblock %}
|
||||||
|
{% block main %}
|
||||||
|
<div class="box box-danger">
|
||||||
|
<form class="form-horizontal" id="addForm" method="post">
|
||||||
|
<input type="hidden" name='id' type='text' value="{{ code.id }}"/>
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="box-body">
|
||||||
|
<fieldset>
|
||||||
|
<legend>
|
||||||
|
<h4>修改字典</h4>
|
||||||
|
</legend>
|
||||||
|
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<label class="col-sm-2 control-label">KEY</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input class="form-control" name="key" type="text" value="{{ code.key }}"/>
|
||||||
|
</div>
|
||||||
|
<label class="col-sm-2 control-label">VALUE</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input class="form-control" name="value" type="text" value="{{ code.value }}"/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<label class="col-sm-2 control-label">父菜单</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<select class="form-control select2" name="parent">
|
||||||
|
<option value={{ code.parent.id }}> {{ code.parent.value }} </option>
|
||||||
|
<option value=""></option>
|
||||||
|
{% for parent in code_parent %}
|
||||||
|
<option value={{ parent.id }}> {{ parent.value }} </option>
|
||||||
|
{% endfor %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="col-sm-2 control-label">描述信息</label>
|
||||||
|
<div class="col-sm-3">
|
||||||
|
<input class="form-control" id="desc" name="desc" type="text" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</fieldset>
|
||||||
|
</div>
|
||||||
|
<div class="box-footer ">
|
||||||
|
<div class="row span7 text-center ">
|
||||||
|
<button type="button" id="btnCancel" class="btn btn-default margin-right ">重置</button>
|
||||||
|
<button type="button" id="btnSave" class="btn btn-info margin-right ">保存</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
$("#btnSave").click(function () {
|
||||||
|
var data = $("#addForm").serialize();
|
||||||
|
$.ajax({
|
||||||
|
type: $("#addForm").attr('method'),
|
||||||
|
url: "{% url 'cmdb:portal-code-update' %}",
|
||||||
|
data: data,
|
||||||
|
cache: false,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert('数据保存成功!', {icon: 1}, function (index) {
|
||||||
|
parent.layer.closeAll(); //关闭所有弹窗
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
layer.alert(msg.error, {icon: 5});
|
||||||
|
//$('errorMessage').html(msg.message)
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*点取消刷新新页面*/
|
||||||
|
$("#btnCancel").click(function () {
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$(function () {
|
||||||
|
//Initialize Select2 Elements
|
||||||
|
$(".select2").select2();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
302
templates/cmdb/device_scan.html
Normal file
302
templates/cmdb/device_scan.html
Normal file
@@ -0,0 +1,302 @@
|
|||||||
|
{% extends "base-left.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'plugins/datatables/jquery.dataTables.min.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
|
||||||
|
<link rel="stylesheet" href="{% static 'plugins/select2/select2.min.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<section class="content">
|
||||||
|
<div id="devlist">
|
||||||
|
<div class="box box-primary" id="liebiao">
|
||||||
|
<div class="box-header">
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnRefresh" class="btn btn-default">
|
||||||
|
<i class="glyphicon glyphicon-repeat"></i> 刷新
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group pull-left"> </div>
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnScan" class="btn btn-default" onclick="doScan()">
|
||||||
|
<i class="glyphicon glyphicon-search"></i> 执行扫描
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group pull-left"> </div>
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnInbound" class="btn btn-default" onclick="doInbound()">
|
||||||
|
<i class="glyphicon glyphicon-floppy-disk"></i> 执行入库
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="btn-group pull-left"> </div>
|
||||||
|
<div class="btn-group pull-left">
|
||||||
|
<button type="button" id="btnDelete" class="btn btn-default">
|
||||||
|
<i class="glyphicon glyphicon-trash"></i> 删除
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box-body">
|
||||||
|
<table id="dtbList" class="display" cellspacing="0" width="100%">
|
||||||
|
<thead>
|
||||||
|
<tr valign="middle">
|
||||||
|
<th><input type="checkbox" id="checkAll"></th>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>主机名</th>
|
||||||
|
<th>IP地址</th>
|
||||||
|
<th>MAC地址</th>
|
||||||
|
<th>认证类型</th>
|
||||||
|
<th>登陆状态</th>
|
||||||
|
<th>系统类型</th>
|
||||||
|
<th>设备类型</th>
|
||||||
|
<th>操作</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
<br>
|
||||||
|
<small>点击【执行入库】可将扫描结果中,登陆状态为:成功(succeed)的设备数据导入正式设备管理数据库。</small>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- /.content -->
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
|
||||||
|
<script src="{% static 'plugins/datatables/jquery.dataTables.min.js' %}"></script>
|
||||||
|
<script src="{% static 'plugins/datatables/dataTables.const-1.js' %}"></script>
|
||||||
|
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
|
||||||
|
<script src="{% static 'plugins/select2/select2.full.min.js' %}"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
// 菜单选中高亮
|
||||||
|
$(function () {
|
||||||
|
$('#CMDB-PORTAL').addClass('active');
|
||||||
|
$('#CDMB-PORTAL-DEVICE_SCAN').addClass('active');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// datatables 初始化配置
|
||||||
|
var oDataTable = null;
|
||||||
|
$(function () {
|
||||||
|
oDataTable = initTable();
|
||||||
|
|
||||||
|
function initTable() {
|
||||||
|
var oTable = $('#dtbList').DataTable($.extend(true, {},
|
||||||
|
DATATABLES_CONSTANT.DATA_TABLES.SERVER_SIDE_OPTION,
|
||||||
|
|
||||||
|
{
|
||||||
|
ajax: {
|
||||||
|
"url": "{% url 'cmdb:portal-device_scan-list' %}",
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
columns: [
|
||||||
|
DATATABLES_CONSTANT.DATA_TABLES.COLUMN.CHECKBOX,
|
||||||
|
{
|
||||||
|
data: "id",
|
||||||
|
width: "5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "sys_hostname",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "hostname",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "mac_address",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "auth_type",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "status",
|
||||||
|
render: function (data, type, row, meta) {
|
||||||
|
if (data == "succeed") {
|
||||||
|
var ret = "<button class='btn btn-info btn-xs'>成功</button>";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
if (data == "failed") {
|
||||||
|
var ret = "<button class='btn btn-danger btn-xs'>失败</button>";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
var ret = "<button class='btn btn-default btn-xs'>未知</button>";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "os_type",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "device_type",
|
||||||
|
//width : "20%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
data: "id",
|
||||||
|
width: "10%",
|
||||||
|
bSortable: "false",
|
||||||
|
render: function (data, type, row, meta) {
|
||||||
|
var ret = "";
|
||||||
|
var ret = "<button title='详情' onclick='doDetail("
|
||||||
|
+ data + ")'><i class='glyphicon glyphicon-list-alt'></i></button>";
|
||||||
|
ret = ret + "<button title='删除' onclick='doDelete("
|
||||||
|
+ data + ")'><i class='glyphicon glyphicon-trash'></i></button>";
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}],
|
||||||
|
}));
|
||||||
|
return oTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
$("#btnRefresh").click(function () {
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
function doDetail(id){
|
||||||
|
window.location.href="/cmdb/portal/device_scan/detail/?id="+id;
|
||||||
|
}
|
||||||
|
|
||||||
|
//checkbox全选
|
||||||
|
$("#checkAll").on("click", function () {
|
||||||
|
if ($(this).prop("checked") === true) {
|
||||||
|
$("input[name='checkList']").prop("checked", $(this).prop("checked"));
|
||||||
|
$('#example tbody tr').addClass('selected');
|
||||||
|
} else {
|
||||||
|
$("input[name='checkList']").prop("checked", false);
|
||||||
|
$('#example tbody tr').removeClass('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
//批量删除
|
||||||
|
$("#btnDelete").click(function () {
|
||||||
|
if ($("input[name='checkList']:checked").length == 0) {
|
||||||
|
layer.msg("请选择要删除的记录");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var arrId = new Array();
|
||||||
|
$("input[name='checkList']:checked").each(function () {
|
||||||
|
//alert($(this).val());
|
||||||
|
arrId.push($(this).val());
|
||||||
|
});
|
||||||
|
|
||||||
|
sId = arrId.join(',');
|
||||||
|
|
||||||
|
layer.alert('确定删除吗?', {
|
||||||
|
title: '提示'
|
||||||
|
, icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸
|
||||||
|
, time: 0 //不自动关闭
|
||||||
|
, btn: ['YES', 'NO']
|
||||||
|
, yes: function (index) {
|
||||||
|
layer.close(index);
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "{% url 'cmdb:portal-device_scan-delete' %}",
|
||||||
|
data: {"id": sId, csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||||
|
cache: false,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert("操作成功", {icon: 1});
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
} else {
|
||||||
|
//alert(msg.message);
|
||||||
|
layer.alert("操作失败", {icon: 2});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
//删除单个数据
|
||||||
|
function doDelete(id) {
|
||||||
|
layer.alert('确定删除吗?', {
|
||||||
|
title: '提示'
|
||||||
|
, icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸
|
||||||
|
, time: 0 //不自动关闭
|
||||||
|
, btn: ['YES', 'NO']
|
||||||
|
, yes: function (index) {
|
||||||
|
layer.close(index);
|
||||||
|
$.ajax({
|
||||||
|
type: "POST",
|
||||||
|
url: "{% url 'cmdb:portal-device_scan-delete' %}",
|
||||||
|
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
|
||||||
|
cache: false,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert('删除成功', {icon: 1});
|
||||||
|
oDataTable.ajax.reload();
|
||||||
|
} else {
|
||||||
|
//alert(msg.message);
|
||||||
|
layer.alert('删除失败', {icon: 2});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
//资产扫描
|
||||||
|
function doScan() {
|
||||||
|
layer.alert('确定开始扫描吗?', {
|
||||||
|
title: '提示'
|
||||||
|
, icon: 3 //0:感叹号 1:对号 2:差号 3:问号 4:小锁 5:哭脸 6:笑脸
|
||||||
|
, time: 0 //不自动关闭
|
||||||
|
, btn: ['YES', 'NO']
|
||||||
|
, yes: function (index) {
|
||||||
|
layer.close(index);
|
||||||
|
$.ajax({
|
||||||
|
type: "GET",
|
||||||
|
url: "{% url 'cmdb:portal-device_scan-exec' %}",
|
||||||
|
cache: false,
|
||||||
|
beforeSend:function(){
|
||||||
|
this.layerIndex = layer.load(2, {
|
||||||
|
shade: [0.1,'#fff']
|
||||||
|
});
|
||||||
|
},
|
||||||
|
success: function (msg) {
|
||||||
|
layer.closeAll('loading');
|
||||||
|
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 {
|
||||||
|
//alert(msg.message);
|
||||||
|
layer.alert('扫描失败', {icon: 2});
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
113
templates/cmdb/device_scan_detail.html
Normal file
113
templates/cmdb/device_scan_detail.html
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
{% extends "base-left.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
|
||||||
|
<section class="content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-12">
|
||||||
|
<div class="box box-primary">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">设备详情</h3>
|
||||||
|
|
||||||
|
<div class="box-tools">
|
||||||
|
<button type="button" class="btn btn-box-tool" data-widget="collapse"><i
|
||||||
|
class="fa fa-minus"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="box-body no-padding">
|
||||||
|
<div class="btn-group pull-right margin">
|
||||||
|
<button type="button" class="btn btn-primary btn-xs margin-r-5" title="返回" id="btnReturn">
|
||||||
|
<i class="fa fa-undo"> 返回</i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-responsive mailbox-messages">
|
||||||
|
<table class="table" id="tbWorkList" style="white-space: nowrap;">
|
||||||
|
<tbody>
|
||||||
|
<tr class="info">
|
||||||
|
<td width="10%"><strong>主机名</strong></td>
|
||||||
|
<td class="text-left">{{ device.sys_hostname }}</td>
|
||||||
|
<td width="10%"><strong>SN编号</strong></td>
|
||||||
|
<td class="text-left">{{ device.sn_number }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>SSH用户名</strong></td>
|
||||||
|
<td>{{ device.username }}</td>
|
||||||
|
<td><strong>SSH端口</strong></td>
|
||||||
|
<td>{{ device.port }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info">
|
||||||
|
<td><strong>认证类型</strong></td>
|
||||||
|
<td>{{ device.auth_type }}</td>
|
||||||
|
<td><strong>登陆状态</strong></td>
|
||||||
|
<td>{{ device.status }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>IP地址</strong></td>
|
||||||
|
<td>{{ device.hostname }}</td>
|
||||||
|
<td><strong>MAC地址</strong></td>
|
||||||
|
<td>{{ device.mac_address }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info">
|
||||||
|
<td><strong>系统类型</strong></td>
|
||||||
|
<td>{{ device.os_type }}</td>
|
||||||
|
<td><strong>设备类型</strong></td>
|
||||||
|
<td>{{ device.device_type }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><strong>入库时间</strong></td>
|
||||||
|
<td>{{ device.add_time }}</td>
|
||||||
|
<td><strong>变更时间</strong></td>
|
||||||
|
<td>{{ device.modify_time }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr class="info">
|
||||||
|
<td><strong>错误信息</strong></td>
|
||||||
|
<td colspan="3">{{ device.error_message }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<br>
|
||||||
|
<div class="box-footer margin-b-10">
|
||||||
|
<small>该设备信息为自动扫描入库设备,不提供修改功能,可通过管理页面【执行入库】按钮将登陆状态为:成功(succeed)的设备迁移到正式设备管理库</small>
|
||||||
|
</div>
|
||||||
|
<!-- /.box-footer -->
|
||||||
|
</div>
|
||||||
|
<!-- /.box-body -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.col -->
|
||||||
|
<!-- TO DO List -->
|
||||||
|
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- /.content -->
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
|
||||||
|
<script src="{% static 'plugins/masonry/masonry.js' %}"></script>
|
||||||
|
|
||||||
|
<script type="text/javascript">
|
||||||
|
$(function () {
|
||||||
|
$('#CMDB-PORTAL').addClass('active');
|
||||||
|
$('#CDMB-PORTAL-DEVICE_SCAN').addClass('active');
|
||||||
|
|
||||||
|
});
|
||||||
|
//返回
|
||||||
|
$("#btnReturn").click(function () {
|
||||||
|
history.back();
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
176
templates/cmdb/scan_config.html
Normal file
176
templates/cmdb/scan_config.html
Normal file
@@ -0,0 +1,176 @@
|
|||||||
|
{% extends "base-left.html" %}
|
||||||
|
{% load staticfiles %}
|
||||||
|
|
||||||
|
{% block css %}
|
||||||
|
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
|
|
||||||
|
<!-- Main content -->
|
||||||
|
<section class="content">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<!-- Horizontal Form -->
|
||||||
|
<div class="box box-default">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">扫面参数配置</h3>
|
||||||
|
</div>
|
||||||
|
<!-- /.box-header -->
|
||||||
|
<!-- form start -->
|
||||||
|
<form class="form-horizontal" id="addForm" method="post">
|
||||||
|
{% csrf_token %}
|
||||||
|
<div class="box-body">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">扫描网段</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" id="net_address" name="net_address" value="{{ hosts.net_address }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">登陆用户</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" id="ssh_username" name="ssh_username" value="{{ hosts.ssh_username }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">SSH端口</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" id="ssh_port" name="ssh_port" value="{{ hosts.ssh_port }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">登陆密码</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" type="password" id="ssh_password" name="ssh_password" value="{{ hosts.ssh_password }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">密钥路径</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" id="ssh_private_key" name="ssh_private_key" value="{{ hosts.ssh_private_key }}">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">运行命令</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<textarea class="form-control" rows="5" id="commands" name="commands" readonly>{{ hosts.commands }}</textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">认证方式</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-control" name="auth_type">
|
||||||
|
<option value="password" {% ifequal hosts.auth_type 'password' %}selected="selected"{% endifequal %}>密码认证</option>
|
||||||
|
<option value="private_key" {% ifequal hosts.auth_type 'private_key' %}selected="selected"{% endifequal %}>私钥认证</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<label class="col-sm-2 control-label">扫描方式</label>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<select class="form-control" name="scan_type">
|
||||||
|
<option value="basic_scan" {% ifequal hosts.scan_type 'basic_scan' %}selected="selected"{% endifequal %}>基础扫描</option>
|
||||||
|
<option value="enhanced_scan" {% ifequal hosts.scan_type 'enhanced_scan' %}selected="selected"{% endifequal %}>加强扫描</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="col-sm-2 control-label">收件邮箱</label>
|
||||||
|
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<input class="form-control" id="email" name="email" value="{{ hosts.email }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<div class="form-group has-feedback">
|
||||||
|
<label class="col-sm-2 control-label">发送邮件</label>
|
||||||
|
<div class="col-sm-10">
|
||||||
|
<label class="control-label">
|
||||||
|
<input type="radio" class="minimal" name="send_email" value="true" {% ifequal hosts.send_email 'true' %}checked{% endifequal %}>是
|
||||||
|
</label>
|
||||||
|
<label class="control-label">
|
||||||
|
<input type="radio" class="minimal" name="send_email" value="false" {% ifequal hosts.send_email 'false' %}checked{% endifequal %}>否
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- /.box-body -->
|
||||||
|
<div class="box-footer">
|
||||||
|
<span class="pull-right">
|
||||||
|
<button type="button" id="btnCancel" class="btn btn-default">取消</button>
|
||||||
|
<button type="button" id="btnSave" class="btn btn-info">保存</button>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<!-- /.box-footer -->
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-4">
|
||||||
|
<div class="box box-default">
|
||||||
|
<div class="box-header with-border">
|
||||||
|
<h3 class="box-title">参数配置说明</h3>
|
||||||
|
</div><!-- /.box-header -->
|
||||||
|
<div class="box-body">
|
||||||
|
<a class="product-title"><strong>扫描网段</strong><span class="label label-danger pull-right">必填参数</span></a>
|
||||||
|
<p><small>网络扫描地址列表,列表中可以包含一个或多个网段,也可以是一个或多个地址区间,例如['192.168.100.0/24', '192.168.100.10-20']</small></p>
|
||||||
|
<a class="product-title"><strong>登陆用户</strong><span class="label label-danger pull-right">必填参数</span></a>
|
||||||
|
<p><small>系统登陆测试使用的用户名,默认root。</small></p>
|
||||||
|
<a class="product-title"><strong>登陆密码</strong><span class="label label-danger pull-right">可选参数</span></a>
|
||||||
|
<p><small>系统登陆测试使用的密码,如过认证方式为密码认证,必须填写密码信息。</small></p>
|
||||||
|
<a class="product-title"><strong>密钥路径</strong><span class="label label-primary pull-right">可选参数</span></a>
|
||||||
|
<p><small>系统登陆测试使用的密钥存放路径,如过认证方式为私钥认证,必须填写密钥路径。</small></p>
|
||||||
|
<a class="product-title"><strong>运行命令</strong><span class="label label-default pull-right">预定义参数</span></a>
|
||||||
|
<p><small>登陆系统后运行的基本命令,用来获取系统基本信息,当扫描方式为加强扫描时,才会执行运行命令,运行命令为预定义,禁止修改。</small></p>
|
||||||
|
<a class="product-title"><strong>认证方式</strong><span class="label label-primary pull-right">可选参数</span></a>
|
||||||
|
<p><small>系统登陆认证方式,包括密码认证和私钥认证两种方式,当扫描方式为加强扫描时,需要设定认证方式。</small></p>
|
||||||
|
<a class="product-title"><strong>扫描方式</strong><span class="label label-primary pull-right">可选参数</span></a>
|
||||||
|
<p><small>系统扫描方式,包括基本扫描和加强扫描,当设置为加强扫描时会登陆系统执行运行命令获取系统信息。</small></p>
|
||||||
|
<a class="product-title"><strong>收件邮箱</strong><span class="label label-primary pull-right">可选参数</span></a>
|
||||||
|
<p><small>用于接收扫描结果邮件,可以通过发送邮件选项设置是否发送通知邮件。</small></p>
|
||||||
|
</div><!-- /.box-body -->
|
||||||
|
</div><!-- /.box -->
|
||||||
|
</div>
|
||||||
|
<!-- /.box -->
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- /.content -->
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
<script src="{% static 'js/plugins/layer/layer.js' %}"></script>
|
||||||
|
<script type="text/javascript">
|
||||||
|
// 菜单选中高亮
|
||||||
|
$(function () {
|
||||||
|
$('#CMDB-PORTAL').addClass('active');
|
||||||
|
$('#CMDB-PORTAL-SCAN_CONFIG').addClass('active');
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
$("#btnSave").click(function () {
|
||||||
|
var hosts = $("#addForm").serialize();
|
||||||
|
$.ajax({
|
||||||
|
type: $("#addForm").attr('method'),
|
||||||
|
url: "{% url 'cmdb:portal-scan_config' %}",
|
||||||
|
cache: false,
|
||||||
|
data: hosts,
|
||||||
|
success: function (msg) {
|
||||||
|
if (msg.result) {
|
||||||
|
layer.alert('数据保存成功!', {icon: 1});
|
||||||
|
} else {
|
||||||
|
layer.alert('数据保存失败', {icon: 5});
|
||||||
|
//$('errorMessage').html(msg.message)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
/*点取消刷新新页面*/
|
||||||
|
$("#btnCancel").click(function () {
|
||||||
|
window.location.reload();
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
Reference in New Issue
Block a user