Web开发基础之CMDB系统开发之三
一,资产主机模块
1,资产主机模型设计
主机展示信息表
常规性的收集:
主机模型:IP地址、连接端口、操作系统(Linux,Windows,Mac)、机器类型(虚拟机,物理机)、主机名、CPU型号、CPU物理个数、内存信息、系统平台、系统平台位数、UUID、SN、探测状态、创建主机时间、更新主机时间
还要收集硬盘信息,网卡信息,注意一台主机,硬盘可能有多个,网卡也会有多个。所以要单独把硬盘和网卡各单独设计成一张表,然后使用一对多跟主机模型连接起来。
硬盘模型:硬盘名、硬盘大小、外键关联(server)
网卡模型:网卡名、Ip地址、外键关联(server)
还需要一张探测表,把探测时候的一些信息记录下来,比如在探测的时候,要输入主机IP,端口,要把这些信息收集下来,并且跟主机表进行关联。
创建新应用resources
python manage.py startapp resources
创建模板文件夹templates和路由urls
settings设置应用
①创建数据模型
class Idc(models.Model): name = models.CharField(max_length=10, verbose_name='机房简称') name_cn = models.CharField(max_length=32, verbose_name='机房名称') address = models.CharField(max_length=64, verbose_name='机房地址') phone = models.CharField(max_length=11, verbose_name='机房座机电话') username = models.CharField(max_length=32, verbose_name='机房负责人姓名') username_email = models.EmailField(verbose_name='机房负责人邮箱') username_phone = models.CharField(max_length=11, verbose_name='机房负责人手机号') class Meta: default_permissions = [] permissions = ( ('add_idc', '添加IDC'), ('show_idc', '查看IDC'), ('delete_idc', '删除IDC'), ('update_idc', '更新IDC'), )
迁移数据
D:\web\syscmdb>python manage.py makemigrations resources Migrations for 'resources': resources\migrations\0001_initial.py - Create model Idc D:\web\syscmdb>python manage.py migrate resources Operations to perform: Apply all migrations: resources Running migrations: Applying resources.0001_initial... OK
查看数据表
②路由
为了防止模板页面报错一次性把路由加齐了
③视图
④模板
注意修改几处名称不对应的地方
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>机房展示</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资源管理</a> </li> <li> <a href="">机房展示</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="col-lg-12"> <div class="ibox float-e-margins"> <div class="ibox-title"> <h5>机房展示 </h5> </div> <div class="ibox-content"> <a class="btn btn-w-m btn-primary" href="{% url 'idc_add' %}">创建机房</a> <table class="table table-striped"> <thead> <tr> <th class="text-center">机房简称</th> <th class="text-center">机房名称</th> <th class="text-center">机房地址</th> <th class="text-center">机房座机电话</th> <th class="text-center">机房负责人姓名</th> <th class="text-center">机房负责人邮箱</th> <th class="text-center">机房负责人手机号</th> <th class="text-center">操作</th> </tr> </thead> <tbody> {% for object in object_list %} <tr> <td class="text-center">{{ object.name }}</td> <td class="text-center">{{ object.name_cn }}</td> <td class="text-center">{{ object.address }}</td> <td class="text-center">{{ object.phone }}</td> <td class="text-center">{{ object.username }}</td> <td class="text-center">{{ object.username_email }}</td> <td class="text-center">{{ object.username_phone }}</td> <td class="text-center"> <a href="{% url 'idc_update' %}?id={{ object.id }}" class="btn btn-primary btn-sm">更新</a> <a class="btn btn-danger btn-sm" onclick="idc_delete({{ object.id }})">删除</a> </td> </tr> {% endfor %} </tbody> </table> <center> <div class="btn-group"> {% if page_obj.has_previous %} <a href="{% url 'idc_list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-white">上一页</a> {% endif %} {% for page in page_range %} {% if page_obj.number == page %} <a class="btn btn-white active">{{ page }}</a> {% else %} <a class="btn btn-white" href="{% url 'idc_list' %}?page={{ page }}">{{ page }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="{% url 'idc_list' %}?page={{ page_obj.next_page_number }}" class="btn btn-white">下一页</a> {% endif %} </div> </center> </div> </div> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> function idc_delete(id) { var data = {}; data['id'] = id; data['csrfmiddlewaretoken'] = "{{ csrf_token }}"; $.ajax({ url: '{% url 'idc_delete' %}', type: 'post', data: data, success: function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.reload() }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } } }) }; </script> {% endblock %}
修改基础模板base.html把IDC机房链接加入
MySQL插入测试数据
mysql> insert into resources_idc values (null,'aliyun','阿里云','杭州','01011111111','张大妈','zhangdama@qq.com','13333333333');
页面查看
3,添加资产机房
①路由
②视图
class IdcAddView(TemplateView): template_name = 'idc_add.html' def post(self, request): data = request.POST print(data) res = {'status': 0, 'msg': '添加成功'} try: idc = Idc() idc.name = data.get('name') idc.name_cn = data.get('name_cn') idc.address = data.get('address') idc.phone = data.get('phone') idc.username = data.get('username') idc.username_email = data.get('username_email') idc.username_phone = data.get('username_phone') idc.save() except Exception as e: print(e) res = {'status': 1, 'msg': '添加失败'} return JsonResponse(res)
③模板
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>创建机房</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资产管理</a> </li> <li> <a href="">创建机房</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="ibox-content"> <form id="submit_form" class="form-horizontal"> {% csrf_token %} <div class="form-group"><label class="col-sm-2 control-label">机房简称</label> <div class="col-sm-6"><input type="text" class="form-control" name="name"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房名称</label> <div class="col-sm-6"><input type="text" class="form-control" name="name_cn"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房地址</label> <div class="col-sm-6"><input type="text" class="form-control" name="address"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房座机电话</label> <div class="col-sm-6"><input type="text" class="form-control" name="phone"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房负责人姓名</label> <div class="col-sm-6"><input type="text" class="form-control" name="username"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房负责人邮箱</label> <div class="col-sm-6"><input type="email" class="form-control" name="username_email"></div> </div> <div class="form-group"><label class="col-sm-2 control-label">机房负责人手机号</label> <div class="col-sm-6"><input type="text" class="form-control" name="username_phone"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"> <div class="col-sm-4 col-sm-offset-2"> <button class="btn btn-white" type="submit">取消</button> <button class="btn btn-primary" type="submit">保存更改</button> </div> </div> </form> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/validate/jquery.validate.js"></script> <script src="/static/js/plugins/validate/messages_zh.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> $(document).ready(function () { $("#submit_form").validate({ rules: { name: { required: true }, username: { required: true }, name_cn: { required: true }, address: { required: true }, username_email: { required: true }, username_phone: { required: true }, phone: { required: true } }, submitHandler: function () { var str = $('#submit_form').serialize(); $.post('{% url 'idc_add' %}', str, function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.href = '{% url 'idc_list' %}'; }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } }); } }); }); </script> {% endblock %}
页面显示
页面查看heards可以看到表单提交的页面信息
补充:后端介绍json转字典
修改前端在load_js内使用以下代码传递json数据
<script> $(document).ready(function () { $("#submit_form").validate({ rules: { name: { required: true }, username: { required: true }, name_cn: { required: true }, address: { required: true }, username_email: { required: true }, username_phone: { required: true }, phone: { required: true } }, submitHandler: function () { //创建对象 var formObject = {}; //收集表单元素 表单数组 var formArray = $("#submit_form").serializeArray(); console.log(formArray) //json对象 $.each(formArray, function (i, item) { formObject[item.name] = item.value; }); console.log(formObject) //json字符串 var formJson = JSON.stringify(formObject); console.log(formJson) $.post('{% url 'idc_add' %}', formJson, function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.href = '{% url 'idc_list' %}'; }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } }); } }); }); </script>
修改settings.py注释以下内容
修改视图
class IdcAddView(TemplateView): template_name = 'idc_add.html' def post(self, request): # 接收到json字符串 data = request.body # 转为json对象 字典 data = json.loads(data) print(data) # 处理不需要数据 csrftoken data.pop('csrfmiddlewaretoken') print(data) res = {'status': 0, 'msg': '添加成功'} try: Idc.objects.create(**data) except Exception as e: print(e) res = {'status': 1, 'msg': '添加失败'} return JsonResponse(res)
添加效果一样
4,更新资产机房
①路由
②视图
class IdcUpdateView(View): def get(self, request): id = request.GET.get('id') idc_obj = Idc.objects.get(id=id) return render(request, 'idc_update.html', {'idc_obj': idc_obj}) def post(self, request): data = request.POST res = {'status': 0, 'msg': '修改成功'} try: idc = Idc.objects.get(id=request.POST.get('id')) idc.name = data.get('name') idc.name_cn = data.get('name_cn') idc.address = data.get('address') idc.phone = data.get('phone') idc.username = data.get('username') idc.username_email = data.get('username_email') idc.username_phone = data.get('username_phone') idc.save() except Exception as e: print(e) res = {'status': 1, 'msg': '修改失败'} return JsonResponse(res)
③模板
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>更新机房</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资产管理</a> </li> <li> <a href="">更新机房</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="ibox-content"> <form id="submit_form" class="form-horizontal"> {% csrf_token %} <div class="form-group"><label class="col-sm-2 control-label">机房简称</label> <div class="col-sm-6"><input type="text" class="form-control" name="name" value="{{ idc_obj.name }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房名称</label> <div class="col-sm-6"><input type="text" class="form-control" name="name_cn" value="{{ idc_obj.name_cn }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房地址</label> <div class="col-sm-6"><input type="text" class="form-control" name="address" value="{{ idc_obj.address }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房座机电话</label> <div class="col-sm-6"><input type="text" class="form-control" name="phone" value="{{ idc_obj.phone }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房负责人姓名</label> <div class="col-sm-6"><input type="text" class="form-control" name="username" value="{{ idc_obj.username }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">机房负责人邮箱</label> <div class="col-sm-6"><input type="email" class="form-control" name="username_email" value="{{ idc_obj.username_email }}"></div> </div> <div class="form-group"><label class="col-sm-2 control-label">机房负责人手机号</label> <div class="col-sm-6"><input type="text" class="form-control" name="username_phone" value="{{ idc_obj.username_phone }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"> <div class="col-sm-4 col-sm-offset-2"> <input type="hidden" value="{{ idc_obj.id }}" name="id"> <button class="btn btn-white" type="submit">取消</button> <button class="btn btn-primary" type="submit">保存更改</button> </div> </div> </form> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/validate/jquery.validate.js"></script> <script src="/static/js/plugins/validate/messages_zh.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> $(document).ready(function () { $("#submit_form").validate({ rules: { name: { required: true }, username: { required: true }, name_cn: { required: true }, address: { required: true }, username_email: { required: true }, username_phone: { required: true }, phone: { required: true } }, submitHandler: function () { var str = $('#submit_form').serialize(); $.post('{% url 'idc_update' %}', str, function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.href = '{% url 'idc_list' %}'; }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } }); } }); }); </script> {% endblock %}
页面显示
5,删除资产机房
①路由
②视图
class IdcDeleteView(View): def post(self, request): res = {'status': 0, 'msg': '删除成功'} idc_id = request.POST.get('id') print(idc_id) try: Idc.objects.get(id=idc_id).delete() except Idc.DoesNotExist: res = {'status': 1, 'msg': '机房不存在,删除失败'} except Exception: res = {'status': 1, 'msg': '未知错误,请联系管理员'} return JsonResponse(res)
删除模板没有单独页面
{% block load_js %} <script src="/static/js/plugins/validate/jquery.validate.js"></script> <script src="/static/js/plugins/validate/messages_zh.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> function idc_delete(id) { var data = {}; data['id'] = id; data['csrfmiddlewaretoken'] = "{{ csrf_token }}"; $.ajax({ url: '{% url 'idc_delete' %}', type: 'post', data: data, success: function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.reload() }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } } }) }; </script> {% endblock %}
页面显示
四,资产用户模块
1,资产用户模型设计
class ServerUser(models.Model): name = models.CharField(max_length=32, verbose_name='名称') username = models.CharField(max_length=32, verbose_name='系统用户') password = models.CharField(max_length=32, verbose_name='系统密码') info = models.TextField(verbose_name='备注') class Meta: default_permissions = [] permissions = ( ('add_serveruser', '添加资产用户'), ('show_serveruser', '查看资产用户'), ('delete_serveruser', '删除资产用户'), ('update_serveruser', '更新资产用户'), )
迁移数据
D:\web\syscmdb>python manage.py makemigrations resources Migrations for 'resources': resources\migrations\0002_serveruser.py - Create model ServerUser D:\web\syscmdb>python manage.py migrate resources Operations to perform: Apply all migrations: resources Running migrations: Applying resources.0002_serveruser... OK
查看数据表
①路由
②视图
③模板
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>资产用户展示</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资源管理</a> </li> <li> <a href="">资产用户展示</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="col-lg-12"> <div class="ibox float-e-margins"> <div class="ibox-title"> <h5>资产用户展示 </h5> </div> <div class="ibox-content"> <a class="btn btn-w-m btn-primary" href="{% url 'serveruser_add' %}">创建资产用户</a> <table class="table table-striped"> <thead> <tr> <th class="text-center">名称</th> <th class="text-center">系统用户</th> <th class="text-center">备注</th> <th class="text-center">操作</th> </tr> </thead> <tbody> {% for object in object_list %} <tr> <td class="text-center">{{ object.name }}</td> <td class="text-center">{{ object.username }}</td> <td class="text-center">{{ object.info }}</td> <td class="text-center"> <a href="{% url 'serveruser_update' %}?id={{ object.id }}" class="btn btn-primary btn-sm">更新</a> <a class="btn btn-danger btn-sm" onclick="serveruser_delete({{ object.id }})">删除</a> </td> </tr> {% endfor %} </tbody> </table> <center> <div class="btn-group"> {% if page_obj.has_previous %} <a href="{% url 'serveruser_list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-white">上一页</a> {% endif %} {% for page in page_range %} {% if page_obj.number == page %} <a class="btn btn-white active">{{ page }}</a> {% else %} <a class="btn btn-white" href="{% url 'serveruser_list' %}?page={{ page }}">{{ page }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="{% url 'serveruser_list' %}?page={{ page_obj.next_page_number }}" class="btn btn-white">下一页</a> {% endif %} </div> </center> </div> </div> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> function serveruser_delete(id) { var data = {}; data['id'] = id; data['csrfmiddlewaretoken'] = "{{ csrf_token }}"; $.ajax({ url: '{% url 'serveruser_delete' %}', type: 'post', data: data, success: function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.reload() }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } } }) }; </script> {% endblock %}
页面查看
3,添加资产用户
①路由
②视图
class ServerUserAddView(TemplateView): template_name = 'serveruser_add.html' def post(self, request): data = request.POST print(data) res = {'status': 0, 'msg': '添加成功'} try: user = ServerUser() user.name = data.get('name') user.username = data.get('username') user.password = data.get('password') user.info = data.get('info') user.save() except Exception as e: print(e) res = {'status': 1, 'msg': '添加失败'} return JsonResponse(res)
③模板
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>创建资产用户</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资产管理</a> </li> <li> <a href="">创建资产用户</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="ibox-content"> <form id="submit_form" class="form-horizontal"> {% csrf_token %} <div class="form-group"><label class="col-sm-2 control-label">名称</label> <div class="col-sm-6"><input type="text" class="form-control" name="name"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">系统用户</label> <div class="col-sm-6"><input type="text" class="form-control" name="username"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">密码</label> <div class="col-sm-6"><input type="password" class="form-control" name="password"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">备注</label> <div class="col-sm-6"><input type="text" class="form-control" name="info"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"> <div class="col-sm-4 col-sm-offset-2"> <button class="btn btn-white" type="submit">取消</button> <button class="btn btn-primary" type="submit">保存更改</button> </div> </div> </form> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/validate/jquery.validate.js"></script> <script src="/static/js/plugins/validate/messages_zh.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> $(document).ready(function () { $("#submit_form").validate({ rules: { name: { required: true }, username: { required: true }, name_cn: { required: true }, address: { required: true }, username_email: { required: true }, username_phone: { required: true }, phone: { required: true } }, submitHandler: function () { var str = $('#submit_form').serialize(); $.post('{% url 'serveruser_add' %}', str, function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.href = '{% url 'serveruser_list' %}'; }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } }); } }); }); </script> {% endblock %}
页面显示
4,更新资产用户
①路由
②视图
class ServerUserUpdateView(View): def get(self, request): return render(request, 'serveruser_update.html', {'serveruser_obj': ServerUser.objects.get(id=request.GET.get('id'))}) def post(self, request): data = request.POST res = {'status': 0, 'msg': '修改成功'} try: user = ServerUser() user.name = data.get('name') user.username = data.get('username') user.password = data.get('password') user.info = data.get('info') user.save() except Exception as e: print(e) res = {'status': 1, 'msg': '修改失败'} return JsonResponse(res)
③模板
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>更新资产用户</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资产管理</a> </li> <li> <a href="">更新资产用户</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="ibox-content"> <form id="submit_form" class="form-horizontal"> {% csrf_token %} <div class="form-group"><label class="col-sm-2 control-label">名称</label> <div class="col-sm-6"><input type="text" class="form-control" name="name" value="{{ serveruser_obj.name }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">系统用户</label> <div class="col-sm-6"><input type="text" class="form-control" name="username" value="{{ serveruser_obj.username }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">密码</label> <div class="col-sm-6"><input type="password" class="form-control" name="password"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"><label class="col-sm-2 control-label">备注</label> <div class="col-sm-6"><input type="text" class="form-control" name="info" value="{{ serveruser_obj.info }}"></div> </div> <div class="hr-line-dashed"></div> <div class="form-group"> <div class="col-sm-4 col-sm-offset-2"> <input type="hidden" value="{{ serveruser_obj.id }}" name="id"> <button class="btn btn-white" type="submit">取消</button> <button class="btn btn-primary" type="submit">保存更改</button> </div> </div> </form> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/validate/jquery.validate.js"></script> <script src="/static/js/plugins/validate/messages_zh.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> $(document).ready(function () { $("#submit_form").validate({ rules: { name: { required: true }, username: { required: true }, name_cn: { required: true }, address: { required: true }, username_email: { required: true }, username_phone: { required: true }, phone: { required: true } }, submitHandler: function () { var str = $('#submit_form').serialize(); $.post('{% url 'serveruser_update' %}', str, function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.href = '{% url 'serveruser_list' %}'; }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } }); } }); }); </script> {% endblock %}
页面显示
5,删除资产用户
①路由
②视图
class ServerUserDeleteView(View): def post(self, request): data = request.POST res = {'status': 0, 'msg': '删除成功'} try: ServerUser.objects.get(id=data.get('id')).delete() except Exception as e: print(e) res = {'status': 1, 'msg': '删除失败'} return JsonResponse(res)
③模板
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>资产用户展示</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资源管理</a> </li> <li> <a href="">资产用户展示</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="col-lg-12"> <div class="ibox float-e-margins"> <div class="ibox-title"> <h5>资产用户展示 </h5> </div> <div class="ibox-content"> <a class="btn btn-w-m btn-primary" href="{% url 'serveruser_add' %}">创建资产用户</a> <table class="table table-striped"> <thead> <tr> <th class="text-center">名称</th> <th class="text-center">系统用户</th> <th class="text-center">备注</th> <th class="text-center">操作</th> </tr> </thead> <tbody> {% for object in object_list %} <tr> <td class="text-center">{{ object.name }}</td> <td class="text-center">{{ object.username }}</td> <td class="text-center">{{ object.info }}</td> <td class="text-center"> <a href="{% url 'serveruser_update' %}?id={{ object.id }}" class="btn btn-primary btn-sm">更新</a> <a class="btn btn-danger btn-sm" onclick="serveruser_delete({{ object.id }})">删除</a> </td> </tr> {% endfor %} </tbody> </table> <center> <div class="btn-group"> {% if page_obj.has_previous %} <a href="{% url 'serveruser_list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-white">上一页</a> {% endif %} {% for page in page_range %} {% if page_obj.number == page %} <a class="btn btn-white active">{{ page }}</a> {% else %} <a class="btn btn-white" href="{% url 'serveruser_list' %}?page={{ page }}">{{ page }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="{% url 'serveruser_list' %}?page={{ page_obj.next_page_number }}" class="btn btn-white">下一页</a> {% endif %} </div> </center> </div> </div> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/validate/jquery.validate.js"></script> <script src="/static/js/plugins/validate/messages_zh.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script> function serveruser_delete(id) { var data = {}; data['id'] = id; data['csrfmiddlewaretoken'] = "{{ csrf_token }}"; $.ajax({ url: '{% url 'serveruser_delete' %}', type: 'post', data: data, success: function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.reload() }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } } }) }; </script> {% endblock %}
页面显示
资产主机模块设计
主机展示信息表
常规性的收集:
主机模型:IP地址、连接端口、操作系统(Linux,Windows,Mac)、机器类型(虚拟机,物理机)、主机名、CPU型号、CPU物理个数、内存信息、系统平台、系统平台位数、UUID、SN、探测状态、创建主机时间、更新主机时间
还要收集硬盘信息,网卡信息,注意一台主机,硬盘可能有多个,网卡也会有多个。所以要单独把硬盘和网卡各单独设计成一张表,然后使用一对多跟主机模型连接起来。
硬盘模型:硬盘名、硬盘大小、外键关联(server)
网卡模型:网卡名、Ip地址、外键关联(server)
还需要一张探测表,把探测时候的一些信息记录下来,比如在探测的时候,要输入主机IP,端口,要把这些信息收集下来,并且跟主机表进行关联。
①创建模型
class Server(models.Model): hostname = models.CharField(max_length=32, verbose_name='主机名') cpu_info = models.CharField(max_length=100, verbose_name='CPU信息') cpu_count = models.IntegerField(verbose_name='CPU个数') mem_info = models.CharField(max_length=32, verbose_name='内存信息') os_system = models.CharField(max_length=32, verbose_name='系统平台') os_system_num = models.IntegerField(verbose_name='系统平台位数') uuid = models.CharField(max_length=64, verbose_name='uuid') sn = models.CharField(max_length=100, verbose_name='sn') scan_status_list = [ (0, '连接异常'), (1, '连接正常') ] scan_status = models.IntegerField(choices=scan_status_list, verbose_name='探测状态') create_date = models.DateTimeField(auto_now_add=True, verbose_name='创建主机时间') update_date = models.DateTimeField(auto_now=True, verbose_name='更新主机时间') # server_auto = models.OneToOneField('ServerAuto') class Disk(models.Model): name = models.CharField(max_length=32, verbose_name='硬盘名字') size = models.CharField(max_length=32, verbose_name='硬盘大小') server = models.ForeignKey('Server') class NetWork(models.Model): name = models.CharField(max_length=32, verbose_name='网卡名字') ip_address = models.CharField(max_length=32, verbose_name='网卡地址') server = models.ForeignKey('Server')
迁移数据
查看数据库表
查看建表语句
服务器表正常创建
硬盘表和网卡表关联外键至服务器表
关联关系
路由
视图
class ServerListView(ListView): template_name = 'server_list.html' model = Server
模板
先把找不到的页面例如add和update相关的链接代码删除
{% extends 'base.html' %} {% block load_css %} <link href="/static/css/plugins/sweetalert/sweetalert.css" rel="stylesheet"> <link href="/static/css/plugins/ladda/ladda-themeless.min.css" rel="stylesheet"> <link href="/static/css/plugins/toastr/toastr.min.css" rel="stylesheet"> {% endblock %} {% block mbx %} <div class="row wrapper border-bottom white-bg page-heading"> <div class="col-sm-4"> <h2>主机展示</h2> <ol class="breadcrumb"> <li> <a href="{% url 'index' %}">首页</a> </li> <li> <a href="">资源管理</a> </li> <li> <a href="">主机展示</a> </li> </ol> </div> </div> {% endblock %} {% block body %} <div class="col-lg-12"> <div class="ibox float-e-margins"> <div class="ibox-title"> <h5>主机展示 </h5> </div> <div class="ibox-content"> <button type="button" class="btn btn-sm btn-primary" data-toggle="modal" data-target="#myModal"> 添加服务器 </button> <div class="modal inmodal fade" id="myModal" tabindex="-1" role="dialog" aria-hidden="true"> <div class="modal-dialog modal-sm"> <div class="modal-content"> <div class="modal-header"> <button type="button" class="close" data-dismiss="modal"><span aria-hidden="true">×</span><span class="sr-only">Close</span> </button> <h4 class="modal-title">添加服务器</h4> </div> <div class="modal-body"> <form id="create_host_form"> {% csrf_token %} <div class="form-group"> <label class="col-md-12 control-label">主机IP</label> <input type="text" class="form-control" placeholder="主机IP" name="ip_inner"> </div> <div class="form-group"> <label class="col-md-12 control-label">主机端口</label> <input type="text" class="form-control" placeholder="主机端口(基于SSH)" name="port"> </div> <div class="form-group"> <label class="col-md-12 control-label">系统用户</label> <select class="select2_demo_1 form-control" name="serveruser_id"> {% for system_user in systemuser_list %} <option value="{{ system_user.id }}">{{ system_user.name }}</option> {% endfor %} </select> </div> <div class="form-group"> <label class="col-md-12 control-label">操作系统</label> <select class="select2_demo_1 form-control" name="os_status"> {% for os_tuple in os_list %} <option value="{{ os_tuple.0 }}">{{ os_tuple.1 }}</option> {% endfor %} </select> </div> <div class="form-group"> <label class="col-md-12 control-label">机器类型</label> <select class="select2_demo_1 form-control" name="system_status"> {% for system_tuple in system_list %} <option value="{{ system_tuple.0 }}">{{ system_tuple.1 }}</option> {% endfor %} </select> </div> </form> </div> <div class="modal-footer"> <button type="button" class="btn btn-white" data-dismiss="modal">关闭</button> <button type="button" class="btn btn-primary" data-style="expand-right" id="submit_host">提交 </button> </div> </div> </div> </div> <table class="table table-striped"> <thead> <tr> <th class="text-center">主机名</th> <th class="text-center">CPU信息</th> <th class="text-center">CPU个数</th> <th class="text-center">内存信息</th> <th class="text-center">系统平台</th> <th class="text-center">系统平台位数</th> <th class="text-center">探测状态</th> <th class="text-center">操作</th> </tr> </thead> <tbody> {% for object in object_list %} <tr> <td class="text-center">{{ object.hostname }}</td> <td class="text-center">{{ object.cpu_info }}</td> <td class="text-center">{{ object.cpu_count }}</td> <td class="text-center">{{ object.mem_info }}</td> <td class="text-center">{{ object.os_system }}</td> <td class="text-center">{{ object.os_system_num }}</td> <td class="text-center">{{ object.scan_status }}</td> <td class="text-center"> <a class="btn btn-xs btn-info" onclick="server_flush({{ object.id }})">刷新探测</a> <a class="btn btn-danger btn-sm" onclick="">删除</a> </td> </tr> {% endfor %} </tbody> </table> <center> <div class="btn-group"> {% if page_obj.has_previous %} <a href="{% url 'server_list' %}?page={{ page_obj.previous_page_number }}" class="btn btn-white">上一页</a> {% endif %} {% for page in page_range %} {% if page_obj.number == page %} <a class="btn btn-white active">{{ page }}</a> {% else %} <a class="btn btn-white" href="{% url 'server_list' %}?page={{ page }}">{{ page }}</a> {% endif %} {% endfor %} {% if page_obj.has_next %} <a href="{% url 'server_list' %}?page={{ page_obj.next_page_number }}" class="btn btn-white">下一页</a> {% endif %} </div> </center> </div> </div> </div> {% endblock %} {% block load_js %} <script src="/static/js/plugins/ladda/spin.min.js"></script> <script src="/static/js/plugins/ladda/ladda.min.js"></script> <script src="/static/js/plugins/ladda/ladda.jquery.min.js"></script> <script src="/static/js/plugins/sweetalert/sweetalert.min.js"></script> <script src="/static/js/plugins/toastr/toastr.min.js"></script> <script> function idc_delete(id) { var data = {}; data['id'] = id; data['csrfmiddlewaretoken'] = "{{ csrf_token }}"; $.ajax({ url: '{% url 'idc_delete' %}', type: 'post', data: data, success: function (res) { if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.reload() }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } } }) }; $('#submit_host').click(function () { var btn = $(this).ladda(); btn.ladda('start'); var data = $('#create_host_form').serialize(); $.ajax({ url: '', type: 'post', data: data, success: function (res) { setTimeout(function () { btn.ladda('stop'); }, 200); if (res.status == 0) { swal({ title: res.msg, type: 'success', confirmButtonText: "OK" }, function () { window.location.href = '{% url 'server_list' %}'; }); } else { swal({ title: res.msg, type: 'error', confirmButtonText: "知道了" }); } } }) }); function server_flush(server_id) { var data = {}; data['server_id'] = server_id; data['csrfmiddlewaretoken'] = "{{ csrf_token }}"; toastr.options = { "closeButton": true, "debug": false, "progressBar": true, "preventDuplicates": false, "positionClass": "toast-top-right", "onclick": null, "showDuration": "400", "hideDuration": "1000", "timeOut": "1000", "extendedTimeOut": "1000", "showEasing": "swing", "hideEasing": "linear", "showMethod": "fadeIn", "hideMethod": "fadeOut", "onHidden": function () { window.location.reload(); } }; $.ajax({ url: '', type: 'post', data: data, success: function (res) { if (res.status == 0) { toastr.success('刷新探测', res.msg); } else { toastr.error('刷新探测', res.msg); } } }) } </script> {% endblock %}
页面显示
没有数据,后期通过检查脚本推送服务器的信息到cmdb系统中,然后就添加到资产主机数据表中。
资产主机探测功能设计
常规的信息收集,有两种方式,
一种是主动采集,一种是客户端收集。
主动探测:一般是通过,SNMP/SSH/TELNET等手段进行远程收集。
客户端采集:需要在每一个要部署的主机中部署一个客户端进行数据采集并且发送到远程服务端进行接收。
Linux客户端部署python3环境
推荐编译安装,本次使用yum安装
yum -y install python3 python3-devel
注意:需要安装python3-devel否则在使用pip3安装模块时会报错
psutil/_psutil_common.c:9:20: 致命错误:Python.h:没有那个文件或目录 #include <Python.h> ^ 编译中断。 error: command 'gcc' failed with exit status 1
收集系统信息python脚本
[root@localhost ~]# cat sysinfo.py #coding:utf-8 import psutil # 版本信息 import platform import os import json import requests import distro class GetData(object): def __init__(self): self.ret = {} def get_hostname(self): return platform.node() def get_cpu_info(self): ret = '' with open('/proc/cpuinfo') as f: for line in f: line_list = line.strip().split(':') key = line_list[0].strip() if key == 'model name': ret = line_list[1] return ret.strip() def get_cpu_count(self): return psutil.cpu_count(logical=False) def get_mem_info(self): return '%.2f' %(psutil.virtual_memory().total/1024/1024/1024) def get_disk_info(self): disk_name_list = [] disk_size_list = [] disk_name_file = os.popen("lsblk | grep disk | awk '{print $1}'") disk_size_file = os.popen("lsblk | grep disk | awk '{print $4}'") for disk_name in disk_name_file.readlines(): disk_name_list.append(disk_name.split()[0]) for disk_size in disk_size_file.readlines(): disk_size_list.append(disk_size.split()[0]) return dict(zip(disk_name_list, disk_size_list)) def get_ip_info(self): ret = {} for net_name in psutil.net_if_addrs(): ret[net_name] = psutil.net_if_addrs()[net_name][0].address return ret def get_os_system(self): #return platform.dist()[0] + ' ' + platform.dist()[1] return distro.linux_distribution()[0]+' '+distro.linux_distribution()[1] def get_os_system_num(self): return platform.architecture()[0].strip('bit') def get_uuid(self): uuid = os.popen('dmidecode -s system-uuid') return uuid.read().strip() def get_sn(self): sn_popen = os.popen("dmidecode -t 1 | grep 'Serial Number:'") sn = sn_popen.read() return sn.strip().strip('Serial Number:') def send_data(self): data_dict = GetData.__dict__ for key,value in data_dict.items(): if 'get_' in key: key = key.replace('get_', '') self.ret[key] = value(self) return self.ret def send_data(url, data): response = requests.post(url, data) print(response.text) if __name__ == '__main__': getdata = GetData().send_data() print(getdata) #send_data(url='http://192.168.1.5:8000/resourcesserver/data', data=json.dumps(getdata))
运行脚本可以收集系统信息
[root@localhost ~]# python3 sysinfo.py {'hostname': 'localhost.localdomain', 'cpu_info': 'Intel(R) Xeon(R) CPU E5-2640 v4 @ 2.40GHz', 'cpu_count': 1, 'mem_info': '5.67', 'disk_info': {'fd0': '4K', 'sda': '100G'}, 'ip_info': {'lo': '127.0.0.1', 'eth0': '192.168.1.100'}, 'os_system': 'CentOS Linux 7', 'os_system_num': '64', 'uuid': 'FDB93742-5ED6-6E02-7729-709EC5DE924B', 'sn': 'VMware-42 37 b9 fd d6 5e 02 6e-77 29 70 9e c5 de 92 4'}
注意:需要禁用CSRF否则会报以下错误
Forbidden (CSRF cookie not set.): /resourcesserver/data
如下图禁用
设置路由视图接收信息
①路由
②视图
注意:视图需要加载json模块
import json
修改脚本把发送地址修改一下
if __name__ == '__main__': getdata = GetData().send_data() print(getdata) send_data(url='http://192.168.1.5:8000/resourcesserver/data', data=json.dumps(getdata))
注意:如果这里resources和server有/分割则可能会出现页面404问题
设置python使用本机IP启动而不使用默认的127.0.0.1启动
注意:设置完这一步需要设置setting.py设置允许所有主机访问
运行脚本发送数据测试
还没有存储在MySQL 编辑视图接收数据至MySQL
class ServerApiView(View): def post(self, request): data = request.body data = json.loads(data) print(data) res = {'status': 0, 'msg': 'ok'} # 添加主机信息 try: server_obj = Server() server_obj.hostname = data['hostname'] server_obj.cpu_info = data['cpu_info'] server_obj.cpu_count = data['cpu_count'] server_obj.mem_info = data['mem_info'] server_obj.os_system = data['os_system'] server_obj.os_system_num = data['os_system_num'] server_obj.uuid = data['uuid'] server_obj.sn = data['sn'] server_obj.scan_status = 1 server_obj.save() # 添加网卡信息 for k, v in data['ip_info'].items(): NetWork.objects.create( name =k, ip_address = v, server = server_obj ) # 硬盘 for k, v in data['disk_info'].items(): Disk.objects.create( name =k, size = v, server = server_obj ) except Exception as e: print(e) res = {'status': 1, 'msg': 'error'} return JsonResponse(res)
再次运行python脚本
页面查看
多次运行会重复添加主机
修改视图 当重复添加时只改变修改的信息 不重复添加主机
class ServerApiView(View): def post(self, request): data = request.body data = json.loads(data) print(data) res = {'status': 0, 'msg': 'ok'} # 添加主机信息 try: try: server_obj = Server.objects.get(uuid=data['uuid']) server_obj.delete() except Exception as e: print(e) server_obj = Server() server_obj.network_set.all().delete() server_obj.disk_set.all().delete() server_obj.hostname = data['hostname'] server_obj.cpu_info = data['cpu_info'] server_obj.cpu_count = data['cpu_count'] server_obj.mem_info = data['mem_info'] server_obj.os_system = data['os_system'] server_obj.os_system_num = data['os_system_num'] server_obj.uuid = data['uuid'] server_obj.sn = data['sn'] server_obj.scan_status = 1 server_obj.save() # 添加网卡信息 for k, v in data['ip_info'].items(): NetWork.objects.create( name =k, ip_address = v, server = server_obj ) # 硬盘 for k, v in data['disk_info'].items(): Disk.objects.create( name =k, size = v, server = server_obj ) except Exception as e: print(e) res = {'status': 1, 'msg': 'error'} return JsonResponse(res)
try代码解析
try: server_obj = Server.objects.get(uuid=data['uuid']) server_obj.delete() except Exception as e: print(e) server_obj = Server()
执行server_obj = Server.objects.get(uuid=data['uuid']) 使用get方法只能匹配到一个条件匹配多个或者没有匹配到均报错 如果匹配到则代表数据库已经存在该主机
则删除该数据对象的数据
如果没有匹配在报错执行except语句 重新把Sever对象赋值给变量server_obj
不管是删除原有对象所有数据重新赋值还是新对象赋值都会更新数据库,不同的是如果是新对象则是新增,如果是老对象则是删除后新增
对于已有的主机会导致原主机的id发生变化
如果希望主机id不发生变化, 可以修改代码
class ServerApiView(View): def post(self, request): data = request.body data = json.loads(data) print(data) res = {'status': 0, 'msg': 'ok'} # 添加主机信息 try: try: server_obj = Server.objects.get(uuid=data['uuid']) server_id = server_obj.id server_obj.delete() server_obj.id = server_id except Exception as e: print(e) server_obj = Server() server_obj.network_set.all().delete() server_obj.disk_set.all().delete() server_obj.hostname = data['hostname'] server_obj.cpu_info = data['cpu_info'] server_obj.cpu_count = data['cpu_count'] server_obj.mem_info = data['mem_info'] server_obj.os_system = data['os_system'] server_obj.os_system_num = data['os_system_num'] server_obj.uuid = data['uuid'] server_obj.sn = data['sn'] server_obj.scan_status = 1 server_obj.save() # 添加网卡信息 for k, v in data['ip_info'].items(): NetWork.objects.create( name =k, ip_address = v, server = server_obj ) # 硬盘 for k, v in data['disk_info'].items(): Disk.objects.create( name =k, size = v, server = server_obj ) except Exception as e: print(e) res = {'status': 1, 'msg': 'error'} return JsonResponse(res)
修改以下代码
server_obj = Server.objects.get(uuid=data['uuid']) server_id = server_obj.id server_obj.delete() server_obj.id = server_id
当发现有重复的uuid则首先把该服务器的id取出来再删除,然后赋值给对象即保证了id不发生变化,否则会自增id
①主动发送检测脚本到被监控机 python sftp传输
②管理主机使用webssh可以实现一些开源的工具webssh(后端建立长连接)xtemjs(前端显示命令行)
③web端ftp
④接入健康的API获取数据通过highchart echart出监控图表