package server import ( "context" "embed" "fmt" "io/fs" "net/http" "sync" "time" "git.mchus.pro/mchus/logpile/internal/models" ) // WebFS holds embedded web files (set by main package) var WebFS embed.FS type Config struct { Port int PreloadFile string } type Server struct { config Config mux *http.ServeMux httpServer *http.Server mu sync.RWMutex result *models.AnalysisResult detectedVendor string jobManager *JobManager } func New(cfg Config) *Server { s := &Server{ config: cfg, mux: http.NewServeMux(), jobManager: NewJobManager(), } s.setupRoutes() return s } func (s *Server) setupRoutes() { // Static files staticContent, err := fs.Sub(WebFS, "static") if err != nil { panic(err) } s.mux.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticContent)))) // Pages s.mux.HandleFunc("/", s.handleIndex) // API endpoints s.mux.HandleFunc("POST /api/upload", s.handleUpload) s.mux.HandleFunc("GET /api/status", s.handleGetStatus) s.mux.HandleFunc("GET /api/parsers", s.handleGetParsers) s.mux.HandleFunc("GET /api/events", s.handleGetEvents) s.mux.HandleFunc("GET /api/sensors", s.handleGetSensors) s.mux.HandleFunc("GET /api/config", s.handleGetConfig) s.mux.HandleFunc("GET /api/serials", s.handleGetSerials) s.mux.HandleFunc("GET /api/firmware", s.handleGetFirmware) s.mux.HandleFunc("GET /api/export/csv", s.handleExportCSV) s.mux.HandleFunc("GET /api/export/json", s.handleExportJSON) s.mux.HandleFunc("GET /api/export/txt", s.handleExportTXT) s.mux.HandleFunc("DELETE /api/clear", s.handleClear) s.mux.HandleFunc("POST /api/shutdown", s.handleShutdown) s.mux.HandleFunc("POST /api/collect", s.handleCollectStart) s.mux.HandleFunc("GET /api/collect/{id}", s.handleCollectStatus) s.mux.HandleFunc("POST /api/collect/{id}/cancel", s.handleCollectCancel) } func (s *Server) Run() error { addr := fmt.Sprintf(":%d", s.config.Port) s.httpServer = &http.Server{ Addr: addr, Handler: s.mux, } return s.httpServer.ListenAndServe() } // Shutdown gracefully shuts down the server func (s *Server) Shutdown() { if s.httpServer != nil { ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second) defer cancel() s.httpServer.Shutdown(ctx) } } // SetResult sets the analysis result (thread-safe) func (s *Server) SetResult(result *models.AnalysisResult) { s.mu.Lock() defer s.mu.Unlock() s.result = result } // GetResult returns the analysis result (thread-safe) func (s *Server) GetResult() *models.AnalysisResult { s.mu.RLock() defer s.mu.RUnlock() return s.result } // SetDetectedVendor sets the detected vendor name func (s *Server) SetDetectedVendor(vendor string) { s.mu.Lock() defer s.mu.Unlock() s.detectedVendor = vendor } // GetDetectedVendor returns the detected vendor name func (s *Server) GetDetectedVendor() string { s.mu.RLock() defer s.mu.RUnlock() return s.detectedVendor }