602 lines
19 KiB
Plaintext
602 lines
19 KiB
Plaintext
<?php
|
||
session_start();
|
||
// 会话超时:30分钟无操作自动登出
|
||
$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' ? '管理员' : '只读用户';
|
||
?>
|
||
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Advantest 爱德万测试 - 运维告警监控平台</title>
|
||
<link rel="stylesheet" href="/static/css/font-awesome.min.css">
|
||
<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:#f9fafc;
|
||
font-family:"Inter","PingFang SC","Microsoft YaHei";
|
||
color:#1d2939;
|
||
line-height:1.6;
|
||
}
|
||
/* 顶部导航栏 高端纯白渐变 */
|
||
.header{
|
||
background:#ffffff;
|
||
border-bottom:1px solid #eef2fb;
|
||
color:#152c5b;
|
||
padding:20px 40px;
|
||
display:flex;
|
||
justify-content:space-between;
|
||
align-items:center;
|
||
box-shadow:0 2px 12px rgba(21,44,91,0.05);
|
||
}
|
||
.header-left{
|
||
display:flex;
|
||
align-items:center;
|
||
gap:14px;
|
||
}
|
||
.header-brand-en{
|
||
font-size:24px;
|
||
font-weight:700;
|
||
letter-spacing:2px;
|
||
color:#152c5b;
|
||
}
|
||
.header-brand-cn{
|
||
font-size:15px;
|
||
color:#64748b;
|
||
}
|
||
.header-right{
|
||
display:flex;
|
||
align-items:center;
|
||
gap:18px;
|
||
font-size:14px;
|
||
}
|
||
.user-info,.time-info{
|
||
color:#475569;
|
||
display:flex;
|
||
align-items:center;
|
||
gap:6px;
|
||
}
|
||
.btn-base{
|
||
border:none;
|
||
padding:9px 20px;
|
||
border-radius:8px;
|
||
cursor:pointer;
|
||
font-size:14px;
|
||
display:flex;
|
||
align-items:center;
|
||
gap:6px;
|
||
transition:all 0.2s ease;
|
||
}
|
||
.btn-outline{
|
||
background:#ffffff;
|
||
color:#152c5b;
|
||
border:1px solid #cbd5e1;
|
||
}
|
||
.btn-outline:hover{
|
||
background:#f7f8fc;
|
||
border-color:#152c5b;
|
||
}
|
||
.btn-primary{
|
||
background:#152c5b;
|
||
color:#fff;
|
||
}
|
||
.btn-primary:hover{
|
||
background:#0f1e42;
|
||
}
|
||
.btn-success{
|
||
background:#10b981;
|
||
color:#fff;
|
||
}
|
||
.btn-success:hover{
|
||
background:#059669;
|
||
}
|
||
/* 加载遮罩 */
|
||
.mask{
|
||
position:fixed;
|
||
left:0;top:0;
|
||
width:100%;height:100%;
|
||
background:rgba(255,255,255,0.85);
|
||
display:none;
|
||
z-index:999;
|
||
align-items:center;
|
||
justify-content:center;
|
||
}
|
||
.mask-box{
|
||
background:#fff;
|
||
padding:30px 40px;
|
||
border-radius:14px;
|
||
font-size:15px;
|
||
display:flex;
|
||
gap:14px;
|
||
align-items:center;
|
||
box-shadow:0 8px 30px rgba(21,44,91,0.12);
|
||
border:1px solid #eef2fb;
|
||
}
|
||
.loading{
|
||
display:inline-block;
|
||
width:20px;height:20px;
|
||
border:2px solid #e2e8f0;
|
||
border-top-color:#152c5b;
|
||
border-radius:50%;
|
||
animation:spin 0.8s linear infinite;
|
||
}
|
||
@keyframes spin{to{transform:rotate(360deg)}}
|
||
/* 主容器 */
|
||
.container{
|
||
padding:36px 40px;
|
||
max-width:1800px;
|
||
margin:0 auto;
|
||
}
|
||
/* 统计卡片区域 */
|
||
.stat-cards{
|
||
display:grid;
|
||
grid-template-columns:repeat(5,1fr);
|
||
gap:28px;
|
||
margin-bottom:36px;
|
||
}
|
||
.stat-card{
|
||
background:#fff;
|
||
border-radius:16px;
|
||
padding:32px;
|
||
box-shadow:0 3px 18px rgba(21,44,91,0.06);
|
||
border:1px solid #eef2fb;
|
||
transition:transform 0.26s,box-shadow 0.26s;
|
||
}
|
||
.stat-card:hover{
|
||
transform:translateY(-6px);
|
||
box-shadow:0 10px 32px rgba(21,44,91,0.09);
|
||
}
|
||
.stat-header{
|
||
display:flex;
|
||
justify-content:space-between;
|
||
align-items:flex-start;
|
||
margin-bottom:20px;
|
||
}
|
||
.stat-title{
|
||
font-size:14px;
|
||
color:#64748b;
|
||
}
|
||
.stat-icon{
|
||
width:46px;height:46px;
|
||
border-radius:12px;
|
||
display:flex;
|
||
align-items:center;
|
||
justify-content:center;
|
||
font-size:20px;
|
||
}
|
||
.stat-card.fire .stat-icon{background:#fee2e2;color:#dc2626}
|
||
.stat-card.active .stat-icon{background:#fef3c7;color:#d97706}
|
||
.stat-card.resolve .stat-icon{background:#dcfce7;color:#16a34a}
|
||
.stat-card.instance .stat-icon{background:#dbeafe;color:#2563eb}
|
||
.stat-card.rate .stat-icon{background:#ffedd5;color:#ea580c}
|
||
.stat-value{
|
||
font-size:44px;
|
||
font-weight:700;
|
||
color:#152c5b;
|
||
letter-spacing:1px;
|
||
line-height:1.1;
|
||
}
|
||
.stat-card.active .stat-value{color:#d97706}
|
||
.stat-sub{
|
||
font-size:12px;
|
||
color:#94a3b8;
|
||
margin-top:8px;
|
||
}
|
||
/* 图表、表格通用卡片 */
|
||
.chart-section,.active-alert-section,.table-section{
|
||
margin-bottom:36px;
|
||
}
|
||
.base-card{
|
||
background:#fff;
|
||
border-radius:16px;
|
||
padding:32px;
|
||
box-shadow:0 3px 18px rgba(21,44,91,0.06);
|
||
border:1px solid #eef2fb;
|
||
}
|
||
.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}
|
||
.table-count{font-size:14px;color:#64748b}
|
||
#chart1{height:420px;width:100%}
|
||
.active-alert-section .base-card{border:1px solid #fee2e2}
|
||
/* 表格样式 */
|
||
.table-wrap{overflow-x:auto}
|
||
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;
|
||
}
|
||
th i{margin-right:6px;font-size:13px;color:#94a3b8}
|
||
td{
|
||
padding:18px;
|
||
border-bottom:1px solid #f1f5f9;
|
||
color:#334155;
|
||
}
|
||
tbody tr{transition:background 0.2s}
|
||
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;
|
||
}
|
||
.status-tag.fire{background:#fee2e2;color:#dc2626}
|
||
.status-tag.resolve{background:#dcfce7;color:#16a34a}
|
||
.severity-tag{
|
||
display:inline-block;
|
||
padding:5px 12px;
|
||
border-radius:8px;
|
||
font-size:13px;
|
||
font-weight:500;
|
||
}
|
||
.severity-critical{background:#fee2e2;color:#dc2626}
|
||
.severity-warning{background:#ffedd5;color:#ea580c}
|
||
.severity-info{background:#dbeafe;color:#2563eb}
|
||
/* 空数据提示 */
|
||
.empty-state{
|
||
text-align:center;
|
||
padding:80px 20px;
|
||
color:#94a3b8;
|
||
font-size:15px;
|
||
}
|
||
.empty-icon{
|
||
font-size:56px;
|
||
margin-bottom:18px;
|
||
opacity:0.35;
|
||
}
|
||
/* 响应式适配 */
|
||
@media (max-width:1440px){.stat-cards{grid-template-columns:repeat(3,1fr)}}
|
||
@media (max-width:768px){
|
||
.header{padding:16px 24px;flex-direction:column;gap:16px;align-items:flex-start}
|
||
.header-right{flex-wrap:wrap}
|
||
.container{padding:24px}
|
||
.stat-cards{grid-template-columns:1fr;gap:20px}
|
||
.stat-card{padding:24px}
|
||
.base-card{padding:24px}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="mask" id="exportMask">
|
||
<div class="mask-box">
|
||
<span class="loading"></span>
|
||
<span>正在生成CSV报表,请稍候...</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="header">
|
||
<div class="header-left">
|
||
<div>
|
||
<div class="header-brand-en">Advantest</div>
|
||
<div class="header-brand-cn">爱德万测试 · 运维告警监控平台</div>
|
||
</div>
|
||
</div>
|
||
<div class="header-right">
|
||
<span class="user-info"><i class="fa fa-user-circle"></i>当前用户:<?php echo $userName; ?>(<?php echo $roleText; ?>)</span>
|
||
<span class="time-info"><i class="fa fa-clock-o"></i>系统时间:<span id="updateTime">--</span></span>
|
||
<button class="btn-base btn-outline" onclick="loadAllData()"><i class="fa fa-refresh"></i>刷新数据</button>
|
||
<button class="btn-base btn-success" onclick="exportCsv()"><i class="fa fa-download"></i>导出CSV报表</button>
|
||
<button class="btn-base btn-primary" id="logoutBtn"><i class="fa fa-sign-out"></i>退出登录</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<!-- 统计卡片 -->
|
||
<div class="stat-cards">
|
||
<div class="stat-card fire">
|
||
<div class="stat-header">
|
||
<div class="stat-title">今日触发告警</div>
|
||
<div class="stat-icon"><i class="fa fa-exclamation-triangle"></i></div>
|
||
</div>
|
||
<div class="stat-value" id="fire">0</div>
|
||
<div class="stat-sub">Total Firing Today</div>
|
||
</div>
|
||
<div class="stat-card active">
|
||
<div class="stat-header">
|
||
<div class="stat-title">当前未恢复告警</div>
|
||
<div class="stat-icon"><i class="fa fa-bolt"></i></div>
|
||
</div>
|
||
<div class="stat-value" id="activeAlert">0</div>
|
||
<div class="stat-sub">Unresolved Now</div>
|
||
</div>
|
||
<div class="stat-card resolve">
|
||
<div class="stat-header">
|
||
<div class="stat-title">今日恢复告警</div>
|
||
<div class="stat-icon"><i class="fa fa-check-circle"></i></div>
|
||
</div>
|
||
<div class="stat-value" id="resolve">0</div>
|
||
<div class="stat-sub">Resolved Today</div>
|
||
</div>
|
||
<div class="stat-card instance">
|
||
<div class="stat-header">
|
||
<div class="stat-title">故障实例总数</div>
|
||
<div class="stat-icon"><i class="fa fa-server"></i></div>
|
||
</div>
|
||
<div class="stat-value" id="instance">0</div>
|
||
<div class="stat-sub">Affected Host Instance</div>
|
||
</div>
|
||
<div class="stat-card rate">
|
||
<div class="stat-header">
|
||
<div class="stat-title">告警恢复率</div>
|
||
<div class="stat-icon"><i class="fa fa-line-chart"></i></div>
|
||
</div>
|
||
<div class="stat-value" id="rate">0%</div>
|
||
<div class="stat-sub">Daily Recovery Rate</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- TOP10图表 -->
|
||
<div class="chart-section">
|
||
<div class="base-card">
|
||
<div class="title-wrap">
|
||
<div class="title-left"><i class="fa fa-bar-chart"></i>今日告警频次 TOP 10</div>
|
||
</div>
|
||
<div id="chart1"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 未恢复活跃告警 -->
|
||
<div class="active-alert-section">
|
||
<div class="base-card">
|
||
<div class="title-wrap">
|
||
<div class="title-left"><i class="fa fa-exclamation-circle"></i>当前未恢复活跃告警</div>
|
||
<div class="table-count">共 <span id="activeCount">0</span> 条未恢复故障</div>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th style="width:100px"><i class="fa fa-tag"></i>告警状态</th>
|
||
<th><i class="fa fa-bullhorn"></i>告警名称</th>
|
||
<th><i class="fa fa-desktop"></i>实例地址</th>
|
||
<th style="width:120px"><i class="fa fa-signal"></i>告警级别</th>
|
||
<th style="width:200px"><i class="fa fa-calendar"></i>故障发生时间</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="active-table-body">
|
||
<tr>
|
||
<td colspan="5">
|
||
<div class="empty-state">
|
||
<div class="empty-icon"><i class="fa fa-check"></i></div>
|
||
<div>当前无未恢复告警,系统运行正常</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 今日全量明细 -->
|
||
<div class="table-section">
|
||
<div class="base-card">
|
||
<div class="title-wrap">
|
||
<div class="title-left"><i class="fa fa-list-alt"></i>今日全量告警明细列表</div>
|
||
<div class="table-count">共 <span id="totalCount">0</span> 条告警记录</div>
|
||
</div>
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th style="width:100px"><i class="fa fa-tag"></i>告警状态</th>
|
||
<th><i class="fa fa-bullhorn"></i>告警名称</th>
|
||
<th><i class="fa fa-desktop"></i>实例地址</th>
|
||
<th style="width:120px"><i class="fa fa-signal"></i>告警级别</th>
|
||
<th style="width:200px"><i class="fa fa-calendar"></i>故障发生时间</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="table-body">
|
||
<tr>
|
||
<td colspan="5">
|
||
<div class="empty-state">
|
||
<div class="empty-icon"><i class="fa fa-folder-open-o"></i></div>
|
||
<div>暂无今日告警数据</div>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<script>
|
||
const api = "http://10.150.117.190:8080/api/index.php";
|
||
let chartInstance = null;
|
||
|
||
function formatTime(dateStr){
|
||
if(!dateStr || dateStr === '0001-01-01 00:00:00' || dateStr === null) 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>`;
|
||
}
|
||
|
||
function loadActiveAlert(){
|
||
$.getJSON(api+"?act=active_firing",function(list){
|
||
$("#activeAlert").text(list.length);
|
||
$("#activeCount").text(list.length);
|
||
if(list.length===0){
|
||
$("#active-table-body").html(`<tr><td colspan="5"><div class="empty-state"><div class="empty-icon"><i class="fa fa-check"></i></div><div>当前无未恢复告警,系统运行正常</div></div></td></tr>`);
|
||
return;
|
||
}
|
||
let html="";
|
||
list.forEach(row=>{
|
||
const t=`<span class="status-tag fire"><i class="fa fa-bolt"></i>触发</span>`;
|
||
html+=`<tr>
|
||
<td>${t}</td>
|
||
<td style="font-weight:500;color:#152c5b">${row.alert_name||'-'}</td>
|
||
<td style="font-family:Consolas,monospace;font-size:13px">${row.instance||'-'}</td>
|
||
<td>${getSeverityTag(row.severity)}</td>
|
||
<td style="color:#475569">${formatTime(row.receive_time)}</td>
|
||
</tr>`;
|
||
});
|
||
$("#active-table-body").html(html);
|
||
}).fail(()=>{
|
||
$("#activeAlert").text("-");
|
||
$("#active-table-body").html(`<tr><td colspan="5"><div class="empty-state" style="color:#dc2626"><div class="empty-icon"><i class="fa fa-exclamation-circle"></i></div><div>活跃告警数据加载失败,请重新登录</div></div></td></tr>`);
|
||
});
|
||
}
|
||
|
||
function loadStats(){
|
||
$.getJSON(api+"?act=day_total",function(res){
|
||
const fire=parseInt(res.fire_count)||0;
|
||
const resolve=parseInt(res.resolve_count)||0;
|
||
const ins=parseInt(res.instance_num)||0;
|
||
const rate=fire>0?Math.round((resolve/fire)*100):0;
|
||
$("#fire").text(fire);
|
||
$("#resolve").text(resolve);
|
||
$("#instance").text(ins);
|
||
$("#rate").text(rate+"%");
|
||
}).fail(()=>{$("#fire,#resolve,#instance,#rate").text("-");});
|
||
}
|
||
|
||
function loadChart(){
|
||
$.getJSON(api+"?act=top_alert",function(list){
|
||
if(!chartInstance){
|
||
chartInstance=echarts.init(document.getElementById("chart1"));
|
||
window.addEventListener("resize",()=>chartInstance.resize());
|
||
}
|
||
const names=list.map(i=>i.alert_name);
|
||
const cnt=list.map(i=>i.cnt);
|
||
chartInstance.setOption({
|
||
tooltip:{trigger:"axis",axisPointer:{type:"shadow"}},
|
||
grid:{left:"3%",right:"4%",bottom:"18%",top:"8%",containLabel:true},
|
||
xAxis:{
|
||
type:"category",
|
||
data:names,
|
||
axisLabel:{rotate:30,fontSize:12,color:"#64748b"},
|
||
axisLine:{lineStyle:{color:"#e2e8f0"}},
|
||
axisTick:{show:false}
|
||
},
|
||
yAxis:{
|
||
type:"value",
|
||
name:"告警次数",
|
||
nameTextStyle:{color:"#64748b",fontSize:13},
|
||
axisLabel:{color:"#64748b",fontSize:12},
|
||
splitLine:{lineStyle:{color:"#f1f5f9",type:"dashed"}},
|
||
axisLine:{show:false}
|
||
},
|
||
series:[{
|
||
type:"bar",
|
||
data:cnt,
|
||
barWidth:"48%",
|
||
itemStyle:{
|
||
color:new echarts.graphic.LinearGradient(0,0,0,1,[
|
||
{offset:0,color:"#3b82f6"},
|
||
{offset:1,color:"#1d4ed8"}
|
||
]),
|
||
borderRadius:[6,6,0,0]
|
||
},
|
||
label:{show:true,position:"top",fontSize:12,color:"#334155"}
|
||
}]
|
||
});
|
||
});
|
||
}
|
||
|
||
function loadTable(){
|
||
$.getJSON(api+"?act=log_list",function(list){
|
||
$("#totalCount").text(list.length);
|
||
if(list.length===0){
|
||
$("#table-body").html(`<tr><td colspan="5"><div class="empty-state"><div class="empty-icon"><i class="fa fa-folder-open-o"></i></div><div>暂无今日告警数据</div></div></td></tr>`);
|
||
return;
|
||
}
|
||
let html="";
|
||
list.forEach(row=>{
|
||
const typeText = row.alert_type == 1
|
||
? `<span class="status-tag fire"><i class="fa fa-bolt"></i>触发</span>`
|
||
: `<span class="status-tag resolve"><i class="fa fa-check"></i>恢复</span>`;
|
||
html += `<tr>
|
||
<td>${typeText}</td>
|
||
<td style="font-weight:500;color:#152c5b">${row.alert_name || '-'}</td>
|
||
<td style="font-family:Consolas,monospace;font-size:13px">${row.instance || '-'}</td>
|
||
<td>${getSeverityTag(row.severity)}</td>
|
||
<td style="color:#475569">${formatTime(row.receive_time)}</td>
|
||
</tr>`;
|
||
});
|
||
$("#table-body").html(html);
|
||
}).fail(()=>{
|
||
$("#table-body").html(`<tr><td colspan="5"><div class="empty-state" style="color:#dc2626"><div class="empty-icon"><i class="fa fa-exclamation-circle"></i></div><div>接口数据加载失败,请检查后端服务</div></div></td></tr>`);
|
||
});
|
||
}
|
||
|
||
function updateTime() {
|
||
const now = new Date();
|
||
const Y = now.getFullYear();
|
||
const M = String(now.getMonth() + 1).padStart(2, '0');
|
||
const D = String(now.getDate()).padStart(2, '0');
|
||
const h = String(now.getHours()).padStart(2, '0');
|
||
const m = String(now.getMinutes()).padStart(2, '0');
|
||
const s = String(now.getSeconds()).padStart(2, '0');
|
||
$("#updateTime").text(`${Y}-${M}-${D} ${h}:${m}:${s}`);
|
||
}
|
||
|
||
function loadAllData() {
|
||
loadStats();
|
||
loadChart();
|
||
loadTable();
|
||
loadActiveAlert();
|
||
updateTime();
|
||
}
|
||
|
||
function exportCsv(){
|
||
const mask = $("#exportMask");
|
||
mask.css("display","flex");
|
||
window.location.href = api + "?act=export_csv";
|
||
setTimeout(()=>mask.hide(),5000);
|
||
}
|
||
|
||
$(function() {
|
||
loadAllData();
|
||
setInterval(loadAllData, 60000);
|
||
setInterval(updateTime, 1000);
|
||
$("#logoutBtn").click(function(){
|
||
location.href="logout.php";
|
||
})
|
||
});
|
||
</script>
|
||
</body>
|
||
</html>
|