package web import ( "encoding/base64" "encoding/json" "net/http" "git.t-juice.club/torjus/oubliette/internal/storage" ) type dashboardData struct { Stats *storage.DashboardStats TopUsernames []storage.TopEntry TopPasswords []storage.TopEntry TopIPs []storage.TopEntry ActiveSessions []storage.Session RecentSessions []storage.Session } func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { ctx := r.Context() stats, err := s.store.GetDashboardStats(ctx) if err != nil { s.logger.Error("failed to get dashboard stats", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } topUsernames, err := s.store.GetTopUsernames(ctx, 10) if err != nil { s.logger.Error("failed to get top usernames", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } topPasswords, err := s.store.GetTopPasswords(ctx, 10) if err != nil { s.logger.Error("failed to get top passwords", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } topIPs, err := s.store.GetTopIPs(ctx, 10) if err != nil { s.logger.Error("failed to get top IPs", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } activeSessions, err := s.store.GetRecentSessions(ctx, 50, true) if err != nil { s.logger.Error("failed to get active sessions", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } recentSessions, err := s.store.GetRecentSessions(ctx, 50, false) if err != nil { s.logger.Error("failed to get recent sessions", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } data := dashboardData{ Stats: stats, TopUsernames: topUsernames, TopPasswords: topPasswords, TopIPs: topIPs, ActiveSessions: activeSessions, RecentSessions: recentSessions, } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := s.tmpl.dashboard.ExecuteTemplate(w, "layout.html", data); err != nil { s.logger.Error("failed to render dashboard", "err", err) } } func (s *Server) handleFragmentStats(w http.ResponseWriter, r *http.Request) { stats, err := s.store.GetDashboardStats(r.Context()) if err != nil { s.logger.Error("failed to get dashboard stats", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := s.tmpl.dashboard.ExecuteTemplate(w, "stats", stats); err != nil { s.logger.Error("failed to render stats fragment", "err", err) } } func (s *Server) handleFragmentActiveSessions(w http.ResponseWriter, r *http.Request) { sessions, err := s.store.GetRecentSessions(r.Context(), 50, true) if err != nil { s.logger.Error("failed to get active sessions", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := s.tmpl.dashboard.ExecuteTemplate(w, "active_sessions", sessions); err != nil { s.logger.Error("failed to render active sessions fragment", "err", err) } } type sessionDetailData struct { Session *storage.Session Logs []storage.SessionLog EventCount int } func (s *Server) handleSessionDetail(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sessionID := r.PathValue("id") session, err := s.store.GetSession(ctx, sessionID) if err != nil { s.logger.Error("failed to get session", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } if session == nil { http.NotFound(w, r) return } logs, err := s.store.GetSessionLogs(ctx, sessionID) if err != nil { s.logger.Error("failed to get session logs", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } events, err := s.store.GetSessionEvents(ctx, sessionID) if err != nil { s.logger.Error("failed to get session events", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } data := sessionDetailData{ Session: session, Logs: logs, EventCount: len(events), } w.Header().Set("Content-Type", "text/html; charset=utf-8") if err := s.tmpl.sessionDetail.ExecuteTemplate(w, "layout.html", data); err != nil { s.logger.Error("failed to render session detail", "err", err) } } type apiEvent struct { T int64 `json:"t"` D int `json:"d"` Data string `json:"data"` } type apiEventsResponse struct { Events []apiEvent `json:"events"` } func (s *Server) handleAPISessionEvents(w http.ResponseWriter, r *http.Request) { ctx := r.Context() sessionID := r.PathValue("id") events, err := s.store.GetSessionEvents(ctx, sessionID) if err != nil { s.logger.Error("failed to get session events", "err", err) http.Error(w, "Internal Server Error", http.StatusInternalServerError) return } resp := apiEventsResponse{Events: make([]apiEvent, len(events))} var baseTime int64 for i, e := range events { ms := e.Timestamp.UnixMilli() if i == 0 { baseTime = ms } resp.Events[i] = apiEvent{ T: ms - baseTime, D: e.Direction, Data: base64.StdEncoding.EncodeToString(e.Data), } } w.Header().Set("Content-Type", "application/json") if err := json.NewEncoder(w).Encode(resp); err != nil { s.logger.Error("failed to encode session events", "err", err) } }