167 lines
4.2 KiB
Go
167 lines
4.2 KiB
Go
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),
|
|
})
|
|
}
|