Kamixitong/app/web/templates/dashboard.html

399 lines
14 KiB
HTML
Raw Normal View History

2025-11-11 21:39:12 +08:00
{% extends "base.html" %}
{% block title %}仪表板 - 软件授权管理系统{% endblock %}
{% block page_title %}仪表板{% endblock %}
{% block content %}
<!-- 统计卡片 -->
<div class="row mb-4">
2025-11-15 23:57:05 +08:00
<div class="col-xl-3 col-md-4 mb-4">
2025-11-11 21:39:12 +08:00
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">产品总数</h5>
<span class="h2 font-weight-bold mb-0" id="total-products">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-primary rounded-circle shadow">
<i class="fas fa-box"></i>
</div>
</div>
</div>
</div>
</div>
</div>
2025-11-15 23:57:05 +08:00
<div class="col-xl-3 col-md-4 mb-4">
2025-11-11 21:39:12 +08:00
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">活跃卡密</h5>
<span class="h2 font-weight-bold mb-0" id="active-licenses">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-warning rounded-circle shadow">
<i class="fas fa-key"></i>
</div>
</div>
</div>
</div>
</div>
</div>
2025-11-15 23:57:05 +08:00
<div class="col-xl-3 col-md-4 mb-4">
2025-11-11 21:39:12 +08:00
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">在线设备</h5>
<span class="h2 font-weight-bold mb-0" id="online-devices">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-success rounded-circle shadow">
<i class="fas fa-desktop"></i>
</div>
</div>
</div>
</div>
</div>
</div>
2025-11-15 23:57:05 +08:00
<div class="col-xl-3 col-md-4 mb-4">
2025-11-11 21:39:12 +08:00
<div class="card card-stats">
<div class="card-body">
<div class="row">
<div class="col">
<h5 class="card-title text-uppercase text-muted mb-0">今日激活</h5>
<span class="h2 font-weight-bold mb-0" id="today-activations">-</span>
</div>
<div class="col-auto">
<div class="icon icon-shape bg-white text-info rounded-circle shadow">
<i class="fas fa-user-plus"></i>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 图表区域 -->
<div class="row mb-4">
<div class="col-lg-8">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">激活趋势最近7天</h6>
</div>
<div class="card-body">
<canvas id="activationChart" height="300"></canvas>
</div>
</div>
</div>
<div class="col-lg-4">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">卡密类型分布</h6>
</div>
<div class="card-body">
<canvas id="licenseTypeChart" height="300"></canvas>
</div>
</div>
</div>
</div>
<!-- 快捷操作 -->
<div class="row">
<div class="col-lg-12">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">快捷操作</h6>
</div>
<div class="card-body">
<div class="row">
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.create_product') }}" class="btn btn-outline-primary w-100">
<i class="fas fa-plus me-2"></i>
创建产品
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.generate_license') }}" class="btn btn-outline-success w-100">
<i class="fas fa-key me-2"></i>
生成卡密
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.create_version') }}" class="btn btn-outline-info w-100">
<i class="fas fa-code-branch me-2"></i>
发布版本
</a>
</div>
<div class="col-md-3 mb-3">
<a href="{{ url_for('web.import_license') }}" class="btn btn-outline-warning w-100">
<i class="fas fa-file-import me-2"></i>
导入卡密
</a>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- 最近活动 -->
<div class="row mt-4">
<div class="col-lg-6">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">最近激活的卡密</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>卡密</th>
<th>产品</th>
<th>激活时间</th>
</tr>
</thead>
<tbody id="recent-activations">
<tr>
<td colspan="3" class="text-center text-muted">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="col-lg-6">
<div class="card shadow">
<div class="card-header bg-white">
<h6 class="m-0 font-weight-bold">待处理工单</h6>
</div>
<div class="card-body">
<div class="table-responsive">
<table class="table table-sm">
<thead>
<tr>
<th>标题</th>
<th>优先级</th>
<th>创建时间</th>
</tr>
</thead>
<tbody id="pending-tickets">
<tr>
<td colspan="3" class="text-center text-muted">加载中...</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js %}
<script>
let activationChart, licenseTypeChart;
// 页面加载完成后初始化
document.addEventListener('DOMContentLoaded', function() {
loadDashboardData();
initCharts();
});
// 加载仪表板数据
function loadDashboardData() {
// 加载总览统计
apiRequest('/api/v1/statistics/overview')
.then(data => {
if (data.success) {
const stats = data.data;
document.getElementById('total-products').textContent = stats.products.total;
document.getElementById('active-licenses').textContent = stats.licenses.active;
document.getElementById('online-devices').textContent = stats.devices.online;
document.getElementById('today-activations').textContent = stats.activations.today;
// 更新卡密类型图表
if (licenseTypeChart) {
licenseTypeChart.data.datasets[0].data = [
stats.licenses.trial,
stats.licenses.formal
];
licenseTypeChart.update();
}
}
})
.catch(error => {
console.error('Failed to load dashboard data:', error);
});
// 加载激活趋势
apiRequest('/api/v1/statistics/activations?days=7')
.then(data => {
if (data.success && activationChart) {
const labels = data.data.activations.map(item => {
const date = new Date(item.date);
return `${date.getMonth() + 1}/${date.getDate()}`;
});
const counts = data.data.activations.map(item => item.count);
activationChart.data.labels = labels;
activationChart.data.datasets[0].data = counts;
activationChart.update();
}
})
.catch(error => {
console.error('Failed to load activation trend:', error);
});
// 加载最近激活
loadRecentActivations();
// 加载待处理工单
loadPendingTickets();
}
// 初始化图表
function initCharts() {
// 激活趋势图表
const activationCtx = document.getElementById('activationChart').getContext('2d');
activationChart = new Chart(activationCtx, {
type: 'line',
data: {
labels: [],
datasets: [{
label: '激活数',
data: [],
borderColor: 'rgb(75, 192, 192)',
backgroundColor: 'rgba(75, 192, 192, 0.1)',
tension: 0.1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
}
}
});
// 卡密类型分布图表
const licenseTypeCtx = document.getElementById('licenseTypeChart').getContext('2d');
licenseTypeChart = new Chart(licenseTypeCtx, {
type: 'doughnut',
data: {
labels: ['试用卡密', '正式卡密'],
datasets: [{
data: [0, 0],
backgroundColor: [
'rgba(255, 206, 86, 0.8)',
'rgba(54, 162, 235, 0.8)'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom'
}
}
}
});
}
// 加载最近激活
function loadRecentActivations() {
2025-11-15 23:57:05 +08:00
apiRequest('/api/v1/licenses?status=1&per_page=5&sort=activate_time&order=desc')
2025-11-11 21:39:12 +08:00
.then(data => {
if (data.success) {
const tbody = document.getElementById('recent-activations');
tbody.innerHTML = '';
if (data.data.licenses.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted">暂无数据</td></tr>';
return;
}
data.data.licenses.forEach(license => {
const row = `
<tr>
<td><code>${license.license_key.substring(0, 8)}...</code></td>
<td>${license.product_name || '-'}</td>
<td>${formatDate(license.activate_time)}</td>
</tr>
`;
tbody.innerHTML += row;
});
}
})
.catch(error => {
console.error('Failed to load recent activations:', error);
});
}
// 加载待处理工单
function loadPendingTickets() {
apiRequest('/api/v1/tickets?status=0&per_page=5')
.then(data => {
if (data.success) {
const tbody = document.getElementById('pending-tickets');
tbody.innerHTML = '';
if (data.data.tickets.length === 0) {
tbody.innerHTML = '<tr><td colspan="3" class="text-center text-muted">暂无数据</td></tr>';
return;
}
data.data.tickets.forEach(ticket => {
const priorityBadge = {
0: '<span class="badge bg-secondary"></span>',
1: '<span class="badge bg-warning"></span>',
2: '<span class="badge bg-danger"></span>'
};
const row = `
<tr>
<td>
<a href="/tickets/${ticket.ticket_id}" class="text-decoration-none">
${ticket.title}
</a>
</td>
<td>${priorityBadge[ticket.priority]}</td>
<td>${formatDate(ticket.create_time)}</td>
</tr>
`;
tbody.innerHTML += row;
});
}
})
.catch(error => {
console.error('Failed to load pending tickets:', error);
});
}
// 定期刷新数据
setInterval(loadDashboardData, 60000); // 每分钟刷新一次
</script>
{% endblock %}