Compare commits
2 Commits
daf9e8a966
...
3bc3801ffb
| Author | SHA1 | Date | |
|---|---|---|---|
| 3bc3801ffb | |||
| 4368111311 |
@@ -1,53 +0,0 @@
|
|||||||
package config
|
|
||||||
|
|
||||||
import (
|
|
||||||
"github.com/gin-gonic/gin"
|
|
||||||
"lst.net/utils/db"
|
|
||||||
logging "lst.net/utils/logger"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ConfigUpdateInput struct {
|
|
||||||
Description *string `json:"description"`
|
|
||||||
Value *string `json:"value"`
|
|
||||||
Enabled *bool `json:"enabled"`
|
|
||||||
AppService *string `json:"app_service"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func RegisterConfigRoutes(l *gin.Engine, baseUrl string) {
|
|
||||||
// seed the db on start up
|
|
||||||
db.SeedConfigs(db.DB)
|
|
||||||
|
|
||||||
configGroup := l.Group(baseUrl + "/api/config")
|
|
||||||
configGroup.GET("/configs", getconfigs)
|
|
||||||
configGroup.POST("/configs", newConfig)
|
|
||||||
}
|
|
||||||
|
|
||||||
func getconfigs(c *gin.Context) {
|
|
||||||
log := logging.New()
|
|
||||||
configs, err := db.GetAllConfigs(db.DB)
|
|
||||||
log.Info("Current Configs", "system", map[string]interface{}{
|
|
||||||
"endpoint": "/api/config/configs",
|
|
||||||
"client_ip": c.ClientIP(),
|
|
||||||
"user_agent": c.Request.UserAgent(),
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(500, gin.H{"message": "There was an error getting the configs", "error": err})
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, gin.H{"message": "Current configs", "data": configs})
|
|
||||||
}
|
|
||||||
|
|
||||||
func newConfig(c *gin.Context) {
|
|
||||||
|
|
||||||
var config ConfigUpdateInput
|
|
||||||
|
|
||||||
err := c.ShouldBindBodyWithJSON(&config)
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
c.JSON(500, gin.H{"message": "Internal Server Error"})
|
|
||||||
}
|
|
||||||
|
|
||||||
c.JSON(200, gin.H{"message": "New config was just added", "data": config})
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1 +1 @@
|
|||||||
package system
|
package servers
|
||||||
|
|||||||
65
backend/cmd/services/system/settings/settings.go
Normal file
65
backend/cmd/services/system/settings/settings.go
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
package settings
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"lst.net/utils/db"
|
||||||
|
logging "lst.net/utils/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
type SettingUpdateInput struct {
|
||||||
|
Description *string `json:"description"`
|
||||||
|
Value *string `json:"value"`
|
||||||
|
Enabled *bool `json:"enabled"`
|
||||||
|
AppService *string `json:"app_service"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func RegisterSettingsRoutes(l *gin.Engine, baseUrl string) {
|
||||||
|
// seed the db on start up
|
||||||
|
db.SeedConfigs(db.DB)
|
||||||
|
|
||||||
|
s := l.Group(baseUrl + "/api/v1")
|
||||||
|
s.GET("/settings", getSettings)
|
||||||
|
s.PATCH("/settings", updateSettingById)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSettings(c *gin.Context) {
|
||||||
|
logger := logging.New()
|
||||||
|
configs, err := db.GetAllConfigs(db.DB)
|
||||||
|
logger.Info("Current Settings", "system", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
logger.Error("Current Settings", "system", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
c.JSON(500, gin.H{"message": "There was an error getting the settings", "error": err})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"message": "Current settings", "data": configs})
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateSettingById(c *gin.Context) {
|
||||||
|
logger := logging.New()
|
||||||
|
var setting SettingUpdateInput
|
||||||
|
|
||||||
|
err := c.ShouldBindBodyWithJSON(&setting)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(500, gin.H{"message": "Internal Server Error"})
|
||||||
|
logger.Error("Current Settings", "system", map[string]interface{}{
|
||||||
|
"endpoint": "/api/v1/settings",
|
||||||
|
"client_ip": c.ClientIP(),
|
||||||
|
"user_agent": c.Request.UserAgent(),
|
||||||
|
"error": err,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(200, gin.H{"message": "Setting was just updated", "data": setting})
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1 +0,0 @@
|
|||||||
package system
|
|
||||||
@@ -83,6 +83,7 @@ func CleanupChannels() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Channel) {
|
func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Channel) {
|
||||||
|
logger := logging.New()
|
||||||
go func() {
|
go func() {
|
||||||
for msg := range broadcaster {
|
for msg := range broadcaster {
|
||||||
switch msg.Channel {
|
switch msg.Channel {
|
||||||
@@ -90,7 +91,9 @@ func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Ch
|
|||||||
// Just forward the message - filtering happens in RunChannel()
|
// Just forward the message - filtering happens in RunChannel()
|
||||||
messageBytes, err := json.Marshal(msg)
|
messageBytes, err := json.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshaling message: %v", err)
|
logger.Error("Error marshaling message", "websocket", map[string]interface{}{
|
||||||
|
"errors": err,
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
channels["logServices"].Broadcast <- messageBytes
|
channels["logServices"].Broadcast <- messageBytes
|
||||||
@@ -99,7 +102,9 @@ func StartBroadcasting(broadcaster chan logging.Message, channels map[string]*Ch
|
|||||||
// Future labels handling
|
// Future labels handling
|
||||||
messageBytes, err := json.Marshal(msg)
|
messageBytes, err := json.Marshal(msg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Error marshaling message: %v", err)
|
logger.Error("Error marshaling message", "websocket", map[string]interface{}{
|
||||||
|
"errors": err,
|
||||||
|
})
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
channels["labels"].Broadcast <- messageBytes
|
channels["labels"].Broadcast <- messageBytes
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func LogServices(broadcaster chan logging.Message) {
|
func LogServices(broadcaster chan logging.Message) {
|
||||||
fmt.Println("[LogServices] started - single channel for all logs")
|
|
||||||
logger := logging.New()
|
logger := logging.New()
|
||||||
|
|
||||||
|
logger.Info("[LogServices] started - single channel for all logs", "websocket", map[string]interface{}{})
|
||||||
|
|
||||||
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
|
||||||
os.Getenv("DB_HOST"),
|
os.Getenv("DB_HOST"),
|
||||||
os.Getenv("DB_PORT"),
|
os.Getenv("DB_PORT"),
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package websocket
|
package websocket
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
"github.com/gorilla/websocket"
|
"github.com/gorilla/websocket"
|
||||||
"lst.net/utils/db"
|
"lst.net/utils/db"
|
||||||
|
logging "lst.net/utils/logger"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -30,7 +32,8 @@ type Client struct {
|
|||||||
ConnectedAt time.Time `json:"connected_at"`
|
ConnectedAt time.Time `json:"connected_at"`
|
||||||
done chan struct{} // For graceful shutdown
|
done chan struct{} // For graceful shutdown
|
||||||
isAlive atomic.Bool
|
isAlive atomic.Bool
|
||||||
//mu sync.Mutex // Protects isAlive if not using atomic
|
lastActive time.Time // Tracks last activity
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) SaveToDB() {
|
func (c *Client) SaveToDB() {
|
||||||
@@ -59,7 +62,10 @@ func (c *Client) SaveToDB() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) MarkDisconnected() {
|
func (c *Client) MarkDisconnected() {
|
||||||
log.Printf("Client %v just lefts us", c.ClientID)
|
logger := logging.New()
|
||||||
|
clientData := fmt.Sprintf("Client %v just lefts us", c.ClientID)
|
||||||
|
logger.Info(clientData, "websocket", map[string]interface{}{})
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
res := db.DB.Model(&db.ClientRecord{}).
|
res := db.DB.Model(&db.ClientRecord{}).
|
||||||
Where("client_id = ?", c.ClientID).
|
Where("client_id = ?", c.ClientID).
|
||||||
@@ -68,10 +74,17 @@ func (c *Client) MarkDisconnected() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if res.RowsAffected == 0 {
|
if res.RowsAffected == 0 {
|
||||||
log.Println("⚠️ No rows updated for client_id:", c.ClientID)
|
|
||||||
|
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
if res.Error != nil {
|
if res.Error != nil {
|
||||||
log.Println("❌ Error updating disconnected_at:", res.Error)
|
|
||||||
|
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": res.Error,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,6 +136,8 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) StartHeartbeat() {
|
func (c *Client) StartHeartbeat() {
|
||||||
|
logger := logging.New()
|
||||||
|
log.Println("Started hearbeat")
|
||||||
ticker := time.NewTicker(pingPeriod)
|
ticker := time.NewTicker(pingPeriod)
|
||||||
defer ticker.Stop()
|
defer ticker.Stop()
|
||||||
|
|
||||||
@@ -140,6 +155,27 @@ func (c *Client) StartHeartbeat() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
now := time.Now()
|
||||||
|
res := db.DB.Model(&db.ClientRecord{}).
|
||||||
|
Where("client_id = ?", c.ClientID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"last_heartbeat": &now,
|
||||||
|
})
|
||||||
|
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
|
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.Error != nil {
|
||||||
|
|
||||||
|
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": res.Error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
case <-c.done:
|
case <-c.done:
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -155,6 +191,75 @@ func (c *Client) Close() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) startServerPings() {
|
||||||
|
ticker := time.NewTicker(60 * time.Second) // Ping every 30s
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
c.Conn.SetWriteDeadline(time.Now().Add(10 * time.Second))
|
||||||
|
if err := c.Conn.WriteMessage(websocket.PingMessage, nil); err != nil {
|
||||||
|
c.Close() // Disconnect if ping fails
|
||||||
|
return
|
||||||
|
}
|
||||||
|
case <-c.done:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) markActive() {
|
||||||
|
c.lastActive = time.Now() // No mutex needed if single-writer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) IsActive() bool {
|
||||||
|
return time.Since(c.lastActive) < 45*time.Second // 1.5x ping interval
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) updateHeartbeat() {
|
||||||
|
//fmt.Println("Updating heatbeat")
|
||||||
|
now := time.Now()
|
||||||
|
logger := logging.New()
|
||||||
|
|
||||||
|
//fmt.Printf("Updating heartbeat for client: %s at %v\n", c.ClientID, now)
|
||||||
|
|
||||||
|
//db.DB = db.DB.Debug()
|
||||||
|
res := db.DB.Model(&db.ClientRecord{}).
|
||||||
|
Where("client_id = ?", c.ClientID).
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"last_heartbeat": &now, // Explicit format
|
||||||
|
})
|
||||||
|
//fmt.Printf("Executed SQL: %v\n", db.DB.Statement.SQL.String())
|
||||||
|
if res.RowsAffected == 0 {
|
||||||
|
|
||||||
|
logger.Info("⚠️ No rows updated for client_id", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if res.Error != nil {
|
||||||
|
|
||||||
|
logger.Error("❌ Error updating disconnected_at", "websocket", map[string]interface{}{
|
||||||
|
"clientID": c.ClientID,
|
||||||
|
"error": res.Error,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// 2. Verify DB connection
|
||||||
|
if db.DB == nil {
|
||||||
|
logger.Error("DB connection is nil", "websocket", map[string]interface{}{})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Test raw SQL execution first
|
||||||
|
testRes := db.DB.Exec("SELECT 1")
|
||||||
|
if testRes.Error != nil {
|
||||||
|
logger.Error("DB ping failed", "websocket", map[string]interface{}{
|
||||||
|
"error": testRes.Error,
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// work on this stats later
|
// work on this stats later
|
||||||
// Add to your admin endpoint
|
// Add to your admin endpoint
|
||||||
// type ConnectionStats struct {
|
// type ConnectionStats struct {
|
||||||
|
|||||||
@@ -35,22 +35,10 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
}
|
}
|
||||||
//defer conn.Close()
|
//defer conn.Close()
|
||||||
|
|
||||||
// Set ping handler on the connection
|
|
||||||
conn.SetPingHandler(func(appData string) error {
|
|
||||||
log.Println("Received ping:", appData)
|
|
||||||
conn.SetReadDeadline(time.Now().Add(60 * time.Second)) // Reset read timeout
|
|
||||||
return nil // Return nil to send pong automatically
|
|
||||||
})
|
|
||||||
|
|
||||||
// Optional: Custom pong handler
|
|
||||||
conn.SetPongHandler(func(appData string) error {
|
|
||||||
log.Println("Received pong:", appData)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// Create new client
|
// Create new client
|
||||||
client := &Client{
|
client := &Client{
|
||||||
Conn: conn,
|
Conn: conn,
|
||||||
|
APIKey: "exampleAPIKey",
|
||||||
Send: make(chan []byte, 256), // Buffered channel
|
Send: make(chan []byte, 256), // Buffered channel
|
||||||
Channels: make(map[string]bool),
|
Channels: make(map[string]bool),
|
||||||
IPAddress: c.ClientIP(),
|
IPAddress: c.ClientIP(),
|
||||||
@@ -73,8 +61,22 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
// return
|
// return
|
||||||
// }
|
// }
|
||||||
|
|
||||||
//client.StartHeartbeat()
|
// Set handlers
|
||||||
// Cleanup on disconnect
|
conn.SetPingHandler(func(string) error {
|
||||||
|
return nil // Auto-responds with pong
|
||||||
|
})
|
||||||
|
|
||||||
|
conn.SetPongHandler(func(string) error {
|
||||||
|
now := time.Now()
|
||||||
|
client.markActive() // Track last pong time
|
||||||
|
client.lastActive = now
|
||||||
|
client.updateHeartbeat()
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
|
||||||
|
// Start server-side ping ticker
|
||||||
|
go client.startServerPings()
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
// Unregister from all channels
|
// Unregister from all channels
|
||||||
for channelName := range client.Channels {
|
for channelName := range client.Channels {
|
||||||
@@ -96,11 +98,6 @@ func SocketHandler(c *gin.Context, channels map[string]*Channel) {
|
|||||||
log.Printf("Client disconnected: %s", client.ClientID)
|
log.Printf("Client disconnected: %s", client.ClientID)
|
||||||
}()
|
}()
|
||||||
|
|
||||||
client.Conn.SetPingHandler(func(appData string) error {
|
|
||||||
log.Printf("Custom ping handler for client %s", client.ClientID)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
// Send welcome message immediately
|
// Send welcome message immediately
|
||||||
welcomeMsg := map[string]string{
|
welcomeMsg := map[string]string{
|
||||||
"status": "connected",
|
"status": "connected",
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import (
|
|||||||
"github.com/gin-contrib/cors"
|
"github.com/gin-contrib/cors"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"lst.net/cmd/services/system/config"
|
"lst.net/cmd/services/system/settings"
|
||||||
"lst.net/cmd/services/websocket"
|
"lst.net/cmd/services/websocket"
|
||||||
|
|
||||||
_ "lst.net/docs"
|
_ "lst.net/docs"
|
||||||
@@ -115,7 +115,7 @@ func main() {
|
|||||||
|
|
||||||
//logging.RegisterLoggerRoutes(r, basePath)
|
//logging.RegisterLoggerRoutes(r, basePath)
|
||||||
websocket.RegisterSocketRoutes(r, basePath)
|
websocket.RegisterSocketRoutes(r, basePath)
|
||||||
config.RegisterConfigRoutes(r, basePath)
|
settings.RegisterSettingsRoutes(r, basePath)
|
||||||
|
|
||||||
r.Any(basePath+"/api", errorApiLoc)
|
r.Any(basePath+"/api", errorApiLoc)
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ type ClientRecord struct {
|
|||||||
IPAddress string `gorm:"not null"`
|
IPAddress string `gorm:"not null"`
|
||||||
UserAgent string `gorm:"size:255"`
|
UserAgent string `gorm:"size:255"`
|
||||||
ConnectedAt time.Time `gorm:"index"`
|
ConnectedAt time.Time `gorm:"index"`
|
||||||
LastHeartbeat time.Time `gorm:"index"`
|
LastHeartbeat time.Time `gorm:"column:last_heartbeat"`
|
||||||
Channels JSONB `gorm:"type:jsonb"`
|
Channels JSONB `gorm:"type:jsonb"`
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
|
|||||||
Reference in New Issue
Block a user