first commit
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
// Package webui is the admin web interface, mirroring email_server/server_web_ui/.
|
||||
// It's mounted at /pymta-manager, matching the Flask blueprint's url_prefix exactly.
|
||||
package webui
|
||||
|
||||
import (
|
||||
"html/template"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gopkg.in/ini.v1"
|
||||
"mailgoserver/internal/db"
|
||||
"mailgoserver/internal/dkim"
|
||||
"mailgoserver/internal/toolbox"
|
||||
)
|
||||
|
||||
const Prefix = "/pymta-manager"
|
||||
|
||||
// App holds every dependency the web routes need, mirroring the module-level
|
||||
// singletons (Session, DKIMManager, settings) that server_web_ui/*.py imports.
|
||||
type App struct {
|
||||
DB *db.DB
|
||||
DKIM *dkim.Manager
|
||||
Cfg *ini.File
|
||||
ConfigPath string
|
||||
Logger *toolbox.Logger
|
||||
SMTPUp func() bool // reports whether the SMTP listeners are currently running
|
||||
|
||||
templates map[string]*template.Template
|
||||
}
|
||||
|
||||
// New builds the web UI. Templates and static assets come from the embedded
|
||||
// filesystem (embed.go), not disk, so no directory paths are needed for them.
|
||||
func New(database *db.DB, dkimMgr *dkim.Manager, cfg *ini.File, configPath string, logger *toolbox.Logger, smtpUp func() bool) (*App, error) {
|
||||
a := &App{DB: database, DKIM: dkimMgr, Cfg: cfg, ConfigPath: configPath, Logger: logger, SMTPUp: smtpUp}
|
||||
if err := a.loadTemplates(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return a, nil
|
||||
}
|
||||
|
||||
type healthStatus struct {
|
||||
Status string
|
||||
Timestamp string
|
||||
Services map[string]string
|
||||
}
|
||||
|
||||
// checkHealth mirrors app.py's SMTPServerApp.check_health.
|
||||
func (a *App) checkHealth() healthStatus {
|
||||
dbStatus := "ok"
|
||||
if err := a.DB.Ping(); err != nil {
|
||||
dbStatus = "error"
|
||||
}
|
||||
smtpStatus := "stopped"
|
||||
if a.SMTPUp != nil && a.SMTPUp() {
|
||||
smtpStatus = "running"
|
||||
}
|
||||
overall := "healthy"
|
||||
if smtpStatus == "stopped" || dbStatus == "error" {
|
||||
overall = "degraded"
|
||||
}
|
||||
return healthStatus{
|
||||
Status: overall,
|
||||
Timestamp: time.Now().Format(time.RFC3339),
|
||||
Services: map[string]string{"smtp_server": smtpStatus, "web_frontend": "running", "database": dbStatus},
|
||||
}
|
||||
}
|
||||
|
||||
// Mux builds the *http.ServeMux for the whole admin UI, mirroring routes.py's
|
||||
// blueprint registration plus every route file's own routes. Everything except
|
||||
// login/MFA and static assets requires a valid, fully-verified session — see
|
||||
// requireAuth in auth.go.
|
||||
func (a *App) Mux() *http.ServeMux {
|
||||
outer := http.NewServeMux()
|
||||
|
||||
staticFS, err := fs.Sub(assets, "static")
|
||||
if err != nil {
|
||||
panic(err) // embed.go's directive is malformed if this ever fails
|
||||
}
|
||||
outer.Handle("GET "+Prefix+"/static/", http.StripPrefix(Prefix+"/static/", http.FileServerFS(staticFS)))
|
||||
|
||||
outer.HandleFunc("GET "+Prefix+"/login", a.loginForm)
|
||||
outer.HandleFunc("POST "+Prefix+"/login", a.loginSubmit)
|
||||
outer.HandleFunc("GET "+Prefix+"/login/mfa", a.mfaForm)
|
||||
outer.HandleFunc("POST "+Prefix+"/login/mfa", a.mfaSubmit)
|
||||
outer.HandleFunc("GET "+Prefix+"/login/passkey/begin", a.passkeyLoginBegin)
|
||||
outer.HandleFunc("POST "+Prefix+"/login/passkey/finish", a.passkeyLoginFinish)
|
||||
outer.HandleFunc("POST "+Prefix+"/logout", a.logout)
|
||||
|
||||
mux := http.NewServeMux()
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/", a.dashboard)
|
||||
mux.HandleFunc("GET "+Prefix+"/account", a.accountPage)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/password", a.changePassword)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/totp/setup", a.totpSetupBegin)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/totp/confirm", a.totpSetupConfirm)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/totp/disable", a.totpDisable)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/passkey/begin", a.passkeyRegisterBegin)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/passkey/finish", a.passkeyRegisterFinish)
|
||||
mux.HandleFunc("POST "+Prefix+"/account/passkey/{id}/remove", a.passkeyRemove)
|
||||
mux.HandleFunc("GET "+Prefix+"/first-login", a.firstLoginForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/first-login", a.firstLoginSubmit)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/admins", a.adminsList)
|
||||
mux.HandleFunc("GET "+Prefix+"/admins/add", a.addAdminForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/admins/add", a.addAdmin)
|
||||
mux.HandleFunc("GET "+Prefix+"/admins/{id}/edit", a.editAdminDomainsForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/admins/{id}/edit", a.editAdminDomains)
|
||||
mux.HandleFunc("POST "+Prefix+"/admins/{id}/remove", a.removeAdmin)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/domains", a.domainsList)
|
||||
mux.HandleFunc("GET "+Prefix+"/domains/add", a.addDomainForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/domains/add", a.addDomain)
|
||||
mux.HandleFunc("POST "+Prefix+"/domains/{id}/delete", a.toggleDomainOff)
|
||||
mux.HandleFunc("GET "+Prefix+"/domains/{id}/edit", a.editDomainForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/domains/{id}/edit", a.editDomain)
|
||||
mux.HandleFunc("POST "+Prefix+"/domains/{id}/toggle", a.toggleDomain)
|
||||
mux.HandleFunc("POST "+Prefix+"/domains/{id}/remove", a.removeDomain)
|
||||
mux.HandleFunc("POST "+Prefix+"/domains/{id}/verify_check", a.verifyDomainCheck)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/senders", a.sendersList)
|
||||
mux.HandleFunc("GET "+Prefix+"/senders/add", a.addSenderForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/senders/add", a.addSender)
|
||||
mux.HandleFunc("POST "+Prefix+"/senders/{id}/delete", a.disableSender)
|
||||
mux.HandleFunc("POST "+Prefix+"/senders/{id}/enable", a.enableSender)
|
||||
mux.HandleFunc("POST "+Prefix+"/senders/{id}/remove", a.removeSender)
|
||||
mux.HandleFunc("GET "+Prefix+"/senders/{id}/edit", a.editSenderForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/senders/{id}/edit", a.editSender)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/ips", a.ipsList)
|
||||
mux.HandleFunc("GET "+Prefix+"/ips/add", a.addIPForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/ips/add", a.addIP)
|
||||
mux.HandleFunc("POST "+Prefix+"/ips/{id}/delete", a.disableIP)
|
||||
mux.HandleFunc("POST "+Prefix+"/ips/{id}/enable", a.enableIP)
|
||||
mux.HandleFunc("POST "+Prefix+"/ips/{id}/remove", a.removeIP)
|
||||
mux.HandleFunc("GET "+Prefix+"/ips/{id}/edit", a.editIPForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/ips/{id}/edit", a.editIP)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/dkim", a.dkimList)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/create", a.createDKIM)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/{id}/regenerate", a.regenerateDKIM)
|
||||
mux.HandleFunc("GET "+Prefix+"/dkim/{id}/edit", a.editDKIMForm)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/{id}/edit", a.editDKIM)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/{id}/toggle", a.toggleDKIM)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/{id}/remove", a.removeDKIM)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/check_dns", a.checkDKIMDNS)
|
||||
mux.HandleFunc("POST "+Prefix+"/dkim/check_spf", a.checkSPFDNS)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/logs", a.logs)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/settings", a.settingsPage)
|
||||
mux.HandleFunc("POST "+Prefix+"/settings_update", a.settingsUpdate)
|
||||
mux.HandleFunc("POST "+Prefix+"/api/settings/test_database", a.testDatabaseConnection)
|
||||
mux.HandleFunc("POST "+Prefix+"/api/settings/upload_cert", a.uploadCert)
|
||||
mux.HandleFunc("POST "+Prefix+"/api/settings/upload_key", a.uploadKey)
|
||||
mux.HandleFunc("GET "+Prefix+"/api/settings/get_public_ip", a.getServerIP)
|
||||
mux.HandleFunc("POST "+Prefix+"/test_attachments_path", a.testAttachmentsPath)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix+"/msg/content/{id}", a.viewMessageContent)
|
||||
mux.HandleFunc("GET "+Prefix+"/msg/attachment/{id}/download", a.downloadAttachment)
|
||||
mux.HandleFunc("GET "+Prefix+"/msg/attachment/{id}/delete", a.deleteAttachment)
|
||||
mux.HandleFunc("POST "+Prefix+"/msg/attachment/{id}/delete", a.deleteAttachment)
|
||||
|
||||
mux.HandleFunc("GET "+Prefix, func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, Prefix+"/", http.StatusFound)
|
||||
})
|
||||
|
||||
outer.Handle(Prefix+"/", a.requireAuth(mux))
|
||||
return outer
|
||||
}
|
||||
Reference in New Issue
Block a user