mirror of
https://github.com/MHSanaei/3x-ui.git
synced 2026-01-13 01:02:46 +00:00
feat: add geo files to nodes and fix func inbounds
This commit is contained in:
parent
b6f336a15c
commit
66662afa4d
4 changed files with 254 additions and 75 deletions
|
|
@ -65,6 +65,10 @@ RUN mkdir -p bin && \
|
||||||
echo "Downloading geo files..." && \
|
echo "Downloading geo files..." && \
|
||||||
curl -sfLRO https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat && \
|
curl -sfLRO https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat && \
|
||||||
curl -sfLRO https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
|
curl -sfLRO https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat && \
|
||||||
|
curl -sfLRo geoip_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat && \
|
||||||
|
curl -sfLRo geosite_IR.dat https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat && \
|
||||||
|
curl -sfLRo geoip_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat && \
|
||||||
|
curl -sfLRo geosite_RU.dat https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat && \
|
||||||
echo "Final files in bin:" && \
|
echo "Final files in bin:" && \
|
||||||
ls -lah && \
|
ls -lah && \
|
||||||
echo "File sizes:" && \
|
echo "File sizes:" && \
|
||||||
|
|
|
||||||
|
|
@ -18,45 +18,7 @@ services:
|
||||||
# If the file doesn't exist, it will be created when XRAY config is first applied
|
# If the file doesn't exist, it will be created when XRAY config is first applied
|
||||||
networks:
|
networks:
|
||||||
- xray-network
|
- xray-network
|
||||||
node2:
|
|
||||||
build:
|
|
||||||
context: ..
|
|
||||||
dockerfile: node/Dockerfile
|
|
||||||
container_name: 3x-ui-node2
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# - NODE_API_KEY=${NODE_API_KEY:-change-me-to-secure-key}
|
|
||||||
- NODE_API_KEY=test-key
|
|
||||||
ports:
|
|
||||||
- "8081:8080"
|
|
||||||
- "44001:44000"
|
|
||||||
volumes:
|
|
||||||
- ./bin/config.json:/app/bin/config.json
|
|
||||||
- ./logs:/app/logs
|
|
||||||
# Note: config.json is mounted directly for persistence
|
|
||||||
# If the file doesn't exist, it will be created when XRAY config is first applied
|
|
||||||
networks:
|
|
||||||
- xray-network
|
|
||||||
|
|
||||||
node3:
|
|
||||||
build:
|
|
||||||
context: ..
|
|
||||||
dockerfile: node/Dockerfile
|
|
||||||
container_name: 3x-ui-node3
|
|
||||||
restart: unless-stopped
|
|
||||||
environment:
|
|
||||||
# - NODE_API_KEY=${NODE_API_KEY:-change-me-to-secure-key}
|
|
||||||
- NODE_API_KEY=test-key
|
|
||||||
ports:
|
|
||||||
- "8082:8080"
|
|
||||||
- "44002:44000"
|
|
||||||
volumes:
|
|
||||||
- ./bin/config.json:/app/bin/config.json
|
|
||||||
- ./logs:/app/logs
|
|
||||||
# Note: config.json is mounted directly for persistence
|
|
||||||
# If the file doesn't exist, it will be created when XRAY config is first applied
|
|
||||||
networks:
|
|
||||||
- xray-network
|
|
||||||
networks:
|
networks:
|
||||||
xray-network:
|
xray-network:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
|
@ -31,11 +34,92 @@ type Manager struct {
|
||||||
// NewManager creates a new XRAY manager instance.
|
// NewManager creates a new XRAY manager instance.
|
||||||
func NewManager() *Manager {
|
func NewManager() *Manager {
|
||||||
m := &Manager{}
|
m := &Manager{}
|
||||||
|
// Download geo files if missing
|
||||||
|
m.downloadGeoFiles()
|
||||||
// Try to load config from file on startup
|
// Try to load config from file on startup
|
||||||
m.LoadConfigFromFile()
|
m.LoadConfigFromFile()
|
||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// downloadGeoFiles downloads geo data files if they are missing.
|
||||||
|
// These files are required for routing rules that use geoip/geosite matching.
|
||||||
|
func (m *Manager) downloadGeoFiles() {
|
||||||
|
// Possible bin folder paths (in order of priority)
|
||||||
|
binPaths := []string{
|
||||||
|
"bin",
|
||||||
|
"/app/bin",
|
||||||
|
"./bin",
|
||||||
|
}
|
||||||
|
|
||||||
|
var binPath string
|
||||||
|
for _, path := range binPaths {
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
binPath = path
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if binPath == "" {
|
||||||
|
logger.Debug("No bin folder found, skipping geo files download")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// List of geo files to download
|
||||||
|
geoFiles := []struct {
|
||||||
|
URL string
|
||||||
|
FileName string
|
||||||
|
}{
|
||||||
|
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip.dat"},
|
||||||
|
{"https://github.com/Loyalsoldier/v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite.dat"},
|
||||||
|
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geoip.dat", "geoip_IR.dat"},
|
||||||
|
{"https://github.com/chocolate4u/Iran-v2ray-rules/releases/latest/download/geosite.dat", "geosite_IR.dat"},
|
||||||
|
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geoip.dat", "geoip_RU.dat"},
|
||||||
|
{"https://github.com/runetfreedom/russia-v2ray-rules-dat/releases/latest/download/geosite.dat", "geosite_RU.dat"},
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadFile := func(url, destPath string) error {
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to download: %w", err)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("bad status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := os.Create(destPath)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to create file: %w", err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(file, resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to write file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, file := range geoFiles {
|
||||||
|
destPath := filepath.Join(binPath, file.FileName)
|
||||||
|
|
||||||
|
// Check if file already exists
|
||||||
|
if _, err := os.Stat(destPath); err == nil {
|
||||||
|
logger.Debugf("Geo file %s already exists, skipping download", file.FileName)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Infof("Downloading geo file: %s", file.FileName)
|
||||||
|
if err := downloadFile(file.URL, destPath); err != nil {
|
||||||
|
logger.Warningf("Failed to download %s: %v", file.FileName, err)
|
||||||
|
} else {
|
||||||
|
logger.Infof("Successfully downloaded %s", file.FileName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// LoadConfigFromFile attempts to load XRAY configuration from config.json file.
|
// LoadConfigFromFile attempts to load XRAY configuration from config.json file.
|
||||||
// It checks multiple possible locations: bin/config.json, config/config.json, and ./config.json
|
// It checks multiple possible locations: bin/config.json, config/config.json, and ./config.json
|
||||||
func (m *Manager) LoadConfigFromFile() error {
|
func (m *Manager) LoadConfigFromFile() error {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
package controller
|
package controller
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/mhsanaei/3x-ui/v2/database/model"
|
"github.com/mhsanaei/3x-ui/v2/database/model"
|
||||||
|
|
@ -104,6 +106,53 @@ func (a *InboundController) getClientTrafficsById(c *gin.Context) {
|
||||||
|
|
||||||
// addInbound creates a new inbound configuration.
|
// addInbound creates a new inbound configuration.
|
||||||
func (a *InboundController) addInbound(c *gin.Context) {
|
func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
|
// Try to get nodeIds from JSON body first (if Content-Type is application/json)
|
||||||
|
// This must be done BEFORE ShouldBind, which reads the body
|
||||||
|
var nodeIdsFromJSON []int
|
||||||
|
var nodeIdFromJSON *int
|
||||||
|
var hasNodeIdsInJSON, hasNodeIdInJSON bool
|
||||||
|
|
||||||
|
if c.ContentType() == "application/json" {
|
||||||
|
// Read raw body to extract nodeIds
|
||||||
|
bodyBytes, err := c.GetRawData()
|
||||||
|
if err == nil && len(bodyBytes) > 0 {
|
||||||
|
// Parse JSON to extract nodeIds
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(bodyBytes, &jsonData); err == nil {
|
||||||
|
// Check for nodeIds array
|
||||||
|
if nodeIdsVal, ok := jsonData["nodeIds"]; ok {
|
||||||
|
hasNodeIdsInJSON = true
|
||||||
|
if nodeIdsArray, ok := nodeIdsVal.([]interface{}); ok {
|
||||||
|
for _, val := range nodeIdsArray {
|
||||||
|
if num, ok := val.(float64); ok {
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, int(num))
|
||||||
|
} else if num, ok := val.(int); ok {
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if num, ok := nodeIdsVal.(float64); ok {
|
||||||
|
// Single number instead of array
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, int(num))
|
||||||
|
} else if num, ok := nodeIdsVal.(int); ok {
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for nodeId (backward compatibility)
|
||||||
|
if nodeIdVal, ok := jsonData["nodeId"]; ok {
|
||||||
|
hasNodeIdInJSON = true
|
||||||
|
if num, ok := nodeIdVal.(float64); ok {
|
||||||
|
nodeId := int(num)
|
||||||
|
nodeIdFromJSON = &nodeId
|
||||||
|
} else if num, ok := nodeIdVal.(int); ok {
|
||||||
|
nodeIdFromJSON = &num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Restore body for ShouldBind
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
inbound := &model.Inbound{}
|
inbound := &model.Inbound{}
|
||||||
err := c.ShouldBind(inbound)
|
err := c.ShouldBind(inbound)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -130,19 +179,38 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
// Handle node assignment in multi-node mode
|
// Handle node assignment in multi-node mode
|
||||||
nodeService := service.NodeService{}
|
nodeService := service.NodeService{}
|
||||||
|
|
||||||
// Get nodeIds from form (array format: nodeIds=1&nodeIds=2)
|
// Get nodeIds from form (for form-encoded requests)
|
||||||
nodeIdsStr := c.PostFormArray("nodeIds")
|
nodeIdsStr := c.PostFormArray("nodeIds")
|
||||||
logger.Debugf("Received nodeIds from form: %v", nodeIdsStr)
|
logger.Debugf("Received nodeIds from form: %v", nodeIdsStr)
|
||||||
|
|
||||||
// Check if nodeIds array was provided (even if empty)
|
// Check if nodeIds array was provided (even if empty)
|
||||||
nodeIdStr := c.PostForm("nodeId")
|
nodeIdStr := c.PostForm("nodeId")
|
||||||
if len(nodeIdsStr) > 0 || nodeIdStr != "" {
|
|
||||||
// Multi-node mode: parse nodeIds array
|
// Determine which source to use: JSON takes precedence over form data
|
||||||
nodeIds := make([]int, 0)
|
useJSON := hasNodeIdsInJSON || hasNodeIdInJSON
|
||||||
for _, idStr := range nodeIdsStr {
|
useForm := (len(nodeIdsStr) > 0 || nodeIdStr != "") && !useJSON
|
||||||
if idStr != "" {
|
|
||||||
if id, err := strconv.Atoi(idStr); err == nil && id > 0 {
|
if useJSON || useForm {
|
||||||
nodeIds = append(nodeIds, id)
|
var nodeIds []int
|
||||||
|
var nodeId *int
|
||||||
|
|
||||||
|
if useJSON {
|
||||||
|
// Use data from JSON
|
||||||
|
nodeIds = nodeIdsFromJSON
|
||||||
|
nodeId = nodeIdFromJSON
|
||||||
|
} else {
|
||||||
|
// Parse nodeIds array from form
|
||||||
|
for _, idStr := range nodeIdsStr {
|
||||||
|
if idStr != "" {
|
||||||
|
if id, err := strconv.Atoi(idStr); err == nil && id > 0 {
|
||||||
|
nodeIds = append(nodeIds, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parse single nodeId from form
|
||||||
|
if nodeIdStr != "" && nodeIdStr != "null" {
|
||||||
|
if parsedId, err := strconv.Atoi(nodeIdStr); err == nil && parsedId > 0 {
|
||||||
|
nodeId = &parsedId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -154,13 +222,10 @@ func (a *InboundController) addInbound(c *gin.Context) {
|
||||||
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
} else if nodeIdStr != "" && nodeIdStr != "null" {
|
} else if nodeId != nil && *nodeId > 0 {
|
||||||
// Backward compatibility: single nodeId
|
// Backward compatibility: single nodeId
|
||||||
nodeId, err := strconv.Atoi(nodeIdStr)
|
if err := nodeService.AssignInboundToNode(inbound.Id, *nodeId); err != nil {
|
||||||
if err == nil && nodeId > 0 {
|
logger.Warningf("Failed to assign inbound %d to node %d: %v", inbound.Id, *nodeId, err)
|
||||||
if err := nodeService.AssignInboundToNode(inbound.Id, nodeId); err != nil {
|
|
||||||
logger.Warningf("Failed to assign inbound %d to node %d: %v", inbound.Id, nodeId, err)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -204,8 +269,53 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get nodeIds from form BEFORE binding to avoid conflict with ShouldBind
|
// Try to get nodeIds from JSON body first (if Content-Type is application/json)
|
||||||
// Get nodeIds from form (array format: nodeIds=1&nodeIds=2)
|
var nodeIdsFromJSON []int
|
||||||
|
var nodeIdFromJSON *int
|
||||||
|
var hasNodeIdsInJSON, hasNodeIdInJSON bool
|
||||||
|
|
||||||
|
if c.ContentType() == "application/json" {
|
||||||
|
// Read raw body to extract nodeIds
|
||||||
|
bodyBytes, err := c.GetRawData()
|
||||||
|
if err == nil && len(bodyBytes) > 0 {
|
||||||
|
// Parse JSON to extract nodeIds
|
||||||
|
var jsonData map[string]interface{}
|
||||||
|
if err := json.Unmarshal(bodyBytes, &jsonData); err == nil {
|
||||||
|
// Check for nodeIds array
|
||||||
|
if nodeIdsVal, ok := jsonData["nodeIds"]; ok {
|
||||||
|
hasNodeIdsInJSON = true
|
||||||
|
if nodeIdsArray, ok := nodeIdsVal.([]interface{}); ok {
|
||||||
|
for _, val := range nodeIdsArray {
|
||||||
|
if num, ok := val.(float64); ok {
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, int(num))
|
||||||
|
} else if num, ok := val.(int); ok {
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if num, ok := nodeIdsVal.(float64); ok {
|
||||||
|
// Single number instead of array
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, int(num))
|
||||||
|
} else if num, ok := nodeIdsVal.(int); ok {
|
||||||
|
nodeIdsFromJSON = append(nodeIdsFromJSON, num)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Check for nodeId (backward compatibility)
|
||||||
|
if nodeIdVal, ok := jsonData["nodeId"]; ok {
|
||||||
|
hasNodeIdInJSON = true
|
||||||
|
if num, ok := nodeIdVal.(float64); ok {
|
||||||
|
nodeId := int(num)
|
||||||
|
nodeIdFromJSON = &nodeId
|
||||||
|
} else if num, ok := nodeIdVal.(int); ok {
|
||||||
|
nodeIdFromJSON = &num
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Restore body for ShouldBind
|
||||||
|
c.Request.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get nodeIds from form (for form-encoded requests)
|
||||||
nodeIdsStr := c.PostFormArray("nodeIds")
|
nodeIdsStr := c.PostFormArray("nodeIds")
|
||||||
logger.Debugf("Received nodeIds from form: %v (count: %d)", nodeIdsStr, len(nodeIdsStr))
|
logger.Debugf("Received nodeIds from form: %v (count: %d)", nodeIdsStr, len(nodeIdsStr))
|
||||||
|
|
||||||
|
|
@ -217,6 +327,7 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
_, hasNodeIds := c.GetPostForm("nodeIds")
|
_, hasNodeIds := c.GetPostForm("nodeIds")
|
||||||
_, hasNodeId := c.GetPostForm("nodeId")
|
_, hasNodeId := c.GetPostForm("nodeId")
|
||||||
logger.Debugf("Form has nodeIds: %v, has nodeId: %v", hasNodeIds, hasNodeId)
|
logger.Debugf("Form has nodeIds: %v, has nodeId: %v", hasNodeIds, hasNodeId)
|
||||||
|
logger.Debugf("JSON has nodeIds: %v (values: %v), has nodeId: %v (value: %v)", hasNodeIdsInJSON, nodeIdsFromJSON, hasNodeIdInJSON, nodeIdFromJSON)
|
||||||
|
|
||||||
inbound := &model.Inbound{
|
inbound := &model.Inbound{
|
||||||
Id: id,
|
Id: id,
|
||||||
|
|
@ -238,20 +349,42 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
// Handle node assignment in multi-node mode
|
// Handle node assignment in multi-node mode
|
||||||
nodeService := service.NodeService{}
|
nodeService := service.NodeService{}
|
||||||
|
|
||||||
if hasNodeIds || hasNodeId {
|
// Determine which source to use: JSON takes precedence over form data
|
||||||
// Multi-node mode: parse nodeIds array
|
useJSON := hasNodeIdsInJSON || hasNodeIdInJSON
|
||||||
nodeIds := make([]int, 0)
|
useForm := (hasNodeIds || hasNodeId) && !useJSON
|
||||||
for _, idStr := range nodeIdsStr {
|
|
||||||
if idStr != "" {
|
if useJSON || useForm {
|
||||||
if id, err := strconv.Atoi(idStr); err == nil && id > 0 {
|
var nodeIds []int
|
||||||
nodeIds = append(nodeIds, id)
|
var nodeId *int
|
||||||
} else {
|
var hasNodeIdsFlag bool
|
||||||
logger.Warningf("Invalid nodeId in array: %s (error: %v)", idStr, err)
|
|
||||||
|
if useJSON {
|
||||||
|
// Use data from JSON
|
||||||
|
nodeIds = nodeIdsFromJSON
|
||||||
|
nodeId = nodeIdFromJSON
|
||||||
|
hasNodeIdsFlag = hasNodeIdsInJSON
|
||||||
|
} else {
|
||||||
|
// Use data from form
|
||||||
|
hasNodeIdsFlag = hasNodeIds
|
||||||
|
// Parse nodeIds array from form
|
||||||
|
for _, idStr := range nodeIdsStr {
|
||||||
|
if idStr != "" {
|
||||||
|
if id, err := strconv.Atoi(idStr); err == nil && id > 0 {
|
||||||
|
nodeIds = append(nodeIds, id)
|
||||||
|
} else {
|
||||||
|
logger.Warningf("Invalid nodeId in array: %s (error: %v)", idStr, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Parse single nodeId from form
|
||||||
|
if nodeIdStr != "" && nodeIdStr != "null" {
|
||||||
|
if parsedId, err := strconv.Atoi(nodeIdStr); err == nil && parsedId > 0 {
|
||||||
|
nodeId = &parsedId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Debugf("Parsed nodeIds: %v", nodeIds)
|
logger.Debugf("Parsed nodeIds: %v, nodeId: %v", nodeIds, nodeId)
|
||||||
|
|
||||||
if len(nodeIds) > 0 {
|
if len(nodeIds) > 0 {
|
||||||
// Assign to multiple nodes
|
// Assign to multiple nodes
|
||||||
|
|
@ -261,19 +394,15 @@ func (a *InboundController) updateInbound(c *gin.Context) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Debugf("Successfully assigned inbound %d to nodes %v", inbound.Id, nodeIds)
|
logger.Debugf("Successfully assigned inbound %d to nodes %v", inbound.Id, nodeIds)
|
||||||
} else if nodeIdStr != "" && nodeIdStr != "null" {
|
} else if nodeId != nil && *nodeId > 0 {
|
||||||
// Backward compatibility: single nodeId
|
// Backward compatibility: single nodeId
|
||||||
nodeId, err := strconv.Atoi(nodeIdStr)
|
if err := nodeService.AssignInboundToNode(inbound.Id, *nodeId); err != nil {
|
||||||
if err == nil && nodeId > 0 {
|
logger.Errorf("Failed to assign inbound %d to node %d: %v", inbound.Id, *nodeId, err)
|
||||||
if err := nodeService.AssignInboundToNode(inbound.Id, nodeId); err != nil {
|
jsonMsg(c, I18nWeb(c, "somethingWentWrong"), err)
|
||||||
logger.Warningf("Failed to assign inbound %d to node %d: %v", inbound.Id, nodeId, err)
|
return
|
||||||
} else {
|
|
||||||
logger.Debugf("Successfully assigned inbound %d to node %d", inbound.Id, nodeId)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
logger.Warningf("Invalid nodeId: %s (error: %v)", nodeIdStr, err)
|
|
||||||
}
|
}
|
||||||
} else if hasNodeIds {
|
logger.Debugf("Successfully assigned inbound %d to node %d", inbound.Id, *nodeId)
|
||||||
|
} else if hasNodeIdsFlag {
|
||||||
// nodeIds was explicitly provided but is empty - unassign all
|
// nodeIds was explicitly provided but is empty - unassign all
|
||||||
if err := nodeService.UnassignInboundFromNode(inbound.Id); err != nil {
|
if err := nodeService.UnassignInboundFromNode(inbound.Id); err != nil {
|
||||||
logger.Warningf("Failed to unassign inbound %d from nodes: %v", inbound.Id, err)
|
logger.Warningf("Failed to unassign inbound %d from nodes: %v", inbound.Id, err)
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue