147 lines
4.2 KiB
Go
147 lines
4.2 KiB
Go
package admin
|
|
|
|
import (
|
|
"context"
|
|
_ "embed"
|
|
"html/template"
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.send.nrw/sendnrw/decent-webui/internal/filesvc"
|
|
"git.send.nrw/sendnrw/decent-webui/internal/mesh"
|
|
)
|
|
|
|
/*** Templates einbetten ***/
|
|
|
|
//go:embed tpl/layout.html
|
|
var layoutHTML string
|
|
|
|
//go:embed tpl/partials_items.html
|
|
var itemsPartialHTML string
|
|
|
|
//go:embed tpl/partials_peers.html
|
|
var peersPartialHTML string
|
|
|
|
var (
|
|
tplLayout = template.Must(template.New("layout").Parse(layoutHTML))
|
|
tplItems = template.Must(template.New("items").Funcs(template.FuncMap{
|
|
"timeRFC3339": func(unixNano int64) string {
|
|
if unixNano == 0 {
|
|
return ""
|
|
}
|
|
return time.Unix(0, unixNano).UTC().Format(time.RFC3339)
|
|
},
|
|
}).Parse(itemsPartialHTML))
|
|
tplPeers = template.Must(template.New("peers").Parse(peersPartialHTML))
|
|
)
|
|
|
|
type Deps struct {
|
|
Store filesvc.MeshStore
|
|
Mesh *mesh.Node
|
|
}
|
|
|
|
// Register hängt alle /admin Routen ein.
|
|
// Auth liegt optional VOR Register (BasicAuth-Middleware), siehe main.go.
|
|
func Register(mux *http.ServeMux, d Deps) {
|
|
// Dashboard
|
|
mux.HandleFunc("/admin", func(w http.ResponseWriter, r *http.Request) {
|
|
renderLayout(w, r, "Files", "/admin/items")
|
|
})
|
|
|
|
// Partials
|
|
mux.HandleFunc("/admin/items", func(w http.ResponseWriter, r *http.Request) {
|
|
// Liste rendern (Pagination optional via ?next=)
|
|
nextQ := strings.TrimSpace(r.URL.Query().Get("next"))
|
|
var nextID filesvc.ID
|
|
if nextQ != "" {
|
|
if n, err := strconv.ParseInt(nextQ, 10, 64); err == nil {
|
|
nextID = filesvc.ID(n)
|
|
}
|
|
}
|
|
items, nextOut, _ := d.Store.List(r.Context(), nextID, 100)
|
|
_ = tplItems.Execute(w, map[string]any{
|
|
"Items": items,
|
|
"Next": nextOut,
|
|
})
|
|
})
|
|
|
|
mux.HandleFunc("/admin/peers", func(w http.ResponseWriter, r *http.Request) {
|
|
peers := d.Mesh.PeerList()
|
|
_ = tplPeers.Execute(w, map[string]any{
|
|
"Peers": peers,
|
|
"Now": time.Now(),
|
|
})
|
|
})
|
|
|
|
// Actions (HTMX POSTs)
|
|
mux.HandleFunc("/admin/items/create", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
name := strings.TrimSpace(r.FormValue("name"))
|
|
if name != "" {
|
|
_, _ = d.Store.Create(r.Context(), name)
|
|
_ = d.Mesh.SyncNow(r.Context()) // prompt push (best effort)
|
|
}
|
|
// Nach Aktion Items partial zurückgeben (HTMX swap)
|
|
http.Redirect(w, r, "/admin/items", http.StatusSeeOther)
|
|
})
|
|
|
|
mux.HandleFunc("/admin/items/rename", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
idStr := r.FormValue("id")
|
|
newName := strings.TrimSpace(r.FormValue("name"))
|
|
if id, err := strconv.ParseInt(idStr, 10, 64); err == nil && newName != "" {
|
|
_, _ = d.Store.Rename(r.Context(), filesvc.ID(id), newName)
|
|
_ = d.Mesh.SyncNow(r.Context())
|
|
}
|
|
http.Redirect(w, r, "/admin/items", http.StatusSeeOther)
|
|
})
|
|
|
|
mux.HandleFunc("/admin/items/delete", func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
|
|
return
|
|
}
|
|
if id, err := strconv.ParseInt(r.FormValue("id"), 10, 64); err == nil {
|
|
_, _ = d.Store.Delete(r.Context(), filesvc.ID(id))
|
|
_ = d.Mesh.SyncNow(r.Context())
|
|
}
|
|
http.Redirect(w, r, "/admin/items", http.StatusSeeOther)
|
|
})
|
|
|
|
mux.HandleFunc("/admin/mesh/syncnow", func(w http.ResponseWriter, r *http.Request) {
|
|
_ = d.Mesh.SyncNow(context.Background())
|
|
http.Redirect(w, r, "/admin/peers", http.StatusSeeOther)
|
|
})
|
|
}
|
|
|
|
func renderLayout(w http.ResponseWriter, _ *http.Request, active string, initial string) {
|
|
_ = tplLayout.Execute(w, map[string]any{
|
|
"Active": active,
|
|
"Init": initial, // initialer HTMX Swap-Endpunkt
|
|
})
|
|
}
|
|
|
|
/*** Optional: einfache BasicAuth (siehe main.go) ***/
|
|
func BasicAuth(user, pass string, next http.Handler) http.Handler {
|
|
if strings.TrimSpace(user) == "" {
|
|
return next // deaktiviert
|
|
}
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
u, p, ok := r.BasicAuth()
|
|
if !ok || u != user || p != pass {
|
|
w.Header().Set("WWW-Authenticate", `Basic realm="admin"`)
|
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|