mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 05:04:22 +00:00
feat(balancers): allow fallback on all strategies + feed burstObservatory from random/roundRobin
Drops the random/roundRobin gate on the Fallback field in BalancerFormModal so every strategy can pick a fallback outbound. syncObservatories now feeds burstObservatory from leastLoad + random + roundRobin balancers (was leastLoad only), matching how leastPing feeds observatory. Fix the JsonEditor "Unexpected end of JSON input" that appeared when switching a balancer between leastPing and another strategy: the obsView watcher was gated on showObsEditor (a boolean OR of the two flags) and missed the case where one observatory swapped for the other in the same tick. Watch the individual flags instead so obsView flips to the surviving editor and the getter stops pointing at a deleted key. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
2ff3c12a42
commit
5f98a2db72
2 changed files with 14 additions and 22 deletions
|
|
@ -61,16 +61,6 @@ const isValid = computed(
|
||||||
() => !tagEmpty.value && !duplicateTag.value && !emptySelector.value,
|
() => !tagEmpty.value && !duplicateTag.value && !emptySelector.value,
|
||||||
);
|
);
|
||||||
|
|
||||||
const fallbackSupported = computed(
|
|
||||||
() => form.strategy === 'leastPing' || form.strategy === 'leastLoad',
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(() => form.strategy, (next) => {
|
|
||||||
if (next !== 'leastPing' && next !== 'leastLoad') {
|
|
||||||
form.fallbackTag = '';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const tagValidateStatus = computed(() => {
|
const tagValidateStatus = computed(() => {
|
||||||
if (tagEmpty.value) return 'error';
|
if (tagEmpty.value) return 'error';
|
||||||
if (duplicateTag.value) return 'warning';
|
if (duplicateTag.value) return 'warning';
|
||||||
|
|
@ -121,8 +111,8 @@ const okText = computed(() =>
|
||||||
</a-select>
|
</a-select>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
|
|
||||||
<a-form-item label="Fallback" :help="fallbackSupported ? '' : 'Available only with Least ping / Least load'">
|
<a-form-item label="Fallback">
|
||||||
<a-select v-model:value="form.fallbackTag" allow-clear :disabled="!fallbackSupported">
|
<a-select v-model:value="form.fallbackTag" allow-clear>
|
||||||
<a-select-option v-for="tag in ['', ...outboundTags]" :key="tag || '__empty'" :value="tag">
|
<a-select-option v-for="tag in ['', ...outboundTags]" :key="tag || '__empty'" :value="tag">
|
||||||
{{ tag || `(${t('none')})` }}
|
{{ tag || `(${t('none')})` }}
|
||||||
</a-select-option>
|
</a-select-option>
|
||||||
|
|
|
||||||
|
|
@ -133,23 +133,25 @@ function syncObservatories() {
|
||||||
delete t.observatory;
|
delete t.observatory;
|
||||||
}
|
}
|
||||||
|
|
||||||
const leastLoads = balancers.filter((b) => b.strategy?.type === 'leastLoad');
|
const burstFeeders = balancers.filter((b) => {
|
||||||
if (leastLoads.length > 0) {
|
const type = b.strategy?.type || 'random';
|
||||||
|
return type === 'leastLoad' || type === 'random' || type === 'roundRobin';
|
||||||
|
});
|
||||||
|
if (burstFeeders.length > 0) {
|
||||||
if (!t.burstObservatory) {
|
if (!t.burstObservatory) {
|
||||||
t.burstObservatory = JSON.parse(JSON.stringify(DEFAULT_BURST_OBSERVATORY));
|
t.burstObservatory = JSON.parse(JSON.stringify(DEFAULT_BURST_OBSERVATORY));
|
||||||
}
|
}
|
||||||
t.burstObservatory.subjectSelector = collectSelectors(leastLoads);
|
t.burstObservatory.subjectSelector = collectSelectors(burstFeeders);
|
||||||
} else {
|
} else {
|
||||||
delete t.burstObservatory;
|
delete t.burstObservatory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildWireBalancer(form) {
|
function buildWireBalancer(form) {
|
||||||
const supportsFallback = form.strategy === 'leastPing' || form.strategy === 'leastLoad';
|
|
||||||
const out = {
|
const out = {
|
||||||
tag: form.tag,
|
tag: form.tag,
|
||||||
selector: [...form.selector],
|
selector: [...form.selector],
|
||||||
fallbackTag: supportsFallback ? form.fallbackTag : '',
|
fallbackTag: form.fallbackTag || '',
|
||||||
};
|
};
|
||||||
if (form.strategy && form.strategy !== 'random') {
|
if (form.strategy && form.strategy !== 'random') {
|
||||||
out.strategy = { type: form.strategy };
|
out.strategy = { type: form.strategy };
|
||||||
|
|
@ -218,11 +220,11 @@ const showObsEditor = computed(() => hasObservatory.value || hasBurstObservatory
|
||||||
|
|
||||||
const obsView = ref('observatory');
|
const obsView = ref('observatory');
|
||||||
|
|
||||||
// Keep the radio selection valid as observatories appear/disappear —
|
// Watch each flag individually — watching showObsEditor (OR of the two)
|
||||||
// e.g. deleting the last leastPing balancer should flip the editor to
|
// misses the case where one observatory swaps for the other in the same
|
||||||
// the burstObservatory pane instead of leaving it pointing at the
|
// tick, leaving obsView pointing at a now-deleted key and JsonEditor
|
||||||
// (now-removed) observatory key.
|
// trying to parse an empty string.
|
||||||
watch(showObsEditor, () => {
|
watch([hasObservatory, hasBurstObservatory], () => {
|
||||||
if (obsView.value === 'observatory' && !hasObservatory.value && hasBurstObservatory.value) {
|
if (obsView.value === 'observatory' && !hasObservatory.value && hasBurstObservatory.value) {
|
||||||
obsView.value = 'burstObservatory';
|
obsView.value = 'burstObservatory';
|
||||||
} else if (obsView.value === 'burstObservatory' && !hasBurstObservatory.value && hasObservatory.value) {
|
} else if (obsView.value === 'burstObservatory' && !hasBurstObservatory.value && hasObservatory.value) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue