- 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>
212 lines
5.5 KiB
Go
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)
|
|
}
|