Files
2026-07-07 13:29:22 +08:00

1067 lines
37 KiB
Plaintext
Raw Permalink 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();
$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' ? '管理员' : '只读用户';
$configFile = "./api/system_config.json";
$sysConfig = json_decode(file_get_contents($configFile),true);
$themeColor = $sysConfig['theme_color'];
$cardBg = $sysConfig['card_bg'];
$bodyBg = $sysConfig['body_bg'];
$fontFamily = $sysConfig['font_family'];
$customLinks = $sysConfig['sidebar_links'];
?>
<!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>
:root{
--theme-main: <?php echo $themeColor; ?>;
--card-bg: <?php echo $cardBg; ?>;
--body-bg: <?php echo $bodyBg; ?>;
--font-main: <?php echo $fontFamily; ?>;
}
*{box-sizing:border-box;margin:0;padding:0}
body{
background:var(--body-bg);
font-family:var(--font-main);
color:#1d2939;
line-height:1.6;
display:flex;
flex-direction:column;
height:100vh;
overflow:hidden;
}
.wrap-main{
display:flex;
flex:1;
overflow:hidden;
}
.sidebar{
width:240px;
background:var(--theme-main);
color:#e2e8f0;
flex-shrink:0;
overflow-y:auto;
}
.sidebar-brand{
padding:26px 24px;
border-bottom:1px solid rgba(255,255,255,0.1);
}
.sidebar-brand-en{
font-size:20px;
font-weight:700;
letter-spacing:1px;
color:#fff;
}
.sidebar-brand-cn{
font-size:13px;
color:#a5b4fc;
margin-top:4px;
}
.sidebar-menu{
padding:16px 0;
}
.menu-title{
padding:10px 24px;
font-size:12px;
color:#94a3b8;
letter-spacing:1px;
}
.menu-item{
display:flex;
align-items:center;
gap:12px;
padding:14px 24px;
cursor:pointer;
font-size:14px;
transition:0.2s;
color:#cbd5e1;
}
.menu-item i{
font-size:16px;
width:20px;
text-align:center;
}
.menu-item.active{
background:rgba(255,255,255,0.12);
color:#fff;
border-left:4px solid #3b82f6;
}
.menu-item:hover:not(.active){
background:rgba(255,255,255,0.06);
color:#fff;
}
.main-content{
flex:1;
overflow-y:auto;
display:flex;
flex-direction:column;
}
.header{
background:#ffffff;
border-bottom:1px solid #eef2fb;
color:var(--theme-main);
padding:20px 40px;
display:flex;
justify-content:space-between;
align-items:center;
box-shadow:0 2px 12px rgba(21,44,91,0.05);
flex-shrink:0;
}
.header-left{
display:flex;
align-items:center;
gap:14px;
}
.header-brand-en{
font-size:24px;
font-weight:700;
letter-spacing:2px;
color:var(--theme-main);
}
.header-brand-cn{
font-size:15px;
color:#64748b;
}
.header-right{
display:flex;
align-items:center;
gap:18px;
font-size:14px;
flex-wrap:wrap;
}
.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:var(--theme-main);
border:1px solid #cbd5e1;
}
.btn-outline:hover{
background:#f7f8fc;
border-color:var(--theme-main);
}
.btn-primary{
background:var(--theme-main);
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:var(--theme-main);
border-radius:50%;
animation:spin 0.8s linear infinite;
}
@keyframes spin{to{transform:rotate(360deg)}}
.container{
padding:36px 40px;
max-width:100%;
flex:1;
}
.page-panel{
display:none;
}
.page-panel.active{
display:block;
}
.stat-cards{
display:grid;
grid-template-columns:repeat(5,1fr);
gap:28px;
margin-bottom:36px;
}
.stat-card{
background:var(--card-bg);
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:var(--theme-main);
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,.disk-section,.setting-section{
margin-bottom:36px;
}
.base-card{
background:var(--card-bg);
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:var(--theme-main);
}
.title-left i{color:#2563eb;font-size:21px}
.table-count{font-size:14px;color:#64748b}
.filter-box{
display:flex;
align-items:center;
gap:10px;
}
.filter-box label{
font-size:14px;
color:#475569;
}
#ipSelect{
padding:6px 10px;
border:1px solid #cbd5e0;
border-radius:6px;
font-size:14px;
min-width:200px;
}
#diskExportBtn{
padding:6px 14px;
background:#2b6cb0;
color:#fff;
border:none;
border-radius:6px;
cursor:pointer;
font-size:14px;
}
#diskExportBtn:hover{
background:#2c5282;
}
#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}
.num-big {
color: #c53030;
font-weight: 500;
}
.empty-state{
text-align:center;
padding:80px 20px;
color:#94a3b8;
font-size:15px;
}
.empty-icon{
font-size:56px;
margin-bottom:18px;
opacity:0.35;
}
.form-row{
display:flex;
gap:16px;
align-items:center;
margin-bottom:22px;
flex-wrap:wrap;
}
.form-label{
width:160px;
font-size:14px;
font-weight:500;
color:#334155;
}
.form-input{
flex:1;
min-width:260px;
padding:10px 12px;
border:1px solid #cbd5e0;
border-radius:8px;
font-size:14px;
}
.color-input{
width:80px;height:40px;border:none;cursor:pointer;
}
.link-item{
display:flex;
gap:10px;
align-items:center;
padding:12px;
border:1px solid #e2e8f0;
border-radius:8px;
margin-bottom:10px;
}
.del-link{
color:#dc2626;
cursor:pointer;
}
@media (max-width:1440px){.stat-cards{grid-template-columns:repeat(3,1fr)}}
@media (max-width:992px){
.wrap-main{flex-direction:column;}
.sidebar{width:100%;height:auto}
}
@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}
.filter-box{flex-wrap:wrap;}
.form-row{flex-direction:column;align-items:flex-start;}
.form-label{width:100%;}
}
</style>
</head>
<body>
<div class="mask" id="exportMask">
<div class="mask-box">
<span class="loading"></span>
<span>正在生成CSV报表,请稍候...</span>
</div>
</div>
<div class="wrap-main">
<div class="sidebar">
<div class="sidebar-brand">
<div class="sidebar-brand-en">Advantest</div>
<div class="sidebar-brand-cn">运维监控管理后台</div>
</div>
<div class="sidebar-menu">
<div class="menu-title">监控面板</div>
<div class="menu-item active" data-page="alertPage">
<i class="fa fa-line-chart"></i>
<span>告警监控总览</span>
</div>
<div class="menu-item" data-page="diskPage">
<i class="fa fa-hdd-o"></i>
<span>服务器磁盘容量</span>
</div>
<div class="menu-title">系统管理</div>
<div class="menu-item" data-page="pwdPage">
<i class="fa fa-key"></i>
<span>修改登录密码</span>
</div>
<div class="menu-item" data-page="stylePage">
<i class="fa fa-paint-brush"></i>
<span>界面外观配置</span>
</div>
<div class="menu-item" data-page="linkPage">
<i class="fa fa-link"></i>
<span>侧边栏外链管理</span>
</div>
<?php foreach($customLinks as $link): ?>
<div class="menu-item" onclick="window.open('<?php echo $link['url']; ?>','<?php echo $link['target']; ?>')">
<i class="fa <?php echo $link['icon']; ?>"></i>
<span><?php echo $link['title']; ?></span>
</div>
<?php endforeach; ?>
</div>
</div>
<div class="main-content">
<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="page-panel active" id="alertPage">
<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="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>
<div class="page-panel" id="diskPage">
<div class="disk-section">
<div class="base-card">
<div class="title-wrap">
<div class="title-left"><i class="fa fa-hdd-o"></i>用户目录磁盘占用统计</div>
<div class="filter-box">
<label>筛选实例IP</label>
<select id="ipSelect">
<option value="all">全部实例</option>
</select>
<button id="diskExportBtn">导出当前表格CSV</button>
</div>
</div>
<div class="table-wrap">
<table>
<thead>
<tr>
<th>实例IP</th>
<th>用户名</th>
<th>目录路径</th>
<th>占用容量</th>
</tr>
</thead>
<tbody id="diskTableBody">
<tr>
<td colspan="4" class="empty-state">
<div class="empty-icon"><i class="fa fa-spinner fa-spin"></i></div>
<div>磁盘数据加载中...</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
<div class="page-panel setting-section" id="pwdPage">
<div class="base-card">
<div class="title-wrap">
<div class="title-left"><i class="fa fa-key"></i>修改登录密码</div>
</div>
<form id="pwdForm">
<div class="form-row">
<label class="form-label">原登录密码</label>
<input class="form-input" type="password" name="old_pwd" placeholder="输入当前密码" required>
</div>
<div class="form-row">
<label class="form-label">新登录密码</label>
<input class="form-input" type="password" name="new_pwd" placeholder="设置新密码" required>
</div>
<div class="form-row">
<label class="form-label">确认新密码</label>
<input class="form-input" type="password" name="confirm_pwd" placeholder="再次输入新密码" required>
</div>
<div class="form-row">
<label class="form-label"></label>
<button type="submit" class="btn-base btn-primary">提交修改密码</button>
</div>
</form>
</div>
</div>
<div class="page-panel setting-section" id="stylePage">
<div class="base-card">
<div class="title-wrap">
<div class="title-left"><i class="fa fa-paint-brush"></i>界面主题样式配置</div>
</div>
<form id="styleForm">
<div class="form-row">
<label class="form-label">侧边栏/主题主色</label>
<input class="color-input" type="color" name="theme_color" value="<?php echo $themeColor; ?>">
<span>十六进制颜色值</span>
</div>
<div class="form-row">
<label class="form-label">卡片背景色</label>
<input class="color-input" type="color" name="card_bg" value="<?php echo $cardBg; ?>">
</div>
<div class="form-row">
<label class="form-label">页面全局背景</label>
<input class="color-input" type="color" name="body_bg" value="<?php echo $bodyBg; ?>">
</div>
<div class="form-row">
<label class="form-label">全局字体</label>
<input class="form-input" name="font_family" value="<?php echo $fontFamily; ?>">
</div>
<div class="form-row">
<label class="form-label"></label>
<button type="submit" class="btn-base btn-primary">保存样式配置(刷新生效)</button>
</div>
</form>
</div>
</div>
<div class="page-panel setting-section" id="linkPage">
<div class="base-card">
<div class="title-wrap">
<div class="title-left"><i class="fa fa-link"></i>侧边栏自定义外链管理</div>
<button class="btn-base btn-outline" id="addLinkBtn"><i class="fa fa-plus"></i>新增一条外链</button>
</div>
<div id="linkListBox"></div>
<div style="margin-top:24px">
<button class="btn-base btn-primary" id="saveLinkBtn">保存全部外链配置</button>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const api = "http://10.150.117.190:8080/api/index.php";
const configApi = "./api/config_save.php";
const pwdApi = "./api/user_pass.php";
let chartInstance = null;
let diskSourceData = [];
let currentDiskIp = "all";
let linkData = [];
$(".menu-item").click(function(){
const pageId = $(this).data("page");
if(!pageId) return;
$(".menu-item").removeClass("active");
$(this).addClass("active");
$(".page-panel").removeClass("active");
$("#"+pageId).addClass("active");
if(pageId === "diskPage") loadDiskData();
if(pageId === "linkPage") loadLinkConfig();
})
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:var(--theme-main)">${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:var(--theme-main)">${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();
if($("#diskPage").hasClass("active")) loadDiskData();
}
function exportCsv(){
const mask = $("#exportMask");
mask.css("display","flex");
window.location.href = api + "?act=export_csv";
setTimeout(()=>mask.hide(),5000);
}
function loadDiskData(){
const tbody = $("#diskTableBody");
tbody.html(`<tr><td colspan="4" class="empty-state"><div class="empty-icon"><i class="fa fa-spinner fa-spin"></i></div><div>正在拉取磁盘指标...</div></td></tr>`);
$.getJSON("/api/df.php").done(ret=>{
if(ret.code !== 0){
tbody.html(`<tr><td colspan="4" class="empty-state" style="color:#dc2626">磁盘接口异常:${ret.msg}</td></tr>`);
return;
}
diskSourceData = ret.list;
if(diskSourceData.length === 0){
tbody.html(`<tr><td colspan="4" class="empty-state">暂无磁盘指标数据</td></tr>`);
return;
}
const ipSel = $("#ipSelect");
ipSel.find("option:not([value='all'])").remove();
const ipSet = new Set();
diskSourceData.forEach(item=>ipSet.add(item.instance_ip));
ipSet.forEach(ip=>ipSel.append(`<option value="${ip}">${ip}</option>`));
currentDiskIp = "all";
renderDiskTable("all");
}).fail(()=>{
tbody.html(`<tr><td colspan="4" class="empty-state" style="color:#dc2626">请求df.php接口失败,请检查服务</td></tr>`);
})
}
function renderDiskTable(filterIp){
currentDiskIp = filterIp;
const tbody = $("#diskTableBody");
tbody.html("");
let showList = filterIp === "all" ? diskSourceData : diskSourceData.filter(i=>i.instance_ip === filterIp);
if(showList.length === 0){
tbody.html(`<tr><td colspan="4" class="empty-state">该实例下无目录数据</td></tr>`);
return;
}
showList.forEach(row=>{
tbody.append(`
<tr>
<td>${row.instance_ip}</td>
<td>${row.username}</td>
<td>${row.directory}</td>
<td class="num-big">${row.size_gb}GB</td>
</tr>
`)
})
}
$("#diskExportBtn").click(function(){
let showList = currentDiskIp === "all" ? diskSourceData : diskSourceData.filter(i=>i.instance_ip === currentDiskIp);
if(showList.length === 0){
alert("无数据可导出");
return;
}
let csv = "\uFEFF实例IP,用户名,目录路径,占用容量(GB)\n";
showList.forEach(item=>{
const line = [
`"${item.instance_ip}"`,
`"${item.username}"`,
`"${item.directory}"`,
`"${item.size_gb}GB"`
];
csv += line.join(",") + "\n";
})
const blob = new Blob([csv], {type:"text/csv;charset=utf-8"});
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = "服务器磁盘容量_"+new Date().getTime()+".csv";
a.click();
URL.revokeObjectURL(url);
})
$("#ipSelect").change(function(){
renderDiskTable($(this).val());
})
$("#pwdForm").submit(function(e){
e.preventDefault();
let data = $(this).serialize() + "&act=change_pwd";
$.post(pwdApi,data,function(res){
alert(res.msg);
if(res.code == 0) location.href="logout.php";
},"json");
})
$("#styleForm").submit(function(e){
e.preventDefault();
let data = $(this).serialize() + "&act=save_style";
$.post(configApi,data,function(res){
alert(res.msg);
location.reload();
},"json");
})
function loadLinkConfig(){
$.post(configApi,"act=get_config",function(res){
if(res.code!=0) return;
linkData = res.data.sidebar_links;
renderLinkList();
},"json");
}
function renderLinkList(){
let html = "";
linkData.forEach((item,idx)=>{
html += `
<div class="link-item">
<input class="form-input link-icon" value="${item.icon}" placeholder="fa图标名">
<input class="form-input link-title" value="${item.title}" placeholder="显示名称">
<input class="form-input link-url" value="${item.url}" placeholder="跳转地址">
<select class="form-input link-target">
<option value="_blank" ${item.target=="_blank"?"selected":""}>新标签打开</option>
<option value="_self" ${item.target=="_self"?"selected":""}>当前页面打开</option>
</select>
<i class="fa fa-trash del-link" data-idx="${idx}"></i>
</div>`;
})
$("#linkListBox").html(html);
}
$("#addLinkBtn").click(function(){
linkData.push({icon:"fa-link",title:"新链接",url:"https://",target:"_blank"});
renderLinkList();
})
$(document).on("click",".del-link",function(){
let idx = $(this).data("idx");
linkData.splice(idx,1);
renderLinkList();
})
$("#saveLinkBtn").click(function(){
let newList = [];
$(".link-item").each(function(){
newList.push({
icon: $(this).find(".link-icon").val(),
title: $(this).find(".link-title").val(),
url: $(this).find(".link-url").val(),
target: $(this).find(".link-target").val()
})
})
$.post(configApi,{
act:"save_sidebar_link",
link_list:JSON.stringify(newList)
},function(res){
alert(res.msg);
location.reload();
},"json");
})
// ====================== 页面初始化 ======================
$(function() {
loadAllData();
setInterval(loadAllData, 60000);
setInterval(updateTime, 1000);
$("#logoutBtn").click(function(){
location.href="logout.php";
})
});
</script>
</body>
</html>