Initial commit

This commit is contained in:
2026-07-07 13:29:22 +08:00
commit 3fdd9ec927
154 changed files with 56566 additions and 0 deletions
+374
View File
@@ -0,0 +1,374 @@
<?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'];
$roleText = $userRole === 'admin' ? '管理员' : '只读用户';
$dbHost = "10.150.117.190";
$dbUser = "root";
$dbPwd = "hp93000";
$dbName = "alert_mail_stat";
$connConfig = mysqli_connect($dbHost, $dbUser, $dbPwd, $dbName);
mysqli_set_charset($connConfig, "utf8mb4");
$configSql = "SELECT k,v FROM sys_config";
$configRes = mysqli_query($connConfig, $configSql);
$sysConfig = [];
while ($row = mysqli_fetch_assoc($configRes)) $sysConfig[$row['k']] = $row['v'];
mysqli_close($connConfig);
$currentLang = $sysConfig['lang'] ?? "zh";
$langMap = [
'zh' => [
'html_lang' => 'zh-CN',
'page_title_list' => '告警分页列表',
'filter_date' => '日期筛选',
'filter_alert_name' => '告警名称',
'filter_severity' => '告警级别',
'filter_status' => '告警状态',
'btn_search' => '查询',
'btn_reset' => '重置',
'page_total' => '共',
'page_record' => '条记录',
'page_size' => '每页',
'page_prev' => '上一页',
'page_next' => '下一页',
'page_first' => '首页',
'page_last' => '末页',
'empty_no_data' => '暂无告警时序数据',
'server_fail' => 'Prometheus 接口请求失败',
'tag' => '状态',
'alert_name' => '告警指标',
'instance_addr' => '实例',
'severity' => '级别',
'occur_time_group' => '最新触发时间',
'occur_time_detail' => '触发时间',
'firing' => '触发',
'resolved' => '恢复',
'critical' => '严重',
'warning' => '警告',
'info' => '信息',
'btn_detail' => '详细模式',
'tip_distinct' => '当前:合并去重(汇总次数)',
'tip_all' => '当前:详细时序(逐条展示触发时间)',
'col_count' => '总触发次数'
],
'en' => [
'html_lang' => 'en',
'page_title_list' => 'Alert Pagination List',
'filter_date' => 'Date',
'filter_alert_name' => 'Metric',
'filter_severity' => 'Severity',
'filter_status' => 'Status',
'btn_search' => 'Search',
'btn_reset' => 'Reset',
'page_total' => 'Total ',
'page_record' => ' records',
'page_size' => 'Per page',
'page_prev' => 'Prev',
'page_next' => 'Next',
'page_first' => 'First',
'page_last' => 'Last',
'empty_no_data' => 'No alert time series data',
'server_fail' => 'Prometheus API request failed',
'tag' => 'Status',
'alert_name' => 'Metric',
'instance_addr' => 'Instance',
'severity' => 'Severity',
'occur_time_group' => 'Last Trigger Time',
'occur_time_detail' => 'Trigger Time',
'firing' => 'Firing',
'resolved' => 'Resolved',
'critical' => 'Critical',
'warning' => 'Warning',
'info' => 'Info',
'btn_detail' => 'Detail Mode',
'tip_distinct' => 'Mode: Deduplicated (Total Count)',
'tip_all' => 'Mode: Raw Time Series (Each Trigger)',
'col_count' => 'Total Triggers'
]
];
$t = $langMap[$currentLang];
?>
<!DOCTYPE html>
<html lang="<?php echo $t['html_lang']; ?>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $t['page_title_list']; ?></title>
<link rel="stylesheet" href="/static/css/font-awesome.min.css">
<script src="/static/js/jquery.min.js"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#f9fafc;font-family:"Inter","PingFang SC","Microsoft YaHei";color:#1d2939;line-height:1.6;height:100vh;overflow:auto;padding:24px;}
.base-card{background:#fff;border-radius:16px;padding:32px;box-shadow:0 3px 18px rgba(21,44,91,0.06);border:1px solid #eef2fb;max-width:1600px;margin:0 auto;}
.title-wrap{display:flex;justify-content:space-between;align-items:center;margin-bottom:26px;padding-bottom:16px;border-bottom:1px solid #eef2fb;}
.title-left{display:flex;align-items:center;gap:12px;font-size:19px;font-weight:600;color:#152c5b;}
.title-left i{color:#2563eb;font-size:21px}
.filter-bar{display:flex;flex-wrap:wrap;gap:16px;margin-bottom:24px;align-items:center;}
.filter-bar label{font-size:14px;color:#475569;display:flex;align-items:center;gap:8px;}
.filter-bar input,.filter-bar select{padding:6px 10px;border:1px solid #cbd5e1;border-radius:6px;}
.btn-base{border:none;padding:9px 16px;border-radius:8px;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:6px;}
.btn-success{background:#10b981;color:#fff;}
.btn-outline{background:#fff;color:#152c5b;border:1px solid #cbd5e1;}
.btn-warning{background:#f59e0b;color:#fff;}
.table-wrap{overflow-x:auto;margin-bottom:24px;}
table{width:100%;border-collapse:collapse;font-size:14px}
th{background:#f9fafc;color:#475569;font-weight:600;padding:16px 18px;text-align:left;border-bottom:2px solid #e2e8f0;white-space:nowrap;}
td{padding:18px;border-bottom:1px solid #f1f5f9;color:#334155;}
tbody tr:hover{background:#f7f8fc}
tbody tr:nth-child(even){background:#fbfcfe}
.status-tag{display:inline-flex;align-items:center;gap:5px;padding:6px 14px;border-radius:24px;font-size:13px;font-weight:500;}
.tag-firing{background:#fee2e2;color:#dc2626}
.tag-resolved{background:#dcfce7;color:#16a34a}
.severity-critical{background:#fee2e2;color:#dc2626;padding:5px 12px;border-radius:8px;font-size:13px;}
.severity-warning{background:#ffedd5;color:#ea580c;padding:5px 12px;border-radius:8px;font-size:13px;}
.severity-info{background:#dbeafe;color:#2563eb;padding:5px 12px;border-radius:8px;font-size:13px;}
.empty-state{text-align:center;padding:80px 20px;color:#94a3b8;font-size:15px;}
.empty-icon{font-size:56px;margin-bottom:18px;opacity:0.35;}
.pagination{display:flex;justify-content:space-between;align-items:center;flex-wrap:wrap;gap:12px;}
.pag-btn{padding:4px 10px;border:1px solid #cbd5e1;border-radius:6px;background:#fff;cursor:pointer;}
.pag-info{margin:0 12px;}
.header-top{display:flex;justify-content:space-between;align-items:center;margin-bottom:24px;max-width:1600px;margin-left:auto;margin-right:auto;}
.user-bar{display:flex;gap:16px;align-items:center;font-size:14px;color:#475569;}
.back-btn{color:#2563eb;text-decoration:none;}
.mode-tip{color:#64748b;font-size:13px;margin-left:10px;}
.count-badge{font-weight:bold;color:#dc2626;}
</style>
</head>
<body>
<div class="header-top">
<a href="dashboard.php" class="back-btn"><i class="fa fa-arrow-left"></i> 返回监控大屏</a>
<div class="user-bar">
<span><i class="fa fa-user-circle"></i> <?php echo $userName; ?> (<?php echo $roleText; ?>)</span>
<a href="logout.php" class="back-btn">退出登录</a>
</div>
</div>
<div class="base-card">
<div class="title-wrap">
<div class="title-left"><i class="fa fa-list"></i><?php echo $t['page_title_list']; ?></div>
<div style="display:flex;align-items:center;">
<button class="btn-base btn-warning" id="btnToggleDetail"><?php echo $t['btn_detail']; ?></button>
<span class="mode-tip" id="modeTip"><?php echo $t['tip_distinct']; ?></span>
</div>
</div>
<div class="filter-bar">
<label><?php echo $t['filter_date']; ?>
<input type="date" id="filterDate">
</label>
<label><?php echo $t['filter_alert_name']; ?>
<input type="text" id="filterMetric" placeholder="metric name">
</label>
<label><?php echo $t['filter_severity']; ?>
<select id="filterSeverity">
<option value="">--</option>
<option value="critical">Critical</option>
<option value="warning">Warning</option>
<option value="info">Info</option>
</select>
</label>
<label><?php echo $t['filter_status']; ?>
<select id="filterStatus">
<option value="">--</option>
<option value="firing"><?php echo $t['firing']; ?></option>
<option value="resolved"><?php echo $t['resolved']; ?></option>
</select>
</label>
<button class="btn-base btn-success" id="btnSearch"><?php echo $t['btn_search']; ?></button>
<button class="btn-base btn-outline" id="btnReset"><?php echo $t['btn_reset']; ?></button>
</div>
<div class="table-wrap">
<table id="dataTable">
<thead id="tableHead">
<!-- JS动态渲染表头 -->
</thead>
<tbody id="tableBody">
<tr>
<td colspan="6" class="empty-state">
<div class="empty-icon"><i class="fa fa-spinner fa-spin"></i></div>
<div>Loading...</div>
</td>
</tr>
</tbody>
</table>
</div>
<div class="pagination">
<div>
<?php echo $t['page_total']; ?><span id="totalCnt">0</span><?php echo $t['page_record']; ?>
</div>
<div style="display:flex;gap:8px;align-items:center;">
<span><?php echo $t['page_size']; ?></span>
<select id="pageSize">
<option value="10">10</option>
<option value="20" selected>20</option>
<option value="50">50</option>
</select>
<button class="pag-btn" id="pageFirst"><?php echo $t['page_first']; ?></button>
<button class="pag-btn" id="pagePrev"><?php echo $t['page_prev']; ?></button>
<span id="pageInfo">1 / 1</span>
<button class="pag-btn" id="pageNext"><?php echo $t['page_next']; ?></button>
<button class="pag-btn" id="pageLast"><?php echo $t['page_last']; ?></button>
</div>
</div>
</div>
<script>
const apiUrl = "./api/api_alert_list.php";
let page = 1;
let pageSize = 20;
let total = 0;
const defaultDate = new Date().toISOString().split('T')[0];
let filter = {
date: defaultDate,
metric: "",
severity: "",
status: ""
};
let distinctMode = 1; // 1汇总去重,0详细逐条
const t = <?php echo json_encode($t); ?>;
$(function(){
$("#filterDate").val(defaultDate);
loadData();
})
// 切换模式按钮
$("#btnToggleDetail").click(function(){
distinctMode = distinctMode === 1 ? 0 : 1;
$("#modeTip").text(distinctMode === 1 ? t.tip_distinct : t.tip_all);
page = 1;
loadData();
})
// 渲染表头
function renderTableHead(){
let headHtml = "";
if(distinctMode === 1){
// 汇总模式:带总次数列
headHtml = `<tr>
<th>${t.tag}</th>
<th>${t.alert_name}</th>
<th>${t.instance_addr}</th>
<th>${t.severity}</th>
<th>${t.col_count}</th>
<th>${t.occur_time_group}</th>
</tr>`;
}else{
// 详细模式:无次数列,纯触发时间
headHtml = `<tr>
<th>${t.tag}</th>
<th>${t.alert_name}</th>
<th>${t.instance_addr}</th>
<th>${t.severity}</th>
<th>${t.occur_time_detail}</th>
</tr>`;
}
$("#tableHead").html(headHtml);
}
function loadData(){
renderTableHead();
$("#tableBody").html(`<tr><td colspan="${distinctMode === 1 ? 6 : 5}" class="empty-state"><div class="empty-icon"><i class="fa fa-spinner fa-spin"></i></div><div>Loading...</div></td></tr>`);
const params = new URLSearchParams();
params.append("page", page);
params.append("page_size", pageSize);
params.append("date", filter.date);
params.append("metric", filter.metric);
params.append("severity", filter.severity);
params.append("status", filter.status);
params.append("distinct", distinctMode);
fetch(`${apiUrl}?${params.toString()}`)
.then(r=>r.json())
.then(res=>{
if(res.code !== 0){
$("#tableBody").html(`<tr><td colspan="${distinctMode === 1 ? 6 : 5}" class="empty-state" style="color:#dc2626"><div class="empty-icon"><i class="fa fa-exclamation-circle"></i></div><div>${res.msg}</div></td></tr>`);
return;
}
const rawList = res.list;
total = res.total;
$("#totalCnt").text(total);
const totalPage = Math.ceil(total / pageSize) || 1;
$("#pageInfo").text(`${page} / ${totalPage}`);
const tbody = $("#tableBody");
if(rawList.length === 0){
tbody.html(`<tr><td colspan="${distinctMode === 1 ? 6 : 5}" class="empty-state"><div class="empty-icon"><i class="fa fa-folder-open-o"></i></div><div>${t.empty_no_data}</div></td></tr>`);
return;
}
let html = "";
rawList.forEach(row=>{
const tagClass = row.state === "firing" ? "tag-firing" : "tag-resolved";
const tagText = row.state === "firing" ? t.firing : t.resolved;
let sevClass = "severity-info";
if(row.severity === "critical") sevClass = "severity-critical";
if(row.severity === "warning") sevClass = "severity-warning";
if(distinctMode === 1){
// 汇总行,带计数
html += `<tr>
<td><span class="status-tag ${tagClass}"><i class="fa fa-bolt"></i>${tagText}</span></td>
<td>${row.metric}</td>
<td>${row.instance}</td>
<td><span class="${sevClass}">${row.severity}</span></td>
<td><span class="count-badge">${row.total_count}</span></td>
<td>${row.time}</td>
</tr>`;
}else{
// 详细模式,无计数
html += `<tr>
<td><span class="status-tag ${tagClass}"><i class="fa fa-bolt"></i>${tagText}</span></td>
<td>${row.metric}</td>
<td>${row.instance}</td>
<td><span class="${sevClass}">${row.severity}</span></td>
<td>${row.time}</td>
</tr>`;
}
})
tbody.html(html);
})
.catch(()=>{
$("#tableBody").html(`<tr><td colspan="${distinctMode === 1 ? 6 : 5}" class="empty-state" style="color:#dc2626"><div class="empty-icon"><i class="fa fa-exclamation-circle"></i></div><div>${t.server_fail}</div></td></tr>`);
})
}
// 查询按钮
$("#btnSearch").click(function(){
filter.date = $("#filterDate").val();
filter.metric = $("#filterMetric").val().trim();
filter.severity = $("#filterSeverity").val();
filter.status = $("#filterStatus").val();
page = 1;
loadData();
})
// 重置:恢复默认去重汇总模式
$("#btnReset").click(function(){
$("#filterDate").val(defaultDate);
$("#filterMetric").val("");
$("#filterSeverity").val("");
$("#filterStatus").val("");
filter = {date: defaultDate, metric: "", severity: "", status: ""};
distinctMode = 1;
$("#modeTip").text(t.tip_distinct);
page = 1;
loadData();
})
$("#pageSize").change(function(){
pageSize = parseInt($(this).val());
page = 1;
loadData();
})
$("#pageFirst").click(()=>{if(page!==1){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(()=>{const tp=Math.ceil(total/pageSize);if(page!==tp){page=tp;loadData();}})
</script>
</body>
</html>