Files
aler_dashboard_git/alert_dashboard/www/alert_list.php.1
T

651 lines
26 KiB
Groff
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
session_start();
header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0");
header("Pragma: no-cache");
header("Expires: Thu, 01 Jan 1970 00:00:00 GMT");
$expire = 1800;
if (empty($_SESSION['user_id']) || (time() - $_SESSION['login_time'] > $expire)) {
session_destroy();
header("Location: login.php");
exit;
}
$_SESSION['login_time'] = time();
$userName = htmlspecialchars($_SESSION['username'] ?? "");
$userRole = $_SESSION['role'] ?? "user";
// 固定配置
$grafanaUrl = "http://10.150.117.190:3000";
$currentLang = "zh";
$langMap = [
'zh' => [
'page_title' => '告警时序监控面板',
'global_search' => '全局检索(告警/实例/级别)',
'filter_instance' => '实例IP模糊匹配',
'start_date' => '起始日期',
'end_date' => '结束日期',
'severity_title' => '告警级别',
'status_title' => '告警状态',
'btn_query' => '查询',
'btn_reset' => '重置筛选',
'btn_detail_mode' => '切换详细模式',
'tip_distinct' => '当前:去重汇总模式',
'tip_raw' => '当前:逐条时序模式',
'stat_total' => '总告警种类',
'stat_firing' => '触发中',
'stat_resolved' => '已恢复',
'stat_critical' => '严重告警',
'hour_chart_title' => '24小时告警触发趋势',
'pie_title' => '告警级别分布占比',
'col_state' => '状态',
'col_alert' => '告警名称',
'col_instance' => '实例地址',
'col_severity' => '级别',
'col_count' => '当日触发次数',
'col_time' => '最新触发时间',
'btn_copy_name' => '复制告警名',
'btn_copy_link' => '复制筛选链接',
'btn_export' => '导出CSV报表',
'btn_refresh_auto' => '60秒自动刷新',
'modal_title' => '告警完整详情',
'modal_grafana' => '跳转Grafana大盘',
'modal_labels' => '全量标签信息',
'empty_tip' => '暂无匹配告警数据,请调整筛选条件',
'storm_tip' => '风暴告警',
'shortcut_tip' => 'Enter快速查询 | ESC关闭弹窗 | Ctrl+F全局搜索'
]
];
$t = $langMap[$currentLang];
$today = date("Y-m-d");
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $t['page_title']; ?></title>
<link rel="stylesheet" href="/static/css/font-awesome.min.css">
<script src="/static/js/jquery.min.js"></script>
<script src="/static/js/chart.js"></script>
<style>
/* 全局企业化基础样式 */
*{box-sizing:border-box;margin:0;padding:0}
body{background:#F5F7FA;font-family:"Microsoft YaHei",system-ui,sans-serif;color:#1D2129;font-size:14px;}
.container{max-width:1400px;margin:0 auto;padding:20px;}
.header-bar{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;}
.back-link{color:#165DFF;text-decoration:none;font-size:14px;}
.user-info{font-size:14px;color:#4E5969;}
/* 卡片通用 */
.card{background:#FFFFFF;border-radius:8px;padding:20px;box-shadow:0 1px 2px rgba(0,0,0,0.06);margin-bottom:16px;}
.card-title{font-size:15px;font-weight:500;color:#1D2129;margin-bottom:14px;}
/* 筛选区域 */
.filter-row{display:flex;flex-wrap:wrap;gap:16px;align-items:flex-end;margin-bottom:16px;}
.filter-full{width:100%;}
.filter-item{display:flex;flex-direction:column;gap:6px;min-width:160px;}
label{font-size:13px;color:#4E5969;}
input[type="date"],input[type="text"]{height:36px;padding:0 12px;border:1px solid #DCDFE6;border-radius:6px;font-size:14px;color:#1D2129;}
.check-group{display:flex;gap:12px;flex-wrap:wrap;}
.check-group label{display:flex;align-items:center;gap:4px;cursor:pointer;}
/* 按钮规范 */
.btn{height:36px;padding:0 16px;border:none;border-radius:6px;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:6px;transition:0.15s;}
.btn-primary{background:#165DFF;color:#fff;}
.btn-primary:hover{background:#0E4BDB;}
.btn-warning{background:#FF7D00;color:#fff;}
.btn-outline{background:#fff;color:#4E5969;border:1px solid #DCDFE6;}
.btn-outline:hover{border-color:#165DFF;color:#165DFF;}
.btn-sm{height:28px;padding:0 8px;font-size:13px;}
/* 统计指标卡片 */
.stat-row{display:grid;grid-template-columns:repeat(4,1fr);gap:16px;margin-bottom:16px;}
.stat-card{padding:16px;border-radius:8px;text-align:center;}
.stat-1{background:#E8F3FF;}
.stat-2{background:#FFECE8;}
.stat-3{background:#E6FFEA;}
.stat-4{background:#FFF7E6;}
.stat-label{font-size:13px;color:#4E5969;}
.stat-num{font-size:26px;font-weight:600;margin:8px 0;}
/* 图表区域 固定高度 防止溢出 */
.chart-row{display:grid;grid-template-columns:2fr 1fr;gap:16px;margin-bottom:16px;}
.chart-box{width:100%;height:260px;position:relative;}
canvas{width:100% !important;height:100% !important;}
/* 表格 */
.table-wrap{overflow-x:auto;}
table{width:100%;border-collapse:collapse;font-size:14px;}
th{background:#F5F7FA;padding:12px 10px;text-align:left;border-bottom:1px solid #E5E6EB;color:#4E5969;font-weight:500;white-space:nowrap;}
td{padding:12px 10px;border-bottom:1px solid #F2F3F5;color:#1D2129;}
.row-firing{background:#FFF7F7;}
.row-resolved{background:#F7FFF9;}
.storm{color:#F53F3F;font-weight:500;}
/* 分页 */
.pagination{display:flex;justify-content:space-between;align-items:center;margin-top:16px;flex-wrap:wrap;gap:12px;}
.pag-btn{height:32px;padding:0 12px;border:1px solid #DCDFE6;background:#fff;border-radius:6px;cursor:pointer;font-size:14px;}
.pag-info{color:#4E5969;}
/* 弹窗 */
.modal-mask{position:fixed;top:0;left:0;width:100%;height:100%;background:rgba(0,0,0,0.4);display:none;align-items:center;justify-content:center;z-index:999;}
.modal-box{width:760px;max-height:80vh;background:#fff;border-radius:8px;padding:24px;overflow-y:auto;}
.modal-head{display:flex;justify-content:space-between;align-items:center;margin-bottom:16px;}
.modal-close{cursor:pointer;font-size:20px;color:#4E5969;}
.labels-block{background:#F5F7FA;padding:12px;border-radius:6px;margin:12px 0;white-space:pre-wrap;font-family:monospace;font-size:13px;}
/* 空数据、提示文字 */
.empty-box{padding:60px;text-align:center;color:#86909C;}
.tip-text{font-size:13px;color:#86909C;margin-left:8px;}
.shortcut-tip{text-align:center;color:#86909C;font-size:13px;margin-top:12px;}
</style>
</head>
<body>
<div class="container">
<div class="header-bar">
<a href="dashboard.php" class="back-link"><i class="fa fa-arrow-left"></i> 返回监控大盘</a>
<div class="user-info"><i class="fa fa-user"></i> <?php echo $userName; ?></div>
</div>
<!-- 筛选区域 -->
<div class="card">
<div class="filter-row filter-full">
<input type="text" id="globalSearch" placeholder="<?php echo $t['global_search']; ?>" style="width:100%;">
</div>
<div class="filter-row">
<div class="filter-item">
<label><?php echo $t['start_date']; ?></label>
<input type="date" id="startDate" value="<?php echo $today; ?>">
</div>
<div class="filter-item">
<label><?php echo $t['end_date']; ?></label>
<input type="date" id="endDate" value="<?php echo $today; ?>">
</div>
<div class="filter-item">
<label><?php echo $t['filter_instance']; ?></label>
<input type="text" id="filterInstance" placeholder="10.150.10.82">
</div>
<div class="filter-item">
<label><?php echo $t['severity_title']; ?></label>
<div class="check-group">
<label><input type="checkbox" name="sev" value="critical"> Critical</label>
<label><input type="checkbox" name="sev" value="warning"> Warning</label>
<label><input type="checkbox" name="sev" value="info"> Info</label>
</div>
</div>
<div class="filter-item">
<label><?php echo $t['status_title']; ?></label>
<div class="check-group">
<label><input type="checkbox" name="sta" value="firing"> Firing</label>
<label><input type="checkbox" name="sta" value="resolved"> Resolved</label>
</div>
</div>
</div>
<div class="filter-row">
<button class="btn btn-primary" id="btnSearch"><i class="fa fa-search"></i> <?php echo $t['btn_query']; ?></button>
<button class="btn btn-outline" id="btnReset"><i class="fa fa-refresh"></i> <?php echo $t['btn_reset']; ?></button>
<button class="btn btn-warning" id="btnToggleDistinct"><i class="fa fa-list"></i> <?php echo $t['btn_detail_mode']; ?></button>
<span class="tip-text" id="modeTip"><?php echo $t['tip_distinct']; ?></span>
<button class="btn btn-outline" id="btnExport"><i class="fa fa-file-excel-o"></i> <?php echo $t['btn_export']; ?></button>
<button class="btn btn-outline" id="btnCopyLink"><i class="fa fa-link"></i> <?php echo $t['btn_copy_link']; ?></button>
<label style="display:flex;align-items:center;gap:6px;">
<input type="checkbox" id="autoRefresh"> <?php echo $t['btn_refresh_auto']; ?>
</label>
</div>
</div>
<!-- 统计卡片 + 图表 -->
<div class="card" id="statCard" style="display:none;">
<div class="stat-row">
<div class="stat-card stat-1">
<div class="stat-label"><?php echo $t['stat_total']; ?></div>
<div class="stat-num" id="statTotal">0</div>
</div>
<div class="stat-card stat-2">
<div class="stat-label"><?php echo $t['stat_firing']; ?></div>
<div class="stat-num" id="statFiring">0</div>
</div>
<div class="stat-card stat-3">
<div class="stat-label"><?php echo $t['stat_resolved']; ?></div>
<div class="stat-num" id="statResolved">0</div>
</div>
<div class="stat-card stat-4">
<div class="stat-label"><?php echo $t['stat_critical']; ?></div>
<div class="stat-num" id="statCritical">0</div>
</div>
</div>
<div class="chart-row">
<div>
<h4 class="card-title"><?php echo $t['hour_chart_title']; ?></h4>
<div class="chart-box">
<canvas id="hourChart"></canvas>
</div>
</div>
<div>
<h4 class="card-title"><?php echo $t['pie_title']; ?></h4>
<div class="chart-box">
<canvas id="pieChart"></canvas>
</div>
</div>
</div>
</div>
<!-- 告警表格 -->
<div class="card">
<div class="table-wrap">
<table>
<thead id="tableHead"></thead>
<tbody id="tableBody">
<tr><td colspan="6" class="empty-box">Loading...</td></tr>
</tbody>
</table>
</div>
<div class="pagination">
<div class="pag-info"><?php echo $t['stat_total']; ?><span id="pageTotal">0</span></div>
<div style="display:flex;gap:8px;align-items:center;">
<span>每页</span>
<select id="pageSizeSel" class="pag-btn">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
</select>
<button class="pag-btn" id="pageFirst">首页</button>
<button class="pag-btn" id="pagePrev">上一页</button>
<span id="pageInfo">1 / 1</span>
<button class="pag-btn" id="pageNext">下一页</button>
<button class="pag-btn" id="pageLast">末页</button>
</div>
</div>
</div>
<div class="shortcut-tip"><?php echo $t['shortcut_tip']; ?></div>
</div>
<!-- 详情弹窗 -->
<div class="modal-mask" id="modalMask">
<div class="modal-box">
<div class="modal-head">
<h3 id="modalTitle"><?php echo $t['modal_title']; ?></h3>
<span class="modal-close" id="modalClose">×</span>
</div>
<div id="modalContent"></div>
<div style="margin-top:16px;">
<a target="_blank" id="grafanaLink" class="btn btn-primary"><i class="fa fa-chart-line"></i> <?php echo $t['modal_grafana']; ?></a>
</div>
</div>
</div>
<script>
const apiUrl = "./api/api_alert_list.php";
const grafanaBase = "<?php echo $grafanaUrl; ?>";
let page = 1;
let pageSize = 20;
let total = 0;
let distinctMode = 1;
let autoRefreshTimer = null;
let hourChart = null;
let pieChart = null;
const stormThreshold = 1000;
const t = <?php echo json_encode($t); ?>;
// 本地筛选记忆
function saveFilter() {
const data = {
start: $("#startDate").val(),
end: $("#endDate").val(),
instance: $("#filterInstance").val(),
global: $("#globalSearch").val(),
sev: $("input[name=sev]:checked").map((i,e)=>e.value).get(),
sta: $("input[name=sta]").map((i,e)=>e.value).get(),
distinct: distinctMode,
pageSize: pageSize
};
localStorage.setItem("alertFilter", JSON.stringify(data));
}
function loadFilter() {
const s = localStorage.getItem("alertFilter");
if (!s) return;
const d = JSON.parse(s);
$("#startDate").val(d.start);
$("#endDate").val(d.end);
$("#filterInstance").val(d.instance);
$("#globalSearch").val(d.global);
pageSize = d.pageSize;
$("#pageSizeSel").val(pageSize);
distinctMode = d.distinct;
$("#modeTip").text(distinctMode === 1 ? t.tip_distinct : t.tip_raw);
$("input[name=sev]").prop("checked", false);
d.sev.forEach(v=>$(`input[name=sev][value="${v}"]`).prop("checked",true));
$("input[name=sta]").prop("checked", false);
d.sta.forEach(v=>$(`input[name=sta][value="${v}"]`).prop("checked",true));
}
// 渲染表头
function renderHead() {
let h = "";
if (distinctMode === 1) {
h = `<tr>
<th>${t['col_state']}</th>
<th>${t['col_alert']}</th>
<th>${t['col_instance']}</th>
<th>${t['col_severity']}</th>
<th>${t['col_count']}</th>
<th>${t['col_time']}</th>
<th>操作</th>
</tr>`;
} else {
h = `<tr>
<th>${t['col_state']}</th>
<th>${t['col_alert']}</th>
<th>${t['col_instance']}</th>
<th>${t['col_severity']}</th>
<th>${t['col_time']}</th>
<th>操作</th>
</tr>`;
}
$("#tableHead").html(h);
}
// 绘图 严格限制尺寸、精简配置 解决撑满页面问题
function drawChart(stat) {
$("#statCard").show();
$("#statTotal").text(stat.total_all);
$("#statFiring").text(stat.total_firing);
$("#statResolved").text(stat.total_resolved);
$("#statCritical").text(stat.total_critical);
const hourData = stat.hour_data;
const hourLabels = [];
const hourVals = [];
for (let i=0;i<24;i++) {
hourLabels.push(i+":00");
hourVals.push(hourData[i]);
}
// 销毁旧图表防止叠加溢出
if (hourChart) hourChart.destroy();
const hCtx = document.getElementById("hourChart").getContext("2d");
hourChart = new Chart(hCtx, {
type: "line",
data: {
labels: hourLabels,
datasets: [{
label: "触发量",
data: hourVals,
borderColor: "#165DFF",
backgroundColor: "rgba(22, 93, 255, 0.08)",
fill: true,
tension: 0.2,
pointRadius: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {display:true,position:"top",labels:{boxWidth:12,font:{size:12}}},
tooltip: {mode:"index",intersect:false}
},
scales: {
x: {grid:{display:false},ticks:{font:{size:11}}},
y: {grid:{color:"#F2F3F5"},ticks:{font:{size:11}}}
}
}
});
if (pieChart) pieChart.destroy();
const pCtx = document.getElementById("pieChart").getContext("2d");
let c=0,w=0,i=0;
$("#tableBody tr").each((idx,tr)=>{
const s = $(tr).find("td:nth-child(4)").text();
if (s.includes("critical")) c++;
else if (s.includes("warning")) w++;
else i++;
})
pieChart = new Chart(pCtx, {
type:"doughnut",
data:{
labels:["Critical","Warning","Info"],
datasets:[{
data:[c,w,i],
backgroundColor:["#F53F3F","#FF7D00","#165DFF"],
borderWidth:0
}]
},
options:{
responsive:true,
maintainAspectRatio:false,
plugins:{legend:{position:"bottom",labels:{boxWidth:12,font:{size:12}}}},
cutout:"60%" // 缩小饼图半径,不会超大
}
});
}
// 加载数据
function loadData() {
saveFilter();
renderHead();
$("#tableBody").html(`<tr><td colspan="${distinctMode===1?7:6}" class="empty-box">Loading...</td></tr>`);
const sevArr = $("input[name=sev]:checked").map((i,e)=>e.value).get();
const staArr = $("input[name=sta]").map((i,e)=>e.value).get();
const params = new URLSearchParams();
params.append("page", page);
params.append("page_size", pageSize);
params.append("start_date", $("#startDate").val());
params.append("end_date", $("#endDate").val());
params.append("instance", $("#filterInstance").val());
params.append("global", $("#globalSearch").val());
params.append("distinct", distinctMode);
sevArr.forEach(v=>params.append("severity[]",v));
staArr.forEach(v=>params.append("status[]",v));
fetch(`${apiUrl}?${params.toString()}`)
.then(r=>r.json())
.then(res=>{
if (res.code !== 0) {
$("#tableBody").html(`<tr><td colspan="${distinctMode===1?7:6}" class="empty-box" style="color:#F53F3F;"><i class="fa fa-exclamation-circle"></i> ${res.msg}</td></tr>`);
$("#statCard").hide();
return;
}
total = res.total;
$("#pageTotal").text(total);
const totalPage = Math.ceil(total / pageSize) || 1;
$("#pageInfo").text(`${page} / ${totalPage}`);
if(res.stat.total_all > 0){
drawChart(res.stat);
}else{
$("#statCard").hide();
}
const list = res.list;
if (list.length === 0) {
$("#tableBody").html(`<tr><td colspan="${distinctMode===1?7:6}" class="empty-box">${t.empty_tip}</td></tr>`);
return;
}
let html = "";
list.forEach(row=>{
const cls = row.state === "firing" ? "row-firing" : "row-resolved";
const tagClass = row.state === "firing" ? "tag-firing" : "tag-resolved";
const tagText = row.state === "firing" ? "Firing" : "Resolved";
let sevCls = "";
if (row.severity === "critical") sevCls = "severity-critical";
else if (row.severity === "warning") sevCls = "severity-warning";
else sevCls = "severity-info";
let storm = "";
if (distinctMode === 1 && row.total_count > stormThreshold) storm = `<span class="storm">${t.storm_tip}</span>`;
if (distinctMode === 1) {
html += `<tr class="${cls}" data-row='${JSON.stringify(row)}'>
<td><span class="${tagClass}">${tagText}</span></td>
<td>${row.metric}</td>
<td>${row.instance}</td>
<td><span class="${sevCls}">${row.severity}</span></td>
<td>${row.total_count} ${storm}</td>
<td>${row.time}</td>
<td>
<button class="btn btn-sm btn-outline copy-name" data-name="${row.metric}">${t.btn_copy_name}</button>
<button class="btn btn-sm btn-outline detail-open">详情</button>
</td>
</tr>`;
} else {
html += `<tr class="${cls}" data-row='${JSON.stringify(row)}'>
<td><span class="${tagClass}">${tagText}</span></td>
<td>${row.metric}</td>
<td>${row.instance}</td>
<td><span class="${sevCls}">${row.severity}</span></td>
<td>${row.time}</td>
<td>
<button class="btn btn-sm btn-outline copy-name" data-name="${row.metric}">${t.btn_copy_name}</button>
<button class="btn btn-sm btn-outline detail-open">详情</button>
</td>
</tr>`;
}
})
$("#tableBody").html(html);
})
.catch(()=>{
$("#tableBody").html(`<tr><td colspan="${distinctMode===1?7:6}" class="empty-box" style="color:#F53F3F;"><i class="fa fa-exclamation-circle"></i> 接口请求失败</td></tr>`);
$("#statCard").hide();
})
}
// 导出CSV
function exportExcel() {
const sevArr = $("input[name=sev]:checked").map((i,e)=>e.value).get();
const staArr = $("input[name=sta]").map((i,e)=>e.value).get();
const params = new URLSearchParams();
params.append("export",1);
params.append("page_size", 9999);
params.append("start_date", $("#startDate").val());
params.append("end_date", $("#endDate").val());
params.append("instance", $("#filterInstance").val());
params.append("global", $("#globalSearch").val());
params.append("distinct", distinctMode);
sevArr.forEach(v=>params.append("severity[]",v));
staArr.forEach(v=>params.append("status[]",v));
fetch(`${apiUrl}?${params.toString()}`)
.then(r=>r.json())
.then(res=>{
if (res.code !== 0) return alert(res.msg);
let csv = "";
if (distinctMode === 1) {
csv = "状态,告警名称,实例,级别,触发次数,最新时间\n";
res.list.forEach(r=>csv += `${r.state},${r.metric},${r.instance},${r.severity},${r.total_count},${r.time}\n`);
} else {
csv = "状态,告警名称,实例,级别,触发时间\n";
res.list.forEach(r=>csv += `${r.state},${r.metric},${r.instance},${r.severity},${r.time}\n`);
}
const blob = new Blob([csv], {type:"text/csv;charset=utf-8"});
const a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = "alert_export_"+new Date().getTime()+".csv";
a.click();
})
}
// 复制筛选链接
function copyLink() {
const sevArr = $("input[name=sev]:checked").map((i,e)=>e.value).get();
const staArr = $("input[name=sta]").map((i,e)=>e.value).get();
const params = new URLSearchParams();
params.append("start_date", $("#startDate").val());
params.append("end_date", $("#endDate").val());
params.append("instance", $("#filterInstance").val());
params.append("global", $("#globalSearch").val());
params.append("distinct", distinctMode);
sevArr.forEach(v=>params.append("severity[]",v));
staArr.forEach(v=>params.append("status[]",v));
const link = location.origin + location.pathname + "?" + params.toString();
navigator.clipboard.writeText(link).then(()=>alert("筛选链接已复制"));
}
// 弹窗详情
function openModal(row) {
const metric = row.metric;
const inst = row.instance;
const grafanaLink = `${grafanaBase}/d/alert-overview?var-alert=${encodeURIComponent(metric)}&var-instance=${encodeURIComponent(inst)}`;
$("#grafanaLink").attr("href", grafanaLink);
let html = `<h4>${metric}</h4>
<p>实例:${inst}|级别:${row.severity}|状态:${row.state}</p>
<div class="labels-block">${JSON.stringify(row.labels, null, 2)}</div>`;
$("#modalContent").html(html);
$("#modalMask").css("display","flex");
}
// 自动刷新开关
function toggleAutoRefresh() {
if ($("#autoRefresh").is(":checked")) {
autoRefreshTimer = setInterval(()=>loadData(), 60000);
} else {
clearInterval(autoRefreshTimer);
}
}
$(function(){
loadFilter();
loadData();
// 切换汇总/详细模式
$("#btnToggleDistinct").click(()=>{
distinctMode = distinctMode === 1 ? 0 : 1;
$("#modeTip").text(distinctMode === 1 ? t.tip_distinct : t.tip_raw);
page = 1;
loadData();
})
// 查询按钮
$("#btnSearch").click(()=>{page=1;loadData();});
// 重置筛选
$("#btnReset").click(()=>{
const today = new Date().toISOString().split('T')[0];
$("#startDate").val(today);
$("#endDate").val(today);
$("#filterInstance").val("");
$("#globalSearch").val("");
$("input[name=sev]").prop("checked",false);
$("input[name=sta]").prop("checked",false);
distinctMode = 1;
$("#modeTip").text(t.tip_distinct);
page = 1;
loadData();
})
// 导出、复制链接、自动刷新
$("#btnExport").click(exportExcel);
$("#btnCopyLink").click(copyLink);
$("#autoRefresh").change(toggleAutoRefresh);
// 每页条数切换
$("#pageSizeSel").change(function(){
pageSize = parseInt($(this).val());
page = 1;
loadData();
})
// 分页按钮
$("#pageFirst").click(()=>{page=1;loadData();});
$("#pagePrev").click(()=>{if(page>1){page--;loadData();}});
$("#pageNext").click(()=>{const tp=Math.ceil(total/pageSize);if(page<tp){page++;loadData();}});
$("#pageLast").click(()=>{page=Math.ceil(total/pageSize);loadData();});
// 复制告警名、打开详情弹窗
$(document).on("click",".copy-name",function(){
const name = $(this).data("name");
navigator.clipboard.writeText(name).then(()=>alert("已复制告警名称:"+name));
})
$(document).on("click",".detail-open",function(){
const rowJson = $(this).closest("tr").attr("data-row");
const row = JSON.parse(rowJson);
openModal(row);
})
// 关闭弹窗
$("#modalClose, #modalMask").click(function(e){
if (e.target === this) $("#modalMask").hide();
})
// 全局快捷键
$(document).keydown(function(e){
if (e.key === "Enter") {
e.preventDefault();
page = 1;
loadData();
}
if (e.key === "Escape") {
$("#modalMask").hide();
}
if (e.ctrlKey && e.key.toLowerCase() === "f") {
e.preventDefault();
$("#globalSearch").focus();
}
})
})
</script>
</body>
</html>