const { createApp } = Vue; const STORAGE_KEY = "hy-app-monitor-refresh-interval"; const UPDATE_OPERATION_STORAGE_KEY = "hy-app-monitor-service-update-operation"; createApp({ data() { return { loading: false, error: "", filter: "all", keyword: "", refreshIntervalSeconds: 10, refreshIntervalOptions: [5, 10, 15, 30, 60], payload: { updatedAt: "", settings: { refreshIntervalSeconds: 10, refreshIntervalOptions: [5, 10, 15, 30, 60], thresholds: {}, }, summary: { total: 0, ok: 0, down: 0, hostTotal: 0, hostMetricOk: 0, hostMetricDown: 0, nacosServiceTotal: 0, nacosInstanceTotal: 0, nacosUnhealthyInstanceTotal: 0, mongoDatabaseTotal: 0, mongoCollectionTotal: 0, mongoOk: 0, }, hosts: [], nacos: { ok: false, summary: {}, services: [], namespaces: [], }, mongo: { ok: false, summary: {}, databases: [], replicaSet: { configured: false, members: [], }, }, }, nacosConfig: { loading: false, error: "", keyword: "", items: [], selectedKey: "", detailLoading: false, detailError: "", validating: false, validationMessage: "", saving: false, saveMessage: "", loadedContent: "", editor: { group: "", dataId: "", type: "", appName: "", description: "", lastModifiedTime: "", md5: "", content: "", }, }, goConfig: { loading: false, error: "", validating: false, validationMessage: "", saving: false, saveMessage: "", loadedContent: "", editorContent: "", diffKeys: [], hosts: [], }, updateOps: { loading: false, error: "", running: false, operationId: "", status: "", stage: "", startedAt: "", finishedAt: "", logDroppedCount: 0, logs: [], message: "", items: [], selectedServices: [], javaGitRef: "main", goGitRef: "main", imageTag: "", skipMavenBuild: false, defaults: { javaGitRef: "main", goGitRef: "main", dockerPlatform: "linux/amd64", }, builder: { harborRegistry: "", harborProject: "", javaRepoRoot: "", golangRepoRoot: "", }, result: null, pollTimer: null, }, restartOps: { loading: false, error: "", running: false, message: "", items: [], selectedServices: [], result: null, }, timer: null, }; }, computed: { summary() { return this.payload.summary || {}; }, thresholds() { return this.payload.settings?.thresholds || {}; }, formattedUpdatedAt() { return this.formatDateTime(this.payload.updatedAt); }, groupedHosts() { const keyword = this.keyword.trim().toLowerCase(); return (this.payload.hosts || []) .map((host) => { const hostMatched = [host.host, host.ip, host.group, host.instanceId] .join(" ") .toLowerCase() .includes(keyword); const filteredItems = (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, item.url, item.detail] .join(" ") .toLowerCase() .includes(keyword); }); const shouldShowBecauseDown = this.filter === "down" && !(host.metrics && host.metrics.ok); const shouldShowBecauseKeyword = keyword ? hostMatched || filteredItems.length > 0 : true; const shouldShow = shouldShowBecauseKeyword && (this.filter === "all" || filteredItems.length > 0 || shouldShowBecauseDown || hostMatched); return { ...host, groupLabel: this.groupLabel(host.group), items: filteredItems, downCount: (host.items || []).filter((item) => !item.ok).length, shouldShow, }; }) .filter((host) => host.shouldShow); }, filteredNacosServices() { const keyword = this.keyword.trim().toLowerCase(); return (this.payload.nacos?.services || []).filter((service) => { if (!keyword) { return true; } const serviceMatched = [service.serviceName, service.groupName, service.rawServiceName] .join(" ") .toLowerCase() .includes(keyword); if (serviceMatched) { return true; } return (service.instances || []).some((instance) => [instance.ip, instance.port, instance.clusterName] .join(" ") .toLowerCase() .includes(keyword) ); }); }, filteredNacosConfigs() { const keyword = this.nacosConfig.keyword.trim().toLowerCase(); return (this.nacosConfig.items || []).filter((item) => { if (!keyword) { return true; } return [item.group, item.dataId, item.type, item.appName, item.description] .join(" ") .toLowerCase() .includes(keyword); }); }, nacosConfigDirty() { return this.nacosConfig.editor.content !== this.nacosConfig.loadedContent; }, goConfigDirty() { return this.goConfig.editorContent !== this.goConfig.loadedContent; }, restartTargetMap() { return Object.fromEntries((this.restartOps.items || []).map((item) => [item.service, item])); }, updateTargetMap() { return Object.fromEntries((this.updateOps.items || []).map((item) => [item.service, item])); }, updateLogText() { return (this.updateOps.logs || []) .map((entry) => { const time = this.formatDateTime(entry.time); const stage = entry.stage ? ` [${entry.stage}]` : ""; const level = entry.level && entry.level !== "info" ? ` (${entry.level})` : ""; return `${time}${stage}${level} ${entry.message || ""}`.trim(); }) .join("\n"); }, }, watch: { refreshIntervalSeconds() { window.localStorage.setItem(STORAGE_KEY, String(this.refreshIntervalSeconds)); this.applyRefreshTimer(); }, }, methods: { groupLabel(group) { return { app: "应用节点", pay: "支付节点", gateway: "网关节点", nacos: "Nacos 节点", mongo: "MongoDB 节点", }[group] || group; }, toneClass(level) { return `tone-${level || "neutral"}`; }, configKey(group, dataId) { return `${group}@@${dataId}`; }, 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`; }, formatMb(value) { if (value == null || Number.isNaN(Number(value))) { return "-"; } return `${Number(value).toFixed(2)} MB`; }, formatBytes(value) { if (value == null || Number.isNaN(Number(value))) { return "-"; } const units = ["B", "KB", "MB", "GB", "TB"]; let amount = Number(value); let index = 0; while (amount >= 1024 && index < units.length - 1) { amount /= 1024; index += 1; } return `${amount.toFixed(2)} ${units[index]}`; }, formatNumber(value) { if (value == null || Number.isNaN(Number(value))) { return "-"; } return Number(value).toLocaleString(); }, formatDateTime(value) { if (!value) { return "-"; } const date = new Date(value); return Number.isNaN(date.getTime()) ? String(value) : date.toLocaleString(); }, formatDuration(seconds) { if (seconds == null || Number.isNaN(Number(seconds))) { return "-"; } const total = Math.max(0, Number(seconds)); const days = Math.floor(total / 86400); const hours = Math.floor((total % 86400) / 3600); const minutes = Math.floor((total % 3600) / 60); const remainSeconds = Math.floor(total % 60); if (days > 0) { return `${days}d ${hours}h ${minutes}m`; } if (hours > 0) { return `${hours}h ${minutes}m ${remainSeconds}s`; } if (minutes > 0) { return `${minutes}m ${remainSeconds}s`; } return `${remainSeconds}s`; }, restartTargetLabel(item) { return `${item.service} · ${item.kind} · ${item.hosts.map((host) => host.host).join(" / ")}`; }, updateTargetLabel(item) { return `${item.service} · ${item.kind} · ${item.hosts.map((host) => host.host).join(" / ")}`; }, updateStatusBadgeClass() { if (this.updateOps.error || this.updateOps.status === "failed") { return "bad"; } if (this.updateOps.running || this.updateOps.status === "running" || this.updateOps.status === "queued") { return "warn"; } if (this.updateOps.status === "success") { return "ok"; } return "neutral"; }, updateStatusLabel() { if (this.updateOps.error || this.updateOps.status === "failed") { return "失败"; } if (this.updateOps.status === "queued") { return "排队中"; } if (this.updateOps.running || this.updateOps.status === "running") { return "执行中"; } if (this.updateOps.status === "success") { return "已完成"; } return "未开始"; }, syncRefreshSettings() { const settings = this.payload.settings || {}; const options = Array.isArray(settings.refreshIntervalOptions) ? settings.refreshIntervalOptions.map((item) => Number(item)).filter((item) => !Number.isNaN(item) && item > 0) : this.refreshIntervalOptions; if (options.length > 0) { this.refreshIntervalOptions = options; } const stored = Number(window.localStorage.getItem(STORAGE_KEY) || 0); const configured = Number(settings.refreshIntervalSeconds || 0); const fallback = this.refreshIntervalOptions[0] || 10; const candidate = this.refreshIntervalOptions.includes(stored) ? stored : this.refreshIntervalOptions.includes(configured) ? configured : fallback; if (candidate !== this.refreshIntervalSeconds) { this.refreshIntervalSeconds = candidate; } else { this.applyRefreshTimer(); } }, persistUpdateOperationId() { if (this.updateOps.operationId) { window.sessionStorage.setItem(UPDATE_OPERATION_STORAGE_KEY, this.updateOps.operationId); return; } window.sessionStorage.removeItem(UPDATE_OPERATION_STORAGE_KEY); }, stopUpdatePolling() { if (this.updateOps.pollTimer) { window.clearInterval(this.updateOps.pollTimer); this.updateOps.pollTimer = null; } }, scrollUpdateLogToBottom() { this.$nextTick(() => { const node = document.getElementById("service-update-log-output"); if (node) { node.scrollTop = node.scrollHeight; } }); }, applyUpdateOperation(operation) { if (!operation) { return; } this.updateOps.operationId = operation.operationId || ""; this.updateOps.status = operation.status || ""; this.updateOps.stage = operation.stage || ""; this.updateOps.startedAt = operation.startedAt || ""; this.updateOps.finishedAt = operation.finishedAt || ""; this.updateOps.logDroppedCount = Number(operation.logDroppedCount || 0); this.updateOps.logs = Array.isArray(operation.logs) ? operation.logs : []; this.updateOps.result = operation.result || null; this.updateOps.running = operation.status === "queued" || operation.status === "running"; if (operation.status === "success") { this.updateOps.message = operation.message || operation.result?.message || "服务更新完成"; this.updateOps.error = ""; } else if (operation.status === "failed") { this.updateOps.error = operation.error || operation.result?.error || "服务更新失败"; this.updateOps.message = operation.message || ""; } else { this.updateOps.message = operation.message || ""; } this.persistUpdateOperationId(); this.scrollUpdateLogToBottom(); }, startUpdatePolling() { this.stopUpdatePolling(); if (!this.updateOps.operationId) { return; } this.updateOps.pollTimer = window.setInterval(() => { this.fetchUpdateOperationStatus(this.updateOps.operationId, { silent: true }); }, 2000); }, async fetchUpdateOperationStatus(operationId, { silent = false } = {}) { if (!operationId) { return; } const wasRunning = this.updateOps.running; try { const query = new URLSearchParams({ id: operationId }); const response = await fetch(`/api/ops/update-status?${query.toString()}`, { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.applyUpdateOperation(payload.operation || {}); if (!this.updateOps.running) { this.stopUpdatePolling(); if (wasRunning) { await this.refresh(); await this.refreshUpdateTargets(); await this.refreshRestartTargets(); } } } catch (error) { this.stopUpdatePolling(); this.updateOps.running = false; if (!silent) { this.updateOps.error = error instanceof Error ? error.message : String(error); } } }, applyRefreshTimer() { if (this.timer) { window.clearInterval(this.timer); } this.timer = window.setInterval(() => this.refresh(), this.refreshIntervalSeconds * 1000); }, resetNacosEditor() { this.nacosConfig.selectedKey = ""; this.nacosConfig.detailError = ""; this.nacosConfig.validationMessage = ""; this.nacosConfig.saveMessage = ""; this.nacosConfig.loadedContent = ""; this.nacosConfig.editor = { group: "", dataId: "", type: "", appName: "", description: "", lastModifiedTime: "", md5: "", content: "", }; }, async readJsonResponse(response) { const text = await response.text(); if (!text) { return {}; } try { return JSON.parse(text); } catch (error) { throw new Error(text); } }, async refresh() { this.loading = true; this.error = ""; try { const response = await fetch("/api/monitor/services", { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.payload = payload; this.syncRefreshSettings(); } catch (error) { this.error = error instanceof Error ? error.message : String(error); } finally { this.loading = false; } }, async refreshNacosConfigs({ preserveSelection = true, reloadSelected = false } = {}) { const previousKey = preserveSelection ? this.nacosConfig.selectedKey : ""; this.nacosConfig.loading = true; this.nacosConfig.error = ""; this.nacosConfig.saveMessage = ""; try { const response = await fetch("/api/nacos/configs", { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.nacosConfig.items = Array.isArray(payload.items) ? payload.items : []; const hasPrevious = previousKey && this.nacosConfig.items.some((item) => this.configKey(item.group, item.dataId) === previousKey); const nextKey = hasPrevious ? previousKey : (this.nacosConfig.items[0] ? this.configKey(this.nacosConfig.items[0].group, this.nacosConfig.items[0].dataId) : ""); if (!nextKey) { this.resetNacosEditor(); return; } this.nacosConfig.selectedKey = nextKey; const sameAsCurrent = this.configKey(this.nacosConfig.editor.group, this.nacosConfig.editor.dataId) === nextKey; if (!sameAsCurrent || reloadSelected || !this.nacosConfig.loadedContent) { await this.openNacosConfigByKey(nextKey); } } catch (error) { this.nacosConfig.error = error instanceof Error ? error.message : String(error); } finally { this.nacosConfig.loading = false; } }, async openNacosConfig(item) { const nextKey = this.configKey(item.group, item.dataId); this.nacosConfig.selectedKey = nextKey; await this.openNacosConfigByKey(nextKey); }, async openNacosConfigByKey(configKey) { const item = (this.nacosConfig.items || []).find((entry) => this.configKey(entry.group, entry.dataId) === configKey); if (!item) { return; } this.nacosConfig.detailLoading = true; this.nacosConfig.detailError = ""; this.nacosConfig.validationMessage = ""; this.nacosConfig.saveMessage = ""; try { const query = new URLSearchParams({ group: item.group, dataId: item.dataId }); const response = await fetch(`/api/nacos/config?${query.toString()}`, { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.nacosConfig.loadedContent = typeof payload.content === "string" ? payload.content : ""; this.nacosConfig.editor = { group: item.group, dataId: item.dataId, type: item.type || payload.item?.type || "", appName: item.appName || "", description: item.description || "", lastModifiedTime: item.lastModifiedTime || "", md5: item.md5 || "", content: this.nacosConfig.loadedContent, }; } catch (error) { this.nacosConfig.detailError = error instanceof Error ? error.message : String(error); } finally { this.nacosConfig.detailLoading = false; } }, async validateNacosConfig() { if (!this.nacosConfig.editor.dataId) { return; } this.nacosConfig.validating = true; this.nacosConfig.detailError = ""; this.nacosConfig.validationMessage = ""; try { const response = await fetch("/api/nacos/config/validate", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ group: this.nacosConfig.editor.group, dataId: this.nacosConfig.editor.dataId, type: this.nacosConfig.editor.type, content: this.nacosConfig.editor.content, }), }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } if (!payload.ok) { throw new Error(payload.message || "config validation failed"); } this.nacosConfig.validationMessage = payload.message || "格式校验通过"; } catch (error) { this.nacosConfig.detailError = error instanceof Error ? error.message : String(error); } finally { this.nacosConfig.validating = false; } }, async saveNacosConfig() { if (!this.nacosConfig.editor.group || !this.nacosConfig.editor.dataId) { return; } this.nacosConfig.saving = true; this.nacosConfig.detailError = ""; this.nacosConfig.saveMessage = ""; try { const response = await fetch("/api/nacos/config", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ group: this.nacosConfig.editor.group, dataId: this.nacosConfig.editor.dataId, type: this.nacosConfig.editor.type, content: this.nacosConfig.editor.content, }), }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.nacosConfig.loadedContent = this.nacosConfig.editor.content; this.nacosConfig.saveMessage = `已保存 ${this.nacosConfig.editor.group}/${this.nacosConfig.editor.dataId}`; await this.refreshNacosConfigs({ preserveSelection: true, reloadSelected: true }); } catch (error) { this.nacosConfig.detailError = error instanceof Error ? error.message : String(error); } finally { this.nacosConfig.saving = false; } }, async refreshGoConfig() { this.goConfig.loading = true; this.goConfig.error = ""; this.goConfig.saveMessage = ""; try { const response = await fetch("/api/runtime/golang-config", { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.goConfig.hosts = Array.isArray(payload.hosts) ? payload.hosts : []; this.goConfig.diffKeys = Array.isArray(payload.diffKeys) ? payload.diffKeys : []; this.goConfig.loadedContent = typeof payload.editorContent === "string" ? payload.editorContent : ""; this.goConfig.editorContent = this.goConfig.loadedContent; } catch (error) { this.goConfig.error = error instanceof Error ? error.message : String(error); } finally { this.goConfig.loading = false; } }, async validateGoConfig() { this.goConfig.validating = true; this.goConfig.error = ""; this.goConfig.validationMessage = ""; try { const response = await fetch("/api/runtime/golang-config/validate", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: this.goConfig.editorContent }), }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } if (!payload.ok) { throw new Error(payload.message || "golang config validation failed"); } this.goConfig.validationMessage = payload.message || "格式校验通过"; } catch (error) { this.goConfig.error = error instanceof Error ? error.message : String(error); } finally { this.goConfig.validating = false; } }, async saveGoConfig() { this.goConfig.saving = true; this.goConfig.error = ""; this.goConfig.saveMessage = ""; try { const response = await fetch("/api/runtime/golang-config", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ content: this.goConfig.editorContent }), }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.goConfig.loadedContent = this.goConfig.editorContent; this.goConfig.saveMessage = payload.message || "golang 配置已保存"; await this.refreshGoConfig(); } catch (error) { this.goConfig.error = error instanceof Error ? error.message : String(error); } finally { this.goConfig.saving = false; } }, async refreshRestartTargets() { this.restartOps.loading = true; this.restartOps.error = ""; try { const response = await fetch("/api/ops/restart-targets", { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.restartOps.items = Array.isArray(payload.items) ? payload.items : []; const available = new Set(this.restartOps.items.map((item) => item.service)); this.restartOps.selectedServices = this.restartOps.selectedServices.filter((service) => available.has(service)); } catch (error) { this.restartOps.error = error instanceof Error ? error.message : String(error); } finally { this.restartOps.loading = false; } }, async refreshUpdateTargets() { this.updateOps.loading = true; this.updateOps.error = ""; try { const response = await fetch("/api/ops/update-targets", { cache: "no-store" }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.updateOps.items = Array.isArray(payload.items) ? payload.items : []; this.updateOps.defaults = payload.defaults || this.updateOps.defaults; this.updateOps.builder = payload.builder || this.updateOps.builder; if (!this.updateOps.javaGitRef) { this.updateOps.javaGitRef = this.updateOps.defaults.javaGitRef || "main"; } else if (this.updateOps.javaGitRef === "main" && this.updateOps.defaults.javaGitRef) { this.updateOps.javaGitRef = this.updateOps.defaults.javaGitRef; } if (!this.updateOps.goGitRef) { this.updateOps.goGitRef = this.updateOps.defaults.goGitRef || "main"; } else if (this.updateOps.goGitRef === "main" && this.updateOps.defaults.goGitRef) { this.updateOps.goGitRef = this.updateOps.defaults.goGitRef; } const available = new Set(this.updateOps.items.map((item) => item.service)); this.updateOps.selectedServices = this.updateOps.selectedServices.filter((service) => available.has(service)); } catch (error) { this.updateOps.error = error instanceof Error ? error.message : String(error); } finally { this.updateOps.loading = false; } }, toggleUpdateService(serviceName) { const next = new Set(this.updateOps.selectedServices); if (next.has(serviceName)) { next.delete(serviceName); } else { next.add(serviceName); } this.updateOps.selectedServices = [...next]; }, async runServiceUpdate() { if (this.updateOps.selectedServices.length === 0) { return; } this.stopUpdatePolling(); this.updateOps.running = true; this.updateOps.error = ""; this.updateOps.message = "已提交更新任务,等待开始执行"; this.updateOps.operationId = ""; this.updateOps.status = "queued"; this.updateOps.stage = "queued"; this.updateOps.startedAt = ""; this.updateOps.finishedAt = ""; this.updateOps.logDroppedCount = 0; this.updateOps.logs = []; this.updateOps.result = null; this.persistUpdateOperationId(); try { const response = await fetch("/api/ops/update-services", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ services: this.updateOps.selectedServices, javaGitRef: this.updateOps.javaGitRef, goGitRef: this.updateOps.goGitRef, imageTag: this.updateOps.imageTag, skipMavenBuild: this.updateOps.skipMavenBuild, }), }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.applyUpdateOperation(payload.operation || {}); this.startUpdatePolling(); await this.fetchUpdateOperationStatus(this.updateOps.operationId, { silent: true }); } catch (error) { this.updateOps.error = error instanceof Error ? error.message : String(error); this.updateOps.running = false; } finally { this.scrollUpdateLogToBottom(); } }, toggleRestartService(serviceName) { const next = new Set(this.restartOps.selectedServices); if (next.has(serviceName)) { next.delete(serviceName); } else { next.add(serviceName); } this.restartOps.selectedServices = [...next]; }, async runRollingRestart() { if (this.restartOps.selectedServices.length === 0) { return; } this.restartOps.running = true; this.restartOps.error = ""; this.restartOps.message = ""; try { const response = await fetch("/api/ops/rolling-restart", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ services: this.restartOps.selectedServices }), }); const payload = await this.readJsonResponse(response); if (!response.ok) { throw new Error(payload.error || `HTTP ${response.status}`); } this.restartOps.result = payload; this.restartOps.message = payload.ok ? (payload.message || "滚动重启完成") : (payload.error || "滚动重启失败"); await this.refresh(); await this.refreshRestartTargets(); } catch (error) { this.restartOps.error = error instanceof Error ? error.message : String(error); } finally { this.restartOps.running = 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.refreshNacosConfigs({ preserveSelection: false, reloadSelected: true }); this.refreshGoConfig(); this.refreshUpdateTargets(); this.refreshRestartTargets(); const lastOperationId = window.sessionStorage.getItem(UPDATE_OPERATION_STORAGE_KEY) || ""; if (lastOperationId) { this.fetchUpdateOperationStatus(lastOperationId, { silent: true }); this.startUpdatePolling(); } }, beforeUnmount() { if (this.timer) { window.clearInterval(this.timer); } this.stopUpdatePolling(); }, }).mount("#app");