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" } }