avoid vcenter totals pages scanning whole database
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -11,6 +12,12 @@ import (
|
||||
"vctp/db"
|
||||
)
|
||||
|
||||
const (
|
||||
vcenterHourlyDetailWindowDays = 45
|
||||
vcenterDailyDefaultLimit = 400
|
||||
vcenterMonthlyDefaultLimit = 200
|
||||
)
|
||||
|
||||
// VcenterList renders a list of vCenters being monitored.
|
||||
// @Summary List vCenters
|
||||
// @Description Lists all vCenters with recorded snapshot totals.
|
||||
@@ -20,9 +27,6 @@ import (
|
||||
// @Router /vcenters [get]
|
||||
func (h *Handler) VcenterList(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
if err := db.SyncVcenterTotalsFromSnapshots(ctx, h.Database.DB()); err != nil {
|
||||
h.Logger.Warn("failed to sync vcenter totals", "error", err)
|
||||
}
|
||||
vcs, err := db.ListVcenters(ctx, h.Database.DB())
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to list vcenters: %v", err), http.StatusInternalServerError)
|
||||
@@ -32,7 +36,7 @@ func (h *Handler) VcenterList(w http.ResponseWriter, r *http.Request) {
|
||||
for _, vc := range vcs {
|
||||
links = append(links, views.VcenterLink{
|
||||
Name: vc,
|
||||
Link: "/vcenters/totals?vcenter=" + url.QueryEscape(vc),
|
||||
Link: "/vcenters/totals/daily?vcenter=" + url.QueryEscape(vc),
|
||||
})
|
||||
}
|
||||
w.Header().Set("Content-Type", "text/html; charset=utf-8")
|
||||
@@ -41,49 +45,117 @@ func (h *Handler) VcenterList(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// VcenterTotals renders totals for a vCenter.
|
||||
// VcenterTotals keeps backward compatibility with the original endpoint and routes to the new pages.
|
||||
// @Summary vCenter totals
|
||||
// @Description Shows per-snapshot totals for a vCenter.
|
||||
// @Description Redirect-style handler for compatibility; use /vcenters/totals/daily or /vcenters/totals/hourly.
|
||||
// @Tags vcenters
|
||||
// @Produce text/html
|
||||
// @Param vcenter query string true "vCenter URL"
|
||||
// @Param type query string false "hourly|daily|monthly (default: hourly)"
|
||||
// @Param limit query int false "Limit results (default 200)"
|
||||
// @Param type query string false "hourly|daily|monthly"
|
||||
// @Param limit query int false "Limit results"
|
||||
// @Success 200 {string} string "HTML page"
|
||||
// @Failure 400 {string} string "Missing vcenter"
|
||||
// @Router /vcenters/totals [get]
|
||||
func (h *Handler) VcenterTotals(w http.ResponseWriter, r *http.Request) {
|
||||
switch strings.ToLower(strings.TrimSpace(r.URL.Query().Get("type"))) {
|
||||
case "hourly", "hourly-detail", "detail", "detailed":
|
||||
h.VcenterTotalsHourlyDetailed(w, r)
|
||||
return
|
||||
case "monthly":
|
||||
h.vcenterTotalsLegacyMonthly(w, r)
|
||||
return
|
||||
default:
|
||||
h.VcenterTotalsDaily(w, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// VcenterTotalsDaily renders the daily-aggregation totals page for one vCenter.
|
||||
// @Summary vCenter daily totals
|
||||
// @Description Shows daily aggregated VM count/vCPU/RAM totals for a vCenter.
|
||||
// @Tags vcenters
|
||||
// @Produce text/html
|
||||
// @Param vcenter query string true "vCenter URL"
|
||||
// @Param limit query int false "Limit results (default 400)"
|
||||
// @Success 200 {string} string "HTML page"
|
||||
// @Failure 400 {string} string "Missing vcenter"
|
||||
// @Router /vcenters/totals/daily [get]
|
||||
func (h *Handler) VcenterTotalsDaily(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vc := r.URL.Query().Get("vcenter")
|
||||
vc, ok := requiredVcenterParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
limit := parsePositiveLimit(r, vcenterDailyDefaultLimit)
|
||||
rows, err := db.ListVcenterTotalsByType(ctx, h.Database.DB(), vc, "daily", limit)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to list daily totals: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.renderVcenterTotalsPage(ctx, w, vc, "daily", rows)
|
||||
}
|
||||
|
||||
// VcenterTotalsHourlyDetailed renders a detailed hourly page over the most recent 45 days.
|
||||
// @Summary vCenter hourly totals (45 days)
|
||||
// @Description Shows detailed hourly VM count/vCPU/RAM totals for the latest 45 days.
|
||||
// @Tags vcenters
|
||||
// @Produce text/html
|
||||
// @Param vcenter query string true "vCenter URL"
|
||||
// @Success 200 {string} string "HTML page"
|
||||
// @Failure 400 {string} string "Missing vcenter"
|
||||
// @Router /vcenters/totals/hourly [get]
|
||||
func (h *Handler) VcenterTotalsHourlyDetailed(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vc, ok := requiredVcenterParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
since := time.Now().AddDate(0, 0, -vcenterHourlyDetailWindowDays)
|
||||
rows, err := db.ListVcenterHourlyTotalsSince(ctx, h.Database.DB(), vc, since)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to list hourly totals: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.renderVcenterTotalsPage(ctx, w, vc, "hourly45", rows)
|
||||
}
|
||||
|
||||
func (h *Handler) vcenterTotalsLegacyMonthly(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
vc, ok := requiredVcenterParam(w, r)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
limit := parsePositiveLimit(r, vcenterMonthlyDefaultLimit)
|
||||
rows, err := db.ListVcenterTotalsByType(ctx, h.Database.DB(), vc, "monthly", limit)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to list monthly totals: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
h.renderVcenterTotalsPage(ctx, w, vc, "monthly", rows)
|
||||
}
|
||||
|
||||
func requiredVcenterParam(w http.ResponseWriter, r *http.Request) (string, bool) {
|
||||
vc := strings.TrimSpace(r.URL.Query().Get("vcenter"))
|
||||
if vc == "" {
|
||||
http.Error(w, "vcenter is required", http.StatusBadRequest)
|
||||
return
|
||||
return "", false
|
||||
}
|
||||
viewType := strings.ToLower(r.URL.Query().Get("type"))
|
||||
if viewType == "" {
|
||||
viewType = "hourly"
|
||||
return vc, true
|
||||
}
|
||||
|
||||
func parsePositiveLimit(r *http.Request, defaultLimit int) int {
|
||||
if defaultLimit <= 0 {
|
||||
defaultLimit = 200
|
||||
}
|
||||
switch viewType {
|
||||
case "hourly", "daily", "monthly":
|
||||
default:
|
||||
viewType = "hourly"
|
||||
}
|
||||
if viewType == "hourly" {
|
||||
if err := db.SyncVcenterTotalsFromSnapshots(ctx, h.Database.DB()); err != nil {
|
||||
h.Logger.Warn("failed to sync vcenter totals", "error", err)
|
||||
if raw := strings.TrimSpace(r.URL.Query().Get("limit")); raw != "" {
|
||||
if parsed, err := strconv.Atoi(raw); err == nil && parsed > 0 {
|
||||
return parsed
|
||||
}
|
||||
}
|
||||
limit := 200
|
||||
if l := r.URL.Query().Get("limit"); l != "" {
|
||||
if v, err := strconv.Atoi(l); err == nil && v > 0 {
|
||||
limit = v
|
||||
}
|
||||
}
|
||||
rows, err := db.ListVcenterTotalsByType(ctx, h.Database.DB(), vc, viewType, limit)
|
||||
if err != nil {
|
||||
http.Error(w, fmt.Sprintf("failed to list totals: %v", err), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
return defaultLimit
|
||||
}
|
||||
|
||||
func (h *Handler) renderVcenterTotalsPage(ctx context.Context, w http.ResponseWriter, vc string, viewType string, rows []db.VcenterTotalRow) {
|
||||
entries := make([]views.VcenterTotalsEntry, 0, len(rows))
|
||||
for _, row := range rows {
|
||||
entries = append(entries, views.VcenterTotalsEntry{
|
||||
@@ -105,28 +177,26 @@ func (h *Handler) VcenterTotals(w http.ResponseWriter, r *http.Request) {
|
||||
func buildVcenterMeta(vcenter string, viewType string) views.VcenterTotalsMeta {
|
||||
active := viewType
|
||||
if active == "" {
|
||||
active = "hourly"
|
||||
active = "daily"
|
||||
}
|
||||
meta := views.VcenterTotalsMeta{
|
||||
ViewType: active,
|
||||
TypeLabel: "Hourly",
|
||||
HourlyLink: "/vcenters/totals?vcenter=" + url.QueryEscape(vcenter) + "&type=hourly",
|
||||
DailyLink: "/vcenters/totals?vcenter=" + url.QueryEscape(vcenter) + "&type=daily",
|
||||
MonthlyLink: "/vcenters/totals?vcenter=" + url.QueryEscape(vcenter) + "&type=monthly",
|
||||
HourlyClass: "web3-button",
|
||||
DailyClass: "web3-button",
|
||||
MonthlyClass: "web3-button",
|
||||
ViewType: active,
|
||||
TypeLabel: "Daily",
|
||||
HourlyLink: "/vcenters/totals/hourly?vcenter=" + url.QueryEscape(vcenter),
|
||||
DailyLink: "/vcenters/totals/daily?vcenter=" + url.QueryEscape(vcenter),
|
||||
HourlyClass: "web3-button",
|
||||
DailyClass: "web3-button",
|
||||
}
|
||||
switch active {
|
||||
case "daily":
|
||||
meta.TypeLabel = "Daily"
|
||||
meta.DailyClass = "web3-button active"
|
||||
case "hourly45", "hourly":
|
||||
meta.ViewType = "hourly45"
|
||||
meta.TypeLabel = fmt.Sprintf("Hourly (last %d days)", vcenterHourlyDetailWindowDays)
|
||||
meta.HourlyClass = "web3-button active"
|
||||
case "monthly":
|
||||
meta.TypeLabel = "Monthly"
|
||||
meta.MonthlyClass = "web3-button active"
|
||||
default:
|
||||
meta.ViewType = "hourly"
|
||||
meta.HourlyClass = "web3-button active"
|
||||
meta.ViewType = "daily"
|
||||
meta.DailyClass = "web3-button active"
|
||||
}
|
||||
return meta
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user