freeleaps-ops/apps/gitea-webhook-ambassador/internal/web/handler/dashboard.go
zhenyus db590f3f27 refactor: update gitea-webhook-ambassador Dockerfile and configuration
- Changed the build process to include a web UI build stage using Node.js.
- Updated Go build stage to copy web UI files to the correct location.
- Removed the main.go file as it is no longer needed.
- Added SQLite database configuration to example config.
- Updated dependencies in go.mod and go.sum, including new packages for JWT and SQLite.
- Modified .gitignore to include new database and configuration files.

Signed-off-by: zhenyus <zhenyus@mathmast.com>
2025-06-10 16:00:52 +08:00

212 lines
5.5 KiB
Go

package handler
import (
"embed"
"encoding/json"
"html/template"
"net/http"
"path"
"freeleaps.com/gitea-webhook-ambassador/internal/handler"
"freeleaps.com/gitea-webhook-ambassador/internal/logger"
)
type DashboardHandler struct {
templates *template.Template
fs embed.FS
projectHandler *handler.ProjectHandler
adminHandler *handler.AdminHandler
logsHandler *handler.LogsHandler
healthHandler *handler.HealthHandler
}
func NewDashboardHandler(fs embed.FS, projectHandler *handler.ProjectHandler, adminHandler *handler.AdminHandler, logsHandler *handler.LogsHandler, healthHandler *handler.HealthHandler) (*DashboardHandler, error) {
templates, err := template.ParseFS(fs, "templates/*.html")
if err != nil {
return nil, err
}
return &DashboardHandler{
templates: templates,
fs: fs,
projectHandler: projectHandler,
adminHandler: adminHandler,
logsHandler: logsHandler,
healthHandler: healthHandler,
}, nil
}
func (h *DashboardHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/login":
h.handleLogin(w, r)
case "/dashboard":
h.handleDashboard(w, r)
case "/api/projects":
h.handleProjects(w, r)
case "/api/keys":
h.handleAPIKeys(w, r)
case "/api/logs":
h.handleLogs(w, r)
case "/api/health":
h.handleHealth(w, r)
default:
// Serve static files
if path.Ext(r.URL.Path) != "" {
h.serveStaticFile(w, r)
return
}
http.NotFound(w, r)
}
}
func (h *DashboardHandler) handleLogin(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
logger.Debug("Serving login page")
h.templates.ExecuteTemplate(w, "login.html", nil)
}
func (h *DashboardHandler) handleDashboard(w http.ResponseWriter, r *http.Request) {
h.templates.ExecuteTemplate(w, "dashboard.html", nil)
}
func (h *DashboardHandler) handleProjects(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.projectHandler.HandleGetProjectMapping(w, r)
case http.MethodPost:
h.projectHandler.HandleCreateProjectMapping(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *DashboardHandler) handleAPIKeys(w http.ResponseWriter, r *http.Request) {
switch r.Method {
case http.MethodGet:
h.adminHandler.HandleListAPIKeys(w, r)
case http.MethodPost:
h.adminHandler.HandleCreateAPIKey(w, r)
case http.MethodDelete:
h.adminHandler.HandleDeleteAPIKey(w, r)
default:
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
}
}
func (h *DashboardHandler) handleLogs(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
h.logsHandler.HandleGetTriggerLogs(w, r)
}
func (h *DashboardHandler) handleHealth(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
return
}
// Capture the health handler response
recorder := newResponseRecorder(w)
h.healthHandler.HandleHealth(recorder, r)
// If it's not JSON or there was an error, just copy the response
if recorder.Header().Get("Content-Type") != "application/json" {
recorder.copyToResponseWriter(w)
return
}
// Parse the health check response and format it for the dashboard
var healthData map[string]interface{}
if err := json.Unmarshal(recorder.Body(), &healthData); err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
// Format the response for the dashboard
response := map[string]string{
"status": "healthy",
}
if healthData["status"] != "ok" {
response["status"] = "unhealthy"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(response)
}
func (h *DashboardHandler) serveStaticFile(w http.ResponseWriter, r *http.Request) {
// Remove leading slash and join with assets directory
filePath := path.Join("assets", r.URL.Path)
data, err := h.fs.ReadFile(filePath)
if err != nil {
http.NotFound(w, r)
return
}
// Set MIME type based on file extension
ext := path.Ext(r.URL.Path)
switch ext {
case ".css":
w.Header().Set("Content-Type", "text/css; charset=utf-8")
case ".js":
w.Header().Set("Content-Type", "application/javascript; charset=utf-8")
case ".png":
w.Header().Set("Content-Type", "image/png")
case ".jpg", ".jpeg":
w.Header().Set("Content-Type", "image/jpeg")
default:
w.Header().Set("Content-Type", "application/octet-stream")
}
// Set caching headers
w.Header().Set("Cache-Control", "public, max-age=31536000")
w.Write(data)
}
// responseRecorder is a custom ResponseWriter that records its mutations
type responseRecorder struct {
headers http.Header
body []byte
statusCode int
original http.ResponseWriter
}
func newResponseRecorder(w http.ResponseWriter) *responseRecorder {
return &responseRecorder{
headers: make(http.Header),
statusCode: http.StatusOK,
original: w,
}
}
func (r *responseRecorder) Header() http.Header {
return r.headers
}
func (r *responseRecorder) Write(body []byte) (int, error) {
r.body = append(r.body, body...)
return len(body), nil
}
func (r *responseRecorder) WriteHeader(statusCode int) {
r.statusCode = statusCode
}
func (r *responseRecorder) Body() []byte {
return r.body
}
func (r *responseRecorder) copyToResponseWriter(w http.ResponseWriter) {
for k, v := range r.headers {
w.Header()[k] = v
}
w.WriteHeader(r.statusCode)
w.Write(r.body)
}