Feat: useInstanceState hook

This commit is contained in:
unitwk 2023-08-31 23:57:34 +08:00
parent 276dc729ad
commit 386dc0c7fb
5 changed files with 232 additions and 43 deletions

View File

@ -14,7 +14,8 @@ export function executeRequest<T>(config: RequestConfig) {
{ {
immediate: false, immediate: false,
shallow: false as any, shallow: false as any,
throwError: true throwError: true,
resetOnExecute: false
} }
); );
} }

View File

@ -0,0 +1,138 @@
import type { InstanceDetail, MapData } from "@/types";
import { t } from "@/lang/i18n";
import { computed, onMounted, onUnmounted, ref, type Ref } from "vue";
import { getInstanceInfo } from "@/services/apis/instance";
export const INSTANCE_STATUS_TEXT: MapData<string> = {
"-1": t("维护中"),
"0": t("未运行"),
"1": t("停止中"),
"2": t("启动中"),
"3": t("运行中")
};
export const TYPE_UNIVERSAL = "universal";
export const TYPE_WEB_SHELL = "universal/web_shell";
export const TYPE_MINECRAFT_MCDR = "universal/mcdr";
export const TYPE_MINECRAFT_JAVA = "minecraft/java";
export const TYPE_MINECRAFT_BUKKIT = "minecraft/java/bukkit";
export const TYPE_MINECRAFT_SPIGOT = "minecraft/java/spigot";
export const TYPE_MINECRAFT_PAPER = "minecraft/java/paper";
export const TYPE_MINECRAFT_FORGE = "minecraft/java/forge";
export const TYPE_MINECRAFT_FABRIC = "minecraft/java/fabric";
export const TYPE_MINECRAFT_BUNGEECORD = "minecraft/java/bungeecord";
export const TYPE_MINECRAFT_VELOCITY = "minecraft/java/velocity";
export const TYPE_MINECRAFT_GEYSER = "minecraft/java/geyser";
export const TYPE_MINECRAFT_SPONGE = "minecraft/java/sponge";
export const TYPE_MINECRAFT_MOHIST = "minecraft/java/mohist";
export const TYPE_MINECRAFT_BEDROCK = "minecraft/bedrock";
export const TYPE_MINECRAFT_BDS = "minecraft/bedrock/bds";
export const TYPE_MINECRAFT_NUKKIT = "minecraft/bedrock/nukkit";
export const TYPE_STEAM_SERVER_UNIVERSAL = "steam/universal";
export const INSTANCE_TYPE_TRANSLATION: MapData<string> = {
[TYPE_UNIVERSAL]: "General Console Application",
[TYPE_STEAM_SERVER_UNIVERSAL]: "Steam Game Server",
[TYPE_MINECRAFT_JAVA]: "MC Java Edition",
[TYPE_MINECRAFT_BEDROCK]: "MC Bedrock Edition",
[TYPE_MINECRAFT_SPIGOT]: "MC Spigot",
[TYPE_MINECRAFT_PAPER]: "MC Paper",
[TYPE_MINECRAFT_BUNGEECORD]: "MC BungeeCord",
[TYPE_MINECRAFT_VELOCITY]: "MC Velocity",
[TYPE_MINECRAFT_BDS]: "MC Bedrock",
[TYPE_MINECRAFT_SPONGE]: "MC Sponge",
[TYPE_MINECRAFT_FORGE]: "MC Forge",
[TYPE_MINECRAFT_FABRIC]: "MC Fabric",
[TYPE_MINECRAFT_BUKKIT]: "MC Bukkit",
[TYPE_MINECRAFT_GEYSER]: "MC Geyser",
[TYPE_MINECRAFT_MCDR]: "MC MCDR",
[TYPE_WEB_SHELL]: "Web Shell"
};
interface Params {
instanceId?: string;
daemonId?: string;
autoRefresh?: boolean;
instanceInfo?: Ref<InstanceDetail | undefined>;
}
export interface InstanceMoreDetail extends InstanceDetail {
moreInfo?: {
isRunning: boolean;
isStopped: boolean;
instanceTypeText: string;
statusText: string;
};
}
export function useInstanceMoreDetail(info: InstanceMoreDetail): InstanceMoreDetail {
const { statusText, isRunning, isStopped, instanceTypeText } = useInstanceInfo({
instanceInfo: ref(info)
});
info.moreInfo = {
statusText: statusText.value,
isRunning: isRunning.value,
isStopped: isStopped.value,
instanceTypeText: instanceTypeText.value
};
return info;
}
export function useInstanceInfo(params: Params) {
let task: NodeJS.Timer | undefined;
const { daemonId, instanceId, instanceInfo, autoRefresh } = params;
const { execute, state, isLoading, isReady } = getInstanceInfo();
let finalState = state;
if (instanceInfo) finalState = instanceInfo;
const isRunning = computed(() => finalState?.value?.status === 3);
const isStopped = computed(() => finalState?.value?.status === 0);
const instanceTypeText = computed(() => {
return INSTANCE_TYPE_TRANSLATION[String(finalState?.value?.config.type)] || t("未知类型");
});
const statusText = computed(
() => String(INSTANCE_STATUS_TEXT[String(finalState?.value?.status)]) || t("未知状态")
);
onMounted(async () => {
if (instanceId && daemonId) {
await execute({
params: {
uuid: instanceId,
remote_uuid: daemonId
}
});
if (autoRefresh) {
task = setInterval(async () => {
await execute({
params: {
uuid: instanceId,
remote_uuid: daemonId
}
});
}, 3000);
}
}
});
onUnmounted(() => {
if (task) clearInterval(task);
task = undefined;
});
return {
isLoading,
isReady,
instanceInfo: state,
execute,
statusText,
isRunning,
isStopped,
instanceTypeText
};
}

View File

@ -1,7 +1,7 @@
<script setup lang="ts"> <script setup lang="ts">
import CardPanel from "@/components/CardPanel.vue"; import CardPanel from "@/components/CardPanel.vue";
import type { LayoutCard } from "@/types/index"; import type { LayoutCard } from "@/types/index";
import { ref, onMounted } from "vue"; import { ref, onMounted, computed } from "vue";
import { t } from "@/lang/i18n"; import { t } from "@/lang/i18n";
import { import {
SearchOutlined, SearchOutlined,
@ -17,6 +17,10 @@ import type { NodeStatus } from "../types/index";
import { message } from "ant-design-vue"; import { message } from "ant-design-vue";
import { computeNodeName } from "../tools/nodes"; import { computeNodeName } from "../tools/nodes";
import Loading from "@/components/Loading.vue"; import Loading from "@/components/Loading.vue";
import { useInstanceInfo } from "@/hooks/useInstance";
import type { InstanceMoreDetail } from "../hooks/useInstance";
import { CheckCircleOutlined, ExclamationCircleOutlined } from "@ant-design/icons-vue";
import { useInstanceMoreDetail } from "../hooks/useInstance";
const props = defineProps<{ const props = defineProps<{
card: LayoutCard; card: LayoutCard;
@ -33,6 +37,15 @@ const currentRemoteNode = ref<NodeStatus>();
const { execute: getNodes, state: nodes } = remoteNodeList(); const { execute: getNodes, state: nodes } = remoteNodeList();
const { execute: getInstances, state: instances, isLoading } = remoteInstances(); const { execute: getInstances, state: instances, isLoading } = remoteInstances();
const instancesMoreInfo = computed(() => {
const newInstances: InstanceMoreDetail[] = [];
for (const instance of instances.value?.data || []) {
const instanceMoreInfo = useInstanceMoreDetail(instance);
newInstances.push(instanceMoreInfo);
}
return newInstances;
});
const initNodes = async () => { const initNodes = async () => {
await getNodes(); await getNodes();
if (!nodes.value?.length) { if (!nodes.value?.length) {
@ -133,7 +146,7 @@ const handleChangeNode = () => {};
</div> </div>
</a-col> </a-col>
<template v-if="!isLoading"> <template v-if="!isLoading">
<a-col v-for="item in instances?.data" :key="item" :span="24" :md="6"> <a-col v-for="item in instancesMoreInfo" :key="item" :span="24" :md="6">
<CardPanel <CardPanel
class="instance-card" class="instance-card"
style="height: 100%" style="height: 100%"
@ -144,11 +157,21 @@ const handleChangeNode = () => {};
<a-typography-paragraph> <a-typography-paragraph>
<div> <div>
{{ t("状态:") }} {{ t("状态:") }}
{{ item.status }} <span v-if="item.moreInfo?.isRunning" class="color-success">
<CheckCircleOutlined />
{{ item.moreInfo?.statusText }}
</span>
<span v-else-if="item.moreInfo?.isStopped" class="color-info">
{{ item.moreInfo?.statusText }}
</span>
<span v-else>
<ExclamationCircleOutlined />
{{ item.moreInfo?.statusText }}
</span>
</div> </div>
<div> <div>
{{ t("类型:") }} {{ t("类型:") }}
{{ item.config.type }} {{ item.moreInfo?.instanceTypeText }}
</div> </div>
<div> <div>
{{ t("启动时间:") }} {{ t("启动时间:") }}

View File

@ -1,9 +1,10 @@
<script setup lang="ts"> <script setup lang="ts">
import type { LayoutCard } from "@/types"; import type { LayoutCard } from "@/types";
import { useLayoutCardTools } from "../../hooks/useCardTools"; import { useLayoutCardTools } from "../../hooks/useCardTools";
import { getInstanceInfo } from "@/services/apis/instance";
import { onMounted } from "vue"; import { onMounted } from "vue";
import { t } from "@/lang/i18n"; import { t } from "@/lang/i18n";
import { useInstanceInfo } from "@/hooks/useInstance";
import { CheckCircleOutlined, ExclamationCircleOutlined } from "@ant-design/icons-vue";
const props = defineProps<{ const props = defineProps<{
card: LayoutCard; card: LayoutCard;
@ -14,7 +15,12 @@ const { getMetaOrRouteValue } = useLayoutCardTools(props.card);
const instanceId = getMetaOrRouteValue("instanceId"); const instanceId = getMetaOrRouteValue("instanceId");
const daemonId = getMetaOrRouteValue("daemonId"); const daemonId = getMetaOrRouteValue("daemonId");
const { execute, state: instanceInfo } = getInstanceInfo(); const { statusText, isRunning, isStopped, instanceTypeText, instanceInfo, execute } =
useInstanceInfo({
instanceId,
daemonId,
autoRefresh: true
});
onMounted(async () => { onMounted(async () => {
if (instanceId && daemonId) { if (instanceId && daemonId) {
@ -37,10 +43,21 @@ onMounted(async () => {
<a-typography-paragraph> <a-typography-paragraph>
{{ t("名称:") }}{{ instanceInfo?.config.nickname }} {{ t("名称:") }}{{ instanceInfo?.config.nickname }}
</a-typography-paragraph> </a-typography-paragraph>
<a-typography-paragraph> {{ t("类型:") }}{{ instanceTypeText }} </a-typography-paragraph>
<a-typography-paragraph> <a-typography-paragraph>
{{ t("类型:") }}{{ instanceInfo?.config.type }} <span>{{ t("状态:") }}</span>
<span v-if="isRunning" class="color-success">
<CheckCircleOutlined />
{{ statusText }}
</span>
<span v-else-if="isStopped" class="color-info">
<ExclamationCircleOutlined />
{{ statusText }}
</span>
<span v-else>
{{ statusText }}
</span>
</a-typography-paragraph> </a-typography-paragraph>
<a-typography-paragraph> {{ t("状态:") }}{{ instanceInfo?.status }} </a-typography-paragraph>
<a-typography-paragraph> <a-typography-paragraph>
{{ t("最后启动:") }}{{ instanceInfo?.config.lastDatetime }} {{ t("最后启动:") }}{{ instanceInfo?.config.lastDatetime }}
</a-typography-paragraph> </a-typography-paragraph>

View File

@ -3,11 +3,12 @@ import CardPanel from "@/components/CardPanel.vue";
import { t } from "@/lang/i18n"; import { t } from "@/lang/i18n";
import type { LayoutCard } from "@/types"; import type { LayoutCard } from "@/types";
import { import {
CloudDownloadOutlined,
CodeOutlined, CodeOutlined,
DownOutlined, DownOutlined,
PauseCircleOutlined, PauseCircleOutlined,
PlayCircleOutlined, PlayCircleOutlined,
PlaySquareOutlined ReconciliationOutlined
} from "@ant-design/icons-vue"; } from "@ant-design/icons-vue";
import { arrayFilter } from "../../tools/array"; import { arrayFilter } from "../../tools/array";
import { useTerminal } from "../../hooks/useTerminal"; import { useTerminal } from "../../hooks/useTerminal";
@ -16,68 +17,77 @@ import { useLayoutCardTools } from "@/hooks/useCardTools";
import { getRandomId } from "../../tools/randId"; import { getRandomId } from "../../tools/randId";
import IconBtn from "@/components/IconBtn.vue"; import IconBtn from "@/components/IconBtn.vue";
import { openInstance, stopInstance } from "@/services/apis/instance"; import { openInstance, stopInstance } from "@/services/apis/instance";
import { CloseOutlined } from "@ant-design/icons-vue";
const props = defineProps<{ const props = defineProps<{
card: LayoutCard; card: LayoutCard;
}>(); }>();
const { getMetaOrRouteValue } = useLayoutCardTools(props.card); const { getMetaOrRouteValue } = useLayoutCardTools(props.card);
const { execute, initTerminalWindow, sendCommand } = useTerminal();
const instanceId = getMetaOrRouteValue("instanceId"); const instanceId = getMetaOrRouteValue("instanceId");
const daemonId = getMetaOrRouteValue("daemonId"); const daemonId = getMetaOrRouteValue("daemonId");
const terminalDomId = computed(() => `terminal-window-${getRandomId()}`); const terminalDomId = computed(() => `terminal-window-${getRandomId()}`);
const commandInputValue = ref(""); const commandInputValue = ref("");
const quickOperations = arrayFilter([ const quickOperations = computed(() =>
{ arrayFilter([
title: t("开启程序"), {
icon: PlayCircleOutlined, title: t("开启程序"),
click: () => { icon: PlayCircleOutlined,
openInstance().execute({ click: () => {
params: { openInstance().execute({
uuid: instanceId || "", params: {
remote_uuid: daemonId || "" uuid: instanceId || "",
} remote_uuid: daemonId || ""
}); }
});
},
props: {}
}, },
props: {} {
}, title: t("关闭程序"),
{ icon: PauseCircleOutlined,
title: t("关闭程序"), click: () => {
icon: PauseCircleOutlined, stopInstance().execute({
click: () => { params: {
stopInstance().execute({ uuid: instanceId || "",
params: { remote_uuid: daemonId || ""
uuid: instanceId || "", }
remote_uuid: daemonId || "" });
} },
}); props: {
}, danger: true
props: { }
danger: true
} }
} ])
]); );
const instanceOperations = arrayFilter([ const instanceOperations = arrayFilter([
{ {
title: t("重启"), title: t("重启"),
icon: PlaySquareOutlined, icon: ReconciliationOutlined,
click: () => {
console.log(3);
}
},
{
title: t("终止"),
icon: CloseOutlined,
click: () => { click: () => {
console.log(3); console.log(3);
} }
}, },
{ {
title: t("更新"), title: t("更新"),
icon: PlaySquareOutlined, icon: CloudDownloadOutlined,
click: () => { click: () => {
console.log(4); console.log(4);
} }
} }
]); ]);
const { execute, initTerminalWindow, sendCommand } = useTerminal();
const handleSendCommand = () => { const handleSendCommand = () => {
sendCommand(commandInputValue.value); sendCommand(commandInputValue.value);
commandInputValue.value = ""; commandInputValue.value = "";
@ -88,6 +98,7 @@ const initTerminal = () => {
if (dom) { if (dom) {
initTerminalWindow(dom); initTerminalWindow(dom);
} }
throw new Error("init terminal failed");
}; };
onMounted(async () => { onMounted(async () => {
@ -97,7 +108,6 @@ onMounted(async () => {
daemonId daemonId
}); });
} }
initTerminal(); initTerminal();
}); });
</script> </script>