mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-06-06 05:04:22 +00:00
Adds Runtime methods AddClient, UpdateUser, and DeleteUser so master
mutates clients on a node via /panel/api/clients/{add,update,del} rather
than pushing the whole inbound. The previous rt.UpdateInbound path made
the node DelInbound+AddInbound on every single-client change, briefly
cycling every other user on the same inbound.
DelInbound no longer filters by enable=true, so a disabled node inbound
actually gets removed from the node instead of being resurrected by the
next snap.
setRemoteTrafficLocked now sweeps any ClientRecord with zero
ClientInbound rows after SyncInbound rebuilds the attachments, which is
how a node-side delete propagates back to master instead of leaving a
detached ghost. ClientService.Delete tombstones the email first so a
snap arriving mid-delete can't re-create the record.
WebSocket broadcasts an "invalidate(clients)" message on every client
mutation so the Clients page refreshes without manual reload.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
143 lines
3.2 KiB
Go
143 lines
3.2 KiB
Go
package runtime
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/mhsanaei/3x-ui/v3/database/model"
|
|
"github.com/mhsanaei/3x-ui/v3/xray"
|
|
)
|
|
|
|
type LocalDeps struct {
|
|
APIPort func() int
|
|
SetNeedRestart func()
|
|
}
|
|
|
|
type Local struct {
|
|
deps LocalDeps
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewLocal(deps LocalDeps) *Local {
|
|
return &Local{deps: deps}
|
|
}
|
|
|
|
func (l *Local) Name() string { return "local" }
|
|
|
|
func (l *Local) withAPI(fn func(api *xray.XrayAPI) error) error {
|
|
l.mu.Lock()
|
|
defer l.mu.Unlock()
|
|
|
|
port := l.deps.APIPort()
|
|
if port <= 0 {
|
|
return errors.New("local xray is not running")
|
|
}
|
|
var api xray.XrayAPI
|
|
if err := api.Init(port); err != nil {
|
|
return err
|
|
}
|
|
defer api.Close()
|
|
return fn(&api)
|
|
}
|
|
|
|
func (l *Local) AddInbound(_ context.Context, ib *model.Inbound) error {
|
|
body, err := json.MarshalIndent(ib.GenXrayInboundConfig(), "", " ")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.AddInbound(body)
|
|
})
|
|
}
|
|
|
|
func (l *Local) DelInbound(_ context.Context, ib *model.Inbound) error {
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.DelInbound(ib.Tag)
|
|
})
|
|
}
|
|
|
|
func (l *Local) UpdateInbound(ctx context.Context, oldIb, newIb *model.Inbound) error {
|
|
_ = l.DelInbound(ctx, oldIb)
|
|
if !newIb.Enable {
|
|
return nil
|
|
}
|
|
return l.AddInbound(ctx, newIb)
|
|
}
|
|
|
|
func (l *Local) AddUser(_ context.Context, ib *model.Inbound, userMap map[string]any) error {
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.AddUser(string(ib.Protocol), ib.Tag, userMap)
|
|
})
|
|
}
|
|
|
|
func (l *Local) RemoveUser(_ context.Context, ib *model.Inbound, email string) error {
|
|
return l.withAPI(func(api *xray.XrayAPI) error {
|
|
return api.RemoveUser(ib.Tag, email)
|
|
})
|
|
}
|
|
|
|
func (l *Local) AddClient(ctx context.Context, ib *model.Inbound, client model.Client) error {
|
|
if !client.Enable {
|
|
return nil
|
|
}
|
|
user := map[string]any{
|
|
"email": client.Email,
|
|
"id": client.ID,
|
|
"security": client.Security,
|
|
"flow": client.Flow,
|
|
"auth": client.Auth,
|
|
"password": client.Password,
|
|
}
|
|
return l.AddUser(ctx, ib, user)
|
|
}
|
|
|
|
func (l *Local) DeleteUser(ctx context.Context, ib *model.Inbound, email string) error {
|
|
if email == "" {
|
|
return nil
|
|
}
|
|
if err := l.RemoveUser(ctx, ib, email); err != nil {
|
|
if strings.Contains(err.Error(), "not found") {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Local) UpdateUser(ctx context.Context, ib *model.Inbound, oldEmail string, payload model.Client) error {
|
|
if oldEmail != "" {
|
|
if err := l.RemoveUser(ctx, ib, oldEmail); err != nil && !strings.Contains(err.Error(), "not found") {
|
|
return err
|
|
}
|
|
}
|
|
if !payload.Enable {
|
|
return nil
|
|
}
|
|
user := map[string]any{
|
|
"email": payload.Email,
|
|
"id": payload.ID,
|
|
"security": payload.Security,
|
|
"flow": payload.Flow,
|
|
"auth": payload.Auth,
|
|
"password": payload.Password,
|
|
}
|
|
return l.AddUser(ctx, ib, user)
|
|
}
|
|
|
|
func (l *Local) RestartXray(_ context.Context) error {
|
|
if l.deps.SetNeedRestart != nil {
|
|
l.deps.SetNeedRestart()
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (l *Local) ResetClientTraffic(_ context.Context, _ *model.Inbound, _ string) error {
|
|
return nil
|
|
}
|
|
|
|
func (l *Local) ResetAllTraffics(_ context.Context) error {
|
|
return nil
|
|
}
|