mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-05-13 17:46:02 +00:00
- Index dashboard regains the 8 cards that were lost in the SPA port (3X-UI panel info, Operation Hours, System Load, Usage, Overall Speed, Total Data, IP Addresses, Connection Stats), plus a Config button that shows the live xray config.json. Version display falls back through panelUpdateInfo → window.__X_UI_CUR_VER__ → '?' so dev mode isn't blank. - Xray config no longer hangs on load: useXraySetting surfaces failures instead of leaving a perpetual spinner, and the Vite dev proxy stops hijacking POST requests to migrated routes (only GETs get bypassed). - Inbound page no longer throws __asyncLoader/emitsOptions errors — inbound.js was missing imports (NumberFormatter, SizeFormatter, Wireguard) and InboundList kept emitting after unmount. - Login round-trip works after logout: a public /csrf-token endpoint bootstraps the SPA before authentication, axios caches the token module-level, and the dev 401 handler navigates to /login.html instead of reloading the dashboard into a redirect loop. - legacy.css mirrors the legacy panel's surface/text variables so dark and ultra-dark themes match main; every SPA entry imports it. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
144 lines
4.2 KiB
Vue
144 lines
4.2 KiB
Vue
<script setup>
|
|
import { useI18n } from 'vue-i18n';
|
|
import {
|
|
BarsOutlined,
|
|
PoweroffOutlined,
|
|
ReloadOutlined,
|
|
ToolOutlined,
|
|
} from '@ant-design/icons-vue';
|
|
|
|
const { t } = useI18n();
|
|
|
|
defineProps({
|
|
status: { type: Object, required: true },
|
|
isMobile: { type: Boolean, default: false },
|
|
ipLimitEnable: { type: Boolean, default: false },
|
|
});
|
|
|
|
defineEmits(['stop-xray', 'restart-xray', 'open-xray-logs', 'open-version-switch']);
|
|
|
|
// Map xray.color → which animation class to apply on the badge dot.
|
|
// The legacy .xray-*-animation classes only override the badge ring
|
|
// color; the actual pulsing comes from .xray-processing-animation
|
|
// (which animates .ant-badge-status-dot via @keyframes runningAnimation).
|
|
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="status.xray.stateMsg" :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="status.xray.stateMsg" :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: var(--color-primary-100, #008771);
|
|
}
|
|
|
|
.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>
|