XrayStatusCard.vue 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. <script setup>
  2. import { computed } from 'vue';
  3. import { useI18n } from 'vue-i18n';
  4. import {
  5. BarsOutlined,
  6. PoweroffOutlined,
  7. ReloadOutlined,
  8. ToolOutlined,
  9. } from '@ant-design/icons-vue';
  10. const { t } = useI18n();
  11. const props = defineProps({
  12. status: { type: Object, required: true },
  13. isMobile: { type: Boolean, default: false },
  14. ipLimitEnable: { type: Boolean, default: false },
  15. });
  16. defineEmits(['stop-xray', 'restart-xray', 'open-logs', 'open-xray-logs', 'open-version-switch']);
  17. const XRAY_STATE_KEYS = {
  18. running: 'pages.index.xrayStatusRunning',
  19. stop: 'pages.index.xrayStatusStop',
  20. error: 'pages.index.xrayStatusError',
  21. };
  22. const stateText = computed(() =>
  23. t(XRAY_STATE_KEYS[props.status.xray.state] ?? 'pages.index.xrayStatusUnknown'),
  24. );
  25. function badgeAnimationClass(color) {
  26. if (color === 'green') return 'xray-running-animation';
  27. if (color === 'orange') return 'xray-stop-animation';
  28. if (color === 'red') return 'xray-error-animation';
  29. return 'xray-processing-animation';
  30. }
  31. </script>
  32. <template>
  33. <a-card hoverable>
  34. <template #title>
  35. <a-space direction="horizontal">
  36. <span>{{ t('pages.index.xrayStatus') }}</span>
  37. <a-tag v-if="isMobile && status.xray.version && status.xray.version !== 'Unknown'" color="green">
  38. v{{ status.xray.version }}
  39. </a-tag>
  40. </a-space>
  41. </template>
  42. <template #extra>
  43. <template v-if="status.xray.state !== 'error'">
  44. <a-badge status="processing" :class="['xray-processing-animation', badgeAnimationClass(status.xray.color)]"
  45. :text="stateText" :color="status.xray.color" />
  46. </template>
  47. <template v-else>
  48. <a-popover>
  49. <template #title>
  50. <a-row type="flex" align="middle" justify="space-between">
  51. <a-col><span>{{ t('pages.index.xrayStatusError') }}</span></a-col>
  52. <a-col>
  53. <BarsOutlined class="cursor-pointer" @click="$emit('open-logs')" />
  54. </a-col>
  55. </a-row>
  56. </template>
  57. <template #content>
  58. <span v-for="(line, i) in (status.xray.errorMsg || '').split('\n')" :key="i" class="error-line">
  59. {{ line }}
  60. </span>
  61. </template>
  62. <a-badge status="processing" :text="stateText" :color="status.xray.color"
  63. :class="['xray-processing-animation', 'xray-error-animation']" />
  64. </a-popover>
  65. </template>
  66. </template>
  67. <template #actions>
  68. <a-space v-if="ipLimitEnable" direction="horizontal" class="action" @click="$emit('open-xray-logs')">
  69. <BarsOutlined />
  70. <span v-if="!isMobile">{{ t('pages.index.logs') }}</span>
  71. </a-space>
  72. <a-space direction="horizontal" class="action" @click="$emit('stop-xray')">
  73. <PoweroffOutlined />
  74. <span v-if="!isMobile">{{ t('pages.index.stopXray') }}</span>
  75. </a-space>
  76. <a-space direction="horizontal" class="action" @click="$emit('restart-xray')">
  77. <ReloadOutlined />
  78. <span v-if="!isMobile">{{ t('pages.index.restartXray') }}</span>
  79. </a-space>
  80. <a-space direction="horizontal" class="action" @click="$emit('open-version-switch')">
  81. <ToolOutlined />
  82. <span v-if="!isMobile">
  83. {{ status.xray.version && status.xray.version !== 'Unknown'
  84. ? `v${status.xray.version}`
  85. : t('pages.index.xraySwitch') }}
  86. </span>
  87. </a-space>
  88. </template>
  89. </a-card>
  90. </template>
  91. <style scoped>
  92. .action {
  93. cursor: pointer;
  94. justify-content: center;
  95. }
  96. .error-line {
  97. display: block;
  98. max-width: 400px;
  99. white-space: pre-wrap;
  100. }
  101. .cursor-pointer {
  102. cursor: pointer;
  103. }
  104. </style>
  105. <style>
  106. /* Legacy xray-*-animation classes — they need to be global so they
  107. * pierce the AD-Vue badge's internal DOM (.ant-badge-status-*). */
  108. .xray-processing-animation .ant-badge-status-dot {
  109. animation: xray-pulse 1.2s linear infinite;
  110. }
  111. .xray-running-animation .ant-badge-status-processing::after {
  112. border-color: #1677ff;
  113. }
  114. .xray-stop-animation .ant-badge-status-processing::after {
  115. border-color: #fa8c16;
  116. }
  117. .xray-error-animation .ant-badge-status-processing::after {
  118. border-color: #f5222d;
  119. }
  120. @keyframes xray-pulse {
  121. 0%,
  122. 50%,
  123. 100% {
  124. transform: scale(1);
  125. opacity: 1;
  126. }
  127. 10% {
  128. transform: scale(1.5);
  129. opacity: 0.2;
  130. }
  131. }
  132. </style>