BalancerFormModal.vue 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. <script setup>
  2. import { computed, reactive, ref, watch } from 'vue';
  3. import { useI18n } from 'vue-i18n';
  4. const { t } = useI18n();
  5. // Balancer add/edit modal — mirrors xray_balancer_modal.html.
  6. // Tag must be unique across other balancers; selector is a tag-mode
  7. // list constrained to existing outbound tags (but lets users type
  8. // new ones for forward-references).
  9. const props = defineProps({
  10. open: { type: Boolean, default: false },
  11. balancer: { type: Object, default: null },
  12. outboundTags: { type: Array, default: () => [] },
  13. // All other balancer tags (excludes the one currently being edited)
  14. // — used for the duplicate-tag check.
  15. otherTags: { type: Array, default: () => [] },
  16. });
  17. const emit = defineEmits(['update:open', 'confirm']);
  18. const STRATEGIES = [
  19. { value: 'random', label: 'Random' },
  20. { value: 'roundRobin', label: 'Round robin' },
  21. { value: 'leastLoad', label: 'Least load' },
  22. { value: 'leastPing', label: 'Least ping' },
  23. ];
  24. const form = reactive({
  25. tag: '',
  26. strategy: 'random',
  27. selector: [],
  28. fallbackTag: '',
  29. });
  30. const isEdit = ref(false);
  31. watch(() => props.open, (next) => {
  32. if (!next) return;
  33. if (props.balancer) {
  34. isEdit.value = true;
  35. form.tag = props.balancer.tag || '';
  36. form.strategy = props.balancer.strategy || 'random';
  37. form.selector = [...(props.balancer.selector || [])];
  38. form.fallbackTag = props.balancer.fallbackTag || '';
  39. } else {
  40. isEdit.value = false;
  41. form.tag = '';
  42. form.strategy = 'random';
  43. form.selector = [];
  44. form.fallbackTag = '';
  45. }
  46. });
  47. const tagEmpty = computed(() => !form.tag?.trim());
  48. const duplicateTag = computed(
  49. () => !!form.tag && props.otherTags.includes(form.tag.trim()),
  50. );
  51. const emptySelector = computed(() => form.selector.length === 0);
  52. const isValid = computed(
  53. () => !tagEmpty.value && !duplicateTag.value && !emptySelector.value,
  54. );
  55. const tagValidateStatus = computed(() => {
  56. if (tagEmpty.value) return 'error';
  57. if (duplicateTag.value) return 'warning';
  58. return 'success';
  59. });
  60. const tagHelp = computed(() => {
  61. if (tagEmpty.value) return 'Tag is required';
  62. if (duplicateTag.value) return 'Tag already used by another balancer';
  63. return '';
  64. });
  65. const selectorValidateStatus = computed(() => (emptySelector.value ? 'error' : 'success'));
  66. const selectorHelp = computed(() => (emptySelector.value ? 'Pick at least one outbound' : ''));
  67. function close() { emit('update:open', false); }
  68. function onOk() {
  69. if (!isValid.value) return;
  70. emit('confirm', { ...form });
  71. }
  72. const title = computed(() =>
  73. isEdit.value
  74. ? `${t('edit')} ${t('pages.xray.Balancers')}`
  75. : `+ ${t('pages.xray.Balancers')}`,
  76. );
  77. const okText = computed(() =>
  78. isEdit.value ? t('pages.client.submitEdit') : t('create'),
  79. );
  80. </script>
  81. <template>
  82. <a-modal :open="open" :title="title" :ok-text="okText" :cancel-text="t('close')"
  83. :ok-button-props="{ disabled: !isValid }" :mask-closable="false" @ok="onOk" @cancel="close">
  84. <a-form :colon="false" :label-col="{ md: { span: 8 } }" :wrapper-col="{ md: { span: 14 } }">
  85. <a-form-item label="Tag" :validate-status="tagValidateStatus" :help="tagHelp" has-feedback>
  86. <a-input v-model:value="form.tag" placeholder="unique balancer tag" />
  87. </a-form-item>
  88. <a-form-item label="Strategy">
  89. <a-select v-model:value="form.strategy">
  90. <a-select-option v-for="s in STRATEGIES" :key="s.value" :value="s.value">{{ s.label }}</a-select-option>
  91. </a-select>
  92. </a-form-item>
  93. <a-form-item label="Selector" :validate-status="selectorValidateStatus" :help="selectorHelp" has-feedback>
  94. <a-select v-model:value="form.selector" mode="tags" :token-separators="[',']">
  95. <a-select-option v-for="tag in outboundTags" :key="tag" :value="tag">{{ tag }}</a-select-option>
  96. </a-select>
  97. </a-form-item>
  98. <a-form-item label="Fallback">
  99. <a-select v-model:value="form.fallbackTag" allow-clear>
  100. <a-select-option v-for="tag in ['', ...outboundTags]" :key="tag || '__empty'" :value="tag">
  101. {{ tag || `(${t('none')})` }}
  102. </a-select-option>
  103. </a-select>
  104. </a-form-item>
  105. </a-form>
  106. </a-modal>
  107. </template>