mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-25 17:05:21 +00:00
277 lines
7 KiB
Go
277 lines
7 KiB
Go
package handlers
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"sort"
|
|
"time"
|
|
|
|
"github.com/labstack/echo/v4"
|
|
"github.com/mentos1386/zdravko/database/models"
|
|
"github.com/mentos1386/zdravko/internal/server/services"
|
|
"github.com/mentos1386/zdravko/web/templates/components"
|
|
)
|
|
|
|
type HistoryOutcome string
|
|
|
|
const (
|
|
HistoryOutcomeHealthy HistoryOutcome = "HEALTHY"
|
|
HistoryOutcomeDegraded HistoryOutcome = "DEGRADED"
|
|
HistoryOutcomeUnknown HistoryOutcome = "UNKNOWN"
|
|
HistoryOutcomeDown HistoryOutcome = "DOWN"
|
|
)
|
|
|
|
type IndexData struct {
|
|
*components.Base
|
|
Targets map[string]TargetsAndStatus
|
|
TargetsLength int
|
|
TimeRange string
|
|
Outcome HistoryOutcome
|
|
}
|
|
|
|
type Target struct {
|
|
Name string
|
|
Visibility models.TargetVisibility
|
|
Group string
|
|
Outcome HistoryOutcome
|
|
History []*HistoryItem
|
|
Uptime float64
|
|
}
|
|
|
|
type History struct {
|
|
List []*HistoryItem
|
|
Uptime float64
|
|
}
|
|
|
|
type HistoryItem struct {
|
|
Outcome HistoryOutcome
|
|
StatusCounts map[models.TargetStatus]int
|
|
Counts int
|
|
Date time.Time
|
|
Checks []*HistoryItemCheck
|
|
SuccessRate float64
|
|
}
|
|
|
|
type HistoryItemCheck struct {
|
|
Name string
|
|
WorkerGroupName string
|
|
Status models.TargetStatus
|
|
StatusCounts map[models.TargetStatus]int
|
|
Counts int
|
|
SuccessRate float64
|
|
}
|
|
|
|
type TargetsAndStatus struct {
|
|
Outcome HistoryOutcome
|
|
Targets []*Target
|
|
}
|
|
|
|
func getDateString(date time.Time) string {
|
|
return date.UTC().Format("2006-01-02T15:04:05")
|
|
}
|
|
|
|
func TargetStatusToHistoryOutcome(status models.TargetStatus) HistoryOutcome {
|
|
switch status {
|
|
case models.TargetStatusSuccess:
|
|
return HistoryOutcomeHealthy
|
|
case models.TargetStatusFailure:
|
|
return HistoryOutcomeDown
|
|
default:
|
|
return HistoryOutcomeUnknown
|
|
}
|
|
}
|
|
|
|
func getHistory(history []*services.TargetHistory, period time.Duration, buckets int) *History {
|
|
historyMap := map[string]*HistoryItem{}
|
|
numOfSuccess := 0.0
|
|
numTotal := 0.0
|
|
|
|
mapKeys := make([]string, buckets)
|
|
|
|
for i := 0; i < buckets; i++ {
|
|
date := time.Now().Add(period * time.Duration(-i)).Truncate(period)
|
|
dateString := getDateString(date)
|
|
mapKeys[i] = dateString
|
|
|
|
historyMap[dateString] = &HistoryItem{
|
|
Outcome: HistoryOutcomeUnknown,
|
|
StatusCounts: map[models.TargetStatus]int{},
|
|
Date: date,
|
|
Checks: []*HistoryItemCheck{},
|
|
SuccessRate: 0.0,
|
|
}
|
|
}
|
|
|
|
for _, _history := range history {
|
|
dateString := getDateString(_history.CreatedAt.Time.Truncate(period))
|
|
|
|
entry, ok := historyMap[dateString]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
numTotal++
|
|
if _history.Status == models.TargetStatusSuccess {
|
|
numOfSuccess++
|
|
}
|
|
|
|
entry.StatusCounts[_history.Status]++
|
|
entry.Counts++
|
|
entry.SuccessRate = 100.0 * float64(entry.StatusCounts[models.TargetStatusSuccess]) / float64(entry.Counts)
|
|
|
|
foundCheck := false
|
|
for _, check := range entry.Checks {
|
|
if check.Name == _history.CheckName && check.WorkerGroupName == _history.WorkerGroupName {
|
|
foundCheck = true
|
|
|
|
check.StatusCounts[_history.Status]++
|
|
check.Counts++
|
|
check.SuccessRate = 100.0 * float64(check.StatusCounts[models.TargetStatusSuccess]) / float64(check.Counts)
|
|
|
|
if check.Status != models.TargetStatusFailure && _history.Status == models.TargetStatusFailure {
|
|
check.Status = models.TargetStatusFailure
|
|
}
|
|
}
|
|
}
|
|
|
|
if !foundCheck {
|
|
successRate := 0.0
|
|
if _history.Status == models.TargetStatusSuccess {
|
|
successRate = 100.0
|
|
}
|
|
entry.Checks = append(entry.Checks, &HistoryItemCheck{
|
|
Name: _history.CheckName,
|
|
WorkerGroupName: _history.WorkerGroupName,
|
|
Status: _history.Status,
|
|
StatusCounts: map[models.TargetStatus]int{
|
|
_history.Status: 1,
|
|
},
|
|
Counts: 1,
|
|
SuccessRate: successRate,
|
|
})
|
|
|
|
sort.Slice(entry.Checks, func(i, j int) bool {
|
|
byName := entry.Checks[i].Name < entry.Checks[j].Name
|
|
byWorkerGroupName := entry.Checks[i].WorkerGroupName < entry.Checks[j].WorkerGroupName
|
|
return byName || (entry.Checks[i].Name == entry.Checks[j].Name && byWorkerGroupName)
|
|
})
|
|
}
|
|
|
|
if entry.SuccessRate == 100.0 {
|
|
entry.Outcome = HistoryOutcomeHealthy
|
|
} else if entry.SuccessRate == 0.0 {
|
|
entry.Outcome = HistoryOutcomeDown
|
|
} else {
|
|
entry.Outcome = HistoryOutcomeDegraded
|
|
}
|
|
|
|
historyMap[dateString] = entry
|
|
}
|
|
|
|
uptime := 0.0
|
|
if numTotal > 0 {
|
|
uptime = 100.0 * numOfSuccess / numTotal
|
|
}
|
|
|
|
historyItems := make([]*HistoryItem, 0, len(historyMap))
|
|
for i := buckets - 1; i >= 0; i-- {
|
|
key := mapKeys[i]
|
|
historyItems = append(historyItems, historyMap[key])
|
|
}
|
|
|
|
return &History{
|
|
List: historyItems,
|
|
Uptime: uptime,
|
|
}
|
|
}
|
|
|
|
func (h *BaseHandler) Index(c echo.Context) error {
|
|
ctx := context.Background()
|
|
targets, err := services.GetTargets(ctx, h.db)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
timeRangeQuery := c.QueryParam("time-range")
|
|
if timeRangeQuery != "48hours" && timeRangeQuery != "60days" && timeRangeQuery != "60minutes" {
|
|
timeRangeQuery = "60days"
|
|
}
|
|
|
|
var timeBuckets int
|
|
var timeInterval time.Duration
|
|
var timeRange services.TargetHistoryDateRange
|
|
switch timeRangeQuery {
|
|
case "60days":
|
|
timeRange = services.TargetHistoryDateRange60Days
|
|
timeInterval = time.Hour * 24
|
|
timeBuckets = 60
|
|
case "48hours":
|
|
timeRange = services.TargetHistoryDateRange48Hours
|
|
timeInterval = time.Hour
|
|
timeBuckets = 48
|
|
case "60minutes":
|
|
timeRange = services.TargetHistoryDateRange60Minutes
|
|
timeInterval = time.Minute
|
|
timeBuckets = 60
|
|
}
|
|
|
|
overallOutcome := HistoryOutcomeUnknown
|
|
outcomeByGroup := make(map[string]HistoryOutcome)
|
|
|
|
targetsWithHistory := make([]*Target, len(targets))
|
|
for i, target := range targets {
|
|
history, err := services.GetTargetHistoryForTarget(ctx, h.db, target.Id, timeRange)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
historyResult := getHistory(history, timeInterval, timeBuckets)
|
|
|
|
if outcomeByGroup[target.Group] == "" {
|
|
outcomeByGroup[target.Group] = HistoryOutcomeUnknown
|
|
}
|
|
|
|
status := historyResult.List[len(historyResult.List)-1]
|
|
if status.Outcome == HistoryOutcomeHealthy {
|
|
if overallOutcome == HistoryOutcomeUnknown {
|
|
overallOutcome = status.Outcome
|
|
}
|
|
if outcomeByGroup[target.Group] == HistoryOutcomeUnknown {
|
|
outcomeByGroup[target.Group] = status.Outcome
|
|
}
|
|
}
|
|
if status.Outcome != HistoryOutcomeHealthy && status.Outcome != HistoryOutcomeUnknown {
|
|
overallOutcome = status.Outcome
|
|
outcomeByGroup[target.Group] = status.Outcome
|
|
}
|
|
|
|
targetsWithHistory[i] = &Target{
|
|
Name: target.Name,
|
|
Visibility: target.Visibility,
|
|
Group: target.Group,
|
|
Outcome: status.Outcome,
|
|
History: historyResult.List,
|
|
Uptime: historyResult.Uptime,
|
|
}
|
|
}
|
|
|
|
targetsByGroup := map[string]TargetsAndStatus{}
|
|
for _, target := range targetsWithHistory {
|
|
targetsByGroup[target.Group] = TargetsAndStatus{
|
|
Outcome: outcomeByGroup[target.Group],
|
|
Targets: append(targetsByGroup[target.Group].Targets, target),
|
|
}
|
|
}
|
|
|
|
c.Response().Header().Set("Cache-Control", "max-age=10")
|
|
|
|
return c.Render(http.StatusOK, "index.tmpl", &IndexData{
|
|
Base: &components.Base{
|
|
NavbarActive: GetPageByTitle(Pages, "Status"),
|
|
Navbar: Pages,
|
|
},
|
|
Targets: targetsByGroup,
|
|
TimeRange: timeRangeQuery,
|
|
Outcome: overallOutcome,
|
|
})
|
|
}
|