Skip to content

Commit cdd0384

Browse files
committed
Add monitor groups on the status page
1 parent a4e4102 commit cdd0384

7 files changed

Lines changed: 56 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,13 @@
33
All notable changes to Gjallar are documented in this file.
44
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
55

6+
## [0.3.0] - 2026-07-04
7+
8+
### Added
9+
10+
- Monitor groups: an optional `group` field on monitors; the status page
11+
shows grouped monitors under a header with an up/total count.
12+
613
## [0.2.0] - 2026-07-04
714

815
### Added

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ Gjallar reloads its configuration on `SIGHUP` (`systemctl reload gjallar`).
5858
The new config is fully validated first — if it is broken, the running
5959
configuration is kept and the error is logged.
6060

61+
### Groups
62+
63+
Give monitors an optional `group: "Hyperion"` and the status page shows them
64+
under a common header with an `up/total` count. Monitors without a group are
65+
listed first, without a header. Groups appear in config order.
66+
6167
### Re-alerts
6268

6369
By default a monitor alerts once when it goes down and once when it recovers.

gjallar.example.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ alerts:
3737
monitors:
3838
# --- HTTP/HTTPS: status code, body regex, TLS certificate expiry ---
3939
- name: website
40+
group: "Public" # optional: monitors sharing a group are shown together
4041
type: http
4142
url: "https://example.com/api/health"
4243
method: GET # default GET

internal/config/config.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ type Alert struct {
5656

5757
type Monitor struct {
5858
Name string `yaml:"name"`
59-
Type string `yaml:"type"` // http | postgres | oracle | ping | prometheus
59+
Group string `yaml:"group"` // optional; groups monitors on the status page
60+
Type string `yaml:"type"` // http | postgres | oracle | ping | prometheus
6061
Interval Duration `yaml:"interval"`
6162
Timeout Duration `yaml:"timeout"`
6263
FailureThreshold int `yaml:"failure_threshold"`

internal/web/server.go

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,16 @@ type IncidentView struct {
8989
Open bool
9090
}
9191

92+
type GroupView struct {
93+
Name string // empty = ungrouped
94+
Up int
95+
Total int
96+
Monitors []MonitorView
97+
}
98+
9299
type overviewData struct {
93100
Title string
94-
Monitors []MonitorView
101+
Groups []GroupView
95102
Incidents []IncidentView
96103
}
97104

@@ -146,8 +153,21 @@ func (s *Server) render(w http.ResponseWriter, t *template.Template, name string
146153

147154
func (s *Server) overview() overviewData {
148155
d := overviewData{Title: s.cfg.SiteTitle}
156+
idx := map[string]int{} // group name -> position, in order of first appearance
149157
for _, m := range s.cfg.Monitors {
150-
d.Monitors = append(d.Monitors, s.monitorView(m))
158+
v := s.monitorView(m)
159+
gi, ok := idx[m.Group]
160+
if !ok {
161+
gi = len(d.Groups)
162+
idx[m.Group] = gi
163+
d.Groups = append(d.Groups, GroupView{Name: m.Group})
164+
}
165+
g := &d.Groups[gi]
166+
g.Monitors = append(g.Monitors, v)
167+
g.Total++
168+
if v.Status == "up" {
169+
g.Up++
170+
}
151171
}
152172
incs, err := s.st.Incidents(incidentCount)
153173
if err != nil {

internal/web/static/style.css

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,8 +43,17 @@ a:hover { text-decoration: underline; }
4343
h2, h3 { color: var(--text); margin: 24px 0 8px; }
4444
h2 { border-left: 4px solid var(--red); padding-left: 10px; }
4545

46+
/* monitor groups */
47+
.group-head {
48+
display: flex; align-items: baseline; gap: 12px;
49+
margin-top: 28px;
50+
}
51+
.group-head h2 { margin: 0 0 8px; }
52+
.group-count { color: var(--muted); font-size: 12px; }
53+
.group-count.bad { color: var(--red); font-weight: 700; }
54+
4655
/* monitor cards */
47-
.cards { display: flex; flex-direction: column; gap: 12px; }
56+
.cards { display: flex; flex-direction: column; gap: 12px; margin-bottom: 12px; }
4857
.card {
4958
background: var(--card);
5059
border: 1px solid var(--border);

internal/web/templates/_monitors.tmpl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{{define "monitors_fragment"}}
2+
{{range .Groups}}
3+
{{if .Name}}
4+
<div class="group-head">
5+
<h2>{{.Name}}</h2>
6+
<span class="group-count{{if lt .Up .Total}} bad{{end}}">{{.Up}}/{{.Total}} up</span>
7+
</div>
8+
{{end}}
29
<section class="cards">
310
{{range .Monitors}}
411
<article class="card">
@@ -17,6 +24,7 @@
1724
</article>
1825
{{end}}
1926
</section>
27+
{{end}}
2028

2129
{{if .Incidents}}
2230
<section class="incidents">

0 commit comments

Comments
 (0)