Files
vnt/vnt-web/ui/src/stores/app.js
T

273 lines
8.0 KiB
JavaScript

import { defineStore } from "pinia";
import { ref, computed, onMounted, onUnmounted, getCurrentInstance } from "vue";
import {
getInstances,
getInstanceInfo,
getConfigList,
getVersion,
startVntApi,
stopVntApi,
restartVntApi,
deleteInstance,
} from "../api";
import { useUiStore } from "./ui";
import { useStartLogStore } from "./startLog";
import { getAccessToken, isDesktop } from "../auth";
export const useAppStore = defineStore("app", () => {
const ui = useUiStore();
const startLog = useStartLogStore();
// 多实例状态:key=file_name, value=该实例 info
const instances = ref({});
const instanceList = ref([]);
const selectedInstance = ref(null);
const configList = ref([]);
const loadingMap = ref({});
// 停止中的实例:key=file_name;点击停止后置位,实例从列表消失或变为 stopped 时清除
const stoppingMap = ref({});
// 页面可见性
const isPageVisible = ref(!document.hidden);
const visibilityHandler = () => {
isPageVisible.value = !document.hidden;
};
let infoTimer = null;
const runningCount = computed(
() => instanceList.value.filter((i) => i.status === "running").length,
);
const startingCount = computed(
() => instanceList.value.filter((i) => i.status === "starting").length,
);
const headerStatusText = computed(() => {
if (runningCount.value > 0) return `运行中 x${runningCount.value}`;
if (startingCount.value > 0) return "启动中...";
return "未启动";
});
const selectedInfo = computed(() =>
selectedInstance.value ? instances.value[selectedInstance.value] || null : null,
);
const selectedConfigName = computed(() => {
if (!selectedInstance.value) return "";
const inst = instanceList.value.find(
(i) => i.file_name === selectedInstance.value,
);
return inst ? inst.config_name || inst.file_name : selectedInstance.value;
});
// 客户端版本号,来自 /api/version,与组网状态无关
const version = ref("");
const fetchVersion = async () => {
try {
version.value = (await getVersion()) || "";
} catch (e) {
console.error("Fetch version error", e);
}
};
const isServerConnected = computed(
() =>
!!(
selectedInfo.value &&
selectedInfo.value.server_info &&
selectedInfo.value.server_info.some((s) => s.connected)
),
);
const serverStatusText = computed(() => {
const si = selectedInfo.value;
if (!si || !si.server_info || !si.server_info.length) return "未配置服务器";
return `${si.server_info.filter((s) => s.connected).length} / ${si.server_info.length} 已连接`;
});
const infoOf = (fileName) => instances.value[fileName] || {};
const fetchInstanceInfo = async (fileName) => {
try {
instances.value[fileName] = await getInstanceInfo(fileName);
} catch (e) {
console.error("Fetch info error", e);
}
};
const fetchInstances = async () => {
try {
const list = (await getInstances()) || [];
instanceList.value = list;
// 清理已消失实例的 info 缓存
for (const key of Object.keys(instances.value)) {
if (!list.some((i) => i.file_name === key)) {
delete instances.value[key];
}
}
// 停止已生效(实例消失或进入 stopped)时清除停止中标记
for (const key of Object.keys(stoppingMap.value)) {
const inst = list.find((i) => i.file_name === key);
if (!inst || inst.status === "stopped") {
delete stoppingMap.value[key];
}
}
// 默认选中逻辑:当前选中失效时优先第一个 running,否则第一个,否则 null
if (
!selectedInstance.value ||
!list.some((i) => i.file_name === selectedInstance.value)
) {
const running = list.find((i) => i.status === "running");
selectedInstance.value = running
? running.file_name
: list.length > 0
? list[0].file_name
: null;
}
// 拉取 running 实例的详情
for (const inst of list) {
if (inst.status === "running") {
fetchInstanceInfo(inst.file_name);
}
}
} catch (e) {
console.error("Fetch instances error", e);
}
};
const fetchConfigList = async () => {
try {
configList.value = (await getConfigList()) || [];
} catch (e) {
console.error("Fetch list error", e);
}
};
const startVnt = async (fileName) => {
if (!fileName) {
ui.toast.error("请先选择一个配置");
return;
}
if (loadingMap.value[fileName]) return;
loadingMap.value[fileName] = true;
try {
await startVntApi(fileName);
startLog.openStartLog(fileName);
fetchInstances();
} catch (e) {
ui.toast.error("启动失败: " + e.message);
} finally {
loadingMap.value[fileName] = false;
}
};
const stopVnt = async (fileName) => {
if (!fileName || loadingMap.value[fileName]) return;
loadingMap.value[fileName] = true;
stoppingMap.value[fileName] = true;
try {
await stopVntApi(fileName);
ui.toast.success("已停止");
if (startLog.logFileName === fileName) {
startLog.stopPolling();
startLog.showStartLog = false;
}
fetchInstances();
} catch (e) {
ui.toast.error("停止失败: " + e.message);
console.error(e);
// 停止失败,恢复可操作状态
delete stoppingMap.value[fileName];
} finally {
loadingMap.value[fileName] = false;
}
};
const restartVnt = async (fileName) => {
if (!fileName || loadingMap.value[fileName]) return;
loadingMap.value[fileName] = true;
try {
await restartVntApi(fileName);
startLog.openStartLog(fileName);
fetchInstances();
} catch (e) {
ui.toast.error("重启失败: " + e.message);
} finally {
loadingMap.value[fileName] = false;
}
};
// 移除已停止(启动失败残留)的实例条目
const dismissInstance = async (fileName) => {
if (!fileName || loadingMap.value[fileName]) return;
loadingMap.value[fileName] = true;
try {
await deleteInstance(fileName);
ui.toast.success("已移除");
if (startLog.logFileName === fileName) {
startLog.stopPolling();
startLog.showStartLog = false;
}
if (selectedInstance.value === fileName) {
selectedInstance.value = null;
}
fetchInstances();
} catch (e) {
ui.toast.error("移除失败: " + e.message);
} finally {
loadingMap.value[fileName] = false;
}
};
// 注入依赖,供启动日志 store 刷新实例与解析配置名
startLog.bindApp({ fetchInstances, instanceList, configList });
const init = async () => {
if (!isDesktop && !getAccessToken()) return;
document.addEventListener("visibilitychange", visibilityHandler);
fetchVersion();
await fetchInstances();
fetchConfigList();
// 页面加载时若有正在启动的实例,恢复其日志弹窗
const starting = instanceList.value.find((i) => i.status === "starting");
if (starting) startLog.openStartLog(starting.file_name);
// 全局 3s 轮询实例列表及 running 实例详情(仅页面可见)
infoTimer = setInterval(() => {
if (isPageVisible.value) fetchInstances();
}, 3000);
};
const destroy = () => {
document.removeEventListener("visibilitychange", visibilityHandler);
startLog.stopPolling();
if (infoTimer) clearInterval(infoTimer);
};
// store 在组件 setup 中被使用时自动挂接生命周期
if (getCurrentInstance()) {
onMounted(init);
onUnmounted(destroy);
}
return {
instances,
instanceList,
selectedInstance,
configList,
loadingMap,
stoppingMap,
isPageVisible,
runningCount,
startingCount,
headerStatusText,
selectedInfo,
selectedConfigName,
version,
isServerConnected,
serverStatusText,
infoOf,
fetchInstances,
fetchInstanceInfo,
fetchConfigList,
startVnt,
stopVnt,
restartVnt,
dismissInstance,
};
});