3x-ui/frontend/src/pages/index/XrayStatusCard.vue
MHSanaei cf5767acd1
i18n: localize sidebar theme toggle, xray-status badge, and nodes menu
The sidebar theme submenu (Theme / Dark / Ultra dark) and the dashboard's
Xray status badge ("Xray is running" etc.) were hardcoded English strings.
Wire them through vue-i18n: ThemeSwitch.vue uses menu.theme/dark/ultraDark,
and XrayStatusCard.vue derives the badge text from the existing
pages.index.xrayStatus{Running,Stop,Error,Unknown} keys (status.js no
longer carries an English stateMsg field).

The "Nodes" menu item was already keyed as menu.nodes but only en-US and
fa-IR had a translation; add it to the other 11 languages, matching the
wording each file already uses for pages.nodes.title.
#4201
2026-05-10 11:56:30 +02:00

151 lines
4.2 KiB
Vue

<script setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
import {
BarsOutlined,
PoweroffOutlined,
ReloadOutlined,
ToolOutlined,
} from '@ant-design/icons-vue';
const { t } = useI18n();
const props = defineProps({
status: { type: Object, required: true },
isMobile: { type: Boolean, default: false },
ipLimitEnable: { type: Boolean, default: false },
});
defineEmits(['stop-xray', 'restart-xray', 'open-logs', 'open-xray-logs', 'open-version-switch']);
const XRAY_STATE_KEYS = {
running: 'pages.index.xrayStatusRunning',
stop: 'pages.index.xrayStatusStop',
error: 'pages.index.xrayStatusError',
};
const stateText = computed(() =>
t(XRAY_STATE_KEYS[props.status.xray.state] ?? 'pages.index.xrayStatusUnknown'),
);
function badgeAnimationClass(color) {
if (color === 'green') return 'xray-running-animation';
if (color === 'orange') return 'xray-stop-animation';
if (color === 'red') return 'xray-error-animation';
return 'xray-processing-animation';
}
</script>
<template>
<a-card hoverable>
<template #title>
<a-space direction="horizontal">
<span>{{ t('pages.index.xrayStatus') }}</span>
<a-tag v-if="isMobile && status.xray.version && status.xray.version !== 'Unknown'" color="green">
v{{ status.xray.version }}
</a-tag>
</a-space>
</template>
<template #extra>
<template v-if="status.xray.state !== 'error'">
<a-badge status="processing" :class="['xray-processing-animation', badgeAnimationClass(status.xray.color)]"
:text="stateText" :color="status.xray.color" />
</template>
<template v-else>
<a-popover>
<template #title>
<a-row type="flex" align="middle" justify="space-between">
<a-col><span>{{ t('pages.index.xrayStatusError') }}</span></a-col>
<a-col>
<BarsOutlined class="cursor-pointer" @click="$emit('open-logs')" />
</a-col>
</a-row>
</template>
<template #content>
<span v-for="(line, i) in (status.xray.errorMsg || '').split('\n')" :key="i" class="error-line">
{{ line }}
</span>
</template>
<a-badge status="processing" :text="stateText" :color="status.xray.color"
:class="['xray-processing-animation', 'xray-error-animation']" />
</a-popover>
</template>
</template>
<template #actions>
<a-space v-if="ipLimitEnable" direction="horizontal" class="action" @click="$emit('open-xray-logs')">
<BarsOutlined />
<span v-if="!isMobile">{{ t('pages.index.logs') }}</span>
</a-space>
<a-space direction="horizontal" class="action" @click="$emit('stop-xray')">
<PoweroffOutlined />
<span v-if="!isMobile">{{ t('pages.index.stopXray') }}</span>
</a-space>
<a-space direction="horizontal" class="action" @click="$emit('restart-xray')">
<ReloadOutlined />
<span v-if="!isMobile">{{ t('pages.index.restartXray') }}</span>
</a-space>
<a-space direction="horizontal" class="action" @click="$emit('open-version-switch')">
<ToolOutlined />
<span v-if="!isMobile">
{{ status.xray.version && status.xray.version !== 'Unknown'
? `v${status.xray.version}`
: t('pages.index.xraySwitch') }}
</span>
</a-space>
</template>
</a-card>
</template>
<style scoped>
.action {
cursor: pointer;
justify-content: center;
}
.error-line {
display: block;
max-width: 400px;
white-space: pre-wrap;
}
.cursor-pointer {
cursor: pointer;
}
</style>
<style>
/* Legacy xray-*-animation classes — they need to be global so they
* pierce the AD-Vue badge's internal DOM (.ant-badge-status-*). */
.xray-processing-animation .ant-badge-status-dot {
animation: xray-pulse 1.2s linear infinite;
}
.xray-running-animation .ant-badge-status-processing::after {
border-color: #1677ff;
}
.xray-stop-animation .ant-badge-status-processing::after {
border-color: #fa8c16;
}
.xray-error-animation .ant-badge-status-processing::after {
border-color: #f5222d;
}
@keyframes xray-pulse {
0%,
50%,
100% {
transform: scale(1);
opacity: 1;
}
10% {
transform: scale(1.5);
opacity: 0.2;
}
}
</style>