添加 dashboard.php

This commit is contained in:
2026-07-02 10:35:16 +08:00
parent a1be34c3b9
commit c74d143c5d
+365
View File
@@ -0,0 +1,365 @@
<?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 = $_SESSION['username'];
$userRole = $_SESSION['role'];
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>企业运维告警监控平台</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css">
<script src="https://cdn.jsdelivr.net/npm/echarts/dist/echarts.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jquery/dist/jquery.min.js"></script>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#f7f8fc;font-family:"Inter","PingFang SC","Microsoft YaHei";color:#1d2939;line-height:1.6}
.header{background:#152c5b;color:#fff;padding:18px 36px;display:flex;justify-content:space-between;align-items:center;box-shadow:0 2px 12px rgba(21,44,91,0.12)}
.header h1{font-size:22px;font-weight:600;display:flex;align-items:center;gap:12px}
.header h1 i{font-size:24px;color:#36d399}
.header-right{display:flex;align-items:center;gap:16px;font-size:14px}
.update-time{color:#cbd5e1;display:flex;align-items:center;gap:6px}
.refresh-btn,.export-btn{border:none;padding:8px 18px;border-radius:6px;cursor:pointer;font-size:14px;display:flex;align-items:center;gap:6px}
.refresh-btn{background:rgba(255,255,255,0.1);color:#fff;border:1px solid rgba(255,255,255,0.2)}
.refresh-btn:hover{background:rgba(255,255,255,0.18)}
.export-btn{background:#10b981;color:#fff}
.export-btn:hover{background:#059669}
.mask{position:fixed;left:0;top:0;width:100%;height:100%;background:rgba(0,0,0,0.45);display:none;z-index:999;align-items:center;justify-content:center}
.mask-box{background:#fff;padding:28px 36px;border-radius:10px;font-size:15px;display:flex;gap:12px;align-items:center;box-shadow:0 8px 30px rgba(0,0,0,0.15)}
.loading{display:inline-block;width:18px;height:18px;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:32px 36px;max-width:1700px;margin:0 auto}
.stat-cards{display:grid;grid-template-columns:repeat(5,1fr);gap:24px;margin-bottom:32px}
.stat-card{background:#fff;border-radius:12px;padding:28px;box-shadow:0 2px 16px rgba(21,44,91,0.06);border:1px solid #eef2fb;transition:transform 0.24s,box-shadow 0.24s}
.stat-card:hover{transform:translateY(-4px);box-shadow:0 8px 24px rgba(21,44,91,0.1)}
.stat-header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:16px}
.stat-title{font-size:14px;color:#64748b}
.stat-icon{width:42px;height:42px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px}
.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:40px;font-weight:700;color:#152c5b;letter-spacing:1px}
.stat-card.active .stat-value{color:#d97706}
.stat-sub{font-size:12px;color:#94a3b8;margin-top:6px}
.chart-section{margin-bottom:32px}
.chart-card{background:#fff;border-radius:12px;padding:28px;box-shadow:0 2px 16px rgba(21,44,91,0.06);border:1px solid #eef2fb}
.chart-title-wrap{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:600;color:#152c5b;margin-bottom:24px;padding-bottom:14px;border-bottom:1px solid #eef2fb}
.chart-title-wrap i{color:#2563eb;font-size:20px}
#chart1{height:400px;width:100%}
.active-alert-section{background:#fff;border-radius:12px;padding:28px;box-shadow:0 2px 16px rgba(21,44,91,0.06);border:1px solid #fecdd3;margin-bottom:32px}
.table-header-wrap{display:flex;justify-content:space-between;align-items:center;margin-bottom:20px;padding-bottom:14px;border-bottom:1px solid #eef2fb}
.table-title-wrap{display:flex;align-items:center;gap:10px;font-size:18px;font-weight:600;color:#152c5b}
.table-title-wrap i{color:#2563eb;font-size:20px}
.table-count{font-size:14px;color:#64748b}
.table-section{background:#fff;border-radius:12px;padding:28px;box-shadow:0 2px 16px rgba(21,44,91,0.06);border:1px solid #eef2fb;margin-bottom:32px}
.table-wrap{overflow-x:auto}
table{width:100%;border-collapse:collapse;font-size:14px}
th{background:#f8fafc;color:#475569;font-weight:600;padding:14px 16px;text-align:left;border-bottom:2px solid #e2e8f0;white-space:nowrap}
th i{margin-right:6px;font-size:13px;color:#94a3b8}
td{padding:16px;border-bottom:1px solid #f1f5f9;color:#334155}
tbody tr{transition:background 0.2s}
tbody tr:hover{background:#f8fafc}
tbody tr:nth-child(even){background:#fbfcfe}
.status-tag{display:inline-flex;align-items:center;gap:4px;padding:5px 12px;border-radius:20px;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:4px 10px;border-radius:6px;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:70px 20px;color:#94a3b8;font-size:15px}
.empty-icon{font-size:52px;margin-bottom:16px;opacity:0.4}
@media (max-width:1400px){.stat-cards{grid-template-columns:repeat(3,1fr)}}
@media (max-width:768px){.header{padding:16px 20px;flex-direction:column;gap:14px;align-items:flex-start}.container{padding:20px}.stat-cards{grid-template-columns:1fr;gap:16px}}
</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><i class="fa fa-bell"></i>企业运维告警监控平台</h1>
<div class="header-right">
<span class="update-time"><i class="fa fa-user-circle"></i>当前用户:<?php echo htmlspecialchars($userName);?><?php echo $userRole==='admin'?'管理员':'只读用户';?></span>
<span class="update-time"><i class="fa fa-clock-o"></i>系统实时时间:<span id="updateTime">--</span></span>
<button class="refresh-btn" onclick="loadAllData()"><i class="fa fa-refresh"></i>刷新数据</button>
<button class="export-btn" onclick="exportCsv()"><i class="fa fa-download"></i>导出CSV报表</button>
<button class="refresh-btn" 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>
<div class="chart-section">
<div class="chart-card">
<div class="chart-title-wrap">
<i class="fa fa-bar-chart"></i>今日告警频次 TOP 10
</div>
<div id="chart1"></div>
</div>
</div>
<div class="active-alert-section">
<div class="table-header-wrap">
<div class="table-title-wrap">
<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:90px"><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:110px"><i class="fa fa-signal"></i>告警级别</th>
<th style="width:180px"><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 class="table-section">
<div class="table-header-wrap">
<div class="table-title-wrap">
<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:90px"><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:110px"><i class="fa fa-signal"></i>告警级别</th>
<th style="width:180px"><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>
<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:"15%",top:"8%",containLabel:true},
xAxis:{
type:"category",
data:names,
axisLabel:{rotate:28,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:"46%",
itemStyle:{
color:new echarts.graphic.LinearGradient(0,0,0,1,[
{offset:0,color:"#3b82f6"},
{offset:1,color:"#1d4ed8"}
]),
borderRadius:[5,5,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>