Turn the status page into a full status page generator
Components can be put in groups and given public names. Public incidents carry posts written for customers (the internal timeline stays internal), the components they affect, and a page for each incident plus paged history. Scheduled maintenance has its own window, shows affected components as under maintenance, and is announced when scheduled, started, ended or canceled. Branding (title, logo, accent checked for contrast, contact link, footer) and a custom domain that serves only the status page. Visitors follow by RSS, by confirmed email, or by webhook that must answer a test request. Admin UI for all of it, tests, README and design notes. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
b4fd31a182
commit
cf4d5b3ce2
12 files changed
+2403
-147
No files matched your search
@@ -69,6 +69,10 @@ CREATE TABLE IF NOT EXISTS events (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT, scope TEXT NOT NULL, ref TEXT NOT NULL, time INTEGER NOT NULL,
|
||||
kind TEXT NOT NULL, actor TEXT NOT NULL, text TEXT NOT NULL);
|
||||
CREATE INDEX IF NOT EXISTS events_ref ON events(scope, ref, time);
|
||||
-- People who follow the public status page by email or webhook. The address is sealed; key is a hash of it, for lookups.
|
||||
CREATE TABLE IF NOT EXISTS subscribers (
|
||||
id TEXT PRIMARY KEY, kind TEXT NOT NULL, target TEXT NOT NULL, key TEXT NOT NULL UNIQUE,
|
||||
confirmed INTEGER NOT NULL, created INTEGER NOT NULL);
|
||||
`
|
||||
|
||||
// OpenStore opens (creating if needed) the database in dir.
|
||||
@@ -87,6 +91,8 @@ func OpenStore(dir string, box *secretbox.Box) (*Store, error) {
|
||||
db.Close()
|
||||
return nil, err
|
||||
}
|
||||
// Older databases have no status column on events: public incident posts carry the status they announce.
|
||||
db.Exec(`ALTER TABLE events ADD COLUMN status TEXT NOT NULL DEFAULT ''`)
|
||||
st := &Store{db: db, box: box}
|
||||
st.backfillDaily()
|
||||
return st, nil
|
||||
@@ -233,24 +239,31 @@ type Event struct {
|
||||
Kind string `json:"kind"`
|
||||
Actor string `json:"actor"`
|
||||
Text string `json:"text"`
|
||||
// Status is set on posts only: the incident status the post announces.
|
||||
Status string `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (s *Store) events(scope, ref string) []Event {
|
||||
out := []Event{}
|
||||
rows, err := s.db.Query(`SELECT time,kind,actor,text FROM events WHERE scope=? AND ref=? ORDER BY time, id`, scope, ref)
|
||||
rows, err := s.db.Query(`SELECT time,kind,actor,text,status FROM events WHERE scope=? AND ref=? ORDER BY time, id`, scope, ref)
|
||||
if err != nil {
|
||||
return out
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var e Event
|
||||
if rows.Scan(&e.Time, &e.Kind, &e.Actor, &e.Text) == nil {
|
||||
if rows.Scan(&e.Time, &e.Kind, &e.Actor, &e.Text, &e.Status) == nil {
|
||||
out = append(out, e)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// addPost records a public incident update: the text and status the status page shows.
|
||||
func (s *Store) addPost(incidentID, status, text string, at time.Time) {
|
||||
s.db.Exec(`INSERT INTO events(scope,ref,time,kind,actor,text,status) VALUES('incident',?,?,'post','',?,?)`, incidentID, at.Unix(), text, status)
|
||||
}
|
||||
|
||||
// prune deletes data past its retention.
|
||||
func (s *Store) prune(now time.Time, messageDays, checkDays, hitDays int) {
|
||||
s.db.Exec(`DELETE FROM hits WHERE time < ?`, now.AddDate(0, 0, -hitDays).Unix())
|
||||
@@ -258,5 +271,6 @@ func (s *Store) prune(now time.Time, messageDays, checkDays, hitDays int) {
|
||||
s.db.Exec(`DELETE FROM checks WHERE time < ?`, now.AddDate(0, 0, -checkDays).Unix())
|
||||
s.db.Exec(`DELETE FROM daily WHERE day < ?`, now.UTC().AddDate(0, 0, -400).Format("2006-01-02"))
|
||||
s.db.Exec(`DELETE FROM sessions WHERE expires < ?`, now.Unix())
|
||||
s.db.Exec(`DELETE FROM subscribers WHERE confirmed=0 AND created < ?`, now.AddDate(0, 0, -7).Unix())
|
||||
s.db.Exec(`DELETE FROM alerts WHERE status='resolved' AND resolved_at < ? AND incident_id=''`, now.AddDate(0, 0, -90).Unix())
|
||||
}
|
||||
Reference in new issue
Block a user