5 Commits

Author SHA1 Message Date
6a631be909 docs(docker): docs about the custom network for the db is seperated 2025-07-25 12:14:51 -05:00
75c17d2065 test(iis): wrapper test for ws 2025-07-25 12:14:05 -05:00
63c053b38c docs(wss): more ws stuff 2025-07-25 12:13:47 -05:00
5bcbdaf3d0 feat(ws server): added in a websocket on port system to help with better logging 2025-07-25 12:13:19 -05:00
074032f20d refactor(app port): changed to have the port be dyncamic on the iis side
docker will default to 8080 and can be adjusted via the docker compose, or passing the same env over
it will change it as well.
2025-07-23 07:36:18 -05:00
24 changed files with 801 additions and 52 deletions

View File

@@ -1,6 +1,9 @@
# uncomment this out to run in productions
# APP_ENV=production
# Server port that will allow vite to talk to the backend.
VITE_SERVER_PORT=4000
# lstv2 loc
LSTV2="C\drive\loc"

View File

@@ -20,6 +20,7 @@ app.Use(async (context, next) =>
if (context.WebSockets.IsWebSocketRequest &&
context.Request.Path.StartsWithSegments("/lst/api/logger/logs"))
{
Console.WriteLine("WebSocket request received!");
try
{
var backendUri = new UriBuilder("ws", "localhost", 8080)

View File

@@ -9,6 +9,10 @@
<!-- Proxy all requests starting with /lst/ to the .NET wrapper (port 4000) -->
<rule name="Proxy to Wrapper" stopProcessing="true">
<match url="^lst/(.*)" />
<conditions>
<!-- Skip this rule if it's a WebSocket request -->
<add input="{HTTP_UPGRADE}" pattern="^WebSocket$" negate="true" />
</conditions>
<action type="Rewrite" url="http://localhost:8080/{R:1}" />
</rule>
</rules>

View File

@@ -1,4 +1,4 @@
package logging
package loggingx
import (
"encoding/json"

View File

@@ -1,4 +1,4 @@
package logging
package loggingx
import (
"github.com/gin-gonic/gin"

View File

@@ -1,4 +1,4 @@
package logging
package loggingx
import (
"fmt"
@@ -20,7 +20,7 @@ var upgrader = websocket.Upgrader{
ReadBufferSize: 1024,
WriteBufferSize: 1024,
CheckOrigin: func(r *http.Request) bool {
fmt.Println("Origin:", r.Header.Get("Origin"))
//fmt.Println("Origin:", r.Header.Get("Origin"))
return true
},
}
@@ -65,6 +65,17 @@ func handleSSE(c *gin.Context) {
"user_agent": c.Request.UserAgent(),
})
c.Header("Access-Control-Allow-Origin", "*")
c.Header("Access-Control-Allow-Credentials", "true")
c.Header("Access-Control-Allow-Headers", "Content-Type")
c.Header("Access-Control-Allow-Methods", "GET, OPTIONS")
// Handle preflight requests
if c.Request.Method == "OPTIONS" {
c.AbortWithStatus(204)
return
}
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
@@ -92,6 +103,11 @@ func handleSSE(c *gin.Context) {
func handleWebSocket(c *gin.Context) {
log := New()
log.Info("WebSocket connection established", "logger", map[string]interface{}{
"endpoint": "/api/logger/logs",
"client_ip": c.ClientIP(),
"user_agent": c.Request.UserAgent(),
})
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Error("WebSocket upgrade failed", "logger", map[string]interface{}{

View File

@@ -0,0 +1,53 @@
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})
}

View File

@@ -0,0 +1 @@
package system

View File

@@ -0,0 +1 @@
package system

View File

@@ -0,0 +1,83 @@
package channelmgt
import (
"database/sql"
"encoding/json"
"fmt"
"os"
"time"
"github.com/lib/pq"
logging "lst.net/utils/logger"
)
// setup the notifiyer
// -- Only needs to be run once in DB
// CREATE OR REPLACE FUNCTION notify_new_log() RETURNS trigger AS $$
// BEGIN
// PERFORM pg_notify('new_log', row_to_json(NEW)::text);
// RETURN NEW;
// END;
// $$ LANGUAGE plpgsql;
// DROP TRIGGER IF EXISTS new_log_trigger ON logs;
// CREATE TRIGGER new_log_trigger
// AFTER INSERT ON logs
// FOR EACH ROW EXECUTE FUNCTION notify_new_log();
func AllLogs(db *sql.DB, broadcaster chan logging.Message) {
fmt.Println("[AllLogs] started")
log := logging.New()
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
os.Getenv("DB_HOST"),
os.Getenv("DB_PORT"),
os.Getenv("DB_USER"),
os.Getenv("DB_PASSWORD"),
os.Getenv("DB_NAME"),
)
listener := pq.NewListener(dsn, 10*time.Second, time.Minute, nil)
err := listener.Listen("new_log")
if err != nil {
log.Panic("Failed to LISTEN on new_log", "logger", map[string]interface{}{
"error": err.Error(),
})
}
fmt.Println("Listening for new logs...")
for {
select {
case notify := <-listener.Notify:
if notify != nil {
fmt.Println("New log notification received")
// Unmarshal the JSON payload of the inserted row
var logData map[string]interface{}
if err := json.Unmarshal([]byte(notify.Extra), &logData); err != nil {
log.Error("Failed to unmarshal notification payload", "logger", map[string]interface{}{
"error": err.Error(),
})
continue
}
// Build message to broadcast
msg := logging.Message{
Channel: "logs", // This matches your logs channel name
Data: logData,
}
broadcaster <- msg
//fmt.Printf("[Broadcasting] sending: %+v\n", msg)
}
case <-time.After(90 * time.Second):
go func() {
log.Debug("Re-pinging Postgres LISTEN", "logger", map[string]interface{}{})
listener.Ping()
}()
}
}
}

View File

@@ -0,0 +1,93 @@
package socketio
import (
"log"
"sync"
"github.com/google/uuid"
"github.com/gorilla/websocket"
logging "lst.net/utils/logger"
)
type Client struct {
ClientID uuid.UUID
Conn *websocket.Conn
APIKey string
IPAddress string
UserAgent string
Send chan []byte
Channels map[string]bool // e.g., {"logs": true, "labels": true}
}
var clients = make(map[*Client]bool)
var clientsLock sync.Mutex
func init() {
var broadcast = make(chan string)
go func() {
for {
msg := <-broadcast
clientsLock.Lock()
for client := range clients {
if client.Channels["logs"] {
err := client.Conn.WriteMessage(websocket.TextMessage, []byte(msg))
if err != nil {
log.Println("Write error:", err)
client.Conn.Close()
//client.MarkDisconnected()
delete(clients, client)
}
}
}
clientsLock.Unlock()
}
}()
}
func StartBroadcasting(broadcaster chan logging.Message) {
go func() {
log.Println("StartBroadcasting goroutine started")
for {
msg := <-broadcaster
//log.Printf("Received msg on broadcaster: %+v\n", msg)
clientsLock.Lock()
for client := range clients {
if client.Channels[msg.Channel] {
log.Println("Sending message to client")
err := client.Conn.WriteJSON(msg)
if err != nil {
log.Println("Write error:", err)
client.Conn.Close()
client.MarkDisconnected()
delete(clients, client)
}
} else {
log.Println("Skipping client, channel mismatch")
}
}
clientsLock.Unlock()
}
}()
}
// func (c *Client) JoinChannel(name string) {
// ch := GetOrCreateChannel(name)
// c.Channels[name] = ch
// ch.Register <- c
// }
// func (c *Client) LeaveChannel(name string) {
// if ch, ok := c.Channels[name]; ok {
// ch.Unregister <- c
// delete(c.Channels, name)
// }
// }
func (c *Client) Disconnect() {
// for _, ch := range c.Channels {
// ch.Unregister <- c
// }
close(c.Send)
}

View File

@@ -0,0 +1,163 @@
package socketio
import (
"encoding/json"
"log"
"net/http"
"time"
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
"lst.net/utils/db"
)
type JoinPayload struct {
Channel string `json:"channel"`
Services []string `json:"services,omitempty"`
APIKey string `json:"apiKey"`
}
// type Channel struct {
// Name string
// Clients map[*Client]bool
// Register chan *Client
// Unregister chan *Client
// Broadcast chan Message
// }
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool { return true }, // allow all origins; customize for prod
}
func SocketHandler(c *gin.Context) {
// Upgrade HTTP to websocket
conn, err := upgrader.Upgrade(c.Writer, c.Request, nil)
if err != nil {
log.Println("Failed to upgrade:", err)
c.AbortWithStatus(http.StatusBadRequest)
return
}
defer conn.Close()
// Create client struct
client := &Client{
Conn: conn,
IPAddress: c.ClientIP(),
UserAgent: c.Request.UserAgent(),
Channels: make(map[string]bool),
}
clientsLock.Lock()
clients[client] = true
clientsLock.Unlock()
defer func() {
clientsLock.Lock()
delete(clients, client)
clientsLock.Unlock()
client.MarkDisconnected()
client.Disconnect()
conn.Close()
}()
for {
// Read message from client
_, msg, err := conn.ReadMessage()
if err != nil {
log.Println("Read error:", err)
clientsLock.Lock()
delete(clients, client)
clientsLock.Unlock()
client.MarkDisconnected()
client.Disconnect()
break
}
var payload JoinPayload
err = json.Unmarshal(msg, &payload)
if err != nil {
log.Println("Invalid JSON payload:", err)
clientsLock.Lock()
delete(clients, client)
clientsLock.Unlock()
client.MarkDisconnected()
client.Disconnect()
continue
}
// Simple API key check (replace with real auth)
if payload.APIKey == "" {
conn.WriteMessage(websocket.TextMessage, []byte("Missing API Key"))
continue
}
client.APIKey = payload.APIKey
// Handle channel subscription, add more here as we get more in.
switch payload.Channel {
case "logs":
client.Channels["logs"] = true
case "logServices":
for _, svc := range payload.Services {
client.Channels["logServices:"+svc] = true
}
case "labels":
client.Channels["labels"] = true
default:
conn.WriteMessage(websocket.TextMessage, []byte("Unknown channel"))
continue
}
// Save client info in DB
client.SaveToDB()
// Confirm subscription
resp := map[string]string{
"status": "subscribed",
"channel": payload.Channel,
}
respJSON, _ := json.Marshal(resp)
conn.WriteMessage(websocket.TextMessage, respJSON)
// You could now start pushing messages to client or keep connection open
// For demo, just wait and keep connection alive
}
}
func (c *Client) SaveToDB() {
// Convert c.Channels (map[string]bool) to map[string]interface{} for JSONB
channels := make(map[string]interface{})
for ch := range c.Channels {
channels[ch] = true
}
clientRecord := &db.ClientRecord{
APIKey: c.APIKey,
IPAddress: c.IPAddress,
UserAgent: c.UserAgent,
Channels: db.JSONB(channels),
ConnectedAt: time.Now(),
LastHeartbeat: time.Now(),
}
if err := db.DB.Create(&clientRecord).Error; err != nil {
log.Println("❌ Error saving client:", err)
} else {
c.ClientID = clientRecord.ClientID // ✅ Assign the generated UUID back to the client
}
}
func (c *Client) MarkDisconnected() {
now := time.Now()
res := db.DB.Model(&db.ClientRecord{}).
Where("client_id = ?", c.ClientID).
Updates(map[string]interface{}{
"disconnected_at": &now,
})
if res.RowsAffected == 0 {
log.Println("⚠️ No rows updated for client_id:", c.ClientID)
}
if res.Error != nil {
log.Println("❌ Error updating disconnected_at:", res.Error)
}
}

View File

@@ -0,0 +1,25 @@
package socketio
import (
"github.com/gin-gonic/gin"
channelmgt "lst.net/cmd/services/websocket/channelMGT"
"lst.net/utils/db"
logging "lst.net/utils/logger"
)
var broadcaster = make(chan logging.Message) // define broadcaster here so its accessible
func RegisterSocketRoutes(r *gin.Engine) {
sqlDB, err := db.DB.DB()
if err != nil {
panic(err)
}
// channels
go channelmgt.AllLogs(sqlDB, broadcaster)
go StartBroadcasting(broadcaster)
wsGroup := r.Group("/ws")
wsGroup.GET("/connect", SocketHandler)
}

View File

@@ -28,6 +28,7 @@ require (
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect
github.com/goccy/go-json v0.10.5 // indirect
github.com/google/uuid v1.6.0
github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgx/v5 v5.7.5 // indirect
@@ -38,6 +39,7 @@ require (
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/lib/pq v1.10.9
github.com/mailru/easyjson v0.9.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect

View File

@@ -24,9 +24,11 @@ import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"github.com/joho/godotenv"
"lst.net/cmd/services/logging"
"lst.net/cmd/services/system/config"
socketio "lst.net/cmd/services/websocket"
_ "lst.net/docs"
"lst.net/utils/db"
logging "lst.net/utils/logger"
)
func main() {
@@ -109,11 +111,18 @@ func main() {
c.JSON(200, gin.H{"message": "pong"})
})
logging.RegisterLoggerRoutes(r, basePath)
//logging.RegisterLoggerRoutes(r, basePath)
socketio.RegisterSocketRoutes(r)
config.RegisterConfigRoutes(r, basePath)
r.Any(basePath+"/api", errorApiLoc)
r.Run(":8080")
// get the server port
port := "8080"
if os.Getenv("VITE_SERVER_PORT") != "" {
port = os.Getenv("VITE_SERVER_PORT")
}
r.Run(":" + port)
}
// func serveViteApp(c *gin.Context) {

View File

@@ -0,0 +1,67 @@
package db
import (
"log"
"time"
"gorm.io/gorm"
)
type Config struct {
gorm.Model
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"uniqueIndex;not null"`
Description string `gorm:"type:text"`
Value string `gorm:"not null"`
Enabled bool `gorm:"default:true"`
AppService string `gorm:"default:system"`
CreatedAt time.Time `gorm:"index"`
UpdatedAt time.Time `gorm:"index"`
DeletedAt gorm.DeletedAt `gorm:"index"`
}
var seedConfigData = []Config{
{Name: "serverPort", Description: "The port the server will listen on if not running in docker", Value: "4000", Enabled: true},
{Name: "server", Description: "The server we will use when connecting to the alplaprod sql", Value: "usmcd1vms006", Enabled: true},
}
func SeedConfigs(db *gorm.DB) error {
for _, cfg := range seedConfigData {
var existing Config
// Try to find config by unique name
result := db.Where("name =?", cfg.Name).First(&existing)
if result.Error != nil {
if result.Error == gorm.ErrRecordNotFound {
// not here lets add it
if err := db.Create(&cfg).Error; err != nil {
log.Printf("Failed to seed config %s: %v", cfg.Name, err)
}
log.Printf("Seeded new config: %s", cfg.Name)
} else {
// Some other error
return result.Error
}
} else {
// only update the fields we want to update.
existing.Description = cfg.Description
if err := db.Save(&existing).Error; err != nil {
log.Printf("Failed to update config %s: %v", cfg.Name, err)
return err
}
log.Printf("Updated existing config: %s", cfg.Name)
}
}
return nil
}
func GetAllConfigs(db *gorm.DB) ([]Config, error) {
var configs []Config
result := db.Find(&configs)
return configs, result.Error
}

View File

@@ -10,6 +10,8 @@ import (
var DB *gorm.DB
type JSONB map[string]interface{}
func InitDB() error {
dsn := fmt.Sprintf("host=%s port=%s user=%s password=%s dbname=%s",
os.Getenv("DB_HOST"),
@@ -25,8 +27,17 @@ func InitDB() error {
return fmt.Errorf("failed to connect to database: %v", err)
}
// Auto-migrate all models
DB.AutoMigrate(&Log{}) // Add other models here
fmt.Println("✅ Connected to database")
// ensures we have the uuid stuff setup properly
DB.Exec(`CREATE EXTENSION IF NOT EXISTS "uuid-ossp"`)
err = DB.AutoMigrate(&Log{}, &Config{}, &ClientRecord{})
if err != nil {
return fmt.Errorf("failed to auto-migrate models: %v", err)
}
fmt.Println("✅ Database migration completed successfully")
return nil
}

View File

@@ -1,19 +1,26 @@
package db
import "time"
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Log struct {
ID uint `gorm:"primaryKey"`
LogID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
Level string `gorm:"size:10;not null"` // "info", "error", etc.
Message string `gorm:"not null"`
Service string `gorm:"size:50"` // Optional: service name
Metadata JSONB `gorm:"type:jsonb"` // Structured fields (e.g., {"user_id": 123})
CreatedAt time.Time `gorm:"index"` // Auto-set by GORM
Service string `gorm:"size:50"`
Metadata JSONB `gorm:"type:jsonb"` // fields (e.g., {"user_id": 123})
CreatedAt time.Time `gorm:"index"`
Checked bool `gorm:"type:boolean;default:false"`
UpdatedAt time.Time
DeletedAt gorm.DeletedAt `gorm:"index"`
}
// JSONB is a helper type for PostgreSQL JSONB fields.
type JSONB map[string]interface{}
//type JSONB map[string]interface{}
// --- CRUD Operations ---
@@ -29,13 +36,13 @@ func CreateLog(level, message, service string, metadata JSONB) error {
}
// GetLogsByLevel fetches logs filtered by severity.
func GetLogsByLevel(level string, limit int) ([]Log, error) {
func GetLogs(level string, limit int, service string) ([]Log, error) {
var logs []Log
err := DB.Where("level = ?", level).Limit(limit).Find(&logs).Error
err := DB.Where("level = ? and service = ?", level, service).Limit(limit).Find(&logs).Error
return logs, err
}
// DeleteOldLogs removes logs older than `days`.
func DeleteOldLogs(days int) error {
return DB.Where("created_at < ?", time.Now().AddDate(0, 0, -days)).Delete(&Log{}).Error
// DeleteOldLogs removes logs older than `days` and by level.
func DeleteOldLogs(days int, level string) error {
return DB.Where("created_at < ? and level = ?", time.Now().AddDate(0, 0, -days), level).Delete(&Log{}).Error
}

View File

@@ -0,0 +1,20 @@
package db
import (
"time"
"github.com/google/uuid"
)
type ClientRecord struct {
ClientID uuid.UUID `gorm:"type:uuid;default:uuid_generate_v4();primaryKey" json:"id"`
APIKey string `gorm:"not null"`
IPAddress string `gorm:"not null"`
UserAgent string `gorm:"size:255"`
ConnectedAt time.Time `gorm:"index"`
LastHeartbeat time.Time `gorm:"index"`
Channels JSONB `gorm:"type:jsonb"`
CreatedAt time.Time
UpdatedAt time.Time
DisconnectedAt *time.Time `gorm:"column:disconnected_at"`
}

View File

@@ -0,0 +1,116 @@
package logging
import (
"encoding/json"
"fmt"
"os"
"strings"
"time"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
"lst.net/utils/db"
)
type CustomLogger struct {
consoleLogger zerolog.Logger
}
type Message struct {
Channel string `json:"channel"`
Data interface{} `json:"data"`
}
// New creates a configured CustomLogger.
func New() *CustomLogger {
// Colorized console output
consoleWriter := zerolog.ConsoleWriter{
Out: os.Stderr,
TimeFormat: "2006-01-02 15:04:05",
}
return &CustomLogger{
consoleLogger: zerolog.New(consoleWriter).
With().
Timestamp().
Logger(),
}
}
func PrettyFormat(level, message string, metadata map[string]interface{}) string {
timestamp := time.Now().Format("2006-01-02 15:04:05")
base := fmt.Sprintf("[%s] %s| Message: %s", strings.ToUpper(level), timestamp, message)
if len(metadata) > 0 {
metaJSON, _ := json.Marshal(metadata)
return fmt.Sprintf("%s | Metadata: %s", base, string(metaJSON))
}
return base
}
func (l *CustomLogger) logToPostgres(level, message, service string, metadata map[string]interface{}) {
err := db.CreateLog(level, message, service, metadata)
if err != nil {
// Fallback to console if DB fails
log.Error().Err(err).Msg("Failed to write log to PostgreSQL")
}
}
// --- Level-Specific Methods ---
func (l *CustomLogger) Info(message, service string, fields map[string]interface{}) {
l.consoleLogger.Info().Fields(fields).Msg(message)
l.logToPostgres("info", message, service, fields)
//PostLog(PrettyFormat("info", message, fields)) // Broadcast pretty message
}
func (l *CustomLogger) Warn(message, service string, fields map[string]interface{}) {
l.consoleLogger.Error().Fields(fields).Msg(message)
l.logToPostgres("warn", message, service, fields)
//PostLog(PrettyFormat("warn", message, fields)) // Broadcast pretty message
// Custom logic for errors (e.g., alerting)
if len(fields) > 0 {
l.consoleLogger.Warn().Msg("Additional error context captured")
}
}
func (l *CustomLogger) Error(message, service string, fields map[string]interface{}) {
l.consoleLogger.Error().Fields(fields).Msg(message)
l.logToPostgres("error", message, service, fields)
//PostLog(PrettyFormat("error", message, fields)) // Broadcast pretty message
// Custom logic for errors (e.g., alerting)
if len(fields) > 0 {
l.consoleLogger.Warn().Msg("Additional error context captured")
}
}
func (l *CustomLogger) Panic(message, service string, fields map[string]interface{}) {
// Log to console (colored, with fields)
l.consoleLogger.Error().
Str("service", service).
Fields(fields).
Msg(message + " (PANIC)") // Explicitly mark as panic
// Log to PostgreSQL (sync to ensure it's saved before crashing)
err := db.CreateLog("panic", message, service, fields) // isCritical=true
if err != nil {
l.consoleLogger.Error().Err(err).Msg("Failed to save panic log to PostgreSQL")
}
// Additional context (optional)
if len(fields) > 0 {
l.consoleLogger.Warn().Msg("Additional panic context captured")
}
panic(message)
}
func (l *CustomLogger) Debug(message, service string, fields map[string]interface{}) {
l.consoleLogger.Debug().Fields(fields).Msg(message)
l.logToPostgres("debug", message, service, fields)
}

View File

@@ -1,10 +1,11 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import { useState } from "react";
import reactLogo from "./assets/react.svg";
import viteLogo from "/vite.svg";
import "./App.css";
import WebSocketViewer from "./WebSocketTest";
function App() {
const [count, setCount] = useState(0)
const [count, setCount] = useState(0);
return (
<>
@@ -13,7 +14,11 @@ function App() {
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
<img
src={reactLogo}
className="logo react"
alt="React logo"
/>
</a>
</div>
<h1>Vite + React</h1>
@@ -28,8 +33,9 @@ function App() {
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
<WebSocketViewer />
</>
)
);
}
export default App
export default App;

View File

@@ -0,0 +1,41 @@
import { useEffect, useRef } from "react";
const WebSocketViewer = () => {
const ws = useRef<any>(null);
useEffect(() => {
// Connect to your Go backend WebSocket endpoint
ws.current = new WebSocket(
(window.location.protocol === "https:" ? "wss://" : "ws://") +
window.location.host +
"/lst/api/logger/logs"
);
ws.current.onopen = () => {
console.log("[WebSocket] Connected");
};
ws.current.onmessage = (event: any) => {
console.log("[WebSocket] Message received:", event.data);
};
ws.current.onerror = (error: any) => {
console.error("[WebSocket] Error:", error);
};
ws.current.onclose = () => {
console.log("[WebSocket] Disconnected");
};
// Cleanup on unmount
return () => {
if (ws.current) {
ws.current.close();
}
};
}, []);
return <div>Check the console for WebSocket messages</div>;
};
export default WebSocketViewer;

View File

@@ -94,9 +94,10 @@ function Update-BuildNumber {
Push-Location $rootDir/backend
Write-Host "Building the app"
go get
swag init -o swagger -g main.go
# swag init -o swagger -g main.go
go build -ldflags "-X main.version=$($version)-$($initialBuildValue)" -o lst_app.exe ./main.go
if ($LASTEXITCODE -ne 0) {
Write-Warning "app build failed!"
@@ -124,6 +125,22 @@ function Update-BuildNumber {
Write-Host "Building wrapper"
Push-Location $rootDir/LstWrapper
Write-Host "Changing the port to match the server port in the env file"
$port = $env:VITE_SERVER_PORT
if (-not $port) {
$port = "8080" # Default port if env var not set
}
$webConfigPath = "web.config"
$content = Get-Content -Path $webConfigPath -Raw
$newContent = $content -replace '(?<=Rewrite" url="http://localhost:)\d+(?=/\{R:1\}")', $port
$newContent | Set-Content -Path $webConfigPath -NoNewline
Write-Host "Updated web.config rewrite port to $port"
#remove the publish folder as we done need it
if (-not (Test-Path "publish")) {
Write-Host "The publish folder is already deleted nothing else to do"
@@ -133,6 +150,15 @@ function Update-BuildNumber {
dotnet publish -c Release -o ./publish
$webConfigPath = "web.config"
$content = Get-Content -Path $webConfigPath -Raw
$newContent = $content -replace '(?<=Rewrite" url="http://localhost:)\d+(?=/\{R:1\}")', "8080"
$newContent | Set-Content -Path $webConfigPath -NoNewline
Write-Host "Updated web.config rewrite port back to 8080"
Pop-Location
Write-Host "Building Docs"

View File

@@ -15,3 +15,4 @@ Write-Host "Pull the new images to our docker system"
docker compose -f ./docker-compose.yml up -d --force-recreate
# in case we get logged out docker login git.tuffraid.net
# create a docker network so we have this for us docker network create -d bridge my-bridge-network