const { createApp } = Vue; createApp({ data() { return { loading: false, error: "", filter: "all", keyword: "", payload: { updatedAt: "", summary: { total: 0, ok: 0, down: 0, hostTotal: 0, hostMetricOk: 0, hostMetricDown: 0, }, hosts: [], }, timer: null, }; }, computed: { summary() { return this.payload.summary || { total: 0, ok: 0, down: 0, hostTotal: 0, hostMetricOk: 0, hostMetricDown: 0, }; }, formattedUpdatedAt() { if (!this.payload.updatedAt) { return "-"; } const date = new Date(this.payload.updatedAt); return Number.isNaN(date.getTime()) ? this.payload.updatedAt : date.toLocaleString(); }, groupedHosts() { const keyword = this.keyword.trim().toLowerCase(); return (this.payload.hosts || []) .map((host) => { const items = (host.items || []).filter((item) => { if (this.filter === "down" && item.ok) { return false; } if (this.filter === "java" && item.kind !== "java") { return false; } if (this.filter === "golang" && item.kind !== "golang") { return false; } if (!keyword) { return true; } return [item.host, item.ip, item.service, item.kind, item.group] .join(" ") .toLowerCase() .includes(keyword); }); return { ...host, groupLabel: this.groupLabel(host.group), items, downCount: items.filter((item) => !item.ok).length, }; }) .filter((host) => host.items.length > 0 || !keyword); }, }, methods: { groupLabel(group) { return { app: "应用节点", pay: "支付节点", gateway: "网关节点", }[group] || group; }, formatPercent(value) { if (value == null || Number.isNaN(Number(value))) { return "-"; } return `${Number(value).toFixed(2)}%`; }, formatGb(used, total) { if (used == null || total == null) { return "-"; } return `${Number(used).toFixed(2)} / ${Number(total).toFixed(2)} GB`; }, async refresh() { this.loading = true; this.error = ""; try { const response = await fetch("/api/monitor/services", { cache: "no-store" }); if (!response.ok) { throw new Error(`HTTP ${response.status}`); } this.payload = await response.json(); } catch (error) { this.error = error instanceof Error ? error.message : String(error); } finally { this.loading = false; } }, }, mounted() { const root = document.getElementById("app"); const bootIndicator = document.getElementById("boot-indicator"); if (root) { root.removeAttribute("v-cloak"); } if (bootIndicator) { bootIndicator.hidden = true; } this.refresh(); this.timer = window.setInterval(() => this.refresh(), 10000); }, beforeUnmount() { if (this.timer) { window.clearInterval(this.timer); } }, }).mount("#app");