feat: Implement initial Go backend and React frontend project structure for the gotail application.
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/rs/zerolog/log"
|
||||
"github.com/seu-usuario/go-react-web-tail/internal/broker"
|
||||
"github.com/seu-usuario/go-react-web-tail/internal/models"
|
||||
)
|
||||
|
||||
// Handler contém os handlers HTTP da aplicação
|
||||
type Handler struct {
|
||||
broker *broker.Broker
|
||||
logFiles []string
|
||||
}
|
||||
|
||||
// New cria uma nova instância do Handler
|
||||
func New(broker *broker.Broker, logFiles []string) *Handler {
|
||||
return &Handler{
|
||||
broker: broker,
|
||||
logFiles: logFiles,
|
||||
}
|
||||
}
|
||||
|
||||
// HandleSSE gerencia conexões Server-Sent Events para streaming de logs
|
||||
func (h *Handler) HandleSSE(w http.ResponseWriter, r *http.Request) {
|
||||
flusher, ok := w.(http.Flusher)
|
||||
if !ok {
|
||||
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
|
||||
clientChan := h.broker.Subscribe()
|
||||
defer h.broker.Unsubscribe(clientChan)
|
||||
|
||||
log.Info().Str("remote_addr", r.RemoteAddr).Msg("SSE client connected")
|
||||
|
||||
for {
|
||||
select {
|
||||
case entry := <-clientChan:
|
||||
data, err := json.Marshal(entry)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Msg("Failed to marshal log entry")
|
||||
continue
|
||||
}
|
||||
fmt.Fprintf(w, "data: %s\n\n", data)
|
||||
flusher.Flush()
|
||||
|
||||
case <-r.Context().Done():
|
||||
log.Info().Str("remote_addr", r.RemoteAddr).Msg("SSE client disconnected")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// HandleFiles retorna a lista de arquivos de log monitorados
|
||||
func (h *Handler) HandleFiles(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(h.logFiles); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode log files")
|
||||
http.Error(w, "Internal server error", http.StatusInternalServerError)
|
||||
}
|
||||
}
|
||||
|
||||
// HandleLastLines retorna as últimas N linhas de um arquivo
|
||||
func (h *Handler) HandleLastLines(w http.ResponseWriter, r *http.Request) {
|
||||
file := r.URL.Query().Get("file")
|
||||
nParam := r.URL.Query().Get("n")
|
||||
|
||||
n := 200
|
||||
if nParam != "" {
|
||||
if parsed, err := strconv.Atoi(nParam); err == nil && parsed > 0 {
|
||||
n = parsed
|
||||
}
|
||||
}
|
||||
|
||||
// Validar que o arquivo está na lista permitida
|
||||
allowed := false
|
||||
for _, f := range h.logFiles {
|
||||
if f == file {
|
||||
allowed = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !allowed {
|
||||
log.Warn().Str("file", file).Str("remote_addr", r.RemoteAddr).Msg("Attempted access to unauthorized file")
|
||||
http.Error(w, "Invalid file", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
entries, err := h.getLastLines(file, n)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Str("file", file).Msg("Failed to read last lines")
|
||||
http.Error(w, "Failed to read file", http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(entries); err != nil {
|
||||
log.Error().Err(err).Msg("Failed to encode entries")
|
||||
}
|
||||
}
|
||||
|
||||
// getLastLines lê as últimas N linhas de um arquivo
|
||||
func (h *Handler) getLastLines(filename string, n int) ([]models.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
|
||||
}
|
||||
|
||||
entries := make([]models.LogEntry, 0, len(buf))
|
||||
for _, line := range buf {
|
||||
entry := models.NewLogEntry(filename, strings.TrimRight(line, "\r"))
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// HandleHealth retorna o status de saúde da aplicação
|
||||
func (h *Handler) HandleHealth(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
json.NewEncoder(w).Encode(map[string]string{
|
||||
"status": "healthy",
|
||||
})
|
||||
}
|
||||
|
||||
// HandleMetrics retorna métricas da aplicação
|
||||
func (h *Handler) HandleMetrics(w http.ResponseWriter, r *http.Request) {
|
||||
activeClients, totalBroadcasts := h.broker.GetMetrics()
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"active_clients": activeClients,
|
||||
"total_broadcasts": totalBroadcasts,
|
||||
"monitored_files": len(h.logFiles),
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user