feat: auto-trunk creation, SIP gateway in metrics, gateway WS broadcast
- SIPGateway field in NodeMetric model - HasNode() engine method - OnNewNode callback in NATS subscriber - Auto-create balance trunk from new node metric - BroadcastGatewayEvent on WS hub - Gateway status indicators in trunks UI (green/red/gray) - SPA fallback for frontend routing
This commit is contained in:
@@ -5,6 +5,8 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
@@ -20,6 +22,7 @@ import (
|
||||
"github.com/pulse-lets-go/internal/engine"
|
||||
"github.com/pulse-lets-go/internal/esl"
|
||||
filelog "github.com/pulse-lets-go/internal/log"
|
||||
"github.com/pulse-lets-go/internal/models"
|
||||
"github.com/pulse-lets-go/internal/nats"
|
||||
)
|
||||
|
||||
@@ -73,6 +76,42 @@ func main() {
|
||||
}
|
||||
defer sub.Close()
|
||||
|
||||
// Авто-создание balance-транков при первой метрике от новой ноды
|
||||
sub.SetOnNewNode(func(nodeID, sipGateway string) {
|
||||
trunks, err := cfgMgr.ReadTrunks()
|
||||
if err != nil {
|
||||
log.Printf("[main] ошибка чтения транков для авто-создания: %v", err)
|
||||
return
|
||||
}
|
||||
// Проверяем идемпотентность: транк уже существует?
|
||||
for _, t := range trunks {
|
||||
if t.Type == "balance" && t.NodeID == nodeID {
|
||||
return
|
||||
}
|
||||
}
|
||||
// Создаём новый balance-транк
|
||||
now := time.Now().UTC()
|
||||
trunkID := "trk-" + randomHex(6)
|
||||
trunk := models.Trunk{
|
||||
ID: trunkID,
|
||||
Name: fmt.Sprintf("%s баланс", nodeID),
|
||||
Type: "balance",
|
||||
NodeID: nodeID,
|
||||
Gateway: sipGateway,
|
||||
Codecs: []string{"PCMU", "PCMA"},
|
||||
Context: "default",
|
||||
Enabled: true,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
trunks = append(trunks, trunk)
|
||||
if err := cfgMgr.SaveTrunks(trunks); err != nil {
|
||||
log.Printf("[main] ошибка сохранения авто-созданного транка: %v", err)
|
||||
return
|
||||
}
|
||||
log.Printf("[nats] авто-создан balance-транк: %s → %s (узел: %s)", trunkID, sipGateway, nodeID)
|
||||
})
|
||||
|
||||
// 6. HTTP API
|
||||
apiHandler := api.NewAPI(eng, cfgMgr, cfg.JWTSecret, cfg.MonitoringAPIKey, sub.IsConnected, cfg)
|
||||
handler := apiHandler.Handler()
|
||||
@@ -105,7 +144,26 @@ func main() {
|
||||
eslClient.GatewaySyncAll(cfg.ESL.SofiaProfile, specs)
|
||||
},
|
||||
func(eventName string, headers map[string]string, body string) {
|
||||
log.Printf("[esl] событие: %s", eventName)
|
||||
gwName := headers["Gateway-Name"]
|
||||
if gwName == "" {
|
||||
return
|
||||
}
|
||||
// Извлекаем trunk_id из имени gateway: "pulse-ingress-trk-X" → "trk-X"
|
||||
prefix := cfg.ESL.GatewayPrefix + "-ingress-"
|
||||
trunkID := strings.TrimPrefix(gwName, prefix)
|
||||
if trunkID == gwName {
|
||||
return // префикс не найден, не наш gateway
|
||||
}
|
||||
|
||||
status := "unknown"
|
||||
switch eventName {
|
||||
case "SOFIA::gateway_register":
|
||||
status = "up"
|
||||
case "SOFIA::gateway_unregister", "SOFIA::gateway_expire":
|
||||
status = "down"
|
||||
}
|
||||
|
||||
apiHandler.BroadcastGatewayEvent(gwName, trunkID, status)
|
||||
},
|
||||
)
|
||||
cancel()
|
||||
@@ -236,6 +294,15 @@ func withSPA(webDir string, apiHandler http.Handler) http.Handler {
|
||||
})
|
||||
}
|
||||
|
||||
// randomHex генерирует случайную hex-строку заданной длины.
|
||||
func randomHex(n int) string {
|
||||
b := make([]byte, n)
|
||||
if _, err := rand.Read(b); err != nil {
|
||||
return fmt.Sprintf("%x", time.Now().UnixNano())[:n]
|
||||
}
|
||||
return hex.EncodeToString(b)[:n]
|
||||
}
|
||||
|
||||
// extractHostFromGateway извлекает хост из SIP URI.
|
||||
// "sip:mts-gw.lan:5060" → "mts-gw.lan"
|
||||
func extractHostFromGateway(gateway string) string {
|
||||
|
||||
Reference in New Issue
Block a user