mirror of
https://github.com/trafficlunar/statsys.git
synced 2026-03-28 11:13:17 +00:00
feat: uptime percentages
This commit is contained in:
parent
9cc207cb66
commit
b36ac9523c
4 changed files with 70 additions and 7 deletions
3
TODO.md
3
TODO.md
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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 }}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue