Browse Source
1.dnoe split stage; 2.add assets manager; 3.1-2never test; 4.before polling taget bak; and db update;master
28 changed files with 3502 additions and 406 deletions
@ -1,4 +1,4 @@ |
|||||
<?xml version="1.0" encoding="UTF-8"?> |
<?xml version="1.0" encoding="UTF-8"?> |
||||
<project version="4"> |
<project version="4"> |
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.8.20 (sftp://root@192.168.3.151:22/root/miniconda3/envs/py38/bin/python)" project-jdk-type="Python SDK" /> |
<component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.8.20 (sftp://root@192.168.204.135:22/root/ENTER/envs/py38/bin/python)" project-jdk-type="Python SDK" /> |
||||
</project> |
</project> |
@ -0,0 +1,178 @@ |
|||||
|
from mycode.DBManager import app_DBM |
||||
|
|
||||
|
class AssetsManager: |
||||
|
def __init__(self): |
||||
|
pass |
||||
|
|
||||
|
def __del__(self): |
||||
|
pass |
||||
|
|
||||
|
def get_IP_assets(self,IP,user,safe_rank): |
||||
|
ip_assets = [] |
||||
|
ip_assets = app_DBM.get_ip_assets_db(IP,user,safe_rank) |
||||
|
if not ip_assets: |
||||
|
ip_assets = [] |
||||
|
return ip_assets |
||||
|
|
||||
|
def get_IP_info(self,IP): |
||||
|
ip_info = app_DBM.get_ip_info_db(IP) |
||||
|
if not ip_info: |
||||
|
ip_info = [] |
||||
|
return ip_info |
||||
|
|
||||
|
def get_assets_users(self,uname): |
||||
|
assets_users = app_DBM.get_assets_users_db(uname) |
||||
|
if not assets_users: |
||||
|
assets_users = [] |
||||
|
return assets_users |
||||
|
|
||||
|
def update_assets_users(self,IP,owner_id,itype = 1): |
||||
|
return app_DBM.update_assets_users_db(IP,owner_id,itype) |
||||
|
|
||||
|
def get_port_latest(self,ip): |
||||
|
return app_DBM.get_port_latest_db(ip) |
||||
|
|
||||
|
def get_port_history(self,ip): |
||||
|
#拿ip_id |
||||
|
strsql = "select id,scan_count from ip_assets where ip_address = %s" |
||||
|
params = (ip,) |
||||
|
ip_data = app_DBM.safe_do_select(strsql,params,1) |
||||
|
ip_id = ip_data[0] |
||||
|
scan_count = ip_data[1] |
||||
|
# 1) 拿批次 |
||||
|
sql_batches = ''' |
||||
|
SELECT DISTINCT scan_count, scan_time |
||||
|
FROM port_assets |
||||
|
WHERE ip_id=%s |
||||
|
ORDER BY scan_count ASC; |
||||
|
''' |
||||
|
batches = app_DBM.safe_do_select(sql_batches, (ip_id,)) |
||||
|
times = [row[1] for row in batches] |
||||
|
counts = [row[0] for row in batches] |
||||
|
if len(times) != scan_count: |
||||
|
print(f"*****数据批次有问题") |
||||
|
# 2) 拿所有端口数据 |
||||
|
sql_ports = ''' |
||||
|
SELECT port, service, version, status, scan_count |
||||
|
FROM port_assets |
||||
|
WHERE ip_id=%s |
||||
|
ORDER BY port ASC, scan_count ASC; |
||||
|
''' |
||||
|
rows = app_DBM.safe_do_select(sql_ports, (ip_id,)) |
||||
|
# 3) 组织成: { port: { service, version, statuses: [...], changed: [...] } } |
||||
|
from collections import OrderedDict |
||||
|
port_dict = OrderedDict() |
||||
|
for port, service, version, status, sc in rows: |
||||
|
entry = port_dict.setdefault(port, { |
||||
|
'service': [], |
||||
|
'version': [], |
||||
|
'statuses': [], |
||||
|
'scancount': [], |
||||
|
'changed': [] |
||||
|
}) |
||||
|
entry['service'].append(service) |
||||
|
entry['version'].append(version) |
||||
|
entry['statuses'].append(status) |
||||
|
entry['scancount'].append(sc) |
||||
|
# 4) 计算 changed 数组:与前一批次对比 |
||||
|
for port, info in port_dict.items(): |
||||
|
service = info['service'] |
||||
|
version = info['version'] |
||||
|
statuses = info['statuses'] |
||||
|
#scancount = info['scancount'] #执行次序不用对比 |
||||
|
changed = [] |
||||
|
for i in range(len(service)): |
||||
|
if i == 0: |
||||
|
changed.append(0) # 第一个批次,默认无变化 |
||||
|
else: |
||||
|
if service[i] != service[i-1] or version[i] != version[i-1] or statuses[i] != statuses[i-1]: |
||||
|
changed.append(1) |
||||
|
else: |
||||
|
changed.append(0) |
||||
|
info['changed'] = changed |
||||
|
return times,port_dict |
||||
|
|
||||
|
def get_ip_url_latest(self,ip): |
||||
|
return app_DBM.get_ip_url_latest_db(ip) |
||||
|
|
||||
|
def get_ip_url_history(self,ip): |
||||
|
return app_DBM.get_ip_url_history_db(ip) |
||||
|
|
||||
|
def get_vul_data(self,ip,nodeName,vulType,vulLevel): |
||||
|
# 先获取该IP最新的task_id |
||||
|
task_id = app_DBM.get_last_task_by_ip(ip) |
||||
|
if not task_id: |
||||
|
return [] |
||||
|
vuls = app_DBM.get_task_vul(task_id, nodeName, vulType, vulLevel) |
||||
|
return vuls |
||||
|
|
||||
|
def del_ip_assets(self,ip): |
||||
|
bsuccess,error = app_DBM.del_ip_assets(ip) |
||||
|
return bsuccess,error |
||||
|
|
||||
|
def get_url_assets(self,url,owner,email): |
||||
|
url_assets = app_DBM.get_url_assets_db(url,owner,email) |
||||
|
return url_assets |
||||
|
|
||||
|
def get_url_to_ip(self,url_id): |
||||
|
last_to_ips,his_to_ip = app_DBM.get_url_to_ip_db(url_id) |
||||
|
return last_to_ips,his_to_ip |
||||
|
|
||||
|
def del_url_assets(self,url_id): |
||||
|
bsuccess, error = app_DBM.del_url_assets_db(url_id) |
||||
|
return bsuccess, error |
||||
|
|
||||
|
def get_owners(self,owner, owner_type, contact, tellnum): |
||||
|
owner_list = [] |
||||
|
owner_list = app_DBM.get_owner_db(owner, owner_type, contact, tellnum) |
||||
|
return owner_list |
||||
|
|
||||
|
def add_update_owner(self,owner_data, do_mode): |
||||
|
''' |
||||
|
:param owner_data: |
||||
|
:param do_mode: |
||||
|
:return: |
||||
|
''' |
||||
|
id = owner_data["id"] |
||||
|
user = owner_data["user"] |
||||
|
type = owner_data["type"] |
||||
|
contact = owner_data["contact"] |
||||
|
phone = owner_data["phone"] |
||||
|
IDno = owner_data["IOno"] |
||||
|
if not user or not type or not contact or not phone or not IDno: |
||||
|
return False, "有信息没有填写,请补充完整!", [] |
||||
|
if do_mode =="add": |
||||
|
strsql = "select ID from assets_user where ID_num = %s" |
||||
|
params = (IDno,) |
||||
|
data = app_DBM.safe_do_select(strsql, params, 1) |
||||
|
if data: |
||||
|
return False, "证件号码已经存在,请重新修改", [] |
||||
|
|
||||
|
strsql = "insert into assets_user (itype,uname,tellnum,tell_username,ID_num) values (%s,%s,%s,%s,%s);" |
||||
|
params = (type,user,phone,contact,IDno) |
||||
|
bok,new_id = app_DBM.safe_do_sql(strsql,params,1) |
||||
|
elif do_mode == "edit": |
||||
|
strsql = "select ID from assets_user where ID_num = %s and ID <> %s" |
||||
|
params = (IDno,id) |
||||
|
data = app_DBM.safe_do_select(strsql, params, 1) |
||||
|
if data: |
||||
|
return False, "证件号码已经存在,请重新修改", [] |
||||
|
|
||||
|
strsql = "update assets_user set itype=%s,uname=%s,tellnum=%s,tell_username=%s,ID_num=%s where ID=%s;" |
||||
|
params = (type, user, phone, contact, IDno,id) |
||||
|
bok,_ = app_DBM.safe_do_sql(strsql,params) |
||||
|
else: |
||||
|
return False,"操作模式超出预期",[] |
||||
|
|
||||
|
if bok: |
||||
|
owner_list = app_DBM.get_owner_db("", "", "", "") |
||||
|
return True,"",owner_list |
||||
|
else: |
||||
|
return False, "数据库操作失败", [] |
||||
|
|
||||
|
def del_owner(self,id): |
||||
|
bsuccess,error = app_DBM.del_owner_db(id) |
||||
|
return bsuccess,error |
||||
|
|
||||
|
|
||||
|
g_AssetsM = AssetsManager() |
@ -1,4 +1,4 @@ |
|||||
from quart import Blueprint |
from quart import Blueprint |
||||
#定义模块 |
#定义模块 |
||||
api = Blueprint('api',__name__) |
api = Blueprint('api',__name__) |
||||
from . import user,task,wsm,system |
from . import user,task,wsm,system,assets |
||||
|
@ -0,0 +1,170 @@ |
|||||
|
from . import api |
||||
|
from quart import Quart, render_template, redirect, url_for, request,jsonify |
||||
|
from mycode.AssetsManager import g_AssetsM |
||||
|
from web.common.utils import login_required |
||||
|
|
||||
|
@api.route('/assets/getipassets',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_IP_assets(): #查询IP资产数据 |
||||
|
data = await request.get_json() |
||||
|
IP = data.get("IP") |
||||
|
user = data.get("user") |
||||
|
safe_rank = data.get("safe_rank") |
||||
|
ip_assets = g_AssetsM.get_IP_assets(IP,user,safe_rank) |
||||
|
return jsonify({"ip_assets":ip_assets}) |
||||
|
|
||||
|
@api.route('/assets/getipinfo',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_IP_info(): #获取该IP资产的基本信息 |
||||
|
data = await request.get_json() |
||||
|
IP = data.get("IP") |
||||
|
ip_info = g_AssetsM.get_IP_info(IP) |
||||
|
return jsonify({"ip_info":ip_info}) |
||||
|
|
||||
|
@api.route('/assets/getassetsuser',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_assets_user(): |
||||
|
data = await request.get_json() |
||||
|
uname = data.get("keyword") |
||||
|
user_list = g_AssetsM.get_assets_users(uname) |
||||
|
return jsonify({"user_list": user_list}) |
||||
|
|
||||
|
@api.route('/assets/updateipinfo',methods=['POST']) |
||||
|
@login_required |
||||
|
async def update_IP_info(): #获取该IP资产的基本信息 |
||||
|
data = await request.get_json() |
||||
|
IP = data.get("cur_ip") |
||||
|
owner_id = data.get("owner_id") |
||||
|
bsuccess,error = g_AssetsM.update_assets_users(IP,owner_id) |
||||
|
return jsonify({"bsuccess":bsuccess}) |
||||
|
|
||||
|
@api.route('/assets/getportlatest',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_port_latest(): #获取端口的最新数据 |
||||
|
data = await request.get_json() |
||||
|
ip = data.get("ip") |
||||
|
if not ip: |
||||
|
return jsonify({'error': '缺少 ip 参数'}), 400 |
||||
|
port_data = g_AssetsM.get_port_latest(ip) |
||||
|
return jsonify({"port_data":port_data}) |
||||
|
|
||||
|
@api.route('/assets/getporthistory',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_port_history(): #获取端口的历史数据 |
||||
|
data = await request.get_json() |
||||
|
ip = data.get("ip") |
||||
|
if not ip: |
||||
|
return jsonify({'error': '缺少 ip 参数'}), 400 |
||||
|
times,port_dict = g_AssetsM.get_port_history(ip) |
||||
|
return jsonify({"times":times,"entries":port_dict}) |
||||
|
|
||||
|
@api.route('/assets/geturllatest',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_url_latest(): |
||||
|
data = await request.get_json() |
||||
|
ip = data.get("ip") |
||||
|
if not ip: |
||||
|
return jsonify({'error': '缺少 ip 参数'}), 400 |
||||
|
ip_url_data = g_AssetsM.get_ip_url_latest(ip) |
||||
|
if not ip_url_data: |
||||
|
ip_url_data=[] |
||||
|
return jsonify({"ip_url_data": ip_url_data}) |
||||
|
|
||||
|
@api.route('/assets/geturlhistory',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_url_history(): |
||||
|
data = await request.get_json() |
||||
|
ip = data.get("ip") |
||||
|
if not ip: |
||||
|
return jsonify({'error': '缺少 ip 参数'}), 400 |
||||
|
ip_url_data = g_AssetsM.get_ip_url_history(ip) |
||||
|
if not ip_url_data: |
||||
|
ip_url_data=[] |
||||
|
return jsonify({"ip_url_data": ip_url_data}) |
||||
|
|
||||
|
@api.route('/assets/getvuldata',methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_vul_data(): |
||||
|
data = await request.get_json() #ip,nodeName,vulType,vulLevel |
||||
|
ip = data.get("ip") |
||||
|
if not ip: |
||||
|
return jsonify({'error': '缺少 ip 参数'}), 400 |
||||
|
nodeName = data.get("nodeName") |
||||
|
vulType = data.get("vulType") |
||||
|
vulLevel = data.get("vulLevel") |
||||
|
vuls = g_AssetsM.get_vul_data(ip,nodeName,vulType,vulLevel) |
||||
|
return jsonify({"vuls": vuls}) |
||||
|
|
||||
|
|
||||
|
@api.route('/assets/delipassets',methods=['POST']) |
||||
|
@login_required |
||||
|
async def del_ip_assets(): |
||||
|
data = await request.get_json() # ip,nodeName,vulType,vulLevel |
||||
|
ip = data.get("IP") |
||||
|
if not ip: |
||||
|
return jsonify({'error': '缺少 ip 参数'}), 400 |
||||
|
bsuccess,error = g_AssetsM.del_ip_assets(ip) |
||||
|
return jsonify({"bsuccess": bsuccess,"error":error}) |
||||
|
|
||||
|
@api.route('/assets/geturlassets', methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_url_assets(): #url_filter,user_filter,email_filter |
||||
|
data = await request.get_json() |
||||
|
url = data.get("url_filter") |
||||
|
owner = data.get("user_filter") |
||||
|
email = data.get("email_filter") |
||||
|
url_assets = g_AssetsM.get_url_assets(url,owner,email) |
||||
|
return jsonify({"url_assets": url_assets}) |
||||
|
|
||||
|
@api.route('/assets/updateurlinfo', methods=['POST']) |
||||
|
@login_required |
||||
|
async def update_url_info(): |
||||
|
data = await request.get_json() |
||||
|
url_id = data.get("cur_url_id") |
||||
|
owner_id = data.get("owner_id") |
||||
|
bsuccess, error = g_AssetsM.update_assets_users(url_id, owner_id,2) |
||||
|
return jsonify({"bsuccess": bsuccess,"error":error}) |
||||
|
|
||||
|
@api.route('/assets/geturltoIP', methods=['POST']) |
||||
|
@login_required |
||||
|
async def get_url_to_ip(): |
||||
|
data = await request.get_json() |
||||
|
url_id = data.get("cur_url_id") |
||||
|
last_to_ips,his_to_ips = g_AssetsM.get_url_to_ip(url_id) |
||||
|
return jsonify({"last_to_ips":last_to_ips,"his_to_ips":his_to_ips}) |
||||
|
|
||||
|
@api.route('/assets/delurlassets', methods=['POST']) |
||||
|
@login_required |
||||
|
async def del_url_assets(): |
||||
|
data = await request.get_json() |
||||
|
url_id = data.get("url") |
||||
|
bsuccess,error = g_AssetsM.del_url_assets(url_id) |
||||
|
return jsonify({"bsuccess": bsuccess, "error": error}) |
||||
|
|
||||
|
@api.route('/assets/getOwners', methods=['POST']) |
||||
|
@login_required |
||||
|
async def getOwners(): |
||||
|
data = await request.get_json() |
||||
|
owner = data.get("owner") |
||||
|
owner_type = data.get("owner_type") |
||||
|
contact = data.get("contact") |
||||
|
tellnum = data.get("tellnum") |
||||
|
owner_list = g_AssetsM.get_owners(owner,owner_type,contact,tellnum) |
||||
|
return jsonify({"owner_list": owner_list}) |
||||
|
|
||||
|
@api.route('/assets/addUpdateOwners', methods=['POST']) |
||||
|
@login_required |
||||
|
async def add_update_Owner(): |
||||
|
data = await request.get_json() #{data,currentMode} |
||||
|
owner_data = data.get("data") |
||||
|
do_mode = data.get("currentMode") |
||||
|
bsuccess,error,owner_list = g_AssetsM.add_update_owner(owner_data, do_mode) |
||||
|
return jsonify({"owner_list": owner_list,"bsuccess":bsuccess,"error":error}) |
||||
|
|
||||
|
@api.route('/assets/delOwners', methods=['POST']) |
||||
|
@login_required |
||||
|
async def del_Owner(): |
||||
|
data = await request.get_json() |
||||
|
id = data.get("id") |
||||
|
bsuccess,error = g_AssetsM.del_owner(id) |
||||
|
return jsonify({"bsuccess": bsuccess, "error": error}) |
File diff suppressed because it is too large
@ -0,0 +1,10 @@ |
|||||
|
<!DOCTYPE html> |
||||
|
<html lang="en"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8"> |
||||
|
<title>Title</title> |
||||
|
</head> |
||||
|
<body> |
||||
|
|
||||
|
</body> |
||||
|
</html> |
@ -0,0 +1,331 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
|
||||
|
{% block title %}ZFSAFE{% endblock %} |
||||
|
|
||||
|
<!-- 页面样式块 --> |
||||
|
{% block style %} |
||||
|
/* 查询条件区域:使用 row 分布,输入框占满所在列 */ |
||||
|
.search-section .form-control, |
||||
|
.search-section .form-select { |
||||
|
width: 100%; |
||||
|
} |
||||
|
/* 查询条件区域,每个条件统一高度且左右间隔均等 */ |
||||
|
.search-section .col { |
||||
|
padding: 0 5px; |
||||
|
} |
||||
|
|
||||
|
/* 表格样式:统一垂直居中 */ |
||||
|
.table thead th, .table tbody td { |
||||
|
vertical-align: middle; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
/* 分页区域右对齐 */ |
||||
|
.pagination-section { |
||||
|
text-align: right; |
||||
|
padding-right: 15px; |
||||
|
} |
||||
|
|
||||
|
/* 固定行高,比如 45px,每页 10 行 */ |
||||
|
.fixed-row-height { |
||||
|
height: 45px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
{% endblock %} |
||||
|
|
||||
|
<!-- 页面内容块 --> |
||||
|
{% block content %} |
||||
|
<div class="container-xxl mt-2"> |
||||
|
<!-- 查询区 --> |
||||
|
<div class="row mb-3"> |
||||
|
<div class="col-2 mb-2"><button class="btn btn-primary me-3" onclick="openModal('add')">新增</button></div> |
||||
|
<div class="col-10"></div> |
||||
|
<div class="col-3 mb-2"><input type="text" class="form-control" id="searchUser" placeholder="资产用户"></div> |
||||
|
<div class="col-2"> |
||||
|
<select class="form-select" id="ownerType"> |
||||
|
<option value="">用户类型</option> |
||||
|
<option value="个人">个人</option> |
||||
|
<option value="私营企业">私营企业</option> |
||||
|
<option value="国有企业">国有企业</option> |
||||
|
<option value="事业单位">事业单位</option> |
||||
|
<option value="政府机构">政府机构</option> |
||||
|
<option value="团体协会">团体协会</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="col-2"><input type="text" class="form-control" id="searchContact" placeholder="联系人"></div> |
||||
|
<div class="col-3"><input type="text" class="form-control" id="searchPhone" placeholder="联系电话"></div> |
||||
|
<div class="col-2 text-end"> |
||||
|
<button class="btn btn-primary" onclick="fetchData()">查询</button> |
||||
|
<button class="btn btn-primary" onclick="exportOwnerData()">导出</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- 表格 --> |
||||
|
<div class="table-responsive"> |
||||
|
<table class="table table-bordered table-hover" id="ownerTable" style="width: 100%; table-layout: fixed;"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th style="width:5%;">序号</th> |
||||
|
<th style="width:30%;">资产用户</th> |
||||
|
<th style="width:10%;">用户类型</th> |
||||
|
<th style="width:10%;">联系人</th> |
||||
|
<th style="width:15%;">联系电话</th> |
||||
|
<th style="width:10%;">关联资产</th> |
||||
|
<th style="width:20%;">操作</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<!-- JS 动态插入 10 行 --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
<!-- 分页 --> |
||||
|
<nav class="mt-2"> |
||||
|
<ul class="pagination justify-content-end" id="userPagination"> |
||||
|
<li class="page-item"><a class="page-link" href="#" id="userPrev">上一页</a></li> |
||||
|
<li class="page-item"><a class="page-link" href="#" id="userNext">下一页</a></li> |
||||
|
</ul> |
||||
|
</nav> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- Modal --> |
||||
|
<div class="modal fade" id="ownerModal" tabindex="-1" aria-hidden="true"> |
||||
|
<div class="modal-dialog"> |
||||
|
<div class="modal-content"> |
||||
|
<div class="modal-header"> |
||||
|
<h5 class="modal-title" id="modalTitle">新增/修改用户</h5> |
||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal"></button> |
||||
|
</div> |
||||
|
<div class="modal-body"> |
||||
|
<form id="ownerForm"> |
||||
|
<input type="hidden" id="ownerId"> |
||||
|
<div class="mb-3"> |
||||
|
<label class="form-label me-2">资产用户:</label> |
||||
|
<input type="text" class="form-control" id="formUser"> |
||||
|
</div> |
||||
|
<div class="mb-3"> |
||||
|
<label class="form-label me-2">用户类型:</label> |
||||
|
<select class="form-select" id="formType"> |
||||
|
<option value="">用户类型</option> |
||||
|
<option value="私营企业">私营企业</option> |
||||
|
<option value="国有企业">国有企业</option> |
||||
|
<option value="事业单位">事业单位</option> |
||||
|
<option value="政府机构">政府机构</option> |
||||
|
<option value="团体协会">团体协会</option> |
||||
|
<option value="个人">个人</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="mb-3"> |
||||
|
<label class="form-label me-2">证件号码:</label> |
||||
|
<input type="text" class="form-control" id="IDno"> |
||||
|
</div> |
||||
|
<div class="mb-1 row gx-3"> |
||||
|
<div class="col-6"> |
||||
|
<label class="form-label me-2">联系人:</label> |
||||
|
<input type="text" class="form-control" id="formContact"> |
||||
|
</div> |
||||
|
<div class="col-6"> |
||||
|
<label class="form-label me-2">联系电话:</label> |
||||
|
<input type="text" class="form-control" id="formPhone"> |
||||
|
</div> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
<div class="modal-footer"> |
||||
|
<button class="btn btn-primary" onclick="saveOwner()">保存</button> |
||||
|
<button class="btn btn-secondary" data-bs-dismiss="modal">关闭</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
{% endblock %} |
||||
|
|
||||
|
<!-- 页面脚本块 --> |
||||
|
{% block script %} |
||||
|
<script> |
||||
|
let currentMode = 'add'; |
||||
|
let ownersData = []; |
||||
|
let currentPage = 1; |
||||
|
const pageSize = 10; |
||||
|
async function postJSON(url, payload) { |
||||
|
const res = await fetch(url, { |
||||
|
method: 'POST', |
||||
|
headers: { 'Content-Type':'application/json' }, |
||||
|
body: JSON.stringify(payload) |
||||
|
}); |
||||
|
if (!res.ok) { |
||||
|
const errorData = await res.json(); |
||||
|
throw new Error(errorData.error || `HTTP错误 ${res.status}`); |
||||
|
} |
||||
|
return res.json(); |
||||
|
} |
||||
|
|
||||
|
function downloadCSV(text, filename) { |
||||
|
const blob = new Blob([text], { type: 'text/csv;charset=utf-8;' }); |
||||
|
const link = document.createElement('a'); |
||||
|
link.href = URL.createObjectURL(blob); |
||||
|
link.download = filename; |
||||
|
link.click(); |
||||
|
URL.revokeObjectURL(link.href); |
||||
|
} |
||||
|
|
||||
|
function fmtCell(val) { |
||||
|
// 如果是数字-数字,比如 "2-4"、"10-12" 等 |
||||
|
if (/^\d+-\d+$/.test(val)) { |
||||
|
return '="' + val + '"'; |
||||
|
} |
||||
|
// 如果里面有中文或逗号,就双引号包裹 |
||||
|
if (/[,\u4e00-\u9fa5]/.test(val)) { |
||||
|
return `"${val.replace(/"/g, '""')}"`; |
||||
|
} |
||||
|
return val; |
||||
|
} |
||||
|
|
||||
|
async function exportOwnerData(){ |
||||
|
//导出数据 |
||||
|
let rows = [ |
||||
|
['序号', '资产用户', '用户类型', '联系人', '联系电话','证件号码','关联IP','关联域名'].map(fmtCell).join(',') |
||||
|
]; |
||||
|
ownersData.forEach((row, i) => { |
||||
|
rows.push([ |
||||
|
(i + 1).toString(), |
||||
|
row[2].toString(), |
||||
|
row[1] || '', |
||||
|
row[4] || '', |
||||
|
row[3] || '', |
||||
|
row[5] || '', |
||||
|
row[6].toString(), |
||||
|
row[7].toString(), |
||||
|
].map(fmtCell).join(',')); |
||||
|
}); |
||||
|
|
||||
|
const csv = '\uFEFF' + rows.join('\r\n'); // 加 BOM |
||||
|
|
||||
|
downloadCSV(csv, `assets_owner.csv`); |
||||
|
} |
||||
|
|
||||
|
function openModal(mode, index = null) { |
||||
|
currentMode = mode; |
||||
|
document.getElementById('ownerForm').reset(); |
||||
|
document.getElementById('ownerId').value = ''; |
||||
|
if (mode === 'edit' && index !== null) { |
||||
|
const data = ownersData[index]; |
||||
|
document.getElementById('modalTitle').innerText = '修改用户'; |
||||
|
document.getElementById('ownerId').value = data[0] || ''; |
||||
|
document.getElementById('formUser').value = data[2] || ''; |
||||
|
document.getElementById('formType').value = data[1] || ''; |
||||
|
document.getElementById('formContact').value = data[4] || ''; |
||||
|
document.getElementById('formPhone').value = data[3] || ''; |
||||
|
document.getElementById('IDno').value = data[5] || ''; |
||||
|
} else { |
||||
|
document.getElementById('modalTitle').innerText = '新增用户'; |
||||
|
} |
||||
|
new bootstrap.Modal(document.getElementById('ownerModal')).show(); |
||||
|
} |
||||
|
|
||||
|
async function saveOwner() { |
||||
|
const data = { |
||||
|
id: document.getElementById('ownerId').value, |
||||
|
user: document.getElementById('formUser').value, |
||||
|
type: document.getElementById('formType').value, |
||||
|
contact: document.getElementById('formContact').value, |
||||
|
phone: document.getElementById('formPhone').value, |
||||
|
IOno: document.getElementById('IDno').value, |
||||
|
}; |
||||
|
try{ |
||||
|
const jsondata = await postJSON('/api/assets/addUpdateOwners', {data,currentMode}); |
||||
|
bsuccess = jsondata.bsuccess; |
||||
|
error = jsondata.error; |
||||
|
if(bsuccess){ |
||||
|
ownersData = jsondata.owner_list || []; |
||||
|
currentPage = 1; |
||||
|
document.querySelector('#ownerModal .btn-close').click(); |
||||
|
renderTable(); |
||||
|
}else { |
||||
|
alert("操作失败"+error) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error("操作失败:",error) |
||||
|
alert("操作失败:"+error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
function renderTable() { |
||||
|
const tableBody = document.querySelector('#ownerTable tbody'); |
||||
|
//const tableBody = document.getElementById('tableBody'); |
||||
|
tableBody.innerHTML = ''; |
||||
|
const start = (currentPage - 1) * pageSize; |
||||
|
const pageData = ownersData.slice(start, start + pageSize); |
||||
|
|
||||
|
pageData.forEach((item, i) => { //IP,itype,uname,tellnum,tell_username,ID_num |
||||
|
const tr = document.createElement('tr'); |
||||
|
tr.innerHTML = ` |
||||
|
<td>${start + i + 1}</td> |
||||
|
<td>${item[2] || ''}</td> |
||||
|
<td>${item[1] || ''}</td> |
||||
|
<td>${item[4] || ''}</td> |
||||
|
<td>${item[3] || ''}</td> |
||||
|
<td>${item[8] || ''}</td> |
||||
|
<td> |
||||
|
<button class="btn btn-info btn-sm me-1" onclick="openModal('edit', ${start + i})">修改</button> |
||||
|
<!-- <button class="btn btn-info btn-sm me-1" onclick="showassets(${start + i})">查看资产</button> --> |
||||
|
<button class="btn btn-danger btn-sm" onclick="delowner(${start + i})">删除</button> |
||||
|
</td> |
||||
|
`; |
||||
|
tableBody.appendChild(tr); |
||||
|
}); |
||||
|
|
||||
|
for (let i = pageData.length; i < pageSize; i++) { |
||||
|
const tr = document.createElement('tr'); |
||||
|
tr.innerHTML = '<td colspan="7"> </td>'; |
||||
|
tableBody.appendChild(tr); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function fetchData() { |
||||
|
const ownerEl = document.getElementById("searchUser"); |
||||
|
const ownerTypeEl = document.getElementById("ownerType"); |
||||
|
const contactEl = document.getElementById("searchContact"); |
||||
|
const tellNumEl = document.getElementById("searchPhone"); |
||||
|
const owner = ownerEl.value; |
||||
|
const owner_type = ownerTypeEl.value; |
||||
|
const contact = contactEl.value; |
||||
|
const tellnum = tellNumEl.value; |
||||
|
try { |
||||
|
const data = await postJSON("/api/assets/getOwners",{owner,owner_type,contact,tellnum}) |
||||
|
ownersData = data.owner_list || []; |
||||
|
currentPage = 1; |
||||
|
renderTable(); |
||||
|
} catch (error) { |
||||
|
console.error("查询资产用户记录出错:", error); |
||||
|
alert("查询失败!"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
async function showassets(index){ |
||||
|
} |
||||
|
|
||||
|
async function delowner(index){ |
||||
|
const data = ownersData[index]; |
||||
|
const id = data[0] |
||||
|
if (!confirm('确认删除?')) return; |
||||
|
try{ |
||||
|
const redata = await postJSON('/api/assets/delOwners', {id}); |
||||
|
bsuccess = redata.bsuccess; |
||||
|
error = redata.error; |
||||
|
if(bsuccess){ |
||||
|
alert("删除成功!"); |
||||
|
await fetchData(); //刷新数据 |
||||
|
} |
||||
|
else{ |
||||
|
alert("删除失败!",error) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error("删除失败!",error); |
||||
|
alert("删除失败!",error) |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
window.onload = fetchData; |
||||
|
</script> |
||||
|
{% endblock %} |
@ -0,0 +1,108 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
|
||||
|
{% block title %}ZFSAFE{% endblock %} |
||||
|
|
||||
|
<!-- 页面样式块 --> |
||||
|
{% block style %} |
||||
|
/* 查询条件区域:使用 row 分布,输入框占满所在列 */ |
||||
|
.search-section .form-control, |
||||
|
.search-section .form-select { |
||||
|
width: 100%; |
||||
|
} |
||||
|
/* 查询条件区域,每个条件统一高度且左右间隔均等 */ |
||||
|
.search-section .col { |
||||
|
padding: 0 5px; |
||||
|
} |
||||
|
|
||||
|
/* 表格样式:统一垂直居中 */ |
||||
|
.table thead th, .table tbody td { |
||||
|
vertical-align: middle; |
||||
|
text-align: center; |
||||
|
} |
||||
|
|
||||
|
/* 分页区域右对齐 */ |
||||
|
.pagination-section { |
||||
|
text-align: right; |
||||
|
padding-right: 15px; |
||||
|
} |
||||
|
|
||||
|
/* 固定行高,比如 45px,每页 10 行 */ |
||||
|
.fixed-row-height { |
||||
|
height: 45px; |
||||
|
overflow: hidden; |
||||
|
} |
||||
|
{% endblock %} |
||||
|
|
||||
|
<!-- 页面内容块 --> |
||||
|
{% block content %} |
||||
|
<div class="container-xxl mt-2"> |
||||
|
<!-- 查询区 --> |
||||
|
<div class="row mb-3"> |
||||
|
<div class="col-2 mb-2"><button class="btn btn-primary me-3" onclick="openModal('add')">导入</button></div> |
||||
|
<div class="col-10"></div> |
||||
|
<div class="col-3 mb-2"><input type="text" class="form-control" id="polltarget" placeholder="检测目标"></div> |
||||
|
<div class="col-3"><input type="text" class="form-control" id="owner" placeholder="所属用户"></div> |
||||
|
<div class="col-2"> |
||||
|
<select class="form-select" id="polling_period"> |
||||
|
<option value="">巡检周期</option> |
||||
|
<option value="1">每日</option> |
||||
|
<option value="2">每周</option> |
||||
|
<option value="3">每月</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="col-2"> |
||||
|
<select class="form-select" id="risk_rank"> |
||||
|
<option value="">风险等级</option> |
||||
|
<option value="0">0</option> |
||||
|
<option value="1">1</option> |
||||
|
<option value="2">2</option> |
||||
|
<option value="3">3</option> |
||||
|
<option value="4">4</option> |
||||
|
<option value="5">5</option> |
||||
|
</select> |
||||
|
</div> |
||||
|
<div class="col-2 text-end"> |
||||
|
<button class="btn btn-primary" onclick="fetchData()">查询</button> |
||||
|
<button class="btn btn-primary" onclick="exportOwnerData()">导出</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
<!-- 表格 --> |
||||
|
<div class="table-responsive"> |
||||
|
<table class="table table-bordered table-hover" id="pollingTable" style="width: 100%; table-layout: fixed;"> |
||||
|
<thead> |
||||
|
<tr> |
||||
|
<th style="width:5%;">序号</th> |
||||
|
<th style="width:20%;">检测目标</th> |
||||
|
<th style="width:20%;">所属用户</th> |
||||
|
<th style="width:10%;">检测周期</th> |
||||
|
<th style="width:15%;">最新检测时间</th> |
||||
|
<th style="width:10%;">风险等级</th> |
||||
|
<th style="width:20%;">操作</th> |
||||
|
</tr> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
<!-- JS 动态插入 10 行 --> |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
<!-- 分页 --> |
||||
|
<nav class="mt-2"> |
||||
|
<ul class="pagination justify-content-end" id="pollingPagination"> |
||||
|
<li class="page-item"><a class="page-link" href="#" id="pollingPrev">上一页</a></li> |
||||
|
<li class="page-item"><a class="page-link" href="#" id="pollingNext">下一页</a></li> |
||||
|
</ul> |
||||
|
</nav> |
||||
|
|
||||
|
</div> |
||||
|
|
||||
|
<!-- --------导入modal---------- --> |
||||
|
|
||||
|
<!-- --------所属用户modal---------- --> |
||||
|
|
||||
|
<!-- --------巡检策略modal---------- --> |
||||
|
|
||||
|
{% endblock %} |
||||
|
|
||||
|
<!-- 页面脚本块 --> |
||||
|
{% block script %} |
||||
|
{% endblock %} |
@ -0,0 +1,16 @@ |
|||||
|
{% extends 'base.html' %} |
||||
|
|
||||
|
{% block title %}ZFSAFE{% endblock %} |
||||
|
|
||||
|
<!-- 页面样式块 --> |
||||
|
{% block style %} |
||||
|
{% endblock %} |
||||
|
|
||||
|
<!-- 页面内容块 --> |
||||
|
{% block content %} |
||||
|
<h3 style="text-align: center;padding: 10px"> 功能规划中,在二期实现。。。</h3> |
||||
|
{% endblock %} |
||||
|
|
||||
|
<!-- 页面脚本块 --> |
||||
|
{% block script %} |
||||
|
{% endblock %} |
Loading…
Reference in new issue