Merge pull request 'fix: persistent state — uptime, status, latency, and logs survive restarts' (#13) from fix/uptime-percentage into develop
This commit was merged in pull request #13.
This commit is contained in:
@@ -260,6 +260,7 @@ func runServe(args []string) {
|
|||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
eng.InitHistory()
|
eng.InitHistory()
|
||||||
|
eng.InitLogs()
|
||||||
eng.Start(ctx)
|
eng.Start(ctx)
|
||||||
|
|
||||||
server.Start(server.ServerConfig{
|
server.Start(server.ServerConfig{
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ func (m *mockStore) GetNode(string) (models.ProbeNode, error) { return m
|
|||||||
func (m *mockStore) GetAllNodes() ([]models.ProbeNode, error) { return nil, nil }
|
func (m *mockStore) GetAllNodes() ([]models.ProbeNode, error) { return nil, nil }
|
||||||
func (m *mockStore) UpdateNodeLastSeen(string) error { return nil }
|
func (m *mockStore) UpdateNodeLastSeen(string) error { return nil }
|
||||||
func (m *mockStore) DeleteNode(string) error { return nil }
|
func (m *mockStore) DeleteNode(string) error { return nil }
|
||||||
|
func (m *mockStore) SaveLog(string) error { return nil }
|
||||||
|
func (m *mockStore) LoadLogs(int) ([]string, error) { return nil, nil }
|
||||||
|
|
||||||
func TestMetricsHandler(t *testing.T) {
|
func TestMetricsHandler(t *testing.T) {
|
||||||
ms := &mockStore{
|
ms := &mockStore{
|
||||||
|
|||||||
@@ -68,6 +68,20 @@ func (e *Engine) AddLog(msg string) {
|
|||||||
if len(e.logStore) > 100 {
|
if len(e.logStore) > 100 {
|
||||||
e.logStore = e.logStore[:100]
|
e.logStore = e.logStore[:100]
|
||||||
}
|
}
|
||||||
|
go func() { _ = e.db.SaveLog(entry) }()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *Engine) InitLogs() {
|
||||||
|
logs, err := e.db.LoadLogs(100)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(logs) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
e.logMu.Lock()
|
||||||
|
defer e.logMu.Unlock()
|
||||||
|
e.logStore = logs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *Engine) GetLogs() []string {
|
func (e *Engine) GetLogs() []string {
|
||||||
@@ -193,6 +207,16 @@ func (e *Engine) Start(ctx context.Context) {
|
|||||||
if s.Type == "push" {
|
if s.Type == "push" {
|
||||||
s.LastCheck = time.Now()
|
s.LastCheck = time.Now()
|
||||||
}
|
}
|
||||||
|
if h, ok := e.GetHistory(s.ID); ok && len(h.Statuses) > 0 {
|
||||||
|
if h.Statuses[len(h.Statuses)-1] {
|
||||||
|
s.Status = "UP"
|
||||||
|
} else {
|
||||||
|
s.Status = "DOWN"
|
||||||
|
}
|
||||||
|
if len(h.Latencies) > 0 {
|
||||||
|
s.Latency = h.Latencies[len(h.Latencies)-1]
|
||||||
|
}
|
||||||
|
}
|
||||||
e.liveState[s.ID] = s
|
e.liveState[s.ID] = s
|
||||||
e.addToTokenIndex(s)
|
e.addToTokenIndex(s)
|
||||||
e.mu.Unlock()
|
e.mu.Unlock()
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ func (d *PostgresDialect) CreateTablesSQL() []string {
|
|||||||
last_seen TIMESTAMP DEFAULT NOW(),
|
last_seen TIMESTAMP DEFAULT NOW(),
|
||||||
version TEXT DEFAULT ''
|
version TEXT DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS logs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
)`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ func (d *SQLiteDialect) CreateTablesSQL() []string {
|
|||||||
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
|
last_seen DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
version TEXT DEFAULT ''
|
version TEXT DEFAULT ''
|
||||||
)`,
|
)`,
|
||||||
|
`CREATE TABLE IF NOT EXISTS logs (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -300,6 +300,34 @@ func (s *SQLStore) DeleteNode(id string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) SaveLog(message string) error {
|
||||||
|
_, err := s.db.Exec(s.q("INSERT INTO logs (message) VALUES (?)"), message)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = s.db.Exec(s.q(`DELETE FROM logs WHERE id NOT IN (
|
||||||
|
SELECT id FROM logs ORDER BY created_at DESC LIMIT 200
|
||||||
|
)`))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *SQLStore) LoadLogs(limit int) ([]string, error) {
|
||||||
|
rows, err := s.db.Query(s.q("SELECT message FROM logs ORDER BY created_at DESC LIMIT ?"), limit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rows.Close()
|
||||||
|
var logs []string
|
||||||
|
for rows.Next() {
|
||||||
|
var msg string
|
||||||
|
if err := rows.Scan(&msg); err != nil {
|
||||||
|
return logs, err
|
||||||
|
}
|
||||||
|
logs = append(logs, msg)
|
||||||
|
}
|
||||||
|
return logs, rows.Err()
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SQLStore) LoadAllHistory(limit int) (map[int][]models.CheckRecord, error) {
|
func (s *SQLStore) LoadAllHistory(limit int) (map[int][]models.CheckRecord, error) {
|
||||||
result := make(map[int][]models.CheckRecord)
|
result := make(map[int][]models.CheckRecord)
|
||||||
rows, err := s.db.Query(s.q(`
|
rows, err := s.db.Query(s.q(`
|
||||||
|
|||||||
@@ -45,6 +45,10 @@ type Store interface {
|
|||||||
UpdateNodeLastSeen(id string) error
|
UpdateNodeLastSeen(id string) error
|
||||||
DeleteNode(id string) error
|
DeleteNode(id string) error
|
||||||
|
|
||||||
|
// Logs
|
||||||
|
SaveLog(message string) error
|
||||||
|
LoadLogs(limit int) ([]string, error)
|
||||||
|
|
||||||
// Backup & Restore
|
// Backup & Restore
|
||||||
ExportData() (models.Backup, error)
|
ExportData() (models.Backup, error)
|
||||||
ImportData(data models.Backup) error
|
ImportData(data models.Backup) error
|
||||||
|
|||||||
@@ -152,11 +152,17 @@ func fmtLatency(d time.Duration) string {
|
|||||||
return dangerStyle.Render(s)
|
return dangerStyle.Render(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func fmtUptime(total, up int) string {
|
func fmtUptime(statuses []bool) string {
|
||||||
if total == 0 {
|
if len(statuses) == 0 {
|
||||||
return subtleStyle.Render("—")
|
return subtleStyle.Render("—")
|
||||||
}
|
}
|
||||||
pct := float64(up) / float64(total) * 100
|
up := 0
|
||||||
|
for _, s := range statuses {
|
||||||
|
if s {
|
||||||
|
up++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pct := float64(up) / float64(len(statuses)) * 100
|
||||||
s := fmt.Sprintf("%.1f%%", pct)
|
s := fmt.Sprintf("%.1f%%", pct)
|
||||||
if pct >= 99 {
|
if pct >= 99 {
|
||||||
return specialStyle.Render(s)
|
return specialStyle.Render(s)
|
||||||
@@ -309,7 +315,7 @@ func (m Model) viewSitesTab() string {
|
|||||||
typeIcon(site.Type, false) + " " + site.Type,
|
typeIcon(site.Type, false) + " " + site.Type,
|
||||||
fmtStatus(site.Status, site.Paused),
|
fmtStatus(site.Status, site.Paused),
|
||||||
fmtLatency(site.Latency),
|
fmtLatency(site.Latency),
|
||||||
fmtUptime(hist.TotalChecks, hist.UpChecks),
|
fmtUptime(hist.Statuses),
|
||||||
spark,
|
spark,
|
||||||
fmtSSL(site),
|
fmtSSL(site),
|
||||||
fmtRetries(site),
|
fmtRetries(site),
|
||||||
@@ -631,7 +637,7 @@ func (m Model) viewDetailPanel() string {
|
|||||||
row("Interval", fmt.Sprintf("%ds", site.Interval))
|
row("Interval", fmt.Sprintf("%ds", site.Interval))
|
||||||
row("Timeout", fmt.Sprintf("%ds", site.Timeout))
|
row("Timeout", fmt.Sprintf("%ds", site.Timeout))
|
||||||
row("Latency", fmtLatency(site.Latency))
|
row("Latency", fmtLatency(site.Latency))
|
||||||
row("Uptime", fmtUptime(hist.TotalChecks, hist.UpChecks))
|
row("Uptime", fmtUptime(hist.Statuses))
|
||||||
|
|
||||||
if site.Type == "http" {
|
if site.Type == "http" {
|
||||||
row("Method", site.Method)
|
row("Method", site.Method)
|
||||||
|
|||||||
Reference in New Issue
Block a user