o Logo
首页
反馈
o Logo
首页 反馈
  1. 首页
  2. 默认分类
  3. 基于glances给页脚添加资源监测

基于glances给页脚添加资源监测

  • 默认分类
  • 发布于 2025-09-18
  • 51 次阅读
o
o

下载python

https://www.python.org/ftp/python/3.12.5/python-3.12.5-amd64.exe

指定安装路径

.\python.exe /quiet InstallAllUsers=1 PrependPath=1 Include_pip=1 TargetDir="C:\Python312"

安装plances

pip install glances==3.* psutil bottle

启动

glances -w

必应时安装VC++运行库

https://aka.ms/vs/17/release/vc_redist.x64.exe

页脚注入

<!--
模块名称:底部服务器资源监控组件
功能概述:
1. 周期性从 Glances API 获取 CPU / 内存 / 网络实时使用数据
2. 响应式显示(PC 与移动端不同布局;移动端只显示首个网卡并允许横向滚动)
3. 状态指示灯基于 CPU 与内存使用率阈值显示健康状况(正常/警告/严重),并在数据陈旧时加发光
4. 失败容错:网络失败后继续展示上次成功数据,标记“延迟”;连续多次失败显示“错误”
5. DOM 更新做差异控制,减少不必要重绘
使用说明:
直接嵌入页面底部;如跨域请为 MONITOR_BASE 部署反向代理。
-->
<style>
:root{
  --monitor-font-size:12px;            /* 基础字体大小(PC) */
  --monitor-line-height:1.3;           /* 指标行高 */
}
@media (max-width:600px){
  :root{ --monitor-font-size:11px; }    /* 中屏字体缩小 */
}
@media (max-width:360px){
  :root{ --monitor-font-size:10px; }    /* 小屏再缩小 */
}

/* 第一行指标容器(PC 默认用 2 行截断策略) */
footer .monitor-metrics-wrap{
  font:var(--monitor-font-size) monospace;
  line-height:var(--monitor-line-height);
  display:-webkit-box;
  -webkit-box-orient:vertical;
  -webkit-line-clamp:2;                 /* 最多显示 2 行(移动端会覆盖) */
  overflow:hidden;
  word-break:break-all;
  white-space:normal;
  max-width:100%;
  color:#555;
  margin-bottom:14px;
}

/* 状态指示灯(颜色通过附加类控制) */
.monitor-status-dot{
  width:10px;height:10px;border-radius:50%;
  background:#aaa;
  display:inline-block;
  position:relative;top:1px;
  margin-right:6px;
  flex:0 0 10px;                        /* 在 flex 中固定宽度避免被压缩 */
}
.monitor-status-ok{background:#2ecc71!important;}   /* 正常 */
.monitor-status-warn{background:#f1c40f!important;} /* 警告 */
.monitor-status-bad{background:#e74c3c!important;}  /* 严重 */
.monitor-status-stale{box-shadow:0 0 4px 1px #ff9800;} /* 数据陈旧高亮边缘 */

/* 徽章(显示“延迟”或“错误”) */
.monitor-badge{
  background:#eee;color:#666;
  font-size:10px;
  padding:0 4px;
  border-radius:3px;
  margin-right:6px;
  display:inline-block;
  position:relative;top:-1px;
  flex:0 0 auto;
}

/* 第二行:标题与时间 */
.monitor-footer-line{
  display:flex;
  justify-content:space-between;
  align-items:center;
  flex-wrap:wrap;
  gap:6px;
  line-height:1.2;
  font:10px monospace;
  color:#888;
}
@media (max-width:600px){
  .monitor-footer-line{font-size:9.5px;}
}
@media (max-width:360px){
  .monitor-footer-line{font-size:9px;}
}

/* 分隔符与网络项基础样式 */
.mm-sep{margin:0 4px;color:#888;flex:0 0 auto;}
.mm-net-item{white-space:nowrap;}

/* 移动端:改为单行横向滚动,防止挤掉状态灯 */
@media (max-width:600px){
  footer .monitor-metrics-wrap{
    display:flex;
    -webkit-line-clamp:unset;           /* 取消多行截断 */
    -webkit-box-orient:unset;
    white-space:nowrap;
    overflow-x:auto;
    overflow-y:hidden;
    align-items:center;
    gap:6px;
    word-break:normal;
    scrollbar-width:none;               /* Firefox 隐藏横向滚动条 */
  }
  footer .monitor-metrics-wrap::-webkit-scrollbar{display:none;} /* WebKit 隐藏滚动条 */
  .mm-sep{margin:0 3px;}
  .mm-item{flex:1 1 auto;min-width:0;}  /* 指标可压缩 */
  [data-f="cpu"],
  [data-f="memUsed"],
  [data-f="memTotal"],
  [data-f="memPct"]{
    display:inline-block;
    min-width:auto;
  }
  #mm-net{flex:1 1 auto;min-width:0;}
}
@media (max-width:360px){
  footer .monitor-metrics-wrap{ /* 预留扩展 */ }
}
</style>

<footer>
  <!-- 指标区 -->
  <div class="monitor-metrics-wrap" id="monitor-metrics-wrap">
    <span id="monitor-status-dot" class="monitor-status-dot"></span>
    <span id="monitor-badge" class="monitor-badge" style="display:none"></span>
    <span class="mm-item">CPU <span data-f="cpu">--%</span></span>
    <span class="mm-sep">|</span>
    <span class="mm-item">内存 <span data-f="memUsed">0.00G</span>/<span data-f="memTotal">0.00G</span> (<span data-f="memPct">0%</span>)</span>
    <span class="mm-sep">|</span>
    <span id="mm-net">
      <span class="mm-net-item" data-net="_none">网络 无活动网卡</span>
    </span>
  </div>
  <!-- 标签 + 更新时间 -->
  <div class="monitor-footer-line">
    <span id="monitor-label">服务器资源占用</span>
    <span id="monitor-ts">更新时间 --:--:--</span>
  </div>
</footer>

<script>
/* ------------------ 可配置参数 ------------------ */
const MONITOR_BASE='https://111.173.104.39:61/api/3'; // Glances API 根路径
const MONITOR_REFRESH_MS=5000;  // 刷新间隔
const MONITOR_MAX_FAIL=5;       // 连续失败阈值
const MONITOR_NET_MAX=8;        // PC 端最多显示网卡数

/* ------------------ 运行时状态 ------------------ */
let monitorLastGood=null;       // 最近一次成功的数据缓存
let monitorFailCount=0;         // 连续失败次数

/* 媒体断点判断(与 CSS 保持一致) */
function isNarrow(){return window.innerWidth<=600;}

/* 百分比格式化(short 预留简化模式) */
function mPct(n,short=false){
  if(!isFinite(n))return'N/A';
  return short?n.toFixed(0)+'%':n.toFixed(1)+'%';
}

/* 速率格式化:默认返回 "X.XKB/s";short 模式用整数 K 或 X.XM */
function mKbps(v,short=false){
  if(!isFinite(v))return short?'0':'0KB/s';
  const kb=v/1024;
  if(short){
    if(kb>=1024)return(kb/1024).toFixed(1)+'M';
    return kb.toFixed(0)+'K';
  }
  return kb.toFixed(1)+'KB/s';
}

/* 兼容多种字段名的网卡名提取 */
function mIface(n){return n?.interface||n?.interface_name||n?.name||'';}

/* 兼容不同版本内存字段 */
function mSafeMem(mem){
  const used=mem.used??mem.active??mem.mem_used??0;
  const total=mem.total??mem.mem_total??(used||1);
  return{used,total};
}

/* 确保状态灯存在(极端情况下被移除时重建) */
function ensureDot(){
  let dot=document.getElementById('monitor-status-dot');
  if(!dot){
    const wrap=document.getElementById('monitor-metrics-wrap');
    if(!wrap) return null;
    dot=document.createElement('span');
    dot.id='monitor-status-dot';
    dot.className='monitor-status-dot';
    wrap.insertBefore(dot,wrap.firstChild);
  }
  return dot;
}

/* 根据 CPU/内存占用决定状态灯颜色;stale 时增加外发光 */
function mSetHealth(cpuPct,memPct,stale){
  const dot=ensureDot();
  if(!dot) return;
  dot.className='monitor-status-dot';
  if(stale)dot.classList.add('monitor-status-stale');
  if(cpuPct<60&&memPct<70)dot.classList.add('monitor-status-ok');
  else if(cpuPct<85&&memPct<85)dot.classList.add('monitor-status-warn');
  else dot.classList.add('monitor-status-bad');
}

/* 按需更新某字段文本 */
function setField(k,val){
  const el=document.querySelector(`[data-f="${k}"]`);
  if(el&&el.textContent!==val)el.textContent=val;
}

/* 渲染 CPU 与内存,对内存返回数值百分比用于状态灯逻辑 */
function renderMem(cpu,mem){
  const narrow=isNarrow();
  const usedRaw=mem.used/1024/1024/1024;
  const totalRaw=mem.total/1024/1024/1024;
  const usedG=usedRaw.toFixed(narrow?1:2)+'G';
  const totalG=totalRaw.toFixed(narrow?1:2)+'G';
  const pct=(mem.used*100/mem.total);
  setField('cpu',mPct(cpu,narrow));
  setField('memUsed',usedG);
  setField('memTotal',totalG);
  setField('memPct',mPct(pct,narrow));
  return pct;
}

/* 渲染网络:
   - 移动端仅显示首个网卡
   - 差异检测后再整体替换,减少重排
*/
function renderNet(list){
  const holder=document.getElementById('mm-net');
  if(!holder)return;
  const narrow=isNarrow();
  if(narrow&&list.length>1)list=list.slice(0,1);

  if(!list.length){
    const existing=holder.querySelector('[data-net="_none"]');
    if(!existing){
      holder.textContent='';
      const sp=document.createElement('span');
      sp.className='mm-net-item';
      sp.setAttribute('data-net','_none');
      sp.textContent='网络 无活动网卡';
      holder.appendChild(sp);
    }
    return;
  }

  const oldMap=new Map();
  holder.childNodes.forEach(n=>{
    if(n.nodeType===1&&n.hasAttribute('data-net')){
      oldMap.set(n.getAttribute('data-net'),n);
    }
  });

  const newNodes=[];
  list.forEach((n,i)=>{
    const key=n.name;
    let span=oldMap.get(key);
    if(span)oldMap.delete(key);
    else{
      span=document.createElement('span');
      span.className='mm-net-item';
      span.setAttribute('data-net',key);
    }
    /* 移动端:使用斜杠 rx/tx;PC:使用 ↓ / ↑ */
    const txt=narrow
      ? `${n.name} ${mKbps(n.rx,true)}/${mKbps(n.tx,true)}`
      : `${n.name} ↓${mKbps(n.rx)} ↑${mKbps(n.tx)}`;
    if(span.textContent!==txt)span.textContent=txt;

    if(i>0){
      const sep=document.createElement('span');
      sep.className='mm-sep';
      sep.textContent='|';
      newNodes.push(sep);
    }
    newNodes.push(span);
  });

  let changed=holder.childNodes.length!==newNodes.length;
  if(!changed){
    for(let i=0;i<newNodes.length;i++){
      if(holder.childNodes[i].textContent!==newNodes[i].textContent){changed=true;break;}
    }
  }
  if(changed){
    holder.textContent='';
    newNodes.forEach(n=>holder.appendChild(n));
  }
}

/* 总渲染:指标、网络、徽章、时间、状态灯 */
function mRender(data,{stale=false,errorMsg=null}={}){
  if(!data)return;
  const badge=document.getElementById('monitor-badge');
  const ts=document.getElementById('monitor-ts');

  const memPct=renderMem(data.cpu,data.mem);
  renderNet(data.net);

  if(errorMsg&&monitorFailCount>=MONITOR_MAX_FAIL){
    badge.style.display='inline-block';badge.textContent='错误';
  }else if(stale){
    badge.style.display='inline-block';badge.textContent='延迟';
  }else{
    badge.style.display='none';
  }

  ts.textContent=(stale?'上次成功 ':'更新时间 ')+new Date(data.ts).toLocaleTimeString();
  mSetHealth(data.cpu,memPct,stale);
}

/* 带超时的 fetch 封装,避免长时间挂起 */
async function mFetch(path,timeout=3000){
  const ctrl=new AbortController();
  const t=setTimeout(()=>ctrl.abort(),timeout);
  try{
    const r=await fetch(MONITOR_BASE+path,{cache:'no-store',signal:ctrl.signal});
    if(!r.ok)throw new Error('HTTP '+r.status);
    return await r.json();
  }finally{
    clearTimeout(t);
  }
}

/* 主更新流程:
   1. 并发请求 CPU/内存/网络
   2. 兼容字段提取
   3. 构造快照并重置失败计数
   4. 失败:使用上次成功数据(stale)或显示占位
*/
async function mUpdate(){
  try{
    const [cpu,memRaw,netRaw]=await Promise.all([
      mFetch('/cpu'),
      mFetch('/mem'),
      mFetch('/network')
    ]);

    const cpuTotal=cpu.total??cpu.current??0;
    const m=mSafeMem(memRaw);

    let netList=Array.isArray(netRaw)?netRaw:(netRaw?Object.values(netRaw):[]);
    netList=netList
      .filter(n=>{
        const nm=mIface(n).toLowerCase();
        return nm&&!nm.includes('loopback'); // 排除 loopback
      })
      .slice(0,MONITOR_NET_MAX)
      .map(n=>{
        const name=mIface(n)||'if';
        const rx=n.rx_per_sec??n.rx??0;
        const tx=n.tx_per_sec??n.tx??0;
        return {name,rx,tx};
      });

    monitorLastGood={
      cpu:cpuTotal,
      mem:{used:m.used,total:m.total},
      net:netList,
      ts:Date.now()
    };
    monitorFailCount=0;
    mRender(monitorLastGood);
  }catch(e){
    monitorFailCount++;
    if(monitorLastGood){
      mRender(monitorLastGood,{stale:true,errorMsg:e.message});
    }else{
      setField('cpu','...');
    }
    console.warn('[monitor]',e.message);
  }
}

/* 启动与轮询 */
mUpdate();
setInterval(mUpdate,MONITOR_REFRESH_MS);

/* 视口尺寸变化时重新渲染(保证格式与缩写自适应) */
window.addEventListener('resize',()=>{
  if(monitorLastGood)mRender(monitorLastGood);
});
</script>

CPU --% | 内存 0.00G/0.00G (0%) | 网络 无活动网卡
服务器资源占用 更新时间 --:--:--