适配项目:https://github.com/huilang-me/CF-Server-Monitor
适配皮肤: https://github.com/volcano-1025/CFSM-Theme-LuminaPlus

修改// @match 替换成你的探针域名即可
负载默认是2.XX 你可以自己调 因为我跑到过峰值4.XX 好像也没啥问题

image

// ==UserScript==
// @name         ✅ CFSM-Theme-LuminaPlus 百分比着色
// @namespace    https://github.com/volcano-1025/CFSM-Theme-LuminaPlus
// @description  CPU / 内存 / 磁盘 / 负载 百分比数值无极过渡着色:<50% 绿 / 66% 黄 / 75% 橙 / >90% 红
// @version      260918
// @author       由 Claude 首发于 260918
// @match        *://替换成你的探针域名即可*
// @grant        none
// @run-at       document-idle
// ==/UserScript==

(function () {
  'use strict';

  /* ── 颜色映射(oklch 无极插值) ──────────────────────────────── */
  // 分段锚点:[阈值, hue, sat%, lgt%]
  // 10 档锚点:[阈值%, r, g, b]  0%=亮绿(低占用) → 100%=红(高占用)
  // 百分比占用色表(CPU/内存/磁盘):0%=绿 → 100%=红
  const STOPS = [
    [0,    85, 255, 99],
    [10,   78, 255, 65],
    [20,   95, 255, 46],
    [30,  118, 255, 27],
    [40,  147, 255,  7],
    [50,  178, 242,  0],
    [60,  255, 255,  0],
    [70,  255, 140,  0],
    [80,  255,  68,  0],
    [90,  255,   0,  0],
    [100, 255,   0,  0],
  ];

  // 负载专用色表:load1 原始值,0=绿 / 0.7=黄 / 1.0=红 / ≥2=深红
  // 10 档,上限取 2.0(超过 2 核满载视为极危)
  const LOAD_STOPS = [
    [0.0,  85, 255,  99],  // 空闲     青绿
    [0.2,  78, 255,  65],  // 极轻     翠绿
    [0.4,  95, 255,  46],  // 轻载     亮绿
    [0.5, 118, 255,  27],  // 正常     绿
    [0.6, 147, 255,   7],  // 偏高     草绿
    [0.7, 178, 242,   0],  // 警戒     黄绿
    [0.8, 255, 255,   0],  // 高负载   黄
    [0.9, 255, 140,   0],  // 满载     橙
    [1.0, 255,  68,   0],  // 过载     红橙
    [1.5, 255,   0,   0],  // 严重过载 红
    [2.0, 255,   0,   0],  // 危险     红
  ];

  function loadColor(load1) {
    const val = Math.max(0, load1);
    for (let i = 1; i < LOAD_STOPS.length; i++) {
      const [p0, r0, g0, b0] = LOAD_STOPS[i - 1];
      const [p1, r1, g1, b1] = LOAD_STOPS[i];
      if (val <= p1) {
        const t = p1 === p0 ? 0 : (val - p0) / (p1 - p0);
        return `rgb(${Math.round(lerp(r0,r1,t))},${Math.round(lerp(g0,g1,t))},${Math.round(lerp(b0,b1,t))})`;
      }
    }
    return `rgb(255,0,0)`;
  }

  function lerp(a, b, t) { return a + (b - a) * t; }

  function pctColor(pct) {
    const p = Math.max(0, Math.min(100, pct));
    for (let i = 1; i < STOPS.length; i++) {
      const [p0, r0, g0, b0] = STOPS[i - 1];
      const [p1, r1, g1, b1] = STOPS[i];
      if (p <= p1) {
        const t = p1 === p0 ? 0 : (p - p0) / (p1 - p0);
        const r = Math.round(lerp(r0, r1, t));
        const g = Math.round(lerp(g0, g1, t));
        const b = Math.round(lerp(b0, b1, t));
        return `rgb(${r},${g},${b})`;
      }
    }
    return `rgb(85,255,99)`;
  }

  /* ── 从文本节点解析百分比数字 ───────────────────────────────── */
  function parseNum(el) {
    // 取元素内所有文本(排除 small/span 单位)
    let raw = '';
    for (const node of el.childNodes) {
      if (node.nodeType === Node.TEXT_NODE) raw += node.textContent;
      // strong / span 里的纯数字也取
      if (node.nodeType === Node.ELEMENT_NODE &&
          !['SMALL'].includes(node.tagName)) {
        raw += node.textContent;
      }
    }
    const n = parseFloat(raw.replace(/[^\d.]/g, ''));
    return isFinite(n) ? n : null;
  }

  /* ── 着色逻辑 ────────────────────────────────────────────────── */
  const TRANSITION = 'color .35s ease';

  // 通用:给一个元素根据其文本数字着色
  function colorEl(el) {
    const pct = parseNum(el);
    if (pct === null) return;
    el.style.color = pctColor(pct);
    el.style.transition = TRANSITION;
  }

  // ① server-card 大卡片 —— div.tabular.text-[13px] 里的 span.font-semibold
  //    有单位 span(%)→ pctColor;无单位(负载)→ loadColor(load1原始值)
  function colorServerCard(wrapper) {
    const num = wrapper.querySelector('span.font-semibold');
    if (!num) return;
    const val = parseFloat(num.textContent);
    if (!isFinite(val)) return;

    const unitSpan = num.nextElementSibling;
    num.style.color = unitSpan ? pctColor(val) : loadColor(val);
    num.style.transition = TRANSITION;
  }

  // ② compact 卡片 —— .compact-node-gauge-head > strong.tabular
  //    负载行文本无 %(如 "1.05"),直接用 loadColor();其余用 pctColor()
  function colorCompact(strong) {
    const val = parseFloat(strong.textContent);
    if (!isFinite(val)) return;

    const isLoad = !strong.textContent.includes('%');
    strong.style.color = isLoad ? loadColor(val) : pctColor(val);
    strong.style.transition = TRANSITION;
  }

  // ③ mini 卡片 —— .mini-metric-value.tabular > strong
  //    负载 strong 无兄弟 small(无单位),其余有 small("%")
  function colorMini(strong) {
    const val = parseFloat(strong.textContent);
    if (!isFinite(val)) return;

    const hasUnit = !!strong.nextElementSibling; // <small> 单位元素
    strong.style.color = hasUnit ? pctColor(val) : loadColor(val);
    strong.style.transition = TRANSITION;
  }

  // ④ 列表行 —— .node-list-gauge-value.tabular
  //    负载列 unit="" 无 <small>;百分比列有 <small>
  function colorListRow(span) {
    let raw = '';
    for (const n of span.childNodes) {
      if (n.nodeType === Node.TEXT_NODE) raw += n.textContent;
    }
    const val = parseFloat(raw.trim());
    if (!isFinite(val)) return;

    const hasUnit = !!span.querySelector('small');
    span.style.color = hasUnit ? pctColor(val) : loadColor(val);
    span.style.transition = TRANSITION;
  }

  /* ── 扫描整个文档 ────────────────────────────────────────────── */
  function scanAll() {
    // ① 大卡片
    document.querySelectorAll(
      '.tabular.text-\\[13px\\].text-\\[var\\(--text-primary\\)\\]'
    ).forEach(colorServerCard);

    // ② compact 卡片
    document.querySelectorAll(
      '.compact-node-gauge-head > strong.tabular'
    ).forEach(colorCompact);

    // ③ mini 卡片
    document.querySelectorAll(
      '.mini-metric-value.tabular > strong'
    ).forEach(colorMini);

    // ④ 列表行
    document.querySelectorAll(
      '.node-list-gauge-value.tabular'
    ).forEach(colorListRow);
  }

  /* ── MutationObserver 监听 React 动态更新 ───────────────────── */
  let timer = null;
  const observer = new MutationObserver(() => {
    clearTimeout(timer);
    timer = setTimeout(scanAll, 80);  // 批量防抖
  });

  observer.observe(document.body, {
    childList: true,
    subtree: true,
    characterData: true,  // 监听文本变化(数值实时刷新)
  });

  // 首次扫描
  scanAll();
})();