From e8c837cf31cd424b90ba9ed7c98f6861616874be Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 7 May 2026 16:28:55 -0500 Subject: [PATCH] up --- pkg/models/statuspage.go | 151 ++++++++++++++++++ resources/views/statuspage.html | 179 ++++++++++++++++++++++ rest/controllers/statuspage_controller.go | 80 ++++++++++ rest/routes/publicas.go | 6 +- 4 files changed, 415 insertions(+), 1 deletion(-) create mode 100644 pkg/models/statuspage.go create mode 100644 resources/views/statuspage.html create mode 100644 rest/controllers/statuspage_controller.go diff --git a/pkg/models/statuspage.go b/pkg/models/statuspage.go new file mode 100644 index 0000000..184dbf1 --- /dev/null +++ b/pkg/models/statuspage.go @@ -0,0 +1,151 @@ +package models + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "time" +) + +// ─── Estructuras de la API pública de Atlassian Statuspage ──────────────────── + +type StatuspageStatus struct { + Indicator string `json:"indicator"` // none, minor, major, critical + Description string `json:"description"` // "All Systems Operational", etc. +} + +type StatuspagePage struct { + ID string `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + TimeZone string `json:"time_zone"` + UpdatedAt time.Time `json:"updated_at"` +} + +type StatuspageComponent struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` // operational, degraded_performance, partial_outage, major_outage, under_maintenance + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + Position int `json:"position"` + Description string `json:"description"` + Showcase bool `json:"showcase"` + GroupID *string `json:"group_id"` + PageID string `json:"page_id"` + Group bool `json:"group"` + OnlyShowIfDegraded bool `json:"only_show_if_degraded"` +} + +type StatuspageIncidentUpdate struct { + ID string `json:"id"` + Status string `json:"status"` + Body string `json:"body"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +type StatuspageIncident struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` // investigating, identified, monitoring, resolved, postmortem + Impact string `json:"impact"` // none, minor, major, critical + PageID string `json:"page_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + MonitoringAt *time.Time `json:"monitoring_at"` + ResolvedAt *time.Time `json:"resolved_at"` + ShortLink string `json:"shortlink"` + IncidentUpdates []StatuspageIncidentUpdate `json:"incident_updates"` + AffectedComponents []StatuspageComponent `json:"components"` +} + +type StatuspageScheduledMaintenance struct { + ID string `json:"id"` + Name string `json:"name"` + Status string `json:"status"` // scheduled, in_progress, verifying, completed + Impact string `json:"impact"` + PageID string `json:"page_id"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + ScheduledFor time.Time `json:"scheduled_for"` + ScheduledUntil time.Time `json:"scheduled_until"` + ShortLink string `json:"shortlink"` + IncidentUpdates []StatuspageIncidentUpdate `json:"incident_updates"` + AffectedComponents []StatuspageComponent `json:"components"` +} + +// StatuspageSummary es la respuesta completa del endpoint /api/v2/summary.json +type StatuspageSummary struct { + Page StatuspagePage `json:"page"` + Status StatuspageStatus `json:"status"` + Components []StatuspageComponent `json:"components"` + Incidents []StatuspageIncident `json:"incidents"` + ScheduledMaintenances []StatuspageScheduledMaintenance `json:"scheduled_maintenances"` +} + +// FetchStatuspageSummary consulta la API pública de Atlassian Statuspage. +// pageID es el identificador de tu página (ej. "yh6f0r4529hb"). +func FetchStatuspageSummary(pageID string) (*StatuspageSummary, error) { + if pageID == "" { + return nil, fmt.Errorf("statuspage: pageID vacío") + } + url := fmt.Sprintf("https://%s.statuspage.io/api/v2/summary.json", pageID) + + client := &http.Client{Timeout: 8 * time.Second} + resp, err := client.Get(url) // #nosec G107 — URL construida desde config, no desde input de usuario + if err != nil { + return nil, fmt.Errorf("statuspage: error de red: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("statuspage: respuesta HTTP %d", resp.StatusCode) + } + + body, err := io.ReadAll(io.LimitReader(resp.Body, 512*1024)) // límite 512 KB + if err != nil { + return nil, fmt.Errorf("statuspage: error leyendo body: %w", err) + } + + var summary StatuspageSummary + if err := json.Unmarshal(body, &summary); err != nil { + return nil, fmt.Errorf("statuspage: error parseando JSON: %w", err) + } + return &summary, nil +} + +// ComponentStatusLabel devuelve una etiqueta legible en español. +func ComponentStatusLabel(status string) string { + switch status { + case "operational": + return "Operacional" + case "degraded_performance": + return "Rendimiento degradado" + case "partial_outage": + return "Interrupción parcial" + case "major_outage": + return "Interrupción mayor" + case "under_maintenance": + return "En mantenimiento" + default: + return status + } +} + +// IndicatorColor devuelve la clase de color Tailwind para el indicador global. +func IndicatorColor(indicator string) string { + switch indicator { + case "none": + return "green" + case "minor": + return "yellow" + case "major": + return "orange" + case "critical": + return "red" + default: + return "gray" + } +} diff --git a/resources/views/statuspage.html b/resources/views/statuspage.html new file mode 100644 index 0000000..70171b0 --- /dev/null +++ b/resources/views/statuspage.html @@ -0,0 +1,179 @@ +{{/* Página pública de estado del sistema — Atlassian Statuspage */}} +
Estado en tiempo real de nuestros servicios
+{{.error}}
+{{.summary.Status.Description}}
+Todos los sistemas funcionan con normalidad
+{{.summary.Status.Description}}
+Estamos investigando el problema
+{{.summary.Status.Description}}
+Algunos servicios presentan interrupciones
+{{.summary.Status.Description}}
+Se está trabajando para restablecer los servicios
+{{.Name}}
+ {{if .IncidentUpdates}} +{{(index .IncidentUpdates 0).Body}}
+ {{end}} +{{.Name}}
++ Programado: {{.ScheduledFor.Format "02/01/2006 15:04"}} — {{.ScheduledUntil.Format "15:04"}} UTC +
+{{.Name}}
+ {{if .Description}}{{.Description}}
{{end}} ++ Actualizado: {{.summary.Page.UpdatedAt.Format "02/01/2006 15:04"}} UTC + · + + Ver en Statuspage + +
+