package api import ( "io" "log" "net/http" "os" "path/filepath" "slices" "sort" "strings" "time" "github.com/gin-gonic/gin" "github.com/gorilla/websocket" ) var upgrader = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true }, } func (h *Handler) StreamLogs(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Printf("websocket upgrade error: %v", err) return } defer conn.Close() clientID := conn.RemoteAddr().String() ch := h.streamer.Subscribe("server", clientID) defer h.streamer.Unsubscribe("server", clientID) for line := range ch { if err := conn.WriteMessage(websocket.TextMessage, []byte(line)); err != nil { break } } } func (h *Handler) ListLogs(c *gin.Context) { profilesDir := h.process.ProfilesDir() files := h.collectLogFiles(profilesDir, "") logsDir := filepath.Join(profilesDir, "logs") if fi, err := os.Stat(logsDir); err == nil && fi.IsDir() { files = append(files, h.collectLogFiles(logsDir, "logs")...) } slices.Sort(files) if files == nil { files = []string{} } c.JSON(http.StatusOK, files) } func (h *Handler) collectLogFiles(dir, prefix string) []string { entries, err := os.ReadDir(dir) if err != nil { return nil } var files []string for _, e := range entries { if !e.IsDir() { ext := filepath.Ext(e.Name()) if ext == ".log" || ext == ".rpt" || e.Name() == "script.log" { if prefix != "" { files = append(files, prefix+"/"+e.Name()) } else { files = append(files, e.Name()) } } } } return files } func (h *Handler) StreamSteamCMDLogs(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Printf("websocket upgrade error: %v", err) return } defer conn.Close() clientID := conn.RemoteAddr().String() ch := h.streamer.Subscribe("steamcmd", clientID) defer h.streamer.Unsubscribe("steamcmd", clientID) for line := range ch { if err := conn.WriteMessage(websocket.TextMessage, []byte(line)); err != nil { break } } } func (h *Handler) StreamRPTLogs(c *gin.Context) { conn, err := upgrader.Upgrade(c.Writer, c.Request, nil) if err != nil { log.Printf("websocket upgrade error: %v", err) return } defer conn.Close() profilesDir := h.process.ProfilesDir() var currentPath string var currentOffset int64 ticker := time.NewTicker(250 * time.Millisecond) defer ticker.Stop() for range ticker.C { path := findLatestRPT(profilesDir) if path != currentPath { currentPath = path currentOffset = 0 if currentPath == "" { continue } conn.WriteMessage(websocket.TextMessage, []byte("--- tailing: "+filepath.Base(currentPath)+" ---")) } if currentPath == "" { continue } fi, err := os.Stat(currentPath) if err != nil { currentPath = "" continue } if fi.Size() <= currentOffset { continue } f, err := os.Open(currentPath) if err != nil { currentPath = "" continue } f.Seek(currentOffset, io.SeekStart) buf := make([]byte, fi.Size()-currentOffset) n, _ := io.ReadFull(f, buf) f.Close() currentOffset += int64(n) for _, line := range strings.Split(string(buf[:n]), "\n") { if line == "" { continue } if err := conn.WriteMessage(websocket.TextMessage, []byte(line)); err != nil { return } } } } func findLatestRPT(dir string) string { pattern := filepath.Join(dir, "arma3server_x64_*.rpt") matches, err := filepath.Glob(pattern) if err != nil || len(matches) == 0 { return "" } sort.Slice(matches, func(i, j int) bool { fi, _ := os.Stat(matches[i]) fj, _ := os.Stat(matches[j]) return fi.ModTime().After(fj.ModTime()) }) return matches[0] } func (h *Handler) GetLog(c *gin.Context) { filename := filepath.Base(c.Param("file")) if filename == "" || strings.ContainsRune(c.Param("file"), os.PathSeparator) { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid filename"}) return } profilesDir := h.process.ProfilesDir() path := filepath.Join(profilesDir, filename) if _, err := os.Stat(path); os.IsNotExist(err) { logsPath := filepath.Join(profilesDir, "logs", filename) if _, err2 := os.Stat(logsPath); err2 == nil { path = logsPath } else { c.JSON(http.StatusNotFound, gin.H{"error": "file not found"}) return } } data, err := os.ReadFile(path) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.String(http.StatusOK, string(data)) }