Feat: get minecraft players

This commit is contained in:
Unitwk 2024-08-19 16:04:14 +08:00
parent d9a62e13d0
commit 27e63eb203
8 changed files with 187 additions and 33 deletions

View File

@ -1,6 +1,6 @@
import Instance from "../../instance/instance";
import InstanceCommand from "../base/command";
import { MCServerStatus } from "common";
import { MCServerStatus, toNumber } from "common";
export default class PingJavaMinecraftServerCommand extends InstanceCommand {
constructor() {
@ -14,15 +14,17 @@ export default class PingJavaMinecraftServerCommand extends InstanceCommand {
const result = await new MCServerStatus(instance.config.pingConfig.port, host).getStatus();
if (result.online) {
instance.info.mcPingOnline = true;
instance.info.currentPlayers = result.current_players;
instance.info.maxPlayers = result.max_players;
instance.info.currentPlayers = toNumber(result.current_players) ?? 0;
instance.info.maxPlayers = toNumber(result.max_players) ?? 0;
instance.info.version = result.version;
instance.info.latency = toNumber(result.latency) ?? 0;
} else {
instance.resetPingInfo();
}
return result;
}
} catch (error) {
instance.resetPingInfo();
// ignore error
}
return null;

View File

@ -18,5 +18,6 @@ export default class PingMinecraftServerTask implements ILifeCycleTask {
async stop(instance: Instance) {
instance.resetPingInfo();
clearInterval(this.task);
this.task = undefined;
}
}

View File

@ -34,12 +34,14 @@ export interface InstanceDetail {
started: number;
status: INSTANCE_STATUS_CODE;
info: {
mcPingOnline: boolean;
currentPlayers: number;
fileLock: number;
maxPlayers: number;
openFrpStatus: boolean;
playersChart: any[];
version: string;
fileLock: number;
playersChart: Array<{ value: string }>;
openFrpStatus: boolean;
latency: number;
};
config: IGlobalInstanceConfig;
watcher?: number;

View File

@ -18,8 +18,10 @@ import {
WarningOutlined,
InfoCircleOutlined,
FrownOutlined,
RedoOutlined
RedoOutlined,
UserOutlined
} from "@ant-design/icons-vue";
import BetweenMenus from "@/components/BetweenMenus.vue";
import { router } from "@/config/router";
import { remoteInstances, remoteNodeList } from "@/services/apis";
@ -468,23 +470,24 @@ onMounted(async () => {
</template>
<template #body>
<a-typography-paragraph>
<div>
{{ t("TXT_CODE_e70a8e24") }}
<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>
{{ t("TXT_CODE_68831be6") }}
{{ item.moreInfo?.instanceTypeText }}
<div class="mb-6">
<a-tag :color="item.moreInfo?.isRunning ? 'green' : ''">
<span v-if="item.moreInfo?.isRunning">
<CheckCircleOutlined />
{{ item.moreInfo?.statusText }}
</span>
<span v-else-if="item.moreInfo?.isStopped" class="color-info">
<ExclamationCircleOutlined />
{{ item.moreInfo?.statusText }}
</span>
<span v-else>
<ExclamationCircleOutlined />
{{ item.moreInfo?.statusText }}
</span>
</a-tag>
<a-tag>
{{ item.moreInfo?.instanceTypeText }}
</a-tag>
</div>
<div>
{{ t("TXT_CODE_d31a684c") }}
@ -494,6 +497,13 @@ onMounted(async () => {
{{ t("TXT_CODE_ae747cc0") }}
{{ parseTimestamp(item.config.endTime) }}
</div>
<div v-if="item.info.mcPingOnline">
<span>{{ t("在线数:") }}</span>
<span style="vertical-align: middle">
<UserOutlined />
{{ item.info.currentPlayers }} / {{ item.info.maxPlayers }}
</span>
</div>
</a-typography-paragraph>
</template>
</CardPanel>
@ -530,6 +540,7 @@ onMounted(async () => {
}
.instance-card {
cursor: pointer;
min-height: 170px;
}
.instance-card:hover {
border: 1px solid var(--color-gray-8);

View File

@ -36,6 +36,17 @@ const getInstanceName = computed(() => {
}
});
const instanceGameServerInfo = computed(() => {
if (instanceInfo.value?.info?.mcPingOnline) {
return {
players: `${instanceInfo.value?.info.currentPlayers} / ${instanceInfo.value?.info.maxPlayers}`,
version: instanceInfo.value?.info.version
};
} else {
return null;
}
});
onMounted(async () => {
if (instanceId && daemonId) {
await execute({
@ -60,6 +71,12 @@ onMounted(async () => {
<a-typography-paragraph>
{{ t("TXT_CODE_68831be6") }}{{ instanceTypeText }}
</a-typography-paragraph>
<a-typography-paragraph v-if="instanceGameServerInfo">
{{ t("玩家数:") }}{{ instanceGameServerInfo.players }}
</a-typography-paragraph>
<a-typography-paragraph v-if="instanceGameServerInfo">
{{ t("游戏版本:") }}{{ instanceGameServerInfo.version }}
</a-typography-paragraph>
<a-typography-paragraph>
<div style="display: flex; gap: 10px">
<div>
@ -112,12 +129,13 @@ onMounted(async () => {
</div>
</a-typography-paragraph>
<a-typography-paragraph>
{{ t("TXT_CODE_ae747cc0") }}{{ parseTimestamp(instanceInfo?.config.endTime) }}
<span>{{ t("TXT_CODE_ae747cc0") }}</span>
<span>{{ parseTimestamp(instanceInfo?.config.endTime) || t("无限制") }}</span>
</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-paragraph v-if="!instanceGameServerInfo">
{{ t("TXT_CODE_8b8e08a6") }}{{ parseTimestamp(instanceInfo?.config.createDatetime) }}
</a-typography-paragraph>
<a-typography-paragraph>
<a-typography-paragraph v-if="!instanceGameServerInfo">
<span>{{ t("TXT_CODE_cec321b4") }}{{ instanceInfo?.config.oe.toUpperCase() }} </span>
<span class="ml-6">
{{ t("TXT_CODE_400a4210") }}{{ instanceInfo?.config.ie.toUpperCase() }}
@ -126,7 +144,7 @@ onMounted(async () => {
<a-typography-paragraph>
<a-typography-text> {{ t("TXT_CODE_30051f9b") }} </a-typography-text>
<a-typography-text :copyable="{ text: instanceInfo?.instanceUuid }"> </a-typography-text>
<a-typography-text class="ml-10"> {{ t("TXT_CODE_5f2d2e30") }} </a-typography-text>
<a-typography-text class="ml-20"> {{ t("TXT_CODE_5f2d2e30") }} </a-typography-text>
<a-typography-text :copyable="{ text: daemonId }"> </a-typography-text>
</a-typography-paragraph>
</template>

View File

@ -11,25 +11,27 @@ import {
ControlOutlined,
DashboardOutlined,
FieldTimeOutlined,
FolderOpenOutlined
FolderOpenOutlined,
UsergroupDeleteOutlined
} from "@ant-design/icons-vue";
import InnerCard from "@/components/InnerCard.vue";
import { LayoutCardHeight } from "../../config/originLayoutConfig";
import { useAppStateStore } from "@/stores/useAppStateStore";
import { useAppRouters } from "@/hooks/useAppRouters";
import { useLayoutCardTools } from "../../hooks/useCardTools";
import { useInstanceInfo } from "@/hooks/useInstance";
import { TYPE_MINECRAFT_JAVA, useInstanceInfo } from "@/hooks/useInstance";
import TermConfig from "./dialogs/TermConfig.vue";
import EventConfig from "./dialogs/EventConfig.vue";
import PingConfig from "./dialogs/PingConfig.vue";
import RconSettings from "./dialogs/RconSettings.vue";
import InstanceDetail from "./dialogs/InstanceDetail.vue";
import { GLOBAL_INSTANCE_NAME } from "../../config/const";
import type { RouteLocationPathRaw } from "vue-router";
import { TYPE_UNIVERSAL, TYPE_WEB_SHELL } from "../../hooks/useInstance";
import McPingSettings from "./dialogs/McPingSettings.vue";
const terminalConfigDialog = ref<InstanceType<typeof TermConfig>>();
const rconSettingsDialog = ref<InstanceType<typeof RconSettings>>();
const mcSettingsDialog = ref<InstanceType<typeof McPingSettings>>();
const eventConfigDialog = ref<InstanceType<typeof EventConfig>>();
const pingConfigDialog = ref<InstanceType<typeof PingConfig>>();
const instanceDetailsDialog = ref<InstanceType<typeof InstanceDetail>>();
@ -102,6 +104,14 @@ const btns = computed(() => {
},
condition: () => state.settings.canFileManager || isAdmin.value
},
{
title: t("Minecraft 状态获取"),
icon: UsergroupDeleteOutlined,
click: () => {
mcSettingsDialog.value?.openDialog();
},
condition: () => instanceInfo.value?.config.type.includes(TYPE_MINECRAFT_JAVA) ?? false
},
{
title: t("TXT_CODE_656a85d8"),
icon: BuildOutlined,
@ -234,6 +244,14 @@ const btns = computed(() => {
:daemon-id="daemonId"
@update="refreshInstanceInfo"
/>
<McPingSettings
ref="mcSettingsDialog"
:instance-info="instanceInfo"
:instance-id="instanceId"
:daemon-id="daemonId"
@update="refreshInstanceInfo"
/>
</template>
<style lang="scss" scoped></style>

View File

@ -115,7 +115,7 @@ const loadImages = async () => {
}
}
} catch (err: any) {
return reportErrorMsg(err.message);
// ignore
}
};
@ -143,7 +143,7 @@ const loadNetworkModes = async () => {
});
if (modes.value) networkModes.value = modes.value;
} catch (err: any) {
return reportErrorMsg(err.message);
// ignore
}
};

View File

@ -0,0 +1,102 @@
<script setup lang="ts">
import { ref, reactive } from "vue";
import { t } from "@/lang/i18n";
import type { InstanceDetail } from "@/types";
import { updateInstanceConfig } from "@/services/apis/instance";
import { message, type FormInstance } from "ant-design-vue";
import { reportErrorMsg } from "@/tools/validator";
const formRef = ref<FormInstance>();
const props = defineProps<{
instanceInfo?: InstanceDetail;
instanceId?: string;
daemonId?: string;
}>();
const emit = defineEmits(["update"]);
const formData = reactive({
ip: "",
port: "",
type: 1
});
const open = ref(false);
const openDialog = () => {
open.value = true;
formData.ip = props.instanceInfo?.config?.pingConfig.ip || "";
formData.port = String(props.instanceInfo?.config?.pingConfig.port || "");
formData.type = props.instanceInfo?.config?.pingConfig.type ?? 1;
};
const { execute, isLoading } = updateInstanceConfig();
const submit = async () => {
try {
await formRef.value?.validateFields();
await execute({
params: {
uuid: props.instanceId ?? "",
daemonId: props.daemonId ?? ""
},
data: {
pingConfig: {
ip: formData.ip,
port: Number(formData.port),
type: formData.type
}
}
});
emit("update");
open.value = false;
return message.success(t("TXT_CODE_d3de39b4"));
} catch (err: any) {
return reportErrorMsg(err.message);
}
};
defineExpose({
openDialog
});
</script>
<template>
<a-modal
v-model:open="open"
centered
:title="t('Minecraft 状态获取')"
:confirm-loading="isLoading"
:ok-text="t('TXT_CODE_abfe9512')"
@ok="submit"
>
<div>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("MCSManager 将使用 Minecraft Ping 协议尝试获取服务器在线人数,版本,延迟等信息。") }}
<br />
{{ t("完成配置后,实例运行时,每分钟刷新一次服务器状态。") }}
</a-typography-text>
</a-typography-paragraph>
<a-form ref="formRef" :model="formData" layout="vertical">
<a-form-item name="port">
<a-typography-title :level="5">{{ t("TXT_CODE_890aa44c") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("TXT_CODE_a4748cb0") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="formData.port" :placeholder="t('TXT_CODE_e2dc0156')" />
</a-form-item>
<a-form-item name="ip">
<a-typography-title :level="5">{{ t("服务器地址") }}</a-typography-title>
<a-typography-paragraph>
<a-typography-text type="secondary">
{{ t("TXT_CODE_8e2be926") }}
</a-typography-text>
</a-typography-paragraph>
<a-input v-model:value="formData.ip" :placeholder="t('默认值localhost')" />
</a-form-item>
</a-form>
</div>
</a-modal>
</template>