mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-15 08:33:03 +00:00
a26dad0607
Mail Gateway rows now expand inline to show the detail the slim ResourcePMGMeta projection couldn't carry: every cluster node with status / role / uptime / loadavg / postfix queue (active/deferred/ hold/incoming counts + oldest message age), quarantine breakdown (spam / virus / attachment / blacklisted), spam-score distribution, top 8 domains by mail volume, and the mail-flow detail strip (bounces in/out, greylist, junk, RBL + pregreet rejects, bytes in/out, average process time). Drawer fetches /api/pmg/instances?id=<id> on first open so the row keeps its slim payload; the new endpoint projects Monitor.GetState().PMGInstances directly under ScopeMonitoringRead with optional id / name filters. Same inline expand-row pattern as the ceph and workloads drawers (TableRow inserted below, colspan all columns).
89 lines
2.8 KiB
Go
89 lines
2.8 KiB
Go
package api
|
|
|
|
import (
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/rs/zerolog/log"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/utils"
|
|
)
|
|
|
|
// PMGInstancesResponse is the canonical payload for
|
|
// `GET /api/pmg/instances`. It exposes the full Proxmox Mail Gateway
|
|
// instance snapshot — per-node cluster status with postfix queue
|
|
// detail, full mail stats (in/out, bytes, bounces, RBL/pregreet),
|
|
// quarantine breakdown by category, spam score distribution, top
|
|
// domain stats, and relay-domain configuration. The Mail Gateway
|
|
// platform-page drawer is the first consumer; the row table keeps
|
|
// reading the slim ResourcePMGMeta projection through /api/resources.
|
|
type PMGInstancesResponse struct {
|
|
Data []models.PMGInstance `json:"data"`
|
|
Meta PMGInstancesMeta `json:"meta"`
|
|
}
|
|
|
|
type PMGInstancesMeta struct {
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
func (r *Router) handleListPMGInstances(w http.ResponseWriter, req *http.Request) {
|
|
if req.Method != http.MethodGet {
|
|
writeErrorResponse(w, http.StatusMethodNotAllowed, "method_not_allowed",
|
|
"Only GET method is allowed", nil)
|
|
return
|
|
}
|
|
|
|
monitor := r.getTenantMonitor(req.Context())
|
|
if monitor == nil {
|
|
writeErrorResponse(w, http.StatusInternalServerError, "no_monitor",
|
|
"Monitor not available", nil)
|
|
return
|
|
}
|
|
|
|
state := monitor.GetState()
|
|
instances := filterPMGInstances(state.PMGInstances, req)
|
|
|
|
response := PMGInstancesResponse{
|
|
Data: instances,
|
|
Meta: PMGInstancesMeta{Total: len(instances)},
|
|
}
|
|
|
|
if err := utils.WriteJSONResponse(w, response); err != nil {
|
|
log.Error().Err(err).Msg("Failed to encode PMG instances response")
|
|
writeErrorResponse(w, http.StatusInternalServerError, "encoding_error",
|
|
"Failed to encode PMG instances", nil)
|
|
}
|
|
}
|
|
|
|
// filterPMGInstances narrows the snapshot by optional `id=` or
|
|
// `name=` query parameters. With neither, returns the full list.
|
|
func filterPMGInstances(instances []models.PMGInstance, req *http.Request) []models.PMGInstance {
|
|
query := req.URL.Query()
|
|
idFilter := strings.TrimSpace(query.Get("id"))
|
|
nameFilter := strings.TrimSpace(query.Get("name"))
|
|
|
|
if idFilter == "" && nameFilter == "" {
|
|
out := make([]models.PMGInstance, len(instances))
|
|
copy(out, instances)
|
|
return out
|
|
}
|
|
|
|
out := make([]models.PMGInstance, 0, len(instances))
|
|
for _, inst := range instances {
|
|
if idFilter != "" && !strings.EqualFold(strings.TrimSpace(inst.ID), idFilter) {
|
|
continue
|
|
}
|
|
if nameFilter != "" && !strings.EqualFold(strings.TrimSpace(inst.Name), nameFilter) {
|
|
continue
|
|
}
|
|
out = append(out, inst)
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (r *Router) registerPMGRoutes() {
|
|
r.mux.HandleFunc("/api/pmg/instances", RequireAuth(r.config, RequireScope(config.ScopeMonitoringRead, r.handleListPMGInstances)))
|
|
}
|