feat: settings navigation and mocked healthchecks in index

This commit is contained in:
Tine 2024-02-12 14:20:38 +01:00
parent 1769342068
commit 18d6c563a5
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
11 changed files with 175 additions and 183 deletions

View file

@ -46,7 +46,8 @@ func main() {
r.HandleFunc("/", h.Index).Methods("GET") r.HandleFunc("/", h.Index).Methods("GET")
// Authenticated routes // Authenticated routes
r.HandleFunc("/settings", h.Authenticated(h.Settings)).Methods("GET") r.HandleFunc("/settings", h.Authenticated(h.SettingsOverviewGET)).Methods("GET")
r.HandleFunc("/settings/healthchecks", h.Authenticated(h.SettingsHealthchecksGET)).Methods("GET")
// OAuth2 // OAuth2
r.HandleFunc("/oauth2/login", h.OAuth2LoginGET).Methods("GET") r.HandleFunc("/oauth2/login", h.OAuth2LoginGET).Methods("GET")

View file

@ -14,8 +14,8 @@ var Pages = []*components.Page{
{Path: "/settings", Title: "Settings"}, {Path: "/settings", Title: "Settings"},
} }
func GetPageByTitle(title string) *components.Page { func GetPageByTitle(pages []*components.Page, title string) *components.Page {
for _, p := range Pages { for _, p := range pages {
if p.Title == title { if p.Title == title {
return p return p
} }

View file

@ -1,6 +1,7 @@
package handlers package handlers
import ( import (
"math/rand"
"net/http" "net/http"
"text/template" "text/template"
@ -8,6 +9,36 @@ import (
"code.tjo.space/mentos1386/zdravko/web/templates/components" "code.tjo.space/mentos1386/zdravko/web/templates/components"
) )
type IndexData struct {
*components.Base
HealthChecks []*HealthCheck
}
type HealthCheck struct {
Domain string
Healthy bool
Uptime string
History []bool
}
func newMockHealthCheck(domain string) *HealthCheck {
randBool := func() bool {
return rand.Intn(2) == 1
}
var history []bool
for i := 0; i < 90; i++ {
history = append(history, randBool())
}
return &HealthCheck{
Domain: domain,
Healthy: randBool(),
Uptime: "100",
History: history,
}
}
func (h *BaseHandler) Index(w http.ResponseWriter, r *http.Request) { func (h *BaseHandler) Index(w http.ResponseWriter, r *http.Request) {
ts, err := template.ParseFS(templates.Templates, ts, err := template.ParseFS(templates.Templates,
"components/base.tmpl", "components/base.tmpl",
@ -18,9 +49,17 @@ func (h *BaseHandler) Index(w http.ResponseWriter, r *http.Request) {
return return
} }
err = ts.ExecuteTemplate(w, "base", &components.Base{ err = ts.ExecuteTemplate(w, "base", &IndexData{
Page: GetPageByTitle("Status"), Base: &components.Base{
Pages: Pages, Page: GetPageByTitle(Pages, "Status"),
Pages: Pages,
},
HealthChecks: []*HealthCheck{
newMockHealthCheck("example.com"),
newMockHealthCheck("example.org"),
newMockHealthCheck("example.net"),
newMockHealthCheck("foo.example.net"),
},
}) })
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -10,13 +10,23 @@ import (
type Settings struct { type Settings struct {
*components.Base *components.Base
User *AuthenticatedUser SettingsPage *components.Page
SettingsPages []*components.Page
User *AuthenticatedUser
} }
func (h *BaseHandler) Settings(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) { var SettingsPages = []*components.Page{
{Path: "/settings", Title: "Overview"},
{Path: "/settings/healthchecks", Title: "Healthchecks"},
{Path: "/settings/workers", Title: "Workers"},
{Path: "/oauth2/logout", Title: "Logout"},
}
func (h *BaseHandler) SettingsOverviewGET(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
ts, err := template.ParseFS(templates.Templates, ts, err := template.ParseFS(templates.Templates,
"components/base.tmpl", "components/base.tmpl",
"pages/settings.tmpl", "components/settings.tmpl",
"pages/settings_overview.tmpl",
) )
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)
@ -25,10 +35,37 @@ func (h *BaseHandler) Settings(w http.ResponseWriter, r *http.Request, user *Aut
err = ts.ExecuteTemplate(w, "base", &Settings{ err = ts.ExecuteTemplate(w, "base", &Settings{
Base: &components.Base{ Base: &components.Base{
Page: GetPageByTitle("Settings"), Page: GetPageByTitle(Pages, "Settings"),
Pages: Pages, Pages: Pages,
}, },
User: user, SettingsPage: GetPageByTitle(SettingsPages, "Overview"),
SettingsPages: SettingsPages,
User: user,
})
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
func (h *BaseHandler) SettingsHealthchecksGET(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) {
ts, err := template.ParseFS(templates.Templates,
"components/base.tmpl",
"components/settings.tmpl",
"pages/settings_healthchecks.tmpl",
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
err = ts.ExecuteTemplate(w, "base", &Settings{
Base: &components.Base{
Page: GetPageByTitle(Pages, "Settings"),
Pages: Pages,
},
SettingsPage: GetPageByTitle(SettingsPages, "Healthchecks"),
SettingsPages: SettingsPages,
User: user,
}) })
if err != nil { if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError) http.Error(w, err.Error(), http.StatusInternalServerError)

View file

@ -44,3 +44,11 @@
.sidebar a.active { .sidebar a.active {
@apply bg-blue-700 text-white; @apply bg-blue-700 text-white;
} }
.healthchecks {
@apply grid justify-items-stretch justify-stretch items-center mt-20 bg-gray-200 shadow-inner p-5 rounded-lg;
}
.healthchecks > div:not(:last-child) {
@apply mb-3;
}

View file

@ -603,8 +603,8 @@ video {
margin-bottom: 1rem; margin-bottom: 1rem;
} }
.mr-1 { .me-2 {
margin-right: 0.25rem; margin-inline-end: 0.5rem;
} }
.mt-10 { .mt-10 {
@ -631,8 +631,8 @@ video {
height: 5rem; height: 5rem;
} }
.h-5 { .h-3 {
height: 1.25rem; height: 0.75rem;
} }
.h-8 { .h-8 {
@ -643,8 +643,8 @@ video {
width: 5rem; width: 5rem;
} }
.w-5 { .w-3 {
width: 1.25rem; width: 0.75rem;
} }
.max-w-screen-md { .max-w-screen-md {
@ -663,8 +663,8 @@ video {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.grid-cols-\[1fr_auto\] { .grid-cols-\[1fr_100\%\] {
grid-template-columns: 1fr auto; grid-template-columns: 1fr 100%;
} }
.flex-col { .flex-col {
@ -679,14 +679,6 @@ video {
justify-content: center; justify-content: center;
} }
.justify-stretch {
justify-content: stretch;
}
.justify-items-stretch {
justify-items: stretch;
}
.gap-2 { .gap-2 {
gap: 0.5rem; gap: 0.5rem;
} }
@ -729,30 +721,16 @@ video {
border-radius: 9999px; border-radius: 9999px;
} }
.rounded-lg {
border-radius: 0.5rem;
}
.bg-gray-100 { .bg-gray-100 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity)); background-color: rgb(243 244 246 / var(--tw-bg-opacity));
} }
.bg-gray-200 {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
}
.bg-green-300 { .bg-green-300 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(134 239 172 / var(--tw-bg-opacity)); background-color: rgb(134 239 172 / var(--tw-bg-opacity));
} }
.bg-green-400 {
--tw-bg-opacity: 1;
background-color: rgb(74 222 128 / var(--tw-bg-opacity));
}
.bg-green-500 { .bg-green-500 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(34 197 94 / var(--tw-bg-opacity)); background-color: rgb(34 197 94 / var(--tw-bg-opacity));
@ -768,22 +746,10 @@ video {
background-color: rgb(239 68 68 / var(--tw-bg-opacity)); background-color: rgb(239 68 68 / var(--tw-bg-opacity));
} }
.stroke-\[3\] {
stroke-width: 3;
}
.p-1 {
padding: 0.25rem;
}
.p-4 { .p-4 {
padding: 1rem; padding: 1rem;
} }
.p-5 {
padding: 1.25rem;
}
.text-center { .text-center {
text-align: center; text-align: center;
} }
@ -798,11 +764,6 @@ video {
line-height: 1.25rem; line-height: 1.25rem;
} }
.text-xl {
font-size: 1.25rem;
line-height: 1.75rem;
}
.text-xs { .text-xs {
font-size: 0.75rem; font-size: 0.75rem;
line-height: 1rem; line-height: 1rem;
@ -836,12 +797,6 @@ video {
text-decoration-line: underline; text-decoration-line: underline;
} }
.shadow-inner {
--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.btn { .btn {
border-radius: 0.25rem; border-radius: 0.25rem;
padding-top: 0.5rem; padding-top: 0.5rem;
@ -940,6 +895,25 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity)); color: rgb(255 255 255 / var(--tw-text-opacity));
} }
.healthchecks {
margin-top: 5rem;
display: grid;
align-items: center;
justify-content: stretch;
justify-items: stretch;
border-radius: 0.5rem;
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
padding: 1.25rem;
--tw-shadow: inset 0 2px 4px 0 rgb(0 0 0 / 0.05);
--tw-shadow-colored: inset 0 2px 4px 0 var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.healthchecks > div:not(:last-child) {
margin-bottom: 0.75rem;
}
.hover\:bg-green-700:hover { .hover\:bg-green-700:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(21 128 61 / var(--tw-bg-opacity)); background-color: rgb(21 128 61 / var(--tw-bg-opacity));

View file

@ -0,0 +1,28 @@
{{define "main"}}
{{ $title := "" }}
{{ $path := "" }}
{{ if ne nil .SettingsPage }}
{{ $title = .SettingsPage.Title }}
{{ $path = .SettingsPage.Path }}
{{ end }}
<div class="grid grid-cols-[1fr_100%] gap-8">
<ul class="sidebar">
{{range .SettingsPages}}
<li>
<a
{{$active := eq .Path $path }}
{{if $active}}aria-current="true"{{end}}
href="{{.Path}}"
class="{{if $active}}active{{end}}">
{{.Title}}
</a>
</li>
{{end}}
</ul>
<div>
{{template "settings" .}}
</div>
</div>
{{end}}

View file

@ -1,5 +1,3 @@
{{define "title"}}Status{{end}}
{{define "main"}} {{define "main"}}
<div class="flex flex-col items-center"> <div class="flex flex-col items-center">
<svg class="feather h-20 w-20 rounded-full bg-green-300 p-4 overflow-visible"><use href="/static/icons/feather-sprite.svg#check" /></svg> <svg class="feather h-20 w-20 rounded-full bg-green-300 p-4 overflow-visible"><use href="/static/icons/feather-sprite.svg#check" /></svg>
@ -11,107 +9,30 @@
<h3 class="text-slate-500">Degraded performance</h3> <h3 class="text-slate-500">Degraded performance</h3>
<p class="text-slate-500 text-sm">Last updated on Feb 10 at 10:55am UTC</p> <p class="text-slate-500 text-sm">Last updated on Feb 10 at 10:55am UTC</p>
</div> </div>
<div class="grid justify-items-stretch justify-stretch items-center mt-20 bg-gray-200 shadow-inner p-5 rounded-lg"> <div class="healthchecks">
{{ range .HealthChecks }}
<div class="grid grid-cols-2 gap-2"> <div class="grid grid-cols-2 gap-2">
<div class="flex items-center"> <div class="flex items-center">
<svg class="feather h-5 w-5 stroke-[3] rounded-full bg-green-400 p-1 mr-1"><use href="/static/icons/feather-sprite.svg#check" /></svg> {{ if .Healthy }}
<p>foo.bar</p> <span class="flex w-3 h-3 me-2 bg-green-500 rounded-full"></span>
{{ else }}
<span class="flex w-3 h-3 me-2 bg-red-500 rounded-full"></span>
{{ end }}
<p>{{ .Domain }}</p>
</div> </div>
<div class="justify-self-end text-sm">69.420% uptime</div> <div class="justify-self-end text-sm">{{ .Uptime }}% uptime</div>
<div class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden"> <div class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden">
{{ range .History }}
{{ if . }}
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div> <div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div> {{ else }}
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-red-500 hover:bg-red-700 flex-auto"></div> <div class="bg-red-500 hover:bg-red-700 flex-auto"></div>
<div class="bg-red-500 hover:bg-red-700 flex-auto"></div> {{ end }}
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div> {{ end }}
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div> </div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-red-500 hover:bg-red-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
<div class="bg-green-500 hover:bg-green-700 flex-auto"></div>
</div>
<div class="text-slate-500 justify-self-start text-sm">90 days ago</div> <div class="text-slate-500 justify-self-start text-sm">90 days ago</div>
<div class="text-slate-500 justify-self-end text-sm">Today</div> <div class="text-slate-500 justify-self-end text-sm">Today</div>
</div> </div>
{{ end }}
</div> </div>
{{end}} {{end}}

View file

@ -1,24 +0,0 @@
{{define "main"}}
<div class="grid grid-cols-[1fr_auto] gap-8">
<ul class="sidebar">
<li>
<a aria-current="true" class="active" href="/settings">Overview</a>
</li>
<li>
<a href="/settings/healthchecks">Healthchecks</a>
</li>
<li>
<a href="/settings/workers">Workers</a>
</li>
<li>
<a href="/oauth2/logout">Logout</a>
</li>
</ul>
<div class="shadow-inner bg-gray-200 p-5 rounded-lg">
<h1 class="text-xl mb-4">Hi {{.User.Email}}!</h1>
<p>Your id is {{.User.ID}}.</p>
<p>Your access expieres at {{.User.OAuth2Expiry}}.</p>
</div>
</div>
{{end}}

View file

@ -0,0 +1,5 @@
{{define "settings"}}
<h1 class="text-3xl">Hello, {{.User.Email}}</h1>
<p> Imagine you see a list of healthchecks and how to configure them.<p>
{{end}}

View file

@ -0,0 +1,3 @@
{{define "settings"}}
<h1 class="text-3xl">Hello, {{.User.Email}}</h1>
{{end}}