feat: uptime percentages

This commit is contained in:
trafficlunar 2025-12-14 14:27:59 +00:00
parent 9cc207cb66
commit b36ac9523c
4 changed files with 70 additions and 7 deletions

View file

@ -1,6 +1,5 @@
- [ ] Accessibility - [ ] Accessibility
- [ ] Better mobile support - [ ] Better mobile support
- [ ] Uptime percentage
- [ ] View incidents - [ ] View incidents
- [ ] Latency graphs? - [ ] Latency graphs?
- [ ] README thumbnail - [ ] README

View file

@ -3,6 +3,7 @@ package main
import ( import (
"database/sql" "database/sql"
"log" "log"
"math"
"time" "time"
_ "modernc.org/sqlite" _ "modernc.org/sqlite"
@ -52,6 +53,7 @@ func InitializeDatabase() {
service.MinuteTimeline = generateRecap(rawTimeline, "minutes") service.MinuteTimeline = generateRecap(rawTimeline, "minutes")
service.HourTimeline = generateRecap(rawTimeline, "hours") service.HourTimeline = generateRecap(rawTimeline, "hours")
service.DayTimeline = generateRecap(rawTimeline, "days") service.DayTimeline = generateRecap(rawTimeline, "days")
calculateUptimePercentages(index)
// incidents // incidents
incidentRows, err := db.Query(`SELECT status, startTime, endTime FROM incidents WHERE service = ?`, service.Name) incidentRows, err := db.Query(`SELECT status, startTime, endTime FROM incidents WHERE service = ?`, service.Name)
@ -189,11 +191,11 @@ func generateRecap(rawTimeline []TimelineEntry, view string) []TimelineEntry {
switch view { switch view {
case "minutes": case "minutes":
timestamp = now.Add(-time.Duration(30-i) * time.Minute).Truncate(time.Minute) timestamp = now.Add(-time.Duration(limit-i) * time.Minute).Truncate(time.Minute)
case "hours": case "hours":
timestamp = now.Add(-time.Duration(24-i) * time.Hour).Truncate(time.Hour) timestamp = now.Add(-time.Duration(limit-i) * time.Hour).Truncate(time.Hour)
case "days": case "days":
day := now.AddDate(0, 0, -(30 - i)) day := now.AddDate(0, 0, -(limit - i))
timestamp = time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, time.UTC) timestamp = time.Date(day.Year(), day.Month(), day.Day(), 0, 0, 0, 0, time.UTC)
} }
@ -212,6 +214,40 @@ func generateRecap(rawTimeline []TimelineEntry, view string) []TimelineEntry {
return timeline return timeline
} }
func calculateUptimePercentages(serviceIndex int) {
service := templateData.Services[serviceIndex]
calculateUptime := func(timeline []TimelineEntry) float64 {
if len(timeline) == 0 {
return 0.0
}
online := 0
total := 0
for _, entry := range timeline {
if entry.Status != "Unknown" {
total++
if entry.Status == "Online" {
online++
}
}
}
if total == 0 {
return 0.0
}
return math.Floor(float64(online)/float64(total)*100*100) / 100
}
service.MinuteUptime = calculateUptime(service.MinuteTimeline)
service.HourUptime = calculateUptime(service.HourTimeline)
service.DayUptime = calculateUptime(service.DayTimeline)
templateData.Services[serviceIndex] = service
}
// add entry to timeline and remove entries older than 30 days // add entry to timeline and remove entries older than 30 days
func AddToTimeline(serviceIndex int, status string) { func AddToTimeline(serviceIndex int, status string) {
service := templateData.Services[serviceIndex] service := templateData.Services[serviceIndex]
@ -297,6 +333,7 @@ func AddToTimeline(serviceIndex int, status string) {
} }
templateData.Services[serviceIndex] = service templateData.Services[serviceIndex] = service
calculateUptimePercentages(serviceIndex)
} }
func AddIncident(serviceIndex int, status string, startTime time.Time) { func AddIncident(serviceIndex int, status string, startTime time.Time) {

View file

@ -32,6 +32,9 @@ type Service struct {
MinuteTimeline []TimelineEntry MinuteTimeline []TimelineEntry
HourTimeline []TimelineEntry HourTimeline []TimelineEntry
DayTimeline []TimelineEntry DayTimeline []TimelineEntry
MinuteUptime float64
HourUptime float64
DayUptime float64
Incidents []Incident Incidents []Incident
} }
@ -74,7 +77,7 @@ func renderError(w http.ResponseWriter, statusCode int, message string) {
} }
func index(w http.ResponseWriter, req *http.Request) { func index(w http.ResponseWriter, req *http.Request) {
// Handle 404 // handle 404
if req.URL.Path != "/" { if req.URL.Path != "/" {
renderError(w, http.StatusNotFound, "Page not found") renderError(w, http.StatusNotFound, "Page not found")
return return

View file

@ -232,15 +232,33 @@
} }
.bars-footer { .bars-footer {
position: relative;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
font-size: 0.75rem; font-size: 0.75rem;
font-family: "JetBrains Mono", monospace; font-family: "JetBrains Mono", monospace;
margin-top: 0.5rem; margin-top: 0.5rem;
color: rgba(0, 0, 0, 0.5); color: rgba(0, 0, 0, 0.4);
width: 100%; width: 100%;
} }
.bars-footer span {
padding: 0 0.5rem;
}
.bars-footer hr {
flex-grow: 1;
border-color: rgba(0, 0, 0, 0.1);
}
.uptime-percentage {
position: absolute;
left: 50%;
transform: translateX(-50%);
background-color: white;
color: rgba(0, 0, 0, 0.5);
}
footer { footer {
margin-top: auto; margin-top: auto;
display: flex; display: flex;
@ -487,6 +505,8 @@
<div class="bars-footer"> <div class="bars-footer">
<span>30 minutes ago</span> <span>30 minutes ago</span>
<hr />
<span class="uptime-percentage">{{ $serviceStatus.MinuteUptime }}% uptime</span>
<span>now</span> <span>now</span>
</div> </div>
{{ else if eq $.View "hours" }} {{ else if eq $.View "hours" }}
@ -503,6 +523,8 @@
<div class="bars-footer"> <div class="bars-footer">
<span>24 hours ago</span> <span>24 hours ago</span>
<hr />
<span class="uptime-percentage">{{ $serviceStatus.HourUptime }}% uptime</span>
<span>now</span> <span>now</span>
</div> </div>
@ -520,6 +542,8 @@
<div class="bars-footer"> <div class="bars-footer">
<span>30 days ago</span> <span>30 days ago</span>
<hr />
<span class="uptime-percentage">{{ $serviceStatus.DayUptime }}% uptime</span>
<span>today</span> <span>today</span>
</div> </div>
{{ end }} {{ end }}