feat: ui, logger and other general improvements

This commit is contained in:
Tine 2024-05-29 16:39:50 +02:00
parent 4afb709b87
commit b68c348f2a
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
10 changed files with 143 additions and 132 deletions

View file

@ -2,7 +2,7 @@ package main
import ( import (
"flag" "flag"
"log" "log/slog"
"os" "os"
"os/signal" "os/signal"
"sync" "sync"
@ -20,7 +20,19 @@ type StartableAndStoppable interface {
Stop() error Stop() error
} }
func setupLogger() {
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
}
logger := slog.New(slog.NewTextHandler(os.Stdout, opts))
slog.SetDefault(logger)
}
func main() { func main() {
setupLogger()
var startServer bool var startServer bool
var startWorker bool var startWorker bool
var startTemporal bool var startTemporal bool
@ -28,47 +40,47 @@ func main() {
flag.BoolVar(&startServer, "server", false, "Start the server") flag.BoolVar(&startServer, "server", false, "Start the server")
flag.BoolVar(&startWorker, "worker", false, "Start the worker") flag.BoolVar(&startWorker, "worker", false, "Start the worker")
flag.BoolVar(&startTemporal, "temporal", false, "Start the temporal") flag.BoolVar(&startTemporal, "temporal", false, "Start the temporal")
flag.Parse() flag.Parse()
println("Starting zdravko...") slog.Info("Starting zdravko...", "server", startServer, "worker", startWorker, "temporal", startTemporal)
println("Server: ", startServer)
println("Worker: ", startWorker)
println("Temporal: ", startTemporal)
if !startServer && !startWorker && !startTemporal { if !startServer && !startWorker && !startTemporal {
log.Fatal("At least one of the following must be set: --server, --worker, --temporal") slog.Error("At least one of the following must be set: --server, --worker, --temporal")
os.Exit(1)
} }
var servers [3]StartableAndStoppable var servers [3]StartableAndStoppable
var wg sync.WaitGroup var wg sync.WaitGroup
if startTemporal { if startTemporal {
log.Println("Setting up Temporal") slog.Info("Setting up Temporal")
cfg := config.NewTemporalConfig() cfg := config.NewTemporalConfig()
temporal, err := temporal.NewTemporal(cfg) temporal, err := temporal.NewTemporal(cfg)
if err != nil { if err != nil {
log.Fatalf("Unable to create temporal: %v", err) slog.Error("Unable to create temporal", "error", err)
os.Exit(1)
} }
servers[0] = temporal servers[0] = temporal
} }
if startServer { if startServer {
log.Println("Setting up Server") slog.Info("Setting up Server")
cfg := config.NewServerConfig() cfg := config.NewServerConfig()
server, err := server.NewServer(cfg) server, err := server.NewServer(cfg)
if err != nil { if err != nil {
log.Fatalf("Unable to create server: %v", err) slog.Error("Unable to create server", "error", err)
os.Exit(1)
} }
servers[1] = server servers[1] = server
} }
if startWorker { if startWorker {
log.Println("Setting up Worker") slog.Info("Setting up Worker")
cfg := config.NewWorkerConfig() cfg := config.NewWorkerConfig()
worker, err := worker.NewWorker(cfg) worker, err := worker.NewWorker(cfg)
if err != nil { if err != nil {
log.Fatalf("Unable to create worker: %v", err) slog.Error("Unable to create worker", "error", err)
os.Exit(1)
} }
servers[2] = worker servers[2] = worker
} }
@ -79,13 +91,14 @@ func main() {
continue continue
} }
println("Starting", srv.Name()) slog.Info("Starting", "name", srv.Name())
wg.Add(1) wg.Add(1)
go func() { go func() {
defer wg.Done() defer wg.Done()
err := srv.Start() err := srv.Start()
if err != nil { if err != nil {
log.Fatalf("Unable to start %s: %v", srv.Name(), err) slog.Error("Unable to start", "name", srv.Name(), "error", err)
os.Exit(1)
} }
}() }()
} }
@ -94,16 +107,16 @@ func main() {
signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) signal.Notify(c, syscall.SIGINT, syscall.SIGTERM)
go func() { go func() {
for sig := range c { for sig := range c {
log.Printf("Received signal: %v", sig) slog.Info("Received signal", "signal", sig)
for _, srv := range servers { for _, srv := range servers {
if srv == nil { if srv == nil {
continue continue
} }
println("Stopping", srv.Name()) slog.Info("Stopping", "name", srv.Name())
err := srv.Stop() err := srv.Stop()
if err != nil { if err != nil {
log.Printf("Unable to stop server %s: %v", srv.Name(), err) slog.Error("Unable to stop server", "name", srv.Name(), "error", err)
} }
} }
} }

View file

@ -25,7 +25,7 @@ func NewServer(cfg *config.ServerConfig) (*Server, error) {
return &Server{ return &Server{
cfg: cfg, cfg: cfg,
echo: echo.New(), echo: echo.New(),
logger: slog.Default().WithGroup("server"), logger: slog.Default(),
}, nil }, nil
} }
@ -51,7 +51,12 @@ func (s *Server) Start() error {
s.worker = NewWorker(temporalClient, s.cfg, s.logger, sqliteDb, kvStore) s.worker = NewWorker(temporalClient, s.cfg, s.logger, sqliteDb, kvStore)
s.echo.Renderer = templates.NewTemplates() templates, err := templates.NewTemplates(s.logger)
if err != nil {
return errors.Wrap(err, "failed to create templates")
}
s.echo.Renderer = templates
s.echo.Use(middleware.Logger()) s.echo.Use(middleware.Logger())
s.echo.Use(middleware.Recover()) s.echo.Use(middleware.Recover())
s.echo.Use(middleware.Secure()) s.echo.Use(middleware.Secure())

View file

@ -66,7 +66,7 @@ type Worker struct {
func NewWorker(cfg *config.WorkerConfig) (*Worker, error) { func NewWorker(cfg *config.WorkerConfig) (*Worker, error) {
return &Worker{ return &Worker{
cfg: cfg, cfg: cfg,
logger: slog.Default().WithGroup("worker"), logger: slog.Default(),
}, nil }, nil
} }

View file

@ -82,18 +82,18 @@ code {
background: repeating-linear-gradient( background: repeating-linear-gradient(
0deg, 0deg,
var(--color-orange-300), var(--color-orange-300),
var(--color-orange-300) 10%, var(--color-orange-300) 33%,
var(--color-orange-400) 10%, var(--color-orange-400) 33%,
var(--color-orange-400) 20% var(--color-orange-400) 66%
); );
} }
#page-index .history.degraded:hover > .bar { #page-index .history.degraded:hover > .bar {
background: repeating-linear-gradient( background: repeating-linear-gradient(
0deg, 0deg,
var(--color-orange-400), var(--color-orange-400),
var(--color-orange-400) 10%, var(--color-orange-400) 33%,
var(--color-orange-500) 10%, var(--color-orange-500) 33%,
var(--color-orange-500) 20% var(--color-orange-500) 66%
); );
} }

View file

@ -1020,10 +1020,6 @@ video {
height: min-content; height: min-content;
} }
.w-12 {
width: 3rem;
}
.w-20 { .w-20 {
width: 5rem; width: 5rem;
} }
@ -1386,16 +1382,16 @@ video {
line-height: 1.5rem; line-height: 1.5rem;
} }
.text-lg {
font-size: 1.125rem;
line-height: 1.75rem;
}
.text-sm { .text-sm {
font-size: 0.875rem; font-size: 0.875rem;
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;
@ -1514,6 +1510,11 @@ video {
color: rgb(107 33 168 / var(--tw-text-opacity)); color: rgb(107 33 168 / var(--tw-text-opacity));
} }
.text-red-500 {
--tw-text-opacity: 1;
color: rgb(239 68 68 / var(--tw-text-opacity));
}
.text-red-600 { .text-red-600 {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(220 38 38 / var(--tw-text-opacity)); color: rgb(220 38 38 / var(--tw-text-opacity));
@ -1743,9 +1744,9 @@ code {
background: repeating-linear-gradient( background: repeating-linear-gradient(
0deg, 0deg,
var(--color-orange-300), var(--color-orange-300),
var(--color-orange-300) 10%, var(--color-orange-300) 33%,
var(--color-orange-400) 10%, var(--color-orange-400) 33%,
var(--color-orange-400) 20% var(--color-orange-400) 66%
); );
} }
@ -1753,9 +1754,9 @@ code {
background: repeating-linear-gradient( background: repeating-linear-gradient(
0deg, 0deg,
var(--color-orange-400), var(--color-orange-400),
var(--color-orange-400) 10%, var(--color-orange-400) 33%,
var(--color-orange-500) 10%, var(--color-orange-500) 33%,
var(--color-orange-500) 20% var(--color-orange-500) 66%
); );
} }
@ -2133,8 +2134,8 @@ code {
justify-content: center; justify-content: center;
} }
.sm\:justify-evenly { .sm\:justify-between {
justify-content: space-evenly; justify-content: space-between;
} }
.sm\:px-8 { .sm\:px-8 {

View file

@ -17,8 +17,12 @@
<link rel="stylesheet" href="/static/css/tailwind.css" /> <link rel="stylesheet" href="/static/css/tailwind.css" />
</head> </head>
<body class="bg-gray-100 flex flex-col"> <body class="bg-gray-100 flex flex-col">
<header class="flex flex-col sm:flex-row items-center sm:justify-evenly p-4 gap-2"> <header
<a href="/" class="text-2xl font-bold">zdravko.mnts.dev</a> class="container max-w-screen-md flex flex-col sm:flex-row items-center sm:justify-between p-4 gap-2"
>
<a href="/" class="hover:underline text-2xl font-bold"
>zdravko.mnts.dev</a
>
<nav class="navbar flex sm:flex-row flex-col flex-wrap space-x-2 gap-1"> <nav class="navbar flex sm:flex-row flex-col flex-wrap space-x-2 gap-1">
{{ range .Navbar }} {{ range .Navbar }}
<a <a
@ -31,21 +35,28 @@
</a> </a>
{{ end }} {{ end }}
</nav> </nav>
<img
src="https://avatars.githubusercontent.com/u/1910649?v=4"
alt="Profile Image"
class="rounded-full w-12 h-12"
></img>
</header> </header>
{{ template "main" . }} {{ template "main" . }}
<div class="container mx-auto"> <div class="container mx-auto">
<footer class="text-center text-gray-600 text-xs mt-8 mb-4"> <footer class="text-center text-gray-600 text-xs mt-8 mb-4">
&copy; {{ Now.UTC.Year }} Zdravko - Powered by
<a class="hover:underline" href="https://zdravko.mnts.dev">Zdravko</a>
-
<a <a
class="hover:underline" class="hover:underline"
href="https://github.com/mentos1386/zdravko" href="https://github.com/mentos1386/zdravko"
>Open Source</a >Open Source</a
> >
- Made with <span class="text-red-500">❤</span> by
<a class="hover:underline" href="https://mnts.dev/about"
>Mentos1386</a
>
and
<a
class="hover:underline"
href="https://github.com/mentos1386/zdravko/graphs/contributors"
>others</a
>.
</footer> </footer>
</div> </div>
<script src="/static/js/htmx.min.js"></script> <script src="/static/js/htmx.min.js"></script>

View file

View file

View file

@ -1,4 +1,22 @@
{{ define "main" }} {{ define "main" }}
{{ $outcomeText := "All services are online." }}
{{ $outcomeIcon := "check" }}
{{ $outcomeColor := "bg-green-300" }}
{{ if eq .Outcome "DOWN" }}
{{ $outcomeText = "Some services are down." }}
{{ $outcomeIcon = "alert-circle" }}
{{ $outcomeColor = "bg-red-300" }}
{{ else if eq .Outcome "DEGRADED" }}
{{ $outcomeText = "Some services are degraded." }}
{{ $outcomeIcon = "alert-triangle" }}
{{ $outcomeColor = "bg-orange-300" }}
{{ else }}
{{ $outcomeText = "We are unable to determine current status." }}
{{ $outcomeIcon = "help-circle" }}
{{ $outcomeColor = "bg-gray-300" }}
{{ end }}
<div <div
id="page-index" id="page-index"
class="container max-w-screen-md flex flex-col mt-20 gap-20" class="container max-w-screen-md flex flex-col mt-20 gap-20"
@ -32,61 +50,21 @@
</div> </div>
</section> </section>
{{ else }} {{ else }}
{{ if eq .Outcome "HEALTHY" }} <div class="flex flex-col items-center">
<div class="flex flex-col items-center"> <svg
<svg class="feather h-20 w-20 rounded-full {{ $outcomeColor }} p-4 overflow-visible"
class="feather h-20 w-20 rounded-full bg-green-300 p-4 overflow-visible" >
> <use href="/static/icons/feather-sprite.svg#{{ $outcomeIcon }}" />
<use href="/static/icons/feather-sprite.svg#check" /> </svg>
</svg> <h1 class="text-gray-800 mt-4 text-xl font-bold">
<h1 class="text-gray-700 mt-4 text-lg">All services are online</h1> {{ $outcomeText }}
<p class="text-gray-700 text-sm"> </h1>
Last updated on <p class="text-gray-700 text-sm">
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }} Last updated on
</p> {{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
</div> </p>
{{ else if eq .Outcome "UNKNOWN" }} </div>
<div class="flex flex-col items-center">
<svg
class="feather h-20 w-20 rounded-full bg-gray-300 p-4 overflow-visible"
>
<use href="/static/icons/feather-sprite.svg#help-circle" />
</svg>
<h1 class="text-gray-700 mt-4 text-lg">
We are unable to determine current status
</h1>
<p class="text-gray-700 text-sm">
Last updated on
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
</p>
</div>
{{ else if eq .Outcome "DOWN" }}
<div class="flex flex-col items-center">
<svg
class="feather h-20 w-20 rounded-full bg-red-300 p-4 overflow-visible"
>
<use href="/static/icons/feather-sprite.svg#alert-circle" />
</svg>
<h1 class="text-gray-700 mt-4 text-lg">Some services are down</h1>
<p class="text-gray-700 text-sm">
Last updated on
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
</p>
</div>
{{ else if eq .Outcome "DEGRADED" }}
<div class="flex flex-col items-center">
<svg
class="feather h-20 w-20 rounded-full bg-orange-300 p-4 overflow-visible"
>
<use href="/static/icons/feather-sprite.svg#alert-triangle" />
</svg>
<h1 class="text-gray-700 mt-4 text-lg">Degraded services</h1>
<p class="text-gray-700 text-sm">
Last updated on
{{ Now.UTC.Format "Jan 02 at 15:04 MST" }}
</p>
</div>
{{ end }}
<div class="targets flex flex-col gap-4"> <div class="targets flex flex-col gap-4">
<div <div
class="inline-flex gap-1 justify-center md:justify-end time-range" class="inline-flex gap-1 justify-center md:justify-end time-range"

View file

@ -3,7 +3,8 @@ package templates
import ( import (
"embed" "embed"
"io" "io"
"log" "io/fs"
"log/slog"
"strings" "strings"
"text/template" "text/template"
"time" "time"
@ -18,6 +19,7 @@ var templates embed.FS
const base = "components/base.tmpl" const base = "components/base.tmpl"
type Templates struct { type Templates struct {
logger *slog.Logger
templates map[string]*template.Template templates map[string]*template.Template
} }
@ -52,40 +54,41 @@ func loadSettings(files ...string) *template.Template {
return load(files...) return load(files...)
} }
func NewTemplates() *Templates { func NewTemplates(logger *slog.Logger) (*Templates, error) {
return &Templates{ t := Templates{
templates: map[string]*template.Template{ logger: logger,
"404.tmpl": load("pages/404.tmpl"), templates: map[string]*template.Template{},
"index.tmpl": load("pages/index.tmpl"),
"incidents.tmpl": load("pages/incidents.tmpl"),
"settings_home.tmpl": loadSettings("pages/settings_home.tmpl"),
"settings_triggers.tmpl": loadSettings("pages/settings_triggers.tmpl"),
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
"settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"),
"settings_targets_create.tmpl": loadSettings("pages/settings_targets_create.tmpl"),
"settings_targets_describe.tmpl": loadSettings("pages/settings_targets_describe.tmpl"),
"settings_incidents.tmpl": loadSettings("pages/settings_incidents.tmpl"),
"settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"),
"settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"),
"settings_worker_groups_create.tmpl": loadSettings("pages/settings_worker_groups_create.tmpl"),
"settings_worker_groups_describe.tmpl": loadSettings("pages/settings_worker_groups_describe.tmpl"),
"settings_checks.tmpl": loadSettings("pages/settings_checks.tmpl"),
"settings_checks_create.tmpl": loadSettings("pages/settings_checks_create.tmpl"),
"settings_checks_describe.tmpl": loadSettings("pages/settings_checks_describe.tmpl"),
},
} }
err := fs.WalkDir(templates, "pages", func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if strings.Contains(path, ".tmpl") {
t.logger.Debug("Loading template", "path", path)
pathWithoutPrefix := strings.TrimPrefix(path, "pages/")
if strings.Contains(path, "settings") {
t.templates[pathWithoutPrefix] = loadSettings(path)
} else {
t.templates[pathWithoutPrefix] = load(path)
}
}
return nil
})
return &t, err
} }
func (t *Templates) Render(w io.Writer, name string, data interface{}, c echo.Context) error { func (t *Templates) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
if t.templates[name] == nil { if t.templates[name] == nil {
log.Printf("template not found: %s", name) t.logger.Error("template not found", "template", name)
return echo.ErrNotFound return echo.ErrNotFound
} }
err := t.templates[name].ExecuteTemplate(w, "base", data) err := t.templates[name].ExecuteTemplate(w, "base", data)
if err != nil { if err != nil {
log.Printf("error rendering template: %s", err) t.logger.Error("error rendering template", "template", err)
} }
return err return err