Skip to content

Instantly share code, notes, and snippets.

@codexss
Last active March 19, 2026 04:33
Show Gist options
  • Select an option

  • Save codexss/e8328f58982c029742d00da8d99cd436 to your computer and use it in GitHub Desktop.

Select an option

Save codexss/e8328f58982c029742d00da8d99cd436 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Server Monitor Widget (Final Version)
// @description Streamlined UI, left-aligned progress bar, and top-left update time.
// ==/UserScript==
// =========================================================
// 1. 配置区域:在这里填入你的 API 地址
// =========================================================
const API_URL = "http://192.168.1.1:43210/index.php?action=get_status";
const USE_MOCK_DATA = false; // 测试时保持 true,对接真实 API 时改为 false
// 定义全局颜色 (支持暗黑模式自动切换)
const Colors = {
bg: Color.dynamic(new Color("#FFFFFF"), new Color("#1C1C1E")), // 背景色
primaryText: Color.dynamic(new Color("#1A202C"), new Color("#FFFFFF")), // 主文本色 (黑/白)
secondaryText: Color.dynamic(new Color("#8A8A8E"), new Color("#8E8E93")),// 次要文本色 (灰)
barBg: Color.dynamic(new Color("#E5E5EA"), new Color("#3A3A3C")), // 进度条底色
barFill: Color.dynamic(new Color("#1A202C"), new Color("#FFFFFF")), // 进度条填充色
statusGreen: new Color("#34C759"), // 运行正常 (绿)
statusRed: new Color("#FF3B30") // 运行异常 (红)
};
// =========================================================
// 2. 主逻辑
// =========================================================
async function main() {
const data = await fetchData();
const family = config.widgetFamily || "small";
const widget = createWidget(data, family);
if (config.runsInWidget) {
Script.setWidget(widget);
} else {
if (family === "small") {
widget.presentSmall();
} else {
widget.presentMedium();
}
}
Script.complete();
}
async function fetchData() {
if (USE_MOCK_DATA) {
return {
"data": [{
"id": 1, "account": "LTAI5tS***", "flow_total": 200, "flow_used": 61.1,
"percentageOfUse": 30.55, "region": "cn-hongkong", "regionName": "中国香港",
"rate95": false, "threshold": 95, "instanceStatus": "Running",
"lastUpdated": "2026-03-19 11:21:27", "remark": "",
"cost": { "enabled": true, "monthly_cost": 0.02, "balance": "0.00", "currency": "USD", "last_updated": "2026-03-19 11:22:43", "error": null }
}],
"system_last_run": 1773890521
};
} else {
const req = new Request(API_URL);
return await req.loadJSON();
}
}
// =========================================================
// 3. UI 构建
// =========================================================
function createWidget(apiResponse, family) {
const widget = new ListWidget();
widget.backgroundColor = Colors.bg;
const padding = family === "small" ? 16 : 20;
widget.setPadding(padding, padding, padding, padding);
const info = apiResponse.data[0];
// 1. 顶部行:左侧刷新时间 + 右侧运行状态指示灯
const topRow = widget.addStack();
topRow.centerAlignContent(); // 垂直居中对齐,保证文字和圆点在同一水平线
// 提取时间部分 (截取空格后的部分,即 11:21:27)
const timeString = info.lastUpdated.split(" ")[1] || "";
const updateText = topRow.addText(timeString);
updateText.font = Font.systemFont(11); // 灰色小字
updateText.textColor = Colors.secondaryText;
topRow.addSpacer(); // 将中间撑开,把红绿点推到最右侧
const isRunning = info.instanceStatus.toLowerCase().includes("run");
const dot = topRow.addImage(SFSymbol.named("circle.fill").image);
dot.tintColor = isRunning ? Colors.statusGreen : Colors.statusRed;
dot.imageSize = new Size(10, 10);
widget.addSpacer(12);
// 2. 核心区:流量大字 (严格靠左对齐)
const flowRow = widget.addStack();
flowRow.bottomAlignContent();
const mainNum = flowRow.addText(info.flow_used.toString());
mainNum.font = Font.blackSystemFont(family === "small" ? 38 : 42);
mainNum.textColor = Colors.primaryText;
flowRow.addSpacer(4);
const unitStack = flowRow.addStack();
unitStack.setPadding(0, 0, family === "small" ? 6 : 8, 0);
const unitText = unitStack.addText("GB");
unitText.font = Font.boldSystemFont(family === "small" ? 18 : 20);
unitText.textColor = Colors.secondaryText;
flowRow.addSpacer(); // 将整行文字推到左边
widget.addSpacer(6);
// 3. 流量占比标签 (严格靠左对齐)
const percentRow = widget.addStack();
const percentLabel = percentRow.addText(`${info.percentageOfUse}% / ${info.flow_total}G`);
percentLabel.font = Font.boldSystemFont(15);
percentLabel.textColor = Colors.secondaryText;
percentRow.addSpacer(); // 将标签推到左边
widget.addSpacer(12);
// 4. 进度条 (强制从左到右填充)
const barRow = widget.addStack();
const barWidth = family === "small" ? 126 : 280;
const barHeight = 8;
const percentage = info.percentageOfUse / 100;
// 进度条灰底轨道
const progressBgStack = barRow.addStack();
progressBgStack.layoutHorizontally();
progressBgStack.size = new Size(barWidth, barHeight);
progressBgStack.backgroundColor = Colors.barBg;
progressBgStack.cornerRadius = barHeight / 2;
// 进度条黑色填充
if (percentage > 0) {
const progressFillStack = progressBgStack.addStack();
const fillWidth = Math.max(barWidth * percentage, barHeight); // 保证最起码是个圆点
progressFillStack.size = new Size(fillWidth, barHeight);
progressFillStack.backgroundColor = Colors.barFill;
progressFillStack.cornerRadius = barHeight / 2;
}
progressBgStack.addSpacer(); // 关键:在填充部分右侧加 Spacer,把填充挤到左边
barRow.addSpacer(); // 确保进度条外部也靠左对齐
widget.addSpacer(); // 弹性空间,把底部的“本月费用”压到最下面
// 5. 底部:本月费用
const costRow = widget.addStack();
costRow.centerAlignContent();
const sym = info.cost.currency === "USD" ? "$" : "¥";
const leftText = costRow.addText("本月费用");
leftText.font = Font.systemFont(14);
leftText.textColor = Colors.secondaryText;
costRow.addSpacer(); // 将左右两块文本撑开
const rightText = costRow.addText(`${sym}${info.cost.monthly_cost}`);
rightText.font = Font.boldSystemFont(16);
rightText.textColor = Colors.primaryText;
return widget;
}
// 执行
await main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment