From e0be95978df3f82b891c68d80def1489d750f34d Mon Sep 17 00:00:00 2001 From: Blake Matthes Date: Sat, 6 Sep 2025 22:26:17 -0500 Subject: [PATCH] feat(controller): added in update server channel and refactors for more actions --- .gitignore | 2 + .../Controller/build app.bru | 4 +- app/src/main.ts | 10 ++ controller/internal/route_handler/router.go | 68 ++++++++++++ controller/update_channel.go | 105 ++++++++++++++++-- 5 files changed, 176 insertions(+), 13 deletions(-) create mode 100644 controller/internal/route_handler/router.go diff --git a/.gitignore b/.gitignore index 509fc0e..f8050a9 100644 --- a/.gitignore +++ b/.gitignore @@ -184,3 +184,5 @@ go.work.sum +lstWrapper/Program_working_node_ws.txt +lstWrapper/web.config.txt diff --git a/LogisticsSupportTool_API_DOCS/Controller/build app.bru b/LogisticsSupportTool_API_DOCS/Controller/build app.bru index 2154c9c..2575b1e 100644 --- a/LogisticsSupportTool_API_DOCS/Controller/build app.bru +++ b/LogisticsSupportTool_API_DOCS/Controller/build app.bru @@ -4,8 +4,8 @@ meta { seq: 1 } -post { - url: http://localhost:8080/build +get { + url: http://localhost:8080/check body: none auth: inherit } diff --git a/app/src/main.ts b/app/src/main.ts index cc99bcc..37d317a 100644 --- a/app/src/main.ts +++ b/app/src/main.ts @@ -66,6 +66,11 @@ const main = async () => { if (process.env.NODE_ENV?.trim() !== "production") { app.use(morgan("tiny")); basePath = "/lst"; + + app.use( + basePath + "/test", + express.static(join(__dirname, "../../controller")) + ); } // docs and api stuff @@ -78,6 +83,11 @@ const main = async () => { express.static(join(__dirname, "../../frontend/dist")) ); + app.use( + basePath + "/test", + express.static(join(__dirname, "../../frontend/dist")) + ); + // register app setupRoutes(app, basePath); diff --git a/controller/internal/route_handler/router.go b/controller/internal/route_handler/router.go new file mode 100644 index 0000000..062176d --- /dev/null +++ b/controller/internal/route_handler/router.go @@ -0,0 +1,68 @@ +package router + +import ( + "fmt" + "net/http" + "os" + "time" + + "github.com/gin-gonic/gin" +) + +type UpdatePayload struct { + Action string `json:"action"` + Target string `json:"target"` +} + +func Setup(basePath string) *gin.Engine { + + r := gin.Default() + + if os.Getenv("NODE") == "production" { + gin.SetMode(gin.ReleaseMode) + } + + r.GET(basePath+"/check", func(c *gin.Context) { + c.JSON(http.StatusAccepted, gin.H{"check": "good"}) + }) + + //logger.RegisterLoggerRoutes(r, basePath, db) + r.POST(basePath+"/update", func(c *gin.Context) { + var payload UpdatePayload + if err := c.BindJSON(&payload); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + + c.Writer.Header().Set("Content-Type", "text/event-stream") + c.Writer.Header().Set("Cache-Control", "no-cache") + c.Writer.Header().Set("Connection", "keep-alive") + + flusher, ok := c.Writer.(http.Flusher) + if !ok { + c.String(http.StatusInternalServerError, "Streaming not supported") + return + } + + steps := []string{"🚀 Starting...", "📂 Copying...", "🔧 Migrating...", "✅ Done!"} + for _, step := range steps { + fmt.Fprintf(c.Writer, "event: log\ndata: %s\n\n", step) + flusher.Flush() // 🔑 actually push chunk + time.Sleep(1 * time.Second) // simulate work + } + }) + + r.Any(basePath+"/", func(c *gin.Context) { errorApiLoc(c) }) + + return r +} + +func errorApiLoc(c *gin.Context) { + + // log.Error("Api endpoint hit that dose not exist", "system", map[string]interface{}{ + // "endpoint": c.Request.URL.Path, + // "client_ip": c.ClientIP(), + // "user_agent": c.Request.UserAgent(), + // }) + c.JSON(http.StatusBadRequest, gin.H{"message": "looks like you have encountered a route that dose not exist"}) +} diff --git a/controller/update_channel.go b/controller/update_channel.go index 14455a5..39e48e5 100644 --- a/controller/update_channel.go +++ b/controller/update_channel.go @@ -1,11 +1,24 @@ package main import ( + "bufio" + "bytes" + "encoding/json" "fmt" + "io" + "log" + "net/http" + "os" + "strings" socketio "github.com/googollee/go-socket.io" ) +type UpdatePayload struct { + Action string `json:"action"` + Target string `json:"target"` +} + func registerUpdateChannel(server *socketio.Server) { server.OnEvent("/", "subscribe:update", func(s socketio.Conn) { @@ -15,21 +28,91 @@ func registerUpdateChannel(server *socketio.Server) { }) // copy files based on the target passed over - server.OnEvent("/", "update", func(s socketio.Conn, target string) { - server.BroadcastToRoom("/", "update", "updateLogs", - fmt.Sprintf("🚀 Copying latest build to %v", target)) - copyBuild(server, target) + server.OnEvent("/", "update", func(s socketio.Conn, payload UpdatePayload) { + switch strings.ToLower(payload.Action) { + case "copy": + server.BroadcastToRoom("/", "update", "updateLogs", + fmt.Sprintf("🚀 Copying latest build to %v", payload.Target)) + copyLatestBuild(server, payload.Target) + + case "update": + updateServer(server, payload.Target) + + default: + server.BroadcastToRoom("/", "update", "updateLogs", + fmt.Sprintf("❓ Unknown action: %s", payload.Action)) + } }) - // update the server - // server.OnEvent("/", "update", func(s socketio.Conn, target string) { - // msg := fmt.Sprintf("🔧 Running updateServer on: %s", target) - // server.BroadcastToRoom("/update", "update", "updateLogs", msg) - - // }) - server.OnEvent("/", "unsubscribe:update", func(s socketio.Conn) { s.Leave("update") s.Emit("updateLogs", "👋 Unsubscribed from update logs") }) } + +func updateServer(server *socketio.Server, target string) { + host, err := os.Hostname() + if err != nil { + server.BroadcastToRoom("/", "update", "updateLogs", "Could not retrieve hostname") + return + } + + if strings.Contains(host, "VMS") || strings.Contains(host, "vms") { + server.BroadcastToRoom("/", "update", "updateLogs", "Your are about to check for a new build and then update the server.") + + return + } + + if target == "" { + server.BroadcastToRoom("/", "update", "updateLogs", "You seem to be on a dev server or not an actual production server, you MUST pass a server over. I.E. USMCD1VMS036") + return + } else { + server.BroadcastToRoom("/", "update", "updateLogs", fmt.Sprintf("Running the update on: %v", target)) + go triggerRemoteUpdate(server, target, UpdatePayload{Action: "update", Target: target}) + } +} + +func copyLatestBuild(server *socketio.Server, target string) { + server.BroadcastToRoom("/", "update", "updateLogs", + fmt.Sprintf("🚀 Copying latest build to %v", target)) + copyBuild(server, target) +} + +func triggerRemoteUpdate(server *socketio.Server, remoteURL string, payload UpdatePayload) { + + basePath := "/api/controller" + if os.Getenv("NODE_ENV") != "production" { + basePath = "/lst/api/controller" + } + + // send POST request with JSON, expect SSE / streaming text back + body, _ := json.Marshal(payload) + + url := fmt.Sprintf("https://%v.alpla.net%v/update", remoteURL, basePath) + //url := fmt.Sprintf("http://localhost:8080%v/update", basePath) + fmt.Println(url) + resp, err := http.Post(url, "application/json", bytes.NewBuffer(body)) + if err != nil { + log.Println("❌ Cannot connect remote:", err) + return + } + defer resp.Body.Close() + + decoder := bufio.NewReader(resp.Body) + for { + line, err := decoder.ReadString('\n') + if err != nil { + if err != io.EOF { + log.Println("❌ Error reading stream:", err) + } + break + } + + //fmt.Println(line) + parsed := strings.TrimSpace(line) + if parsed != "" { + log.Println("📡 Remote log:", parsed) + server.BroadcastToRoom("/", "update", "updateLogs", parsed) + } + } +}