This commit is contained in:
+10
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"id": "pigway-agent",
|
||||
"name": "PIGWay Raspberry Pi Agent",
|
||||
"version": "1.6.12",
|
||||
"version": "1.6.13",
|
||||
"protocol_version": 1,
|
||||
"entrypoint": "worker.py",
|
||||
"runtime": "python3",
|
||||
@@ -33,6 +33,15 @@
|
||||
"type": "string",
|
||||
"required": true,
|
||||
"secret": true
|
||||
},
|
||||
"management_url": {
|
||||
"label": {
|
||||
"zh": "访问地址",
|
||||
"en": "Access URL"
|
||||
},
|
||||
"type": "string",
|
||||
"required": false,
|
||||
"format": "http-origin"
|
||||
}
|
||||
},
|
||||
"ui": {
|
||||
|
||||
+29
-1
@@ -311,7 +311,7 @@ export function renderConfig() {
|
||||
);
|
||||
$("mainSavebar").classList.toggle(
|
||||
"hidden",
|
||||
["bulk_sync", "plugins"].includes(b.dataset.configSection),
|
||||
["bulk_sync", "plugins", "access"].includes(b.dataset.configSection),
|
||||
);
|
||||
if (b.dataset.configSection === "services") loadServices();
|
||||
if (b.dataset.configSection === "processes") loadProcesses();
|
||||
@@ -348,6 +348,7 @@ export function renderConfig() {
|
||||
bindBulkSync();
|
||||
updateConditional();
|
||||
addStorageSettings();
|
||||
installAccessSettings();
|
||||
}
|
||||
export function markChanged() {
|
||||
let n = 0;
|
||||
@@ -365,3 +366,30 @@ export function markChanged() {
|
||||
: tr("尚未修改");
|
||||
$("saveState").className = n ? "" : "muted";
|
||||
}
|
||||
|
||||
function installAccessSettings() {
|
||||
const host = appState.fleetHosts.find(h => h.id === appState.currentConfigHost);
|
||||
if (!host) return;
|
||||
$("configMenu").insertAdjacentHTML("beforeend", `<button data-config-section="access">${pluginText("访问地址", "Access URL")}</button>`);
|
||||
$("configRows").insertAdjacentHTML("beforeend", `<section class="config-page" data-config-page="access"><h2>${pluginText("访问地址", "Access URL")}</h2><p class="muted">${pluginText("设置 Gateway 管理页面地址,首页显示链接按钮;留空隐藏入口。", "Set the Gateway management URL to show a link on the overview. Leave blank to hide it.")}</p><div class="field"><label>${pluginText("访问地址", "Access URL")}<input id="gatewayAccessUrl" type="url" value="${esc(host.connection?.management_url || "")}" placeholder="https://gateway.example.com"></label></div><div class="action-row"><button id="saveGatewayAccess">${pluginText("保存访问地址", "Save access URL")}</button><span id="gatewayAccessMessage" role="status"></span></div></section>`);
|
||||
$("saveGatewayAccess").onclick = async () => {
|
||||
const button = $("saveGatewayAccess"), message = $("gatewayAccessMessage");
|
||||
const value = $("gatewayAccessUrl").value.trim();
|
||||
if (value) {
|
||||
try {
|
||||
const url = new URL(value);
|
||||
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.pathname !== "/" || url.search || url.hash) throw Error();
|
||||
} catch {
|
||||
message.textContent = pluginText("请使用不含路径或账号密码的 HTTP(S) 根地址", "Use an HTTP(S) origin without paths or credentials");
|
||||
message.className = "error"; return;
|
||||
}
|
||||
}
|
||||
button.disabled = true;
|
||||
try {
|
||||
const result = await get("/api/v1/hosts", {method: "PUT", headers: {"Content-Type": "application/json"}, body: JSON.stringify({operation: "save", id: host.id, adapter_id: host.adapter_id, connection: {management_url: value}})});
|
||||
appState.fleetHosts = result.hosts;
|
||||
message.textContent = pluginText("已保存", "Saved"); message.className = "success";
|
||||
} catch (error) { message.textContent = error.message; message.className = "error"; }
|
||||
finally { button.disabled = false; }
|
||||
};
|
||||
}
|
||||
|
||||
+11
-2
@@ -8,10 +8,19 @@ import { bytes, formatUptime, meter } from "/assets/js/overview.js";
|
||||
import { fanMetric } from "./hardware.js";
|
||||
import { ownerName, rgbName } from "./schema.js";
|
||||
|
||||
function managementLink(host) {
|
||||
try {
|
||||
const url = new URL(host.connection?.management_url);
|
||||
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password) return "";
|
||||
const label = appState.LANG === "zh" ? "打开 Gateway 管理界面" : "Open Gateway management";
|
||||
return `<a class="docker-icon" style="margin-left: -7px" href="${esc(url.href)}" target="_blank" rel="noopener noreferrer" title="${esc(label)}" aria-label="${esc(label)}"><svg viewBox="0 0 24 24" aria-hidden="true"><path d="M14 4h6v6M20 4l-10 10M11 4H4v16h16v-7"/></svg></a>`;
|
||||
} catch { return ""; }
|
||||
}
|
||||
|
||||
export function agentHostCard(entry) {
|
||||
const h = entry.host;
|
||||
if (!entry.online)
|
||||
return `<article class="panel host-panel offline-card"><div class="host-head"><div><h2>${esc(h.name || h.id)}</h2><div class="device-details"><span>ID ${esc(h.id)}</span><span>${esc(h.url || tr("本机"))}</span></div></div><span class="badge semantic-status" data-semantic="error">${tr("离线")}</span></div><div class="error">${esc(entry.error || tr("连接失败"))}</div></article>`;
|
||||
return `<article class="panel host-panel offline-card"><div class="host-head"><div><div class="host-title"><h2>${esc(h.name || h.id)}</h2>${managementLink(h)}</div><div class="device-details"><span>ID ${esc(h.id)}</span><span>${esc(h.url || tr("本机"))}</span></div></div><span class="badge semantic-status" data-semantic="error">${tr("离线")}</span></div><div class="error">${esc(entry.error || tr("连接失败"))}</div></article>`;
|
||||
const s = entry.status,
|
||||
y = s.system || {},
|
||||
n = s.network || {},
|
||||
@@ -35,5 +44,5 @@ export function agentHostCard(entry) {
|
||||
const hardwareNames = hardware.map(p => p.name).filter(Boolean).join(" · ");
|
||||
const percent = n => Number.isFinite(n) ? `${n.toFixed(1)}%` : "--";
|
||||
const progress = n => Number.isFinite(n) ? meter(n) : "";
|
||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(m.name || h.name || m.id)}</h2><span class="uptime-badge">${esc(formatUptime(s.uptime_seconds))}</span></div><div class="device-details"><span>ID ${esc(m.id || h.id)}</span><span>${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}</span><span>${esc(v.manufacturer || "")} ${esc(v.model || "--")}</span><span>${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}</span><span>${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}</span><span>${esc(v.os || "--")}</span><span>${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}</span><span>${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}</span><span>${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}</span></div></div><span class="badge semantic-status" data-semantic="online">${tr("在线")}</span></div><div class="host-grid"><div class="metric" data-refresh-group="system"><div class="muted">${tr("处理器负载")}</div><div class="value">${percent(y.cpu_percent)}</div>${progress(y.cpu_percent)}<div class="muted">${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}</div></div><div class="metric" data-refresh-group="system"><div class="muted">${tr("内存占用")}</div><div class="value">${percent(y.memory_percent)}</div>${progress(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}</div></div><div class="metric" data-refresh-group="network"><div class="muted">${tr("主网络")}</div><div class="value">↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s</div><div class="muted">${esc(n.interface || "--")} · ${n.kind === "WIF" ? `${esc(n.metric)} dBm · ` : n.link_speed_mbps ? `${esc(n.link_speed_mbps)} Mbps · ` : ""}${esc(n.ip_label)} ${esc(n.ip)} · ${n.link === true ? (appState.LANG === "zh" ? "已连接" : "Connected") : n.link === false ? (appState.LANG === "zh" ? "已断开" : "Disconnected") : (appState.LANG === "zh" ? "状态未确认" : "Unconfirmed")}</div></div>${Array.isArray(y.storage_inventory) ? storageCards(y,entry.snapshot?.resources) : `<div class="metric" data-refresh-group="storage"><div class="muted">${tr("磁盘占用")}</div><div class="value">${percent(y.disk_percent)}</div>${progress(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}</div></div>`}<div class="metric" data-refresh-group="display"><div class="pi-alert-heading"><span class="muted">${appState.LANG === "zh" ? "主要告警" : "Primary alert"}</span>${hardwareState === "online" ? "" : `<span class="semantic-status" data-semantic="${hardwareState === 'offline' ? 'error' : 'unknown'}">${esc(hardwareLabel)}</span>`}</div><div class="value">${esc(displayText)}</div>${hardwareNames ? `<div class="muted">${esc(hardwareNames)}</div>` : ""}</div>${(d.alerts || []).length || !dataIsCurrent(entry) || !Array.isArray(d.alerts) ? `<div class="metric" data-refresh-group="alerts"><div class="muted">${tr("当前告警")}</div><div class="value">${(d.alerts || []).length}</div><div class="muted">${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}</div></div>` : ""}</div><details class="group" data-overview-details="services"><summary class="smart-summary"><strong class="smart-summary-title">${tr("服务与进程监控")}</strong>${summaryStatus(monitorHealth(s.watches, dataIsCurrent(entry)), appState.LANG)}</summary><div style="margin-top:12px"><div class="rows">${(s.watches || []).map((w) => `<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span>${runtimeStatus(w.up, appState.LANG)}</div>`).join("") || `<div class="muted">${tr("暂无数据")}</div>`}</div></div></details></article>`;
|
||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(m.name || h.name || m.id)}</h2>${managementLink(h)}<span class="uptime-badge">${esc(formatUptime(s.uptime_seconds))}</span></div><div class="device-details"><span>ID ${esc(m.id || h.id)}</span><span>${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}</span><span>${esc(v.manufacturer || "")} ${esc(v.model || "--")}</span><span>${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}</span><span>${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}</span><span>${esc(v.os || "--")}</span><span>${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}</span><span>${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}</span><span>${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}</span></div></div><span class="badge semantic-status" data-semantic="online">${tr("在线")}</span></div><div class="host-grid"><div class="metric" data-refresh-group="system"><div class="muted">${tr("处理器负载")}</div><div class="value">${percent(y.cpu_percent)}</div>${progress(y.cpu_percent)}<div class="muted">${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}</div></div><div class="metric" data-refresh-group="system"><div class="muted">${tr("内存占用")}</div><div class="value">${percent(y.memory_percent)}</div>${progress(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}</div></div><div class="metric" data-refresh-group="network"><div class="muted">${tr("主网络")}</div><div class="value">↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s</div><div class="muted">${esc(n.interface || "--")} · ${n.kind === "WIF" ? `${esc(n.metric)} dBm · ` : n.link_speed_mbps ? `${esc(n.link_speed_mbps)} Mbps · ` : ""}${esc(n.ip_label)} ${esc(n.ip)} · ${n.link === true ? (appState.LANG === "zh" ? "已连接" : "Connected") : n.link === false ? (appState.LANG === "zh" ? "已断开" : "Disconnected") : (appState.LANG === "zh" ? "状态未确认" : "Unconfirmed")}</div></div>${Array.isArray(y.storage_inventory) ? storageCards(y,entry.snapshot?.resources) : `<div class="metric" data-refresh-group="storage"><div class="muted">${tr("磁盘占用")}</div><div class="value">${percent(y.disk_percent)}</div>${progress(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}</div></div>`}<div class="metric" data-refresh-group="display"><div class="pi-alert-heading"><span class="muted">${appState.LANG === "zh" ? "主要告警" : "Primary alert"}</span>${hardwareState === "online" ? "" : `<span class="semantic-status" data-semantic="${hardwareState === 'offline' ? 'error' : 'unknown'}">${esc(hardwareLabel)}</span>`}</div><div class="value">${esc(displayText)}</div>${hardwareNames ? `<div class="muted">${esc(hardwareNames)}</div>` : ""}</div>${(d.alerts || []).length || !dataIsCurrent(entry) || !Array.isArray(d.alerts) ? `<div class="metric" data-refresh-group="alerts"><div class="muted">${tr("当前告警")}</div><div class="value">${(d.alerts || []).length}</div><div class="muted">${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}</div></div>` : ""}</div><details class="group" data-overview-details="services"><summary class="smart-summary"><strong class="smart-summary-title">${tr("服务与进程监控")}</strong>${summaryStatus(monitorHealth(s.watches, dataIsCurrent(entry)), appState.LANG)}</summary><div style="margin-top:12px"><div class="rows">${(s.watches || []).map((w) => `<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span>${runtimeStatus(w.up, appState.LANG)}</div>`).join("") || `<div class="muted">${tr("暂无数据")}</div>`}</div></div></details></article>`;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user