const { createApp } = Vue; createApp({ data() { return { loading: false, error: "", filter: "all", keyword: "", payload: { updatedAt: "", summary: { total: 0, ok: 0, down: 0 }, items: [], }, timer: null, }; }, computed: { summary() { return this.payload.summary || { total: 0, ok: 0, down: 0 }; }, formattedUpdatedAt() { if (!this.payload.updatedAt) { return "-"; } const date = new Date(this.payload.updatedAt); return Number.isNaN(date.getTime()) ? this.payload.updatedAt : date.toLocaleString(); }, filteredItems() { const keyword = this.keyword.toLowerCase(); return (this.payload.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); }); }, groupedHosts() { const map = new Map(); for (const item of this.filteredItems) { if (!map.has(item.host)) { map.set(item.host, { host: item.host, ip: item.ip, group: item.group, groupLabel: this.groupLabel(item.group), downCount: 0, items: [], }); } const group = map.get(item.host); group.items.push(item); if (!item.ok) { group.downCount += 1; } } return [...map.values()]; }, }, methods: { groupLabel(group) { return { app: "应用节点", pay: "支付节点", gateway: "网关节点", }[group] || group; }, 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() { this.refresh(); this.timer = window.setInterval(() => this.refresh(), 10000); }, beforeUnmount() { if (this.timer) { window.clearInterval(this.timer); } }, }).mount("#app");