Skip to content

Commit 965e635

Browse files
committed
feat: redesigned host page. Added information about SSL certificate, last outage
1 parent a29f2b1 commit 965e635

File tree

6 files changed

+140
-146
lines changed

6 files changed

+140
-146
lines changed

pkg/dialer/http.go

+12-4
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,15 @@ func (d *Dialer) httpCall(ctx context.Context, h *types.Host) (response types.Ht
2121
}
2222

2323
var start, connect, dns, tlsHandshake time.Time
24+
var tlsState *tls.ConnectionState
2425
req = req.WithContext(httptrace.WithClientTrace(ctx, &httptrace.ClientTrace{
25-
DNSStart: func(dsi httptrace.DNSStartInfo) { dns = time.Now() },
26-
DNSDone: func(ddi httptrace.DNSDoneInfo) { response.DNS = time.Since(dns) },
27-
TLSHandshakeStart: func() { tlsHandshake = time.Now() },
28-
TLSHandshakeDone: func(cs tls.ConnectionState, err error) { response.TLSHandshake = time.Since(tlsHandshake) },
26+
DNSStart: func(dsi httptrace.DNSStartInfo) { dns = time.Now() },
27+
DNSDone: func(ddi httptrace.DNSDoneInfo) { response.DNS = time.Since(dns) },
28+
TLSHandshakeStart: func() { tlsHandshake = time.Now() },
29+
TLSHandshakeDone: func(cs tls.ConnectionState, err error) {
30+
response.TLSHandshake = time.Since(tlsHandshake)
31+
tlsState = &cs
32+
},
2933
ConnectStart: func(network, addr string) { connect = time.Now() },
3034
ConnectDone: func(network, addr string, err error) { response.Connect = time.Since(connect) },
3135
GotFirstResponseByte: func() { response.TTFB = time.Since(start) },
@@ -62,6 +66,10 @@ func (d *Dialer) httpCall(ctx context.Context, h *types.Host) (response types.Ht
6266
response.Time = time.Since(startTime)
6367
response.Code = resp.StatusCode
6468

69+
if tlsState != nil && len(tlsState.PeerCertificates) > 0 {
70+
response.SSLCertExpiry = &tlsState.PeerCertificates[0].NotAfter
71+
}
72+
6573
b, err := io.ReadAll(resp.Body)
6674
if err != nil {
6775
log.Printf("[ERROR] read body %v", err)

pkg/monitor/stats.go

+36-51
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ func (m *Monitor) StatsByID(ctx context.Context, id string, dayReport bool) (*ty
119119

120120
var details *types.Details
121121
if !dayReport {
122-
details = genUptimeAndResponseTime(history)
122+
details = getDetails(history)
123123
}
124124

125125
if !dayReport && len(history) > 90 {
@@ -271,6 +271,10 @@ func genChart(history []*types.HttpResponse, interval time.Duration, dayReport b
271271
uptime = (uptime * 100) / (len(points) - unknown)
272272
}
273273

274+
sort.Slice(points, func(i, j int) bool {
275+
return points[i].TS.Before(points[j].TS)
276+
})
277+
274278
return types.Chart{
275279
Points: points,
276280
Intervals: genIntervals(points),
@@ -315,83 +319,64 @@ func genIntervals(points []*types.Point) []string {
315319
}
316320
return intervals
317321
}
318-
func genUptimeAndResponseTime(responses []*types.HttpResponse) *types.Details {
322+
func getDetails(responses []*types.HttpResponse) *types.Details {
319323
d := &types.Details{}
320324

321325
last30DaysUp := 0
322326
last30DaysCount := 0
323327
uptime30Days := 0.0
324328
responseTime30Days := time.Duration(0)
325-
326-
last7DaysUp := 0
327-
last7DaysCount := 0
328-
uptime7Days := 0.0
329-
responseTime7Days := time.Duration(0)
330-
331-
last24HoursUp := 0
332-
last24HoursCount := 0
333-
uptime24Hours := 0.0
334-
responseTime24Hours := time.Duration(0)
329+
var lastOutageTS *time.Time
330+
var lastOutageUpTS *time.Time
335331

336332
for _, r := range responses {
337-
if r.Timestamp.After(time.Now().Add(-time.Hour * 24)) {
338-
last24HoursCount++
339-
if r.StatusType != types.DOWN {
340-
last24HoursUp++
341-
}
342-
responseTime24Hours += r.Time
343-
}
344-
if r.Timestamp.After(time.Now().Add(-time.Hour * 24 * 7)) {
345-
last7DaysCount++
346-
if r.StatusType != types.DOWN {
347-
last7DaysUp++
348-
}
349-
responseTime7Days += r.Time
350-
}
351333
if r.Timestamp.After(time.Now().Add(-time.Hour * 24 * 30)) {
352334
last30DaysCount++
353335
if r.StatusType != types.DOWN {
354336
last30DaysUp++
355337
}
356338
responseTime30Days += r.Time
357339
}
340+
if r.StatusType == types.DOWN {
341+
if lastOutageTS == nil {
342+
lastOutageTS = &r.Timestamp
343+
} else if lastOutageTS.Before(r.Timestamp) {
344+
lastOutageTS = &r.Timestamp
345+
}
346+
} else if lastOutageTS != nil && lastOutageUpTS == nil {
347+
lastOutageUpTS = &r.Timestamp
348+
}
358349
}
359350

360351
uptime30Days = float64(last30DaysUp) * 100 / float64(last30DaysCount)
361352
if last30DaysUp != 0 {
362353
responseTime30Days /= time.Duration(last30DaysUp)
363354
}
364-
365355
if uptime30Days == math.Trunc(uptime30Days) {
366-
d.Uptime = append(d.Uptime, fmt.Sprintf("%.0f", uptime30Days))
367-
} else {
368-
d.Uptime = append(d.Uptime, fmt.Sprintf("%.1f", uptime30Days))
369-
}
370-
371-
uptime7Days = float64(last7DaysUp) * 100 / float64(last7DaysCount)
372-
if last7DaysUp != 0 {
373-
responseTime7Days /= time.Duration(last7DaysUp)
374-
}
375-
if uptime7Days == math.Trunc(uptime7Days) {
376-
d.Uptime = append(d.Uptime, fmt.Sprintf("%.0f", uptime7Days))
356+
d.Uptime = fmt.Sprintf("%.0f", uptime30Days)
377357
} else {
378-
d.Uptime = append(d.Uptime, fmt.Sprintf("%.1f", uptime7Days))
358+
d.Uptime = fmt.Sprintf("%.1f", uptime30Days)
379359
}
360+
d.ResponseTime = formatDuration(responseTime30Days)
380361

381-
uptime24Hours = float64(last24HoursUp) * 100 / float64(last24HoursCount)
382-
if last24HoursUp != 0 {
383-
responseTime24Hours /= time.Duration(last24HoursUp)
384-
}
385-
if uptime24Hours == math.Trunc(uptime24Hours) {
386-
d.Uptime = append(d.Uptime, fmt.Sprintf("%.0f", uptime24Hours))
387-
} else {
388-
d.Uptime = append(d.Uptime, fmt.Sprintf("%.1f", uptime24Hours))
362+
if lastOutageTS != nil {
363+
d.LastOutage = &types.LastOutageDetails{
364+
Since: formatDuration(time.Since(*lastOutageTS)),
365+
TS: lastOutageTS.Format("January 2, 2006 15:04:05"),
366+
}
367+
if lastOutageUpTS != nil {
368+
d.LastOutage.Duration = formatDuration(lastOutageUpTS.Sub(*lastOutageTS))
369+
} else {
370+
d.LastOutage.Duration = formatDuration(time.Since(*lastOutageTS))
371+
}
389372
}
390373

391-
d.ResponseTime = []string{
392-
formatDuration(responseTime30Days),
393-
formatDuration(responseTime7Days),
394-
formatDuration(responseTime24Hours),
374+
if len(responses) > 0 && responses[len(responses)-1].SSLCertExpiry != nil {
375+
expireAt := responses[len(responses)-1].SSLCertExpiry
376+
d.SSL = &types.SSLDetails{
377+
ExpireInDays: int(expireAt.Sub(time.Now()).Hours() / 24),
378+
ExpireTS: expireAt.Format("January 2, 2006"),
379+
}
395380
}
396381

397382
return d

templates/common/style.html

+42-31
Original file line numberDiff line numberDiff line change
@@ -72,21 +72,10 @@
7272
&:last-child {
7373
border-bottom: none;
7474
}
75-
76-
h1 {
77-
width: 100%;
78-
font-size: 16px;
79-
font-weight: 400;
80-
color: var(--color-fg);
81-
margin: 0;
82-
padding: 0;
83-
}
8475
}
85-
8676
.container, &.container {
8777
gap: 6px;
8878
}
89-
9079
section {
9180
width: calc(100% - 2px);
9281
background: var(--color-fg-reverse);
@@ -133,7 +122,7 @@
133122
gap: 4px;
134123
p, a {
135124
font-size: 14px;
136-
font-weight: 600;
125+
font-weight: 500;
137126
&:nth-child(2) {
138127
font-size: 13px;
139128
font-weight: 300;
@@ -300,27 +289,45 @@
300289
}
301290
}
302291
}
303-
.blocks {
304-
display: flex;
292+
&.details {
293+
width: 100%;
294+
gap: 6px;
305295
flex-direction: row;
306-
justify-content: space-around;
307296
flex-wrap: wrap;
297+
background: transparent;
298+
border: none;
308299

309-
.block {
310-
padding: 14px 0;
311-
text-align: center;
312-
img {
313-
max-width: 100%;
314-
max-height: 100%;
315-
}
316-
p:first-child {
317-
font-size: 22px;
318-
font-weight: 700;
319-
}
320-
p:last-child {
321-
font-size: 12px;
322-
color: var(--color-subtitle);
323-
}
300+
.panel {
301+
flex-grow: 1;
302+
flex-basis: 0;
303+
align-self: stretch;
304+
background: var(--color-fg-reverse);
305+
border: solid var(--color-section-bg) 1px;
306+
}
307+
308+
h2 {
309+
font-size: 20px;
310+
font-weight: 600;
311+
color: var(--color-fg);
312+
margin: 6px 0 0 0;
313+
padding: 0;
314+
}
315+
h3 {
316+
font-size: 14px;
317+
font-weight: 500;
318+
color: var(--color-subtitle);
319+
margin: 0;
320+
padding: 0;
321+
}
322+
323+
.time {
324+
display: flex;
325+
flex-direction: row;
326+
justify-content: space-between;
327+
}
328+
329+
@media only screen and (max-width: 600px) {
330+
flex-direction: column;
324331
}
325332
}
326333
.event {
@@ -367,6 +374,10 @@
367374
}
368375
}
369376
}
377+
img {
378+
max-width: 100%;
379+
max-height: 100%;
380+
}
370381

371382
.head:has(input:checked) ~ .services {
372383
display: flex;
@@ -466,7 +477,7 @@
466477
.container {
467478
width: 96%;
468479
}
469-
header, section {
480+
header, section, .panel {
470481
border-radius: 3px;
471482
}
472483
footer > section {

templates/public.html

+29-54
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
Uptime over the past&nbsp;<span class="bp600">30</span><span class="bp800">60</span><span class="bp1000">90</span>&nbsp;days.
5555
{{ end }}
5656
</div>
57+
5758
<section>
5859
{{ if .Hosts }}
5960
{{ range $row := .Hosts }}
@@ -149,70 +150,44 @@
149150
{{ else }}
150151
<div class="panel"><h1>No hosts found</h1></div>
151152
{{ end }}
152-
153-
{{ if .IsHost }}
154-
<div class="panel blocks">
155-
<div class="block">
156-
<p>{{ index ((index .Hosts 0).Details.Uptime) 0 }}<small>%</small></p>
157-
<p>Uptime 30d</p>
158-
</div>
159-
<div class="block">
160-
<p>{{ index ((index .Hosts 0).Details.Uptime) 1 }}<small>%</small></p>
161-
<p>Uptime 7d</p>
162-
</div>
163-
<div class="block">
164-
<p>{{ index ((index .Hosts 0).Details.Uptime) 2 }}<small>%</small></p>
165-
<p>Uptime 24h</p>
166-
</div>
167-
</div>
168-
{{ end }}
169153
</section>
170154

171155
{{ if .IsHost }}
172-
<section>
173-
<div class="panel blocks">
174-
<div class="block">
175-
<img src="/response-time/{{ (index .Hosts 0).ID }}">
176-
</div>
177-
<div class="block">
178-
<p>{{ index ((index .Hosts 0).Details.ResponseTime) 0 }}</p>
179-
<p>Response time 30d</p>
180-
</div>
181-
<div class="block">
182-
<p>{{ index ((index .Hosts 0).Details.ResponseTime) 1 }}</p>
183-
<p>Response time 7d</p>
156+
<section class="details">
157+
<div class="panel">
158+
<div class="head"><div class="info"><p>Last 30 days</p></div></div>
159+
<div class="time">
160+
<h2>{{ (index .Hosts 0).Details.Uptime }}%</h2>
161+
<h2>{{ (index .Hosts 0).Details.ResponseTime }}</h2>
184162
</div>
185-
<div class="block">
186-
<p>{{ index ((index .Hosts 0).Details.ResponseTime) 2 }}</p>
187-
<p>Response time 24h</p>
163+
<div class="time">
164+
<h3>Uptime</h3>
165+
<h3>Response time</h3>
188166
</div>
189167
</div>
168+
{{ if (index .Hosts 0).Details.SSL }}
169+
<div class="panel">
170+
<div class="head"><div class="info"><p>SSL certificate expire in</p></div></div>
171+
<h2>{{ (index .Hosts 0).Details.SSL.ExpireInDays }} days</h2>
172+
<h3>{{ (index .Hosts 0).Details.SSL.ExpireTS }}</h3>
173+
</div>
174+
{{ end }}
175+
<!-- <div class="panel">-->
176+
<!-- <div class="head"><div class="info"><p>Last outage was</p></div></div>-->
177+
<!-- {{ if (index .Hosts 0).Details.LastOutage }}-->
178+
<!-- <h2>{{ (index .Hosts 0).Details.LastOutage.Since }} ago</h2>-->
179+
<!-- <h3>for {{ (index .Hosts 0).Details.LastOutage.Duration }} | {{ (index .Hosts 0).Details.LastOutage.TS }}</h3>-->
180+
<!-- {{ else }}-->
181+
<!-- <h2>Never</h2>-->
182+
<!-- {{ end }}-->
183+
<!-- </div>-->
190184
</section>
191-
{{ end }}
192185

193-
{{ if .Events }}
194186
<section>
195-
{{ range $val := .Events }}
196-
<div class="panel event">
197-
<div class="details">
198-
<div class="icon status-{{ .Status }}">
199-
{{ if eq .Status "up" }}
200-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12l5 5l10 -10"/></svg>
201-
{{ else if eq .Status "down" }}
202-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M18 6l-12 12"/><path d="M6 6l12 12"/></svg>
203-
{{ else if eq .Status "degraded" }}
204-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 9v-1a3 3 0 0 1 6 0v1"/><path d="M8 9h8a6 6 0 0 1 1 3v3a5 5 0 0 1 -10 0v-3a6 6 0 0 1 1 -3"/><path d="M3 13l4 0"/><path d="M17 13l4 0"/><path d="M12 20l0 -6"/><path d="M4 19l3.35 -2"/><path d="M20 19l-3.35 -2"/><path d="M4 7l3.75 2.4"/><path d="M20 7l-3.75 2.4"/></svg>
205-
{{ else }}
206-
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 8a3.5 3 0 0 1 3.5 -3h1a3.5 3 0 0 1 3.5 3a3 3 0 0 1 -2 3a3 4 0 0 0 -2 4" /><path d="M12 19l0 .01"/></svg>
207-
{{end}}
208-
</div>
209-
<p>{{ .Text }}</p>
210-
</div>
211-
<div class="ts">
212-
<p>{{ .Date }}</p>
213-
</div>
187+
<div class="panel">
188+
<div class="head"><div class="info"><p>Average response time</p></div></div>
189+
<div class="block"><img src="/response-time/{{ (index .Hosts 0).ID }}"></div>
214190
</div>
215-
{{ end }}
216191
</section>
217192
{{ end }}
218193
</main>

0 commit comments

Comments
 (0)