init 全新仓库,清除全部历史

This commit is contained in:
2026-07-09 09:54:43 +08:00
commit 0c2fe393df
154 changed files with 56586 additions and 0 deletions
+707
View File
@@ -0,0 +1,707 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>告警监控看板 | Alert Dashboard</title>
<script src="/static/js/echarts.min.js"></script>
<script src="/static/js/jquery.min.js"></script>
<style>
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
background: #f0f2f5;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
color: #1f2937;
line-height: 1.5;
}
/* 顶部导航栏 */
.header {
background: linear-gradient(135deg, #1e3a8a 0%, #1e40af 50%, #2563eb 100%);
color: #fff;
padding: 16px 32px;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 0 2px 8px rgba(30, 64, 175, 0.15);
}
.header h1 {
font-size: 20px;
font-weight: 600;
display: flex;
align-items: center;
gap: 10px;
}
.header h1::before {
content: "";
width: 8px;
height: 8px;
background: #10b981;
border-radius: 50%;
box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.2);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { box-shadow: 0 0 0 4px rgba(16, 185, 129, 0.2); }
50% { box-shadow: 0 0 0 8px rgba(16, 185, 129, 0.1); }
}
.header-right {
display: flex;
align-items: center;
gap: 12px;
font-size: 13px;
}
.refresh-btn, .export-btn {
background: rgba(255, 255, 255, 0.15);
border: 1px solid rgba(255, 255, 255, 0.3);
color: #fff;
padding: 6px 16px;
border-radius: 4px;
cursor: pointer;
font-size: 13px;
transition: all 0.2s;
}
.refresh-btn:hover, .export-btn:hover {
background: rgba(255, 255, 255, 0.25);
}
.export-btn {
background: rgba(16, 185, 129, 0.2);
border-color: rgba(16, 185, 129, 0.4);
}
.update-time {
opacity: 0.85;
}
/* 导出加载遮罩 */
.mask {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background: rgba(0,0,0,0.4);
display: none;
z-index: 999;
align-items: center;
justify-content: center;
}
.mask-box {
background: #fff;
padding: 24px 32px;
border-radius: 8px;
font-size: 14px;
display: flex;
gap:10px;
align-items: center;
}
/* 主内容区 */
.container {
padding: 24px 32px;
max-width: 1600px;
margin: 0 auto;
}
/* 统计卡片 */
.stat-cards {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 20px;
margin-bottom: 24px;
}
.stat-card {
background: #fff;
border-radius: 8px;
padding: 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
position: relative;
overflow: hidden;
transition: transform 0.2s, box-shadow 0.2s;
}
.stat-card:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.stat-card::before {
content: "";
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
}
.stat-card.fire::before { background: linear-gradient(180deg, #ef4444, #dc2626); }
.stat-card.resolve::before { background: linear-gradient(180deg, #10b981, #059669); }
.stat-card.instance::before { background: linear-gradient(180deg, #3b82f6, #2563eb); }
.stat-card.rate::before { background: linear-gradient(180deg, #f59e0b, #d97706); }
.stat-card .label {
font-size: 14px;
color: #6b7280;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.stat-card .label .icon {
width: 28px;
height: 28px;
border-radius: 6px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
font-weight: bold;
}
.stat-card.fire .icon { background: #fef2f2; color: #dc2626; }
.stat-card.resolve .icon { background: #ecfdf5; color: #059669; }
.stat-card.instance .icon { background: #eff6ff; color: #2563eb; }
.stat-card.rate .icon { background: #fffbeb; color: #d97706; }
.stat-card .value {
font-size: 36px;
font-weight: 700;
line-height: 1.2;
font-family: "DIN Alternate", -apple-system, sans-serif;
}
.stat-card.fire .value { color: #dc2626; }
.stat-card.resolve .value { color: #059669; }
.stat-card.instance .value { color: #2563eb; }
.stat-card.rate .value { color: #d97706; }
.stat-card .sub {
font-size: 12px;
color: #9ca3af;
margin-top: 8px;
}
/* 图表区 */
.chart-section {
display: grid;
grid-template-columns: 1fr;
gap: 20px;
margin-bottom: 24px;
}
.chart-card {
background: #fff;
border-radius: 8px;
padding: 20px 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
}
.chart-card .chart-title {
font-size: 16px;
font-weight: 600;
color: #111827;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f3f4f6;
display: flex;
align-items: center;
gap: 8px;
}
.chart-card .chart-title::before {
content: "";
width: 3px;
height: 16px;
background: #2563eb;
border-radius: 2px;
}
#chart1 {
height: 380px;
width: 100%;
}
/* 表格区 */
.table-section {
background: #fff;
border-radius: 8px;
padding: 20px 24px;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.06), 0 1px 2px rgba(0, 0, 0, 0.04);
}
.table-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #f3f4f6;
}
.table-title {
font-size: 16px;
font-weight: 600;
color: #111827;
display: flex;
align-items: center;
gap: 8px;
}
.table-title::before {
content: "";
width: 3px;
height: 16px;
background: #2563eb;
border-radius: 2px;
}
.table-count {
font-size: 13px;
color: #6b7280;
}
.table-wrap {
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
th {
background: #f9fafb;
color: #374151;
font-weight: 600;
padding: 12px 16px;
text-align: left;
border-bottom: 2px solid #e5e7eb;
white-space: nowrap;
}
td {
padding: 12px 16px;
border-bottom: 1px solid #f3f4f6;
color: #4b5563;
}
tbody tr {
transition: background 0.15s;
}
tbody tr:hover {
background: #f9fafb;
}
tbody tr:nth-child(even) {
background: #fafbfc;
}
tbody tr:nth-child(even):hover {
background: #f3f4f6;
}
/* 状态标签 */
.status-tag {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 12px;
font-weight: 500;
line-height: 1.4;
}
.status-tag.fire {
background: #fef2f2;
color: #dc2626;
border: 1px solid #fecaca;
}
.status-tag.resolve {
background: #ecfdf5;
color: #059669;
border: 1px solid #a7f3d0;
}
/* 级别标签 */
.severity-tag {
display: inline-block;
padding: 2px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 500;
}
.severity-critical { background: #fef2f2; color: #dc2626; }
.severity-warning { background: #fffbeb; color: #d97706; }
.severity-info { background: #eff6ff; color: #2563eb; }
/* 空状态 */
.empty-state {
text-align: center;
padding: 60px 20px;
color: #9ca3af;
font-size: 14px;
}
.empty-state .empty-icon {
font-size: 48px;
margin-bottom: 12px;
opacity: 0.5;
}
/* 加载动画 */
.loading {
display: inline-block;
width: 16px;
height: 16px;
border: 2px solid #e5e7eb;
border-top-color: #2563eb;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
@keyframes spin {
to { transform: rotate(360deg); }
}
/* 响应式 */
@media (max-width: 1200px) {
.stat-cards {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.header {
padding: 12px 16px;
flex-direction: column;
gap: 10px;
align-items: flex-start;
}
.container {
padding: 16px;
}
.stat-cards {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<!-- 导出加载遮罩 -->
<div class="mask" id="exportMask">
<div class="mask-box">
<span class="loading"></span>
<span>正在生成CSV报表,请稍候...</span>
</div>
</div>
<!-- 顶部导航 -->
<div class="header">
<h1>告警监控看板</h1>
<div class="header-right">
<span class="update-time">最后更新:<span id="updateTime">--</span></span>
<button class="refresh-btn" onclick="loadAllData()">🔄 刷新数据</button>
<button class="export-btn" onclick="exportCsv()">📥 导出CSV报表</button>
</div>
</div>
<div class="container">
<!-- 统计卡片 -->
<div class="stat-cards">
<div class="stat-card fire">
<div class="label">
<span class="icon">!</span>
<span>今日触发告警</span>
</div>
<div class="value" id="fire">0</div>
<div class="sub">Firing Alerts</div>
</div>
<div class="stat-card resolve">
<div class="label">
<span class="icon"></span>
<span>今日恢复告警</span>
</div>
<div class="value" id="resolve">0</div>
<div class="sub">Resolved Alerts</div>
</div>
<div class="stat-card instance">
<div class="label">
<span class="icon">#</span>
<span>涉及故障实例</span>
</div>
<div class="value" id="instance">0</div>
<div class="sub">Affected Instances</div>
</div>
<div class="stat-card rate">
<div class="label">
<span class="icon">%</span>
<span>恢复率</span>
</div>
<div class="value" id="rate">0%</div>
<div class="sub">Recovery Rate</div>
</div>
</div>
<!-- 图表区 -->
<div class="chart-section">
<div class="chart-card">
<div class="chart-title">今日告警次数 TOP 10</div>
<div id="chart1"></div>
</div>
</div>
<!-- 表格区 -->
<div class="table-section">
<div class="table-header">
<div class="table-title">告警明细列表</div>
<div class="table-count"><span id="totalCount">0</span> 条记录</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th style="width:80px">状态</th>
<th>告警名称</th>
<th>实例地址</th>
<th style="width:100px">级别</th>
<th style="width:160px">故障开始时间</th>
<th style="width:160px">接收时间</th>
</tr>
</thead>
<tbody id="table-body">
<tr>
<td colspan="6">
<div class="empty-state">
<div class="empty-icon">📊</div>
<div>数据加载中...</div>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<script>
const api = "http://10.150.117.190:8080/api/oldapi.php";
let chartInstance = null;
// 格式化时间
function formatTime(dateStr) {
if (!dateStr || dateStr === '0000-00-00 00:00:00') return '-';
return dateStr;
}
// 级别标签
function getSeverityTag(severity) {
const s = (severity || '').toLowerCase();
if (s === 'critical') return `<span class="severity-tag severity-critical">严重</span>`;
if (s === 'warning') return `<span class="severity-tag severity-warning">警告</span>`;
if (s === 'info') return `<span class="severity-tag severity-info">信息</span>`;
return `<span class="severity-tag severity-info">${severity || '-'}</span>`;
}
// 1、顶部统计卡片
function loadStats() {
$.getJSON(api + "?act=day_total", function(res) {
const fire = parseInt(res.fire_count) || 0;
const resolve = parseInt(res.resolve_count) || 0;
const instance = parseInt(res.instance_num) || 0;
const rate = fire > 0 ? Math.round((resolve / fire) * 100) : 0;
$("#fire").text(fire);
$("#resolve").text(resolve);
$("#instance").text(instance);
$("#rate").text(rate + "%");
}).fail(function() {
$("#fire").text("-");
$("#resolve").text("-");
$("#instance").text("-");
$("#rate").text("-");
});
}
// 2、TOP告警柱状图
function loadChart() {
$.getJSON(api + "?act=top_alert", function(list) {
if (!chartInstance) {
chartInstance = echarts.init(document.getElementById("chart1"));
window.addEventListener("resize", function() {
chartInstance.resize();
});
}
const names = list.map(item => item.alert_name);
const counts = list.map(item => item.cnt);
chartInstance.setOption({
tooltip: {
trigger: "axis",
axisPointer: { type: "shadow" }
},
grid: {
left: "3%",
right: "4%",
bottom: "15%",
top: "10%",
containLabel: true
},
xAxis: {
type: "category",
data: names,
axisLabel: {
rotate: 25,
fontSize: 12,
color: "#6b7280",
interval: 0
},
axisLine: { lineStyle: { color: "#e5e7eb" } },
axisTick: { show: false }
},
yAxis: {
type: "value",
name: "次数",
nameTextStyle: { color: "#9ca3af", fontSize: 12 },
axisLabel: { color: "#6b7280", fontSize: 12 },
axisLine: { show: false },
splitLine: { lineStyle: { color: "#f3f4f6", type: "dashed" } }
},
series: [{
type: "bar",
data: counts,
barWidth: "45%",
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#3b82f6" },
{ offset: 1, color: "#2563eb" }
]),
borderRadius: [4, 4, 0, 0]
},
emphasis: {
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: "#60a5fa" },
{ offset: 1, color: "#3b82f6" }
])
}
},
label: {
show: true,
position: "top",
fontSize: 12,
color: "#374151"
}
}]
});
});
}
// 3、告警明细表格(已删除恢复时间列,不再渲染ends_at)
function loadTable() {
$.getJSON(api + "?act=log_list", function(list) {
$("#totalCount").text(list.length);
if (list.length === 0) {
$("#table-body").html(`
<tr>
<td colspan="6">
<div class="empty-state">
<div class="empty-icon">📭</div>
<div>暂无告警记录</div>
</div>
</td>
</tr>
`);
return;
}
let html = "";
list.forEach(function(row) {
const typeText = row.alert_type == 1
? '<span class="status-tag fire">触发</span>'
: '<span class="status-tag resolve">恢复</span>';
html += `<tr>
<td>${typeText}</td>
<td style="font-weight:500;color:#1f2937">${row.alert_name || '-'}</td>
<td style="font-family:monospace;font-size:12px">${row.instance || '-'}</td>
<td>${getSeverityTag(row.severity)}</td>
<td>${formatTime(row.starts_at)}</td>
<td style="color:#6b7280">${formatTime(row.receive_time)}</td>
</tr>`;
});
$("#table-body").html(html);
}).fail(function() {
$("#table-body").html(`
<tr>
<td colspan="6">
<div class="empty-state" style="color:#ef4444">
<div class="empty-icon">⚠️</div>
<div>数据加载失败,请检查接口</div>
</div>
</td>
</tr>
`);
});
}
// 更新时间
function updateTime() {
const now = new Date();
const str = now.getFullYear() + "-"
+ String(now.getMonth() + 1).padStart(2, "0") + "-"
+ String(now.getDate()).padStart(2, "0") + " "
+ String(now.getHours()).padStart(2, "0") + ":"
+ String(now.getMinutes()).padStart(2, "0") + ":"
+ String(now.getSeconds()).padStart(2, "0");
$("#updateTime").text(str);
}
// 加载全部数据
function loadAllData() {
loadStats();
loadChart();
loadTable();
updateTime();
}
// ====================== 导出CSV函数 ======================
function exportCsv(){
const mask = $("#exportMask");
mask.css("display","flex");
window.location.href = api + "?act=export_csv";
setTimeout(()=>{
mask.hide();
},3000);
}
// 初始化
$(function() {
loadAllData();
// 每60秒自动刷新
setInterval(loadAllData, 60000);
});
</script>
</body>
</html>