feat(logs): add initial log loading and tab management improvements

- Implement fetchLastLines API to load initial log entries
- Add addInitialLogs function to useSSE hook for prepending logs
- Improve tab handling with better closable behavior and state management
- Add backend support for fetching last N lines from log files
This commit is contained in:
Luiz Costa
2025-11-18 11:32:05 -03:00
parent ac4f232e5e
commit dfe9b10bb8
7 changed files with 383 additions and 14 deletions
+68 -2
View File
@@ -6,12 +6,14 @@ import (
"encoding/json"
"flag"
"fmt"
"bufio"
"io/fs"
"log"
"net/http"
"os"
"os/signal"
"regexp"
"strings"
"sync"
"syscall"
@@ -28,6 +30,9 @@ var (
username string
)
// Lista global de arquivos de log (apenas argumentos após flags)
var logFiles []string
// init() é executado antes de main(). É o lugar ideal para configurar flags.
func init() {
flag.StringVar(&port, "port", ":8080", "Porta para o servidor web (ex: :8080, 9090)")
@@ -118,7 +123,67 @@ func handleSSE(w http.ResponseWriter, r *http.Request) {
// handleFiles lista os arquivos de log disponíveis
func handleFiles(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(os.Args[1:])
json.NewEncoder(w).Encode(logFiles)
}
func handleLastLines(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
file := r.URL.Query().Get("file")
nParam := r.URL.Query().Get("n")
n := 200
if nParam != "" {
fmt.Sscanf(nParam, "%d", &n)
}
allowed := false
for _, f := range logFiles {
if f == file {
allowed = true
break
}
}
if !allowed {
w.WriteHeader(http.StatusBadRequest)
w.Write([]byte("arquivo inválido"))
return
}
entries, err := getLastLines(file, n)
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte("erro ao ler arquivo"))
return
}
json.NewEncoder(w).Encode(entries)
}
func getLastLines(filename string, n int) ([]LogEntry, error) {
f, err := os.Open(filename)
if err != nil {
return nil, err
}
defer f.Close()
scanner := bufio.NewScanner(f)
buf := make([]string, 0, n)
for scanner.Scan() {
line := scanner.Text()
if len(buf) == n {
buf = buf[1:]
}
buf = append(buf, line)
}
if err := scanner.Err(); err != nil {
return nil, err
}
tsRe1 := regexp.MustCompile(`\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}`)
tsRe2 := regexp.MustCompile(`\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}`)
entries := make([]LogEntry, 0, len(buf))
for _, line := range buf {
t := tsRe1.FindString(line)
if t == "" {
t = tsRe2.FindString(line)
}
entries = append(entries, LogEntry{Filename: filename, Line: strings.TrimRight(line, "\r"), Timestamp: t})
}
return entries, nil
}
// startTailing inicia o monitoramento dos arquivos
@@ -184,7 +249,7 @@ func requestAuth(w http.ResponseWriter) {
func main() {
flag.Parse()
logFiles := flag.Args()
logFiles = flag.Args()
if len(logFiles) < 1 {
fmt.Println("Uso: web-tail-pro [opções] <arquivo1.log> <arquivo2.log> ...")
fmt.Println("\nOpções:")
@@ -212,6 +277,7 @@ func main() {
mux.Handle("/", http.FileServer(http.FS(distFS)))
mux.HandleFunc("/logs", handleSSE)
mux.HandleFunc("/api/files", handleFiles)
mux.HandleFunc("/api/last", handleLastLines)
protectedHandler := authMiddleware(mux)