mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-09-03 17:07:15 +00:00
7100fbcd08
* feat(model): add MemberWeights to SubBalancer Per-inbound leastLoad weights, stored with the same gorm json serializer as InboundIds so AutoMigrate adds the text column on every dialect (postgresModelSettled sees the missing column and re-runs). Absent entries mean weight 1.0; only meaningful for strategy leastLoad. * feat(sub): accept memberWeights on the sub-balancer API Parsed as one JSON form field (gin cannot bind bracket-keyed maps from urlencoded bodies). validate() rejects weights under any strategy but leastLoad — xray would silently ignore costs there, so storing them would pretend a knob exists. Non-positive weights error instead of defaulting: a zero usually means a typo'd "never pick this node". Entries for inbounds no longer selected are dropped on save. * feat(sub): emit leastLoad strategy costs from member weights costs[] is built after the tagging loop reuses the exact retagged tags (bal-N-protocol[-k]) and each member's owning inbound id. Members without a configured weight default to 1.0, but costs are omitted entirely unless at least one explicit weight survives — an all-1.0 array would bloat every subscription response for no effect. * feat(sub-balancers): leastLoad member weight inputs Weight fields render only under leastLoad and hide on strategy change without dropping their values, so an accidental toggle away and back loses nothing until save; non-leastLoad submits strip them entirely because xray would ignore costs. Weights travel as one JSON form field (gin cannot bind bracket-keyed maps) and every locale gets the three new keys in the same commit per the dead-keys rule. * docs(api): document memberWeights on sub-balancers leastLoad-only JSON form field; update notes that omitting it clears stored weights. Regenerated openapi artifacts via make gen + the docs copy/gen:api step nothing checks automatically. * fix(api-docs): use the allowed object ParamType for memberWeights * fix(sub-balancers): cap the member-weight list height Many selected inbounds pushed the modal body past the viewport. The weight rows now scroll inside a 220px viewport, mirroring the inbound picker's listHeight so both lists read the same. * fix(sub): anchor leastLoad cost matches to exact member tags Verified against xray-core: without regexp, WeightManager matches costs by substring (strings.Index), so the bare tag "bal-1-vless" also hits the deduplicated "bal-1-vless-2" and both members get the first entry's weight. Anchored ^tag$ regexps make every cost entry match only its own member. Also confirmed value<=0 makes xray derive a weight from the first digit of the matched tag — validating weights > 0 server-side was the right call. * fix(sub-balancers): keep member weights across the enabled toggle The table's toggleEnabled re-posted a full-row payload without memberWeights, and the update path treats an absent key as "erase" — flipping the switch silently dropped every configured weight. Round-trip the stored weights through the toggle payload, and prove persistence with a re-Get in the weight-validation test (the returned struct alone would stay green even if Save skipped the column). * fix(sub-balancers): address review on member weights - omitempty on MemberWeights: the panel sends null for every pre-existing and non-leastLoad balancer, which failed the hand-written zod response schema on every fetch (zod .optional() accepts undefined only; switched to .nullish() per repo convention) and drifted the generated contract. Regenerated openapi artifacts + docs copy + MDX. - Bound weights to the positive float32 range: xray decodes costs as float32, so an over-range value makes clients reject the whole subscription document and an underflow decays to the tag-digit fallback weight. Tests for both directions. - Trim six comment blocks to the 2-line cap from CLAUDE.md. --------- Co-authored-by: DIMFLIX <dimflix@users.noreply.github.com>
62 lines
3.4 KiB
Plaintext
62 lines
3.4 KiB
Plaintext
---
|
|
title: Subscription Balancers
|
|
description: 'Client-side balancers for the JSON subscription: each enabled
|
|
balancer is emitted as one extra config document whose members are the proxy
|
|
outbounds of the selected inbounds (routing.balancers + burstObservatory).
|
|
Managed in Settings → Sub Balancers.'
|
|
full: true
|
|
_openapi:
|
|
preload:
|
|
- ./public/openapi.json
|
|
toc:
|
|
- depth: 2
|
|
title: List all subscription balancers in sort order (sort_order asc, id asc).
|
|
url: '#list-all-subscription-balancers-in-sort-order-sort_order-asc-id-asc'
|
|
- depth: 2
|
|
title: Create a subscription balancer. It appears in the JSON subscription of
|
|
every client that sits on at least one selected inbound.
|
|
url: '#create-a-subscription-balancer-it-appears-in-the-json-subscription-of-every-client-that-sits-on-at-least-one-selected-inbound'
|
|
- depth: 2
|
|
title: Update a balancer by id. Accepts the same form fields as create (full-row
|
|
update, including the enabled toggle); omitting memberWeights clears
|
|
stored weights.
|
|
url: '#update-a-balancer-by-id-accepts-the-same-form-fields-as-create-full-row-update-including-the-enabled-toggle-omitting-memberweights-clears-stored-weights'
|
|
- depth: 2
|
|
title: Delete a balancer by id.
|
|
url: '#delete-a-balancer-by-id'
|
|
- depth: 2
|
|
title: Delete a balancer by id (POST alias of DELETE for clients that cannot
|
|
send DELETE).
|
|
url: '#delete-a-balancer-by-id-post-alias-of-delete-for-clients-that-cannot-send-delete'
|
|
structuredData:
|
|
headings:
|
|
- content: List all subscription balancers in sort order (sort_order asc, id asc).
|
|
id: list-all-subscription-balancers-in-sort-order-sort_order-asc-id-asc
|
|
- content: Create a subscription balancer. It appears in the JSON subscription of
|
|
every client that sits on at least one selected inbound.
|
|
id: create-a-subscription-balancer-it-appears-in-the-json-subscription-of-every-client-that-sits-on-at-least-one-selected-inbound
|
|
- content: Update a balancer by id. Accepts the same form fields as create
|
|
(full-row update, including the enabled toggle); omitting
|
|
memberWeights clears stored weights.
|
|
id: update-a-balancer-by-id-accepts-the-same-form-fields-as-create-full-row-update-including-the-enabled-toggle-omitting-memberweights-clears-stored-weights
|
|
- content: Delete a balancer by id.
|
|
id: delete-a-balancer-by-id
|
|
- content: Delete a balancer by id (POST alias of DELETE for clients that cannot
|
|
send DELETE).
|
|
id: delete-a-balancer-by-id-post-alias-of-delete-for-clients-that-cannot-send-delete
|
|
contents: []
|
|
---
|
|
|
|
{/* This file was generated by Fumadocs. Do not edit this file directly. Any changes should be made by running the generation command again. */}
|
|
|
|
export default function Layout(props) {
|
|
const { APIPage, OpenAPIPage } = props.components ?? {};
|
|
// "APIPage" is the old name from v10, this allows both for backward compatibility
|
|
const Comp = OpenAPIPage ?? APIPage;
|
|
return (
|
|
<>
|
|
{props.children}
|
|
<Comp document="./public/openapi.json" webhooks={[]} operations={[{"path":"/panel/api/sub-balancers","method":"get"},{"path":"/panel/api/sub-balancers","method":"post"},{"path":"/panel/api/sub-balancers/{id}","method":"post"},{"path":"/panel/api/sub-balancers/{id}","method":"delete"},{"path":"/panel/api/sub-balancers/{id}/del","method":"post"}]} showTitle />
|
|
</>
|
|
);
|
|
} |