Files
3m-ui/backend/internal/system/api.go
T

112 lines
2.9 KiB
Go

package system
import (
"fmt"
"net/http"
"path/filepath"
"time"
"github.com/gin-gonic/gin"
)
type Handler struct {
svc *Service
dbPath string
mihomoCfg string
}
func NewHandler(svc *Service) *Handler {
return &Handler{svc: svc}
}
func (h *Handler) WithBackupPaths(dbPath, mihomoConfig string) *Handler {
h.dbPath = dbPath
h.mihomoCfg = mihomoConfig
return h
}
func (h *Handler) RegisterRoutes(rg *gin.RouterGroup) {
rg.GET("/status", h.GetSystemStatus)
rg.GET("/backup", h.ExportBackup)
rg.POST("/backup/restore-db", h.RestoreDatabase)
rg.POST("/templates/reverse-proxy", h.ReverseProxy)
rg.POST("/templates/acme", h.ACME)
}
func (h *Handler) GetSystemStatus(c *gin.Context) {
stats := h.svc.GetStatus()
c.JSON(http.StatusOK, stats)
}
func (h *Handler) ExportBackup(c *gin.Context) {
name := fmt.Sprintf("3m-ui-backup-%s.zip", time.Now().UTC().Format("20060102-150405"))
c.Header("Content-Type", "application/zip")
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%q", name))
if err := WriteZip(c.Writer, BackupPaths{DatabasePath: h.dbPath, MihomoConfig: h.mihomoCfg}); err != nil {
_ = c.Error(err)
return
}
}
func (h *Handler) RestoreDatabase(c *gin.Context) {
file, err := c.FormFile("database")
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "multipart field database is required"})
return
}
f, err := file.Open()
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
defer f.Close()
if h.dbPath == "" {
c.JSON(http.StatusInternalServerError, gin.H{"error": "database path is not configured"})
return
}
if err := RestoreDatabase(h.dbPath, f); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{
"status": "ok",
"message": "database restored; restart the panel process to reopen SQLite connections",
"path": filepath.Base(h.dbPath),
})
}
func (h *Handler) ReverseProxy(c *gin.Context) {
var req struct {
Kind string `json:"kind"`
Domain string `json:"domain"`
Upstream string `json:"upstream"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
out, err := ReverseProxyTemplate(req.Kind, req.Domain, req.Upstream)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"config": out})
}
func (h *Handler) ACME(c *gin.Context) {
var req struct {
Domain string `json:"domain"`
Email string `json:"email"`
Webroot string `json:"webroot"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if req.Domain == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "domain is required"})
return
}
c.JSON(http.StatusOK, gin.H{"command": ACMECommand(req.Domain, req.Email, req.Webroot)})
}