|
@@ -14,6 +14,9 @@ function getSystemInfo() {
|
|
|
return r.json();
|
|
return r.json();
|
|
|
})
|
|
})
|
|
|
.then(function (d) {
|
|
.then(function (d) {
|
|
|
|
|
+ dashSetUsage("dk-cpu", d.cpu_usage != null ? Number(d.cpu_usage) : null);
|
|
|
|
|
+ dashSetUsage("dk-mem", d.mem_percent != null ? Number(d.mem_percent) : null);
|
|
|
|
|
+ dashSetUsage("dk-disk", d.disk_percent != null ? Number(d.disk_percent) : null);
|
|
|
if (!cpuChart || !memChart || !diskChart) {
|
|
if (!cpuChart || !memChart || !diskChart) {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
@@ -140,6 +143,7 @@ function getLoadInfo() {
|
|
|
return setText(id, data[id], "page1");
|
|
return setText(id, data[id], "page1");
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
+ dashSetUptime(data ? data.uptime : null);
|
|
|
})
|
|
})
|
|
|
.catch(function (e) {
|
|
.catch(function (e) {
|
|
|
console.error("load info failed:", e);
|
|
console.error("load info failed:", e);
|
|
@@ -148,6 +152,7 @@ function getLoadInfo() {
|
|
|
return setText(id, null, "page1");
|
|
return setText(id, null, "page1");
|
|
|
}
|
|
}
|
|
|
);
|
|
);
|
|
|
|
|
+ dashSetUptime(null);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -183,6 +188,7 @@ function getServiceInfo() {
|
|
|
].forEach(function (id) {
|
|
].forEach(function (id) {
|
|
|
return setText(id, data[id], "page1");
|
|
return setText(id, data[id], "page1");
|
|
|
});
|
|
});
|
|
|
|
|
+ dashRenderStatus(data);
|
|
|
})
|
|
})
|
|
|
.catch(function (e) {
|
|
.catch(function (e) {
|
|
|
console.error("getAllServiceStatus() failed:", e);
|
|
console.error("getAllServiceStatus() failed:", e);
|
|
@@ -209,6 +215,7 @@ function getServiceInfo() {
|
|
|
].forEach(function (id) {
|
|
].forEach(function (id) {
|
|
|
return setText(id, null, "page1");
|
|
return setText(id, null, "page1");
|
|
|
});
|
|
});
|
|
|
|
|
+ dashRenderStatus(null);
|
|
|
});
|
|
});
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -467,6 +474,7 @@ function initChart(id, option) {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
function initPage1() {
|
|
function initPage1() {
|
|
|
|
|
+ dashBuildRows();
|
|
|
cpuChart = initChart("cpu-chart", gaugeOption());
|
|
cpuChart = initChart("cpu-chart", gaugeOption());
|
|
|
memChart = initChart("mem-chart", gaugeOption());
|
|
memChart = initChart("mem-chart", gaugeOption());
|
|
|
diskChart = initChart("disk-chart", {
|
|
diskChart = initChart("disk-chart", {
|
|
@@ -540,3 +548,124 @@ function exitPage1() {
|
|
|
memChart = null;
|
|
memChart = null;
|
|
|
diskChart = null;
|
|
diskChart = null;
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+// ===================== 运行总览看板 (仅移动端显示) =====================
|
|
|
|
|
+
|
|
|
|
|
+// 看板模块: 展示名 / 服务单元 / 状态字段 / 版本字段 (与 /api/system/serviceStatus 一一对应)
|
|
|
|
|
+var dashModules = [
|
|
|
|
|
+ { name: "设备自动联网", unit: "networkd.service", status: "var1", version: "var11" },
|
|
|
|
|
+ { name: "同步系统时间", unit: "timesyncd.service", status: "var2", version: "var22" },
|
|
|
|
|
+ { name: "地理坐标定位", unit: "gnss.service", status: "var3", version: "var33" },
|
|
|
|
|
+ { name: "远程运维通道", unit: "sshd-mqtt-bridge.service", status: "var4", version: "var44" },
|
|
|
|
|
+ { name: "相机图像采集", unit: "camera-capture.service", status: "var5", version: "var55" },
|
|
|
|
|
+ { name: "应用安装卸载", unit: "app-install.service", status: "var6", version: "var66" },
|
|
|
|
|
+ { name: "本地配置管理", unit: "web-ui.service", status: "var7", version: "var77" },
|
|
|
|
|
+ { name: "公网配置管理", unit: "frp-client.service", status: "var8", version: "var88" },
|
|
|
|
|
+ { name: "设备自动升级", unit: "upgrade.service", status: "var9", version: "var99" },
|
|
|
|
|
+ { name: "开机任务执行", unit: "local-startup.service", status: "var10", version: "var1010" }
|
|
|
|
|
+];
|
|
|
|
|
+
|
|
|
|
|
+// 首次进入页面时生成模块卡片骨架, 之后仅原地更新, 避免每次刷新整块重建造成闪烁
|
|
|
|
|
+function dashBuildRows() {
|
|
|
|
|
+ var grid = document.getElementById("dash-grid");
|
|
|
|
|
+ if (!grid || grid.children.length) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var html = "";
|
|
|
|
|
+ dashModules.forEach(function (m, i) {
|
|
|
|
|
+ html +=
|
|
|
|
|
+ '<div class="dash-mod" id="dash-mod-' + i + '">' +
|
|
|
|
|
+ '<div class="dash-mod-head"><span class="dash-name">' + m.name + '</span>' +
|
|
|
|
|
+ '<span class="dash-badge" id="dash-badge-' + i + '"><span class="dash-dot" id="dash-dot-' + i + '"></span><span id="dash-text-' + i + '">--</span></span></div>' +
|
|
|
|
|
+ '<div class="dash-unit">' + m.unit + "</div>" +
|
|
|
|
|
+ '<div class="dash-info" id="dash-info-' + i + '">--</div>' +
|
|
|
|
|
+ "</div>";
|
|
|
|
|
+ });
|
|
|
|
|
+ grid.innerHTML = html;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 用 /api/system/serviceStatus 的数据刷新模块状态与"服务正常"计数
|
|
|
|
|
+// data 为 null 时(接口失败)整块置灰为 "--"
|
|
|
|
|
+function dashRenderStatus(data) {
|
|
|
|
|
+ var okCount = 0;
|
|
|
|
|
+ dashModules.forEach(function (m, i) {
|
|
|
|
|
+ var v = data ? data[m.status] : null;
|
|
|
|
|
+ var level = "na";
|
|
|
|
|
+ var text = "--";
|
|
|
|
|
+ if (v && typeof v === "object") {
|
|
|
|
|
+ level = v.level || "na";
|
|
|
|
|
+ text = v.text || "--";
|
|
|
|
|
+ }
|
|
|
|
|
+ // ok=运行中, info=一次性任务已完成, 两者都视为正常
|
|
|
|
|
+ if (level === "ok" || level === "info") {
|
|
|
|
|
+ okCount++;
|
|
|
|
|
+ }
|
|
|
|
|
+ var mod = document.getElementById("dash-mod-" + i);
|
|
|
|
|
+ var dot = document.getElementById("dash-dot-" + i);
|
|
|
|
|
+ var badge = document.getElementById("dash-badge-" + i);
|
|
|
|
|
+ var txt = document.getElementById("dash-text-" + i);
|
|
|
|
|
+ var info = document.getElementById("dash-info-" + i);
|
|
|
|
|
+ if (mod) {
|
|
|
|
|
+ mod.className = "dash-mod dash-" + level;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (badge) {
|
|
|
|
|
+ badge.className = "dash-badge dash-" + level;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (dot) {
|
|
|
|
|
+ dot.className = "dash-dot dash-" + level;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (txt) {
|
|
|
|
|
+ txt.textContent = text;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (info) {
|
|
|
|
|
+ var ver = data ? data[m.version] : null;
|
|
|
|
|
+ info.textContent = ver && typeof ver === "object" ? ver.text || "--" : ver || "--";
|
|
|
|
|
+ }
|
|
|
|
|
+ });
|
|
|
|
|
+ setText(
|
|
|
|
|
+ "dk-svc",
|
|
|
|
|
+ {
|
|
|
|
|
+ level: okCount === dashModules.length ? "ok" : "warn",
|
|
|
|
|
+ text: okCount + " / " + dashModules.length
|
|
|
|
|
+ },
|
|
|
|
|
+ "page1"
|
|
|
|
|
+ );
|
|
|
|
|
+ var foot = document.getElementById("dash-updated");
|
|
|
|
|
+ if (foot) {
|
|
|
|
|
+ foot.textContent = data
|
|
|
|
|
+ ? "数据更新于 " + new Date().toTimeString().slice(0, 8)
|
|
|
|
|
+ : "数据获取失败, 稍后自动重试";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 更新占用率指标: 数值 + 迷你进度条; >=85% 红色(hot), >=60% 黄色(warm), 其余绿色
|
|
|
|
|
+function dashSetUsage(id, pct) {
|
|
|
|
|
+ var el = document.getElementById(id);
|
|
|
|
|
+ if (!el) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var bar = document.getElementById(id + "-bar");
|
|
|
|
|
+ if (pct == null || isNaN(pct)) {
|
|
|
|
|
+ el.textContent = "--";
|
|
|
|
|
+ el.className = "dash-kpi-value";
|
|
|
|
|
+ if (bar) {
|
|
|
|
|
+ bar.style.width = "0";
|
|
|
|
|
+ }
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ var tone = pct >= 85 ? " hot" : pct >= 60 ? " warm" : "";
|
|
|
|
|
+ el.textContent = pct.toFixed(1) + "%";
|
|
|
|
|
+ el.className = "dash-kpi-value" + tone;
|
|
|
|
|
+ if (bar) {
|
|
|
|
|
+ bar.style.width = Math.min(100, pct) + "%";
|
|
|
|
|
+ bar.className = "dash-bar-fill" + tone;
|
|
|
|
|
+ }
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 更新"持续开机"KPI, 仅显示文本
|
|
|
|
|
+function dashSetUptime(text) {
|
|
|
|
|
+ var el = document.getElementById("dk-uptime");
|
|
|
|
|
+ if (el) {
|
|
|
|
|
+ el.textContent = text || "--";
|
|
|
|
|
+ }
|
|
|
|
|
+}
|