2025-11-15 23:57:24 +08:00
|
|
|
{% extends "base.html" %}
|
|
|
|
|
|
|
|
|
|
{% block title %}操作日志{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block content %}
|
|
|
|
|
<div class="container-fluid">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-12">
|
|
|
|
|
<div class="card">
|
|
|
|
|
<div class="card-header">
|
|
|
|
|
<h3 class="card-title">操作日志</h3>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="card-body">
|
|
|
|
|
<!-- 筛选表单 -->
|
|
|
|
|
<form id="logFilterForm" class="mb-3">
|
|
|
|
|
<div class="row">
|
|
|
|
|
<div class="col-md-3">
|
|
|
|
|
<label for="action" class="form-label">操作类型</label>
|
|
|
|
|
<select class="form-select" id="action" name="action">
|
|
|
|
|
<option value="">全部</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-3">
|
|
|
|
|
<label for="target_type" class="form-label">目标类型</label>
|
|
|
|
|
<select class="form-select" id="target_type" name="target_type">
|
|
|
|
|
<option value="">全部</option>
|
|
|
|
|
<option value="ADMIN">管理员</option>
|
|
|
|
|
<option value="PRODUCT">产品</option>
|
|
|
|
|
<option value="VERSION">版本</option>
|
|
|
|
|
<option value="LICENSE">卡密</option>
|
|
|
|
|
<option value="DEVICE">设备</option>
|
|
|
|
|
<option value="TICKET">工单</option>
|
|
|
|
|
</select>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-3">
|
|
|
|
|
<label for="start_date" class="form-label">开始日期</label>
|
|
|
|
|
<input type="date" class="form-control" id="start_date" name="start_date">
|
|
|
|
|
</div>
|
|
|
|
|
<div class="col-md-3">
|
|
|
|
|
<label for="end_date" class="form-label">结束日期</label>
|
|
|
|
|
<input type="date" class="form-control" id="end_date" name="end_date">
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="row mt-2">
|
|
|
|
|
<div class="col-md-12">
|
|
|
|
|
<button type="submit" class="btn btn-primary">筛选</button>
|
|
|
|
|
<button type="button" class="btn btn-secondary" id="resetFilter">重置</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</form>
|
|
|
|
|
|
|
|
|
|
<!-- 日志表格 -->
|
|
|
|
|
<div class="table-responsive">
|
|
|
|
|
<table class="table table-bordered table-striped">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>ID</th>
|
|
|
|
|
<th>操作员</th>
|
|
|
|
|
<th>操作类型</th>
|
|
|
|
|
<th>目标类型</th>
|
|
|
|
|
<th>目标ID</th>
|
|
|
|
|
<th>详情</th>
|
|
|
|
|
<th>IP地址</th>
|
|
|
|
|
<th>时间</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody id="logTableBody">
|
|
|
|
|
<!-- 日志数据将通过AJAX加载 -->
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 分页 -->
|
|
|
|
|
<div id="pagination" class="d-flex justify-content-center">
|
|
|
|
|
<!-- 分页控件将通过JavaScript生成 -->
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<!-- 系统日志模态框 -->
|
|
|
|
|
<div class="modal fade" id="systemLogModal" tabindex="-1" aria-labelledby="systemLogModalLabel" aria-hidden="true">
|
|
|
|
|
<div class="modal-dialog modal-xl">
|
|
|
|
|
<div class="modal-content">
|
|
|
|
|
<div class="modal-header">
|
|
|
|
|
<h5 class="modal-title" id="systemLogModalLabel">系统日志</h5>
|
|
|
|
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-body">
|
|
|
|
|
<pre id="systemLogContent" style="max-height: 500px; overflow-y: auto;"></pre>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="modal-footer">
|
|
|
|
|
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
{% endblock %}
|
|
|
|
|
|
|
|
|
|
{% block scripts %}
|
|
|
|
|
<script>
|
|
|
|
|
// 全局变量
|
|
|
|
|
let currentPage = 1;
|
|
|
|
|
const perPage = 20;
|
|
|
|
|
|
|
|
|
|
// 页面加载完成后初始化
|
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
|
|
|
// 加载操作类型列表
|
|
|
|
|
loadActionList();
|
|
|
|
|
|
|
|
|
|
// 加载日志数据
|
|
|
|
|
loadLogs();
|
|
|
|
|
|
|
|
|
|
// 绑定筛选表单事件
|
|
|
|
|
document.getElementById('logFilterForm').addEventListener('submit', function(e) {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
currentPage = 1;
|
|
|
|
|
loadLogs();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 绑定重置按钮事件
|
|
|
|
|
document.getElementById('resetFilter').addEventListener('click', function() {
|
|
|
|
|
document.getElementById('logFilterForm').reset();
|
|
|
|
|
currentPage = 1;
|
|
|
|
|
loadLogs();
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// 加载操作类型列表
|
|
|
|
|
function loadActionList() {
|
2025-11-22 16:48:45 +08:00
|
|
|
apiRequest('/api/v1/logs/actions')
|
2025-11-15 23:57:24 +08:00
|
|
|
.then(data => {
|
|
|
|
|
if (data.success) {
|
|
|
|
|
const actionSelect = document.getElementById('action');
|
|
|
|
|
data.data.actions.forEach(action => {
|
|
|
|
|
const option = document.createElement('option');
|
|
|
|
|
option.value = action;
|
|
|
|
|
option.textContent = action;
|
|
|
|
|
actionSelect.appendChild(option);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('加载操作类型列表失败:', error);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 加载日志数据
|
|
|
|
|
function loadLogs(page = 1) {
|
|
|
|
|
currentPage = page;
|
|
|
|
|
|
|
|
|
|
// 构建查询参数
|
|
|
|
|
const params = new URLSearchParams();
|
|
|
|
|
params.append('page', currentPage);
|
|
|
|
|
params.append('per_page', perPage);
|
|
|
|
|
|
|
|
|
|
// 添加筛选条件
|
|
|
|
|
const action = document.getElementById('action').value;
|
|
|
|
|
const target_type = document.getElementById('target_type').value;
|
|
|
|
|
const start_date = document.getElementById('start_date').value;
|
|
|
|
|
const end_date = document.getElementById('end_date').value;
|
|
|
|
|
|
|
|
|
|
if (action) params.append('action', action);
|
|
|
|
|
if (target_type) params.append('target_type', target_type);
|
|
|
|
|
if (start_date) params.append('start_date', start_date);
|
|
|
|
|
if (end_date) params.append('end_date', end_date);
|
|
|
|
|
|
|
|
|
|
// 发送请求
|
2025-11-22 16:48:45 +08:00
|
|
|
apiRequest(`/api/v1/logs?${params.toString()}`)
|
2025-11-15 23:57:24 +08:00
|
|
|
.then(data => {
|
|
|
|
|
if (data.success) {
|
|
|
|
|
renderLogs(data.data.logs);
|
|
|
|
|
renderPagination(data.data.pagination);
|
|
|
|
|
} else {
|
|
|
|
|
alert('加载日志失败: ' + data.message);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('加载日志失败:', error);
|
|
|
|
|
alert('加载日志失败,请稍后重试');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 渲染日志数据
|
|
|
|
|
function renderLogs(logs) {
|
|
|
|
|
const tbody = document.getElementById('logTableBody');
|
|
|
|
|
tbody.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
if (logs.length === 0) {
|
|
|
|
|
const tr = document.createElement('tr');
|
|
|
|
|
tr.innerHTML = '<td colspan="8" class="text-center">暂无日志数据</td>';
|
|
|
|
|
tbody.appendChild(tr);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logs.forEach(log => {
|
|
|
|
|
const tr = document.createElement('tr');
|
|
|
|
|
tr.innerHTML = `
|
|
|
|
|
<td>${log.log_id}</td>
|
|
|
|
|
<td>${log.admin_username || '系统'}</td>
|
|
|
|
|
<td>${log.action}</td>
|
|
|
|
|
<td>${log.target_type}</td>
|
|
|
|
|
<td>${log.target_id || '-'}</td>
|
|
|
|
|
<td>
|
|
|
|
|
${log.details ? `<button class="btn btn-sm btn-info" onclick="showDetails('${log.details}')">查看详情</button>` : '-'}
|
|
|
|
|
</td>
|
|
|
|
|
<td>${log.ip_address || '-'}</td>
|
|
|
|
|
<td>${log.create_time}</td>
|
|
|
|
|
`;
|
|
|
|
|
tbody.appendChild(tr);
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 渲染分页控件
|
|
|
|
|
function renderPagination(pagination) {
|
|
|
|
|
const paginationDiv = document.getElementById('pagination');
|
|
|
|
|
paginationDiv.innerHTML = '';
|
|
|
|
|
|
|
|
|
|
if (pagination.pages <= 1) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 上一页按钮
|
|
|
|
|
if (pagination.has_prev) {
|
|
|
|
|
const prevButton = document.createElement('button');
|
|
|
|
|
prevButton.className = 'btn btn-outline-primary me-1';
|
|
|
|
|
prevButton.textContent = '上一页';
|
|
|
|
|
prevButton.onclick = () => loadLogs(pagination.page - 1);
|
|
|
|
|
paginationDiv.appendChild(prevButton);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 页码按钮
|
|
|
|
|
for (let i = 1; i <= pagination.pages; i++) {
|
|
|
|
|
const pageButton = document.createElement('button');
|
|
|
|
|
pageButton.className = `btn ${i === pagination.page ? 'btn-primary' : 'btn-outline-primary'} me-1`;
|
|
|
|
|
pageButton.textContent = i;
|
|
|
|
|
pageButton.onclick = () => loadLogs(i);
|
|
|
|
|
paginationDiv.appendChild(pageButton);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 下一页按钮
|
|
|
|
|
if (pagination.has_next) {
|
|
|
|
|
const nextButton = document.createElement('button');
|
|
|
|
|
nextButton.className = 'btn btn-outline-primary ms-1';
|
|
|
|
|
nextButton.textContent = '下一页';
|
|
|
|
|
nextButton.onclick = () => loadLogs(pagination.page + 1);
|
|
|
|
|
paginationDiv.appendChild(nextButton);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 显示详情
|
|
|
|
|
function showDetails(details) {
|
|
|
|
|
try {
|
|
|
|
|
const parsedDetails = JSON.parse(details);
|
|
|
|
|
alert(JSON.stringify(parsedDetails, null, 2));
|
|
|
|
|
} catch (e) {
|
|
|
|
|
alert(details);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 查看系统日志
|
|
|
|
|
function viewSystemLogs() {
|
2025-11-22 16:48:45 +08:00
|
|
|
apiRequest('/api/v1/logs/file')
|
2025-11-15 23:57:24 +08:00
|
|
|
.then(data => {
|
|
|
|
|
if (data.success) {
|
|
|
|
|
document.getElementById('systemLogContent').textContent = data.data.content;
|
|
|
|
|
new bootstrap.Modal(document.getElementById('systemLogModal')).show();
|
|
|
|
|
} else {
|
|
|
|
|
alert('加载系统日志失败: ' + data.message);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
.catch(error => {
|
|
|
|
|
console.error('加载系统日志失败:', error);
|
|
|
|
|
alert('加载系统日志失败,请稍后重试');
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
</script>
|
|
|
|
|
{% endblock %}
|