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 (
"flag"
"log"
"log/slog"
"os"
"os/signal"
"sync"
@ -20,7 +20,19 @@ type StartableAndStoppable interface {
Stop() error
}
func setupLogger() {
opts := &slog.HandlerOptions{
Level: slog.LevelDebug,
}
logger := slog.New(slog.NewTextHandler(os.Stdout, opts))
slog.SetDefault(logger)
}
func main() {
setupLogger()
var startServer bool
var startWorker bool
var startTemporal bool
@ -28,47 +40,47 @@ func main() {
flag.BoolVar(&startServer, "server", false, "Start the server")
flag.BoolVar(&startWorker, "worker", false, "Start the worker")
flag.BoolVar(&startTemporal, "temporal", false, "Start the temporal")
flag.Parse()
println("Starting zdravko...")
println("Server: ", startServer)
println("Worker: ", startWorker)
println("Temporal: ", startTemporal)
slog.Info("Starting zdravko...", "server", startServer, "worker", startWorker, "temporal", 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 wg sync.WaitGroup
if startTemporal {
log.Println("Setting up Temporal")
slog.Info("Setting up Temporal")
cfg := config.NewTemporalConfig()
temporal, err := temporal.NewTemporal(cfg)
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
}
if startServer {
log.Println("Setting up Server")
slog.Info("Setting up Server")
cfg := config.NewServerConfig()
server, err := server.NewServer(cfg)
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
}
if startWorker {
log.Println("Setting up Worker")
slog.Info("Setting up Worker")
cfg := config.NewWorkerConfig()
worker, err := worker.NewWorker(cfg)
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
}
@ -79,13 +91,14 @@ func main() {
continue
}
println("Starting", srv.Name())
slog.Info("Starting", "name", srv.Name())
wg.Add(1)
go func() {
defer wg.Done()
err := srv.Start()
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)
go func() {
for sig := range c {
log.Printf("Received signal: %v", sig)
slog.Info("Received signal", "signal", sig)
for _, srv := range servers {
if srv == nil {
continue
}
println("Stopping", srv.Name())
slog.Info("Stopping", "name", srv.Name())
err := srv.Stop()
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{
cfg: cfg,
echo: echo.New(),
logger: slog.Default().WithGroup("server"),
logger: slog.Default(),
}, nil
}
@ -51,7 +51,12 @@ func (s *Server) Start() error {
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.Recover())
s.echo.Use(middleware.Secure())

View file

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

View file

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

View file

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

View file

@ -17,8 +17,12 @@
<link rel="stylesheet" href="/static/css/tailwind.css" />
</head>
<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">
<a href="/" class="text-2xl font-bold">zdravko.mnts.dev</a>
<header
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">
{{ range .Navbar }}
<a
@ -31,21 +35,28 @@
</a>
{{ end }}
</nav>
<img
src="https://avatars.githubusercontent.com/u/1910649?v=4"
alt="Profile Image"
class="rounded-full w-12 h-12"
></img>
</header>
{{ template "main" . }}
<div class="container mx-auto">
<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
class="hover:underline"
href="https://github.com/mentos1386/zdravko"
>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>
</div>
<script src="/static/js/htmx.min.js"></script>

View file

View file

View file

@ -1,4 +1,22 @@
{{ 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
id="page-index"
class="container max-w-screen-md flex flex-col mt-20 gap-20"
@ -32,61 +50,21 @@
</div>
</section>
{{ else }}
{{ if eq .Outcome "HEALTHY" }}
<div class="flex flex-col items-center">
<svg
class="feather h-20 w-20 rounded-full bg-green-300 p-4 overflow-visible"
class="feather h-20 w-20 rounded-full {{ $outcomeColor }} p-4 overflow-visible"
>
<use href="/static/icons/feather-sprite.svg#check" />
<use href="/static/icons/feather-sprite.svg#{{ $outcomeIcon }}" />
</svg>
<h1 class="text-gray-700 mt-4 text-lg">All services are online</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 "UNKNOWN" }}
<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 class="text-gray-800 mt-4 text-xl font-bold">
{{ $outcomeText }}
</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="inline-flex gap-1 justify-center md:justify-end time-range"

View file

@ -3,7 +3,8 @@ package templates
import (
"embed"
"io"
"log"
"io/fs"
"log/slog"
"strings"
"text/template"
"time"
@ -18,6 +19,7 @@ var templates embed.FS
const base = "components/base.tmpl"
type Templates struct {
logger *slog.Logger
templates map[string]*template.Template
}
@ -52,40 +54,41 @@ func loadSettings(files ...string) *template.Template {
return load(files...)
}
func NewTemplates() *Templates {
return &Templates{
templates: map[string]*template.Template{
"404.tmpl": load("pages/404.tmpl"),
"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"),
},
func NewTemplates(logger *slog.Logger) (*Templates, error) {
t := Templates{
logger: logger,
templates: map[string]*template.Template{},
}
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 {
if t.templates[name] == nil {
log.Printf("template not found: %s", name)
t.logger.Error("template not found", "template", name)
return echo.ErrNotFound
}
err := t.templates[name].ExecuteTemplate(w, "base", data)
if err != nil {
log.Printf("error rendering template: %s", err)
t.logger.Error("error rendering template", "template", err)
}
return err