3 Commits
v2.17 ... v2.20

Author SHA1 Message Date
RobbieHan
2646254e52 upload&auto updatee 2019-02-21 20:28:46 +08:00
RobbieHan
e3490b3af7 tags&filters 2019-02-17 22:40:06 +08:00
RobbieHan
b58be33aac change_compare 2019-02-12 21:21:00 +08:00
14 changed files with 3057 additions and 9 deletions

View File

@@ -4,7 +4,7 @@
from django import forms
from .models import Code, DeviceInfo, ConnectionInfo
from .models import Code, DeviceInfo, ConnectionInfo, DeviceFile
class CodeCreateForm(forms.ModelForm):
@@ -97,3 +97,9 @@ class ConnectionInfoForm(forms.ModelForm):
raise forms.ValidationError('认证类型为[密码]时,必须设置密码信息!')
if auth_type == 'private_key' and len(private_key) == 0:
raise forms.ValidationError('认证类型为[密钥]时,必须设置密钥信息!')
class DeviceFileUploadForm(forms.ModelForm):
class Meta:
model = DeviceFile
fields = '__all__'

View File

@@ -20,7 +20,7 @@ class AbstractMode(models.Model):
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='备注')
desc = models.CharField(max_length=100, blank=True, default='', verbose_name='备注')
class Meta:
verbose_name = '字典'

View File

@@ -1,13 +1,46 @@
import os
from django.dispatch import receiver
from django.db.models.signals import post_delete
from django.db.models.signals import post_delete, post_save
from .models import DeviceFile
from .models import DeviceFile, DeviceInfo, ConnectionInfo
from utils.db_utils import MongodbDriver
@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)
os.remove(instance.file_content.path)
@receiver(post_save, sender=DeviceInfo)
def auto_compare_diff(sender, instance, **kwargs):
record = instance.history.latest()
prev_record = record.prev_record
ope_type = {'~': 'update', '+': 'create', '-': 'delete'}
compare_result = {
'id': record.id,
'changed_by': record.changed_by.name,
'history_type': ope_type[record.history_type],
'history_date': record.history_date
}
changes = {}
if prev_record is not None:
delta = record.diff_against(prev_record)
for change in delta.changes:
changes[change.field] = [change.old, change.new]
compare_result['changes'] = changes
if compare_result['changes'] or compare_result['history_type'] == 'create':
try:
mongo = MongodbDriver(collection='change_compare')
mongo.insert(compare_result)
except Exception as e:
pass
@receiver(post_delete, sender=DeviceInfo)
def auto_delete_connection(sender, instance, **kwargs):
dev_connection = getattr(instance, 'dev_connection')
if dev_connection:
ConnectionInfo.objects.filter(id=dev_connection).delete()

View File

@@ -0,0 +1,3 @@
# @Time : 2019/2/17 21:28
# @Author : RobbieHan
# @File : __init__.py.py

View File

@@ -0,0 +1,60 @@
from django import template
from django.db.models.query import QuerySet
from django.contrib.auth import get_user_model
from cmdb.models import Code, Cabinet
register = template.Library()
User = get_user_model()
@register.simple_tag
def get_con(context, arg, field):
if isinstance(context, QuerySet):
context = context.values()
instance = [con for con in context if con['id'] == arg]
if instance:
return instance[0][field]
return ''
@register.filter(name='compare_result')
def get_change_compare(changes):
change_compare = []
for key, value in changes.items():
if key in ['network_type', 'service_type', 'operation_type']:
log = replace_log(key, value, Code, 'value')
elif key == 'dev_cabinet':
log = replace_log(key, value, Cabinet, 'number')
elif key == 'leader':
log = replace_log(key, value, User, 'name')
else:
log = '字段:"%(field)s",由:"%(old)s",变更为:"%(new)s"' % {
'field': key,
'old': value[0],
'new': value[1]
}
change_compare.append(log)
return ''.join(str(i) for i in change_compare)
def replace_log(key, value, model, field):
old = value[0]
new = value[1]
log_format = '字段:"%(field)s",由:"%(old)s",变更为:"%(new)s"'
try:
data = model.objects.filter(id=old).values()[0]
old_data = data[field]
except Exception:
old_data = old
try:
data = model.objects.filter(id=new).values()[0]
new_data = data[field]
except Exception:
new_data = new
return log_format % {
'field': key,
'old': old_data,
'new': new_data
}

View File

@@ -33,4 +33,9 @@ urlpatterns = [
path('eam/device/list/', views_eam.DeviceListView.as_view(), name='eam-device-list'),
path('eam/device/delete/', views_eam.DeviceDeleteView.as_view(), name='eam-device-delete'),
path('eam/device/device2connection/', views_eam.Device2ConnectionView.as_view(), name='eam-device-device2connection'),
path('eam/device/detail/', views_eam.DeviceDetailView.as_view(), name='eam-device-detail'),
path('eam/device/upload/', views_eam.DeviceFileUploadView.as_view(), name='eam-device-upload'),
path('eam/device/file_delete/', views_eam.DeviceFileDeleteView.as_view(), name='eam-device-file_delete'),
path('eam/device/auto_update_device_info/', views_eam.AutoUpdateDeviceInfo.as_view(),
name='eam-device-auto_update_device_info'),
]

View File

@@ -5,12 +5,15 @@ from django.contrib.auth import get_user_model
from django.shortcuts import get_object_or_404
from django.http import JsonResponse
from django.shortcuts import render
from django.forms.models import model_to_dict
from system.mixin import LoginRequiredMixin
from custom import (BreadcrumbMixin, SandboxDeleteView,
SandboxListView, SandboxUpdateView, SandboxCreateView)
from .models import Cabinet, DeviceInfo, Code, ConnectionInfo
from .forms import DeviceCreateForm, DeviceUpdateForm, ConnectionInfoForm
from .models import Cabinet, DeviceInfo, Code, ConnectionInfo, DeviceFile
from .forms import DeviceCreateForm, DeviceUpdateForm, ConnectionInfoForm, DeviceFileUploadForm
from utils.db_utils import MongodbDriver
from utils.sandbox_utils import LoginExecution
User = get_user_model()
@@ -157,4 +160,76 @@ class Device2ConnectionView(LoginRequiredMixin, View):
form_errors = str(form.errors)
errors = re.findall(pattern, form_errors)
res['error'] = errors[0]
return JsonResponse(res)
return JsonResponse(res)
class DeviceDetailView(LoginRequiredMixin, BreadcrumbMixin, TemplateView):
template_name = 'cmdb/deviceinfo_detail.html'
def get_context_data(self, **kwargs):
device = get_object_or_404(DeviceInfo, pk=int(self.request.GET['id']))
mongo = MongodbDriver()
logs = mongo.find(id=int(self.request.GET['id']), sort_by='history_date')
all_file = device.devicefile_set.all()
device_public = get_device_public()
kwargs['device'] = device
kwargs['logs'] = logs
kwargs['all_file'] = all_file
kwargs.update(device_public)
return super().get_context_data(**kwargs)
class DeviceFileUploadView(LoginRequiredMixin, View):
def get(self, request):
ret = dict()
device = get_object_or_404(DeviceInfo, pk=request.GET['id'])
ret['device'] = device
return render(request, 'cmdb/deviceinfo_upload.html', ret)
def post(self, request):
res = dict(result=False)
device_file = DeviceFile()
upload_form = DeviceFileUploadForm(
request.POST, request.FILES, instance=device_file
)
if upload_form.is_valid():
upload_form.save()
res['result'] = True
return JsonResponse(res)
class DeviceFileDeleteView(SandboxDeleteView):
model = DeviceFile
class AutoUpdateDeviceInfo(LoginRequiredMixin, View):
def post(self, request):
res = dict(status='fail')
if 'id' in request.POST and request.POST['id']:
device = get_object_or_404(DeviceInfo, pk=int(request.POST['id']))
con_id = device.dev_connection
conn = ConnectionInfo.objects.filter(id=con_id)
if con_id and conn:
try:
conn_info = conn.get()
kwargs = model_to_dict(conn_info, exclude=['id', 'auth_type'])
auth_type = conn_info.auth_type
le = LoginExecution()
data = le.login_execution(auth_type=auth_type, **kwargs)
conn_info.status = data['status']
conn_info.save()
if data['status'] == 'succeed':
device.sys_hostname = data['sys_hostname']
device.mac_address = data['mac_address']
device.sn_number = data['sn_number']
device.os_type = data['os_type']
device.device_type = data['device_type']
device.save()
res['status'] = 'success'
except conn.model.DoesNotExist:
res['status'] = 'con_empty'
else:
res['status'] = 'con_empty'
return JsonResponse(res)

18
apps/utils/db_utils.py Normal file
View File

@@ -0,0 +1,18 @@
import pymongo
class MongodbDriver(object):
def __init__(self, db='device', collection='change_compare'):
self.client = pymongo.MongoClient('127.0.0.1', 27017)
self.db = self.client[db]
self.col = self.db[collection]
def insert(self, content):
return self.col.insert(content)
def find(self, sort_by, **filters,):
data = self.col.find(filters)
if sort_by:
data.sort(sort_by, pymongo.DESCENDING)
return data

View File

@@ -44,6 +44,7 @@ INSTALLED_APPS = [
'simple_history',
'system',
'cmdb',
'cmdb.templatetags',
]
MIDDLEWARE = [

File diff suppressed because it is too large Load Diff

View File

@@ -40,7 +40,7 @@
</div>
<label class="col-sm-2 control-label">描述信息</label>
<div class="col-sm-3">
<input class="form-control" id="desc" name="desc" type="text" />
<input class="form-control" id="desc" name="desc" type="text" value="{{ code.desc }}"/>
</div>
</div>

View File

@@ -339,5 +339,9 @@
}
});
}
function doDetail(id){
window.location.href="{% url 'cmdb:eam-device-detail' %}?id="+id;
}
</script>
{% endblock %}

View File

@@ -0,0 +1,316 @@
{% extends "base-left.html" %}
{% load staticfiles %}
{% load extra_tags %}
{% block css %}
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
{% endblock %}
{% block content %}
<section class="content">
<div class="nav-tabs-custom">
<ul class="nav nav-tabs">
<li class="active"><a href="#activity" data-toggle="tab">设备详情</a></li>
<li><a href="#history" data-toggle="tab">历史纪录</a></li>
</ul>
<div class="tab-content">
<div class="active tab-pane" id="activity">
<div class="box-body no-padding">
<div class="btn-group pull-right margin">
<button type="button" class="btn btn-primary btn-sm margin-r-5"
onclick="doUpload({{ device.id }})">
<i class="fa fa-cloud-upload"> 上传资料</i>
</button>
<button type="button" class="btn btn-primary btn-sm margin-r-5" title="认证管理" onclick="doDevice2Connection({{ device.id }})">
<i class="fa fa-user"> 认证管理</i>
</button>
<button type="button" class="btn btn-primary btn-sm margin-r-5" title="自动更新" onclick="doAutoUpdate({{ device.id }})">
<i class="fa fa-circle-o-notch"> 自动更新</i>
</button>
<button type="button" class="btn btn-primary btn-sm margin-r-5" title="编辑" onclick="doUpdate({{ device.id }})">
<i class="fa fa-pencil"> 编辑</i>
</button>
<button type="button" id="btnReturn" class="btn btn-primary btn-sm">
<i class="fa fa-arrow-left"></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>系统类型</strong></td>
<td>{{ device.os_type }}</td>
<td><strong>设备类型</strong></td>
<td>{{ device.device_type }}</td>
</tr>
<tr class="info">
<td><strong>设备地址</strong></td>
<td>{{ device.hostname }}</td>
<td><strong>MAC地址</strong></td>
<td>{{ device.mac_address }}</td>
</tr>
<tr>
<td><strong>网络类型</strong></td>
<td>{% get_con all_code device.network_type 'value' %}</td>
<td><strong>服务类型</strong></td>
<td>{% get_con all_code device.service_type 'value' %}</td>
</tr>
<tr class="info">
<td><strong>业务类型</strong></td>
<td>{% get_con all_code device.operation_type 'value' %}</td>
<td><strong>机柜信息</strong></td>
<td>{% get_con all_cabinet device.dev_cabinet 'number' %}</td>
</tr>
<tr>
<td><strong>购买日期</strong></td>
<td>{{ device.buyDate }}</td>
<td><strong>质保日期</strong></td>
<td>{{ device.warrantyDate }}</td>
</tr>
<tr class="info">
<td><strong>所属</strong></td>
<td>{% if device.parent %}
{{ device.parent.sys_hostname }}({{ device.parent.hostname }})
{% endif %}
</td>
<td><strong>责任人</strong></td>
<td>{% get_con all_user device.leader 'name' %}</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>{{ device.changed_by.name }}</td>
<td><strong></strong></td>
<td></td>
</tr>
<tr>
<td><strong>备注信息</strong></td>
<td colspan="3">{{ device.desc }}</td>
</tr>
</tbody>
</table>
</div>
<br>
<div class="box-footer">
<ul class="mailbox-attachments clearfix" id="imageContainer">
{% for file in all_file %}
<li class="imageItem">
<div class="mailbox-attachment-info">
<a href="/media/{{ file.file_content }}" target="_blank"><i
class="fa fa-file-text"></i>
<small>{{ file.file_content|cut:'asset_file/' }}</small>
</a>
<span class="mailbox-attachment-size">
<b>上传人</b>{{ file.upload_user }}
<a href="/media/{{ file.file_content }}" download="{{ file.file_content }}"
class="btn btn-primary btn-xs pull-right">
<i class="fa fa-cloud-download" title="下载文件"></i>
</a>
<button class="btn btn-adn btn-xs pull-right margin-r-5"
onclick="doDelete({{ file.id }})">
<i class="fa fa-trash" title="删除文件"> </i>
</button>
</span>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- /.tab-pane -->
<div class="tab-pane" id="history">
<div class="box-body">
<ul class="todo-list">
{% for log in logs %}
<li>
<!-- drag handle -->
<span class="handle">
<small class="text-maroon">
<i class="glyphicon glyphicon-time"></i>
{{ log.history_date }}
&nbsp;&nbsp;
{{ log.changed_by }}
&nbsp;&nbsp;
{{ log.history_type }}
</small>
</span>
<span class="text-sm">
{{ log.changes | compare_result }}
</span>
<!--
<button class="btn btn-xs btn-danger pull-right">还原数据</button>
-->
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- /.tab-pane -->
</div>
<!-- /.tab-content -->
</div>
</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-EAM').addClass('active');
$('#CMDB-EAM-DEVICE').addClass('active');
});
$("#btnReturn").click(function(){
history.back();
});
// 资产文件瀑布流
$('#imageContainer').masonry({
columnWidth: 10,
itemSelector: '.imageItem'
});
//上传资料
function doUpload(id) {
var div = layer.open({
type: 2,
title: '上传设备文件',
shadeClose: false,
maxmin: true,
area: ['770px', '400px'],
content: ["/cmdb/eam/device/upload/" + '?id=' + id],
end: function () {
window.location.reload();
}
});
layer.full(div)
}
//删除文件
function doDelete(id) {
layer.alert('确定删除吗?', {
title: '提示'
, icon: 3
, time: 0
, btn: ['YES', 'NO']
, yes: function (index) {
layer.close(index);
$.ajax({
type: "POST",
url: "{% url 'cmdb:eam-device-file_delete' %}",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
cache: false,
success: function (msg) {
if (msg.result) {
layer.alert('删除成功', {icon: 1}, function () {
parent.location.reload()
});
} else {
//alert(msg.message);
layer.alert('删除失败', {icon: 2});
}
return;
}
});
}
});
}
//自动更新设备信息
function doAutoUpdate(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",
data: {"id": id, csrfmiddlewaretoken: '{{ csrf_token }}'},
url: "{% url 'cmdb:eam-device-auto_update_device_info' %}",
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}, function(){
parent.location.reload()
});
}
else if (msg.status == 'con_empty') {
layer.alert('请先添加认证信息!', {icon: 4});
}
else {
//alert(msg.message);
layer.alert('设备信息更新失败!', {icon: 2});
}
return;
}
});
}
});
}
//修改数据
function doUpdate(id) {
var div=layer.open({
type: 2,
title: '编辑',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-device-update' %}" + '?id=' + id, 'no'],
end: function () {
window.location.reload();
}
});
layer.full(div )
}
function doDevice2Connection(id) {
layer.open({
type: 2,
title: '认证管理',
shadeClose: false,
maxmin: true,
area: ['800px', '400px'],
content: ["{% url 'cmdb:eam-device-device2connection' %}" + '?id=' + id, 'no'],
end: function () {
oDataTable.ajax.reload();
}
});
}
</script>
{% endblock %}

View File

@@ -0,0 +1,65 @@
{% extends 'base-layer.html' %}
{% load staticfiles %}
{% block css %}
<link rel="stylesheet" href="{%static 'plugins/select2/select2.min.css' %}">
<link rel="stylesheet" href="{% static 'js/plugins/layer/skin/layer.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap/css/bootstrap-datetimepicker.min.css' %}">
<link rel="stylesheet" href="{% static 'plugins/fileinput/fileinput.css' %}">
{% endblock %}
{% block main %}
<div class="box box-danger">
<form class="form-horizontal" id="addForm" method="post">
{% csrf_token %}
<div class="box-body">
<fieldset>
<div class="form-group has-feedback">
<div class="col-sm-12">
<div class="file-loading">
<input id="file_content" name="file_content" type="file" multiple="multiple"/>
</div>
</div>
</div>
</fieldset>
<fieldset>
<div class="form-group has-feedback">
<label class="col-sm-2 control-label"></label>
<div class="col-sm-12">
<p class="text-red">同时最多可上传4个文件支持文件格式png", "jpg", "gif", "zip", "rar"大小不得超过10M</p>
</div>
</div>
</fieldset>
</div>
</form>
</div>
{% endblock %}
{% block javascripts %}
<script src="{% static 'plugins/fileinput/fileinput.js' %}"></script>
<script src="{% static 'plugins/fileinput/zh.js' %}"></script>
<script type="text/javascript">
//上传文件
$(document).on('ready', function() {
$("#file_content").fileinput({
language: "zh",
showUpload: true,
allowedFileExtensions: ["png", "jpg", "gif", "zip", "rar"],
uploadUrl: "{% url 'cmdb:eam-device-upload' %}",
uploadExtraData: {
'csrfmiddlewaretoken': '{{ csrf_token }}',
'device': '{{ device.id }}',
'upload_user': '{{ request.user.name }}',
},
maxFileCount: 4,
autoReplace: true,
maxFileSize: 10240,
});
});
</script>
{% endblock %}