Add config profiles with inbound assignment for nodes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -95,12 +95,47 @@ CREATE TABLE IF NOT EXISTS node_protocols (
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_node_protocols_protocol ON node_protocols(protocol_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS config_profiles (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS inbounds (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
profile_id UUID NOT NULL REFERENCES config_profiles(id) ON DELETE CASCADE,
|
||||
tag TEXT NOT NULL,
|
||||
protocol_id UUID NOT NULL REFERENCES protocols(id) ON DELETE RESTRICT,
|
||||
port INT NOT NULL DEFAULT 443,
|
||||
network TEXT NOT NULL DEFAULT 'tcp',
|
||||
security TEXT NOT NULL DEFAULT 'none',
|
||||
listen TEXT NOT NULL DEFAULT '0.0.0.0',
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
sort_order INT NOT NULL DEFAULT 0,
|
||||
remark TEXT NOT NULL DEFAULT '',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
UNIQUE (profile_id, tag)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_inbounds_profile ON inbounds(profile_id);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS node_inbounds (
|
||||
node_id UUID NOT NULL REFERENCES nodes(id) ON DELETE CASCADE,
|
||||
inbound_id UUID NOT NULL REFERENCES inbounds(id) ON DELETE CASCADE,
|
||||
enabled BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
PRIMARY KEY (node_id, inbound_id)
|
||||
);
|
||||
`
|
||||
if _, err := db.Exec(schema); err != nil {
|
||||
return err
|
||||
}
|
||||
// Additive migrations for existing deployments.
|
||||
_, _ = db.Exec(`ALTER TABLE nodes ADD COLUMN IF NOT EXISTS runtime_log TEXT NOT NULL DEFAULT ''`)
|
||||
_, _ = db.Exec(`ALTER TABLE nodes ADD COLUMN IF NOT EXISTS profile_id UUID REFERENCES config_profiles(id) ON DELETE SET NULL`)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -227,5 +262,9 @@ func GetStats(db *sql.DB) (models.DashboardStats, error) {
|
||||
return s, err
|
||||
}
|
||||
err = db.QueryRow(`SELECT COUNT(*) FROM nodes WHERE status = 'online'`).Scan(&s.NodesOnline)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
err = db.QueryRow(`SELECT COUNT(*) FROM config_profiles`).Scan(&s.ProfilesTotal)
|
||||
return s, err
|
||||
}
|
||||
|
||||
@@ -275,16 +275,57 @@ ORDER BY p.sort_order`, nodeID)
|
||||
"code": code, "installed": inst, "enabled": en, "port": port,
|
||||
})
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if installed == nil {
|
||||
installed = []string{}
|
||||
}
|
||||
if enabled == nil {
|
||||
enabled = []string{}
|
||||
}
|
||||
return map[string]any{
|
||||
"protocols": enabled, // backward-compatible health list
|
||||
|
||||
payload := map[string]any{
|
||||
"protocols": enabled,
|
||||
"protocols_installed": installed,
|
||||
"protocols_enabled": enabled,
|
||||
"protocol_items": items,
|
||||
}, rows.Err()
|
||||
}
|
||||
|
||||
n, err := GetNode(db, nodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if n != nil && n.ProfileID != nil {
|
||||
payload["profile"] = map[string]any{
|
||||
"id": n.ProfileID.String(),
|
||||
"name": n.ProfileName,
|
||||
}
|
||||
inbounds, err := ListNodeInbounds(db, nodeID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var inList []map[string]any
|
||||
for _, in := range inbounds {
|
||||
if !in.NodeEnabled || !in.Enabled {
|
||||
continue
|
||||
}
|
||||
inList = append(inList, map[string]any{
|
||||
"id": in.ID.String(),
|
||||
"tag": in.Tag,
|
||||
"protocol": in.ProtocolCode,
|
||||
"port": in.Port,
|
||||
"network": in.Network,
|
||||
"security": in.Security,
|
||||
"listen": in.Listen,
|
||||
"remark": in.Remark,
|
||||
})
|
||||
}
|
||||
if inList == nil {
|
||||
inList = []map[string]any{}
|
||||
}
|
||||
payload["inbounds"] = inList
|
||||
}
|
||||
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
+28
-9
@@ -17,10 +17,13 @@ func scanNode(scanner interface {
|
||||
n := &models.Node{}
|
||||
var lastSeen sql.NullTime
|
||||
var status string
|
||||
var profileID uuid.NullUUID
|
||||
var profileName sql.NullString
|
||||
err := scanner.Scan(
|
||||
&n.ID, &n.Name, &n.Host, &n.SSHPort, &n.SSHUser, &n.SSHAuthType, &n.SSHSecretEnc,
|
||||
&n.NodePort, &n.SecretKey, &status, &n.InstallMode, &n.LastError, &n.InstallLog,
|
||||
&n.RuntimeLog, &n.AgentVersion, &lastSeen, &n.CreatedAt, &n.UpdatedAt,
|
||||
&profileID, &profileName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -30,13 +33,25 @@ func scanNode(scanner interface {
|
||||
t := lastSeen.Time
|
||||
n.LastSeenAt = &t
|
||||
}
|
||||
if profileID.Valid {
|
||||
id := profileID.UUID
|
||||
n.ProfileID = &id
|
||||
}
|
||||
if profileName.Valid {
|
||||
n.ProfileName = profileName.String
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
|
||||
const nodeColumns = `
|
||||
id, name, host, ssh_port, ssh_user, ssh_auth_type, ssh_secret_enc,
|
||||
node_port, secret_key, status, install_mode, last_error, install_log,
|
||||
runtime_log, agent_version, last_seen_at, created_at, updated_at`
|
||||
n.id, n.name, n.host, n.ssh_port, n.ssh_user, n.ssh_auth_type, n.ssh_secret_enc,
|
||||
n.node_port, n.secret_key, n.status, n.install_mode, n.last_error, n.install_log,
|
||||
n.runtime_log, n.agent_version, n.last_seen_at, n.created_at, n.updated_at,
|
||||
n.profile_id, COALESCE(cp.name, '')`
|
||||
|
||||
const nodeFrom = `
|
||||
FROM nodes n
|
||||
LEFT JOIN config_profiles cp ON cp.id = n.profile_id`
|
||||
|
||||
func CreateNode(db *sql.DB, n *models.Node) error {
|
||||
if n.ID == uuid.Nil {
|
||||
@@ -52,19 +67,19 @@ func CreateNode(db *sql.DB, n *models.Node) error {
|
||||
INSERT INTO nodes (
|
||||
id, name, host, ssh_port, ssh_user, ssh_auth_type, ssh_secret_enc,
|
||||
node_port, secret_key, status, install_mode, last_error, install_log,
|
||||
runtime_log, agent_version, last_seen_at, created_at, updated_at
|
||||
runtime_log, agent_version, last_seen_at, created_at, updated_at, profile_id
|
||||
) VALUES (
|
||||
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18
|
||||
$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19
|
||||
)`,
|
||||
n.ID, n.Name, n.Host, n.SSHPort, n.SSHUser, n.SSHAuthType, n.SSHSecretEnc,
|
||||
n.NodePort, n.SecretKey, string(n.Status), n.InstallMode, n.LastError, n.InstallLog,
|
||||
n.RuntimeLog, n.AgentVersion, n.LastSeenAt, n.CreatedAt, n.UpdatedAt,
|
||||
n.RuntimeLog, n.AgentVersion, n.LastSeenAt, n.CreatedAt, n.UpdatedAt, n.ProfileID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetNode(db *sql.DB, id uuid.UUID) (*models.Node, error) {
|
||||
row := db.QueryRow(`SELECT `+nodeColumns+` FROM nodes WHERE id = $1`, id)
|
||||
row := db.QueryRow(`SELECT `+nodeColumns+nodeFrom+` WHERE n.id = $1`, id)
|
||||
n, err := scanNode(row)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
@@ -73,7 +88,7 @@ func GetNode(db *sql.DB, id uuid.UUID) (*models.Node, error) {
|
||||
}
|
||||
|
||||
func ListNodes(db *sql.DB) ([]models.Node, error) {
|
||||
rows, err := db.Query(`SELECT ` + nodeColumns + ` FROM nodes ORDER BY created_at DESC`)
|
||||
rows, err := db.Query(`SELECT ` + nodeColumns + nodeFrom + ` ORDER BY n.created_at DESC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -115,7 +130,6 @@ WHERE id = $1`, id, strings.TrimRight(chunk, "\n"))
|
||||
}
|
||||
|
||||
func SetNodeRuntimeLog(db *sql.DB, id uuid.UUID, logText string) error {
|
||||
// Cap stored log size to keep DB rows reasonable.
|
||||
const max = 200_000
|
||||
if len(logText) > max {
|
||||
logText = logText[len(logText)-max:]
|
||||
@@ -133,6 +147,11 @@ WHERE id = $1`, id, version, string(status))
|
||||
return err
|
||||
}
|
||||
|
||||
func SetNodeProfile(db *sql.DB, nodeID uuid.UUID, profileID *uuid.UUID) error {
|
||||
_, err := db.Exec(`UPDATE nodes SET profile_id = $2, updated_at = NOW() WHERE id = $1`, nodeID, profileID)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteNode(db *sql.DB, id uuid.UUID) error {
|
||||
res, err := db.Exec(`DELETE FROM nodes WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"github.com/orohi/vpn-panel/internal/models"
|
||||
)
|
||||
|
||||
func CreateProfile(db *sql.DB, p *models.ConfigProfile) error {
|
||||
if p.ID == uuid.Nil {
|
||||
p.ID = uuid.New()
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
p.CreatedAt = now
|
||||
p.UpdatedAt = now
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO config_profiles (id, name, description, created_at, updated_at)
|
||||
VALUES ($1, $2, $3, $4, $5)`, p.ID, p.Name, p.Description, p.CreatedAt, p.UpdatedAt)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateProfile(db *sql.DB, p *models.ConfigProfile) error {
|
||||
_, err := db.Exec(`
|
||||
UPDATE config_profiles SET name = $2, description = $3, updated_at = NOW() WHERE id = $1`,
|
||||
p.ID, p.Name, p.Description)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteProfile(db *sql.DB, id uuid.UUID) error {
|
||||
res, err := db.Exec(`DELETE FROM config_profiles WHERE id = $1`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, _ := res.RowsAffected()
|
||||
if n == 0 {
|
||||
return fmt.Errorf("profile not found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetProfile(db *sql.DB, id uuid.UUID) (*models.ConfigProfile, error) {
|
||||
p := &models.ConfigProfile{}
|
||||
err := db.QueryRow(`
|
||||
SELECT id, name, description, created_at, updated_at,
|
||||
(SELECT COUNT(*) FROM inbounds i WHERE i.profile_id = config_profiles.id),
|
||||
(SELECT COUNT(*) FROM nodes n WHERE n.profile_id = config_profiles.id)
|
||||
FROM config_profiles WHERE id = $1`, id).Scan(
|
||||
&p.ID, &p.Name, &p.Description, &p.CreatedAt, &p.UpdatedAt,
|
||||
&p.InboundCount, &p.NodeCount,
|
||||
)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inbounds, err := ListInboundsByProfile(db, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.Inbounds = inbounds
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func ListProfiles(db *sql.DB) ([]models.ConfigProfile, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT id, name, description, created_at, updated_at,
|
||||
(SELECT COUNT(*) FROM inbounds i WHERE i.profile_id = config_profiles.id),
|
||||
(SELECT COUNT(*) FROM nodes n WHERE n.profile_id = config_profiles.id)
|
||||
FROM config_profiles
|
||||
ORDER BY name ASC`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var list []models.ConfigProfile
|
||||
for rows.Next() {
|
||||
var p models.ConfigProfile
|
||||
if err := rows.Scan(
|
||||
&p.ID, &p.Name, &p.Description, &p.CreatedAt, &p.UpdatedAt,
|
||||
&p.InboundCount, &p.NodeCount,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, p)
|
||||
}
|
||||
return list, rows.Err()
|
||||
}
|
||||
|
||||
func scanInbound(scanner interface {
|
||||
Scan(dest ...any) error
|
||||
}) (*models.Inbound, error) {
|
||||
in := &models.Inbound{}
|
||||
err := scanner.Scan(
|
||||
&in.ID, &in.ProfileID, &in.Tag, &in.ProtocolID, &in.ProtocolCode, &in.ProtocolName,
|
||||
&in.Port, &in.Network, &in.Security, &in.Listen, &in.Enabled, &in.SortOrder, &in.Remark,
|
||||
&in.CreatedAt, &in.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return in, nil
|
||||
}
|
||||
|
||||
const inboundColumns = `
|
||||
i.id, i.profile_id, i.tag, i.protocol_id, p.code, p.name,
|
||||
i.port, i.network, i.security, i.listen, i.enabled, i.sort_order, i.remark,
|
||||
i.created_at, i.updated_at`
|
||||
|
||||
func ListInboundsByProfile(db *sql.DB, profileID uuid.UUID) ([]models.Inbound, error) {
|
||||
rows, err := db.Query(`
|
||||
SELECT `+inboundColumns+`
|
||||
FROM inbounds i
|
||||
JOIN protocols p ON p.id = i.protocol_id
|
||||
WHERE i.profile_id = $1
|
||||
ORDER BY i.sort_order ASC, i.tag ASC`, profileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var list []models.Inbound
|
||||
for rows.Next() {
|
||||
in, err := scanInbound(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, *in)
|
||||
}
|
||||
return list, rows.Err()
|
||||
}
|
||||
|
||||
func GetInbound(db *sql.DB, id uuid.UUID) (*models.Inbound, error) {
|
||||
row := db.QueryRow(`
|
||||
SELECT `+inboundColumns+`
|
||||
FROM inbounds i
|
||||
JOIN protocols p ON p.id = i.protocol_id
|
||||
WHERE i.id = $1`, id)
|
||||
in, err := scanInbound(row)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
return in, err
|
||||
}
|
||||
|
||||
func CreateInbound(db *sql.DB, in *models.Inbound) error {
|
||||
if in.ID == uuid.Nil {
|
||||
in.ID = uuid.New()
|
||||
}
|
||||
if in.Tag == "" {
|
||||
in.Tag = strings.ToUpper(in.ProtocolCode) + "-" + fmt.Sprintf("%d", in.Port)
|
||||
}
|
||||
if in.Network == "" {
|
||||
in.Network = "tcp"
|
||||
}
|
||||
if in.Security == "" {
|
||||
in.Security = "none"
|
||||
}
|
||||
if in.Listen == "" {
|
||||
in.Listen = "0.0.0.0"
|
||||
}
|
||||
now := time.Now().UTC()
|
||||
in.CreatedAt = now
|
||||
in.UpdatedAt = now
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO inbounds (
|
||||
id, profile_id, tag, protocol_id, port, network, security, listen, enabled, sort_order, remark, created_at, updated_at
|
||||
) VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13)`,
|
||||
in.ID, in.ProfileID, in.Tag, in.ProtocolID, in.Port, in.Network, in.Security, in.Listen,
|
||||
in.Enabled, in.SortOrder, in.Remark, in.CreatedAt, in.UpdatedAt,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func UpdateInbound(db *sql.DB, in *models.Inbound) error {
|
||||
_, err := db.Exec(`
|
||||
UPDATE inbounds SET
|
||||
tag = $2, protocol_id = $3, port = $4, network = $5, security = $6,
|
||||
listen = $7, enabled = $8, sort_order = $9, remark = $10, updated_at = NOW()
|
||||
WHERE id = $1`,
|
||||
in.ID, in.Tag, in.ProtocolID, in.Port, in.Network, in.Security,
|
||||
in.Listen, in.Enabled, in.SortOrder, in.Remark,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func DeleteInbound(db *sql.DB, id uuid.UUID) error {
|
||||
_, err := db.Exec(`DELETE FROM inbounds WHERE id = $1`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func ToggleInbound(db *sql.DB, id uuid.UUID) error {
|
||||
_, err := db.Exec(`UPDATE inbounds SET enabled = NOT enabled, updated_at = NOW() WHERE id = $1`, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// AssignProfileToNode sets profile and enables all profile inbounds on the node.
|
||||
func AssignProfileToNode(db *sql.DB, nodeID uuid.UUID, profileID uuid.UUID) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
|
||||
if _, err := tx.Exec(`UPDATE nodes SET profile_id = $2, updated_at = NOW() WHERE id = $1`, nodeID, profileID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM node_inbounds WHERE node_id = $1`, nodeID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`
|
||||
INSERT INTO node_inbounds (node_id, inbound_id, enabled)
|
||||
SELECT $1, i.id, TRUE FROM inbounds i WHERE i.profile_id = $2 AND i.enabled = TRUE`, nodeID, profileID); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := tx.Commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
return ApplyProfileInboundsToNodeProtocols(db, nodeID)
|
||||
}
|
||||
|
||||
func ClearNodeProfile(db *sql.DB, nodeID uuid.UUID) error {
|
||||
tx, err := db.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() { _ = tx.Rollback() }()
|
||||
if _, err := tx.Exec(`UPDATE nodes SET profile_id = NULL, updated_at = NOW() WHERE id = $1`, nodeID); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := tx.Exec(`DELETE FROM node_inbounds WHERE node_id = $1`, nodeID); err != nil {
|
||||
return err
|
||||
}
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func SetNodeInboundEnabled(db *sql.DB, nodeID, inboundID uuid.UUID, enabled bool) error {
|
||||
_, err := db.Exec(`
|
||||
INSERT INTO node_inbounds (node_id, inbound_id, enabled) VALUES ($1, $2, $3)
|
||||
ON CONFLICT (node_id, inbound_id) DO UPDATE SET enabled = EXCLUDED.enabled`, nodeID, inboundID, enabled)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return ApplyProfileInboundsToNodeProtocols(db, nodeID)
|
||||
}
|
||||
|
||||
func ListNodeInbounds(db *sql.DB, nodeID uuid.UUID) ([]models.Inbound, error) {
|
||||
n, err := GetNode(db, nodeID)
|
||||
if err != nil || n == nil || n.ProfileID == nil {
|
||||
return nil, err
|
||||
}
|
||||
rows, err := db.Query(`
|
||||
SELECT `+inboundColumns+`, COALESCE(ni.enabled, FALSE)
|
||||
FROM inbounds i
|
||||
JOIN protocols p ON p.id = i.protocol_id
|
||||
LEFT JOIN node_inbounds ni ON ni.inbound_id = i.id AND ni.node_id = $1
|
||||
WHERE i.profile_id = $2
|
||||
ORDER BY i.sort_order ASC, i.tag ASC`, nodeID, *n.ProfileID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var list []models.Inbound
|
||||
for rows.Next() {
|
||||
in := models.Inbound{}
|
||||
if err := rows.Scan(
|
||||
&in.ID, &in.ProfileID, &in.Tag, &in.ProtocolID, &in.ProtocolCode, &in.ProtocolName,
|
||||
&in.Port, &in.Network, &in.Security, &in.Listen, &in.Enabled, &in.SortOrder, &in.Remark,
|
||||
&in.CreatedAt, &in.UpdatedAt, &in.NodeEnabled,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
list = append(list, in)
|
||||
}
|
||||
return list, rows.Err()
|
||||
}
|
||||
|
||||
// ApplyProfileInboundsToNodeProtocols mirrors enabled node inbounds into node_protocols.
|
||||
func ApplyProfileInboundsToNodeProtocols(db *sql.DB, nodeID uuid.UUID) error {
|
||||
rows, err := db.Query(`
|
||||
SELECT i.protocol_id, i.port, BOOL_OR(ni.enabled AND i.enabled) AS active
|
||||
FROM inbounds i
|
||||
JOIN nodes n ON n.profile_id = i.profile_id AND n.id = $1
|
||||
JOIN node_inbounds ni ON ni.inbound_id = i.id AND ni.node_id = $1
|
||||
GROUP BY i.protocol_id, i.port`, nodeID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
type row struct {
|
||||
protocolID uuid.UUID
|
||||
port int
|
||||
active bool
|
||||
}
|
||||
var active []row
|
||||
seen := map[uuid.UUID]row{}
|
||||
for rows.Next() {
|
||||
var r row
|
||||
if err := rows.Scan(&r.protocolID, &r.port, &r.active); err != nil {
|
||||
return err
|
||||
}
|
||||
if prev, ok := seen[r.protocolID]; ok {
|
||||
if r.active {
|
||||
prev.active = true
|
||||
prev.port = r.port
|
||||
seen[r.protocolID] = prev
|
||||
}
|
||||
continue
|
||||
}
|
||||
seen[r.protocolID] = r
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range seen {
|
||||
active = append(active, r)
|
||||
}
|
||||
|
||||
// Reset then apply.
|
||||
if _, err := db.Exec(`
|
||||
UPDATE node_protocols SET installed = FALSE, enabled = FALSE, updated_at = NOW()
|
||||
WHERE node_id = $1`, nodeID); err != nil {
|
||||
return err
|
||||
}
|
||||
for _, r := range active {
|
||||
if !r.active {
|
||||
continue
|
||||
}
|
||||
if err := UpsertNodeProtocol(db, nodeID, r.protocolID, true, true, r.port); err != nil {
|
||||
return err
|
||||
}
|
||||
_ = SetProtocolEnabled(db, r.protocolID, true)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user