mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-08-16 16:20:59 +00:00
feat(inbounds): add a narrow endpoint for subscription sort order (#6179)
* feat(inbounds): add a narrow endpoint for subscription sort order Changing an inbound's position in subscription output currently goes through /update/:id, which takes a whole inbound: the caller has to send settings and the entire client list back, and whatever it read before the edit is what gets written. Two people reordering and editing clients in the same inbound race on one blob, and the reorder wins by overwriting. Mirror the existing /setEnable/:id shape. The handler takes only the index and the service reads the stored inbound, so nothing in the request can reach the settings JSON. Node-owned inbounds are marked dirty in the same transaction and pushed through the existing runtime update. * fix(nodes): scope sub sort index updates --------- Co-authored-by: n0ctal <293235942+n0ctal@users.noreply.github.com>
This commit is contained in:
@@ -458,6 +458,16 @@ func (r *Remote) UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Remote) SetInboundSubSortIndex(ctx context.Context, ib *model.Inbound, index int) error {
|
||||
id, err := r.resolveRemoteID(ctx, ib.Tag)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
payload := url.Values{"subSortIndex": []string{strconv.Itoa(index)}}
|
||||
_, err = r.do(ctx, http.MethodPost, "panel/api/inbounds/"+strconv.Itoa(id)+"/subSortIndex", payload)
|
||||
return err
|
||||
}
|
||||
|
||||
// ReconcileInbound pushes ib only when its wire payload differs from the last
|
||||
// successful push, or when the node no longer reports the tag (existsOnNode
|
||||
// false) — a node that dropped/restarted must still be re-seeded. Returns
|
||||
|
||||
@@ -54,6 +54,37 @@ func TestRemoteDo_AcceptsNormalResponse(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoteSetInboundSubSortIndexSendsOnlyNarrowField(t *testing.T) {
|
||||
var posted url.Values
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
switch req.URL.Path {
|
||||
case "/panel/api/inbounds/list":
|
||||
_, _ = w.Write([]byte(`{"success":true,"obj":[{"id":42,"tag":"remote-tag"}]}`))
|
||||
case "/panel/api/inbounds/42/subSortIndex":
|
||||
if err := req.ParseForm(); err != nil {
|
||||
t.Fatalf("ParseForm: %v", err)
|
||||
}
|
||||
posted = req.PostForm
|
||||
_, _ = w.Write([]byte(`{"success":true}`))
|
||||
default:
|
||||
http.NotFound(w, req)
|
||||
}
|
||||
}))
|
||||
defer srv.Close()
|
||||
r := NewRemote(nodeForPlainServer(t, srv, "verify", "tok"), nil)
|
||||
ib := &model.Inbound{Tag: "remote-tag", Settings: `{"clients":[{"email":"newer"}]}`}
|
||||
if err := r.SetInboundSubSortIndex(context.Background(), ib, 7); err != nil {
|
||||
t.Fatalf("SetInboundSubSortIndex: %v", err)
|
||||
}
|
||||
if got := posted.Get("subSortIndex"); got != "7" {
|
||||
t.Fatalf("subSortIndex = %q, want 7", got)
|
||||
}
|
||||
if len(posted) != 1 {
|
||||
t.Fatalf("posted fields = %v, want only subSortIndex", posted)
|
||||
}
|
||||
}
|
||||
|
||||
// TestReadCappedBody_Boundary pins the cap+1 contract cheaply (no large allocs):
|
||||
// a body of exactly limit is accepted; limit+1 and beyond are rejected.
|
||||
func TestReadCappedBody_Boundary(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user