feat: groups for monitors

This commit is contained in:
Tine 2024-03-03 15:28:25 +01:00
parent 571fee81f5
commit 24e8414e03
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
13 changed files with 258 additions and 116 deletions

View file

@ -58,8 +58,9 @@ type Monitor struct {
CreatedAt *Time `db:"created_at"`
UpdatedAt *Time `db:"updated_at"`
Id string `db:"id"`
Name string `db:"name"`
Id string `db:"id"`
Name string `db:"name"`
Group string `db:"group"`
Schedule string `db:"schedule"`
Script string `db:"script"`

View file

@ -9,6 +9,7 @@ CREATE TABLE oauth2_states (
CREATE TABLE monitors (
id TEXT NOT NULL,
name TEXT NOT NULL,
"group" TEXT NOT NULL DEFAULT 'default',
schedule TEXT NOT NULL,
script TEXT NOT NULL,

View file

@ -17,8 +17,8 @@ primary_region = 'waw'
ROOT_URL = 'https://zdravko.mnts.dev'
TEMPORAL_SERVER_HOST = 'server.process.zdravko.internal:7233'
TEMPORAL_DATABASE_PATH = '/data/temporal-7.db'
DATABASE_PATH = '/data/zdravko-7.db'
TEMPORAL_DATABASE_PATH = '/data/temporal-8.db'
DATABASE_PATH = '/data/zdravko-8.db'
[processes]
server = '--temporal --server'

View file

@ -13,14 +13,15 @@ import (
type IndexData struct {
*components.Base
HealthChecks []*HealthCheck
Monitors map[string][]*Monitor
MonitorsLength int
TimeRange string
Status models.MonitorStatus
}
type HealthCheck struct {
type Monitor struct {
Name string
Group string
Status models.MonitorStatus
History *History
}
@ -96,7 +97,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
overallStatus := models.MonitorSuccess
monitorsWithHistory := make([]*HealthCheck, len(monitors))
monitorsWithHistory := make([]*Monitor, len(monitors))
for i, monitor := range monitors {
history, err := services.GetMonitorHistoryForMonitor(ctx, h.db, monitor.Id)
if err != nil {
@ -118,21 +119,26 @@ func (h *BaseHandler) Index(c echo.Context) error {
overallStatus = status
}
monitorsWithHistory[i] = &HealthCheck{
monitorsWithHistory[i] = &Monitor{
Name: monitor.Name,
Group: monitor.Group,
Status: status,
History: historyResult,
}
}
monitorsByGroup := map[string][]*Monitor{}
for _, monitor := range monitorsWithHistory {
monitorsByGroup[monitor.Group] = append(monitorsByGroup[monitor.Group], monitor)
}
return c.Render(http.StatusOK, "index.tmpl", &IndexData{
Base: &components.Base{
NavbarActive: GetPageByTitle(Pages, "Status"),
Navbar: Pages,
},
HealthChecks: monitorsWithHistory,
MonitorsLength: len(monitors),
TimeRange: timeRange,
Status: overallStatus,
Monitors: monitorsByGroup,
TimeRange: timeRange,
Status: overallStatus,
})
}

View file

@ -5,6 +5,7 @@ import (
"database/sql"
"fmt"
"net/http"
"slices"
"strings"
"code.tjo.space/mentos1386/zdravko/database/models"
@ -17,12 +18,14 @@ import (
type CreateMonitor struct {
Name string `validate:"required"`
Group string `validate:"required"`
WorkerGroups string `validate:"required"`
Schedule string `validate:"required,cron"`
Script string `validate:"required"`
}
type UpdateMonitor struct {
Group string `validate:"required"`
WorkerGroups string `validate:"required"`
Schedule string `validate:"required,cron"`
Script string `validate:"required"`
@ -35,7 +38,8 @@ type MonitorWithWorkerGroupsAndStatus struct {
type SettingsMonitors struct {
*Settings
Monitors []*MonitorWithWorkerGroupsAndStatus
Monitors map[string][]*MonitorWithWorkerGroupsAndStatus
MonitorGroups []string
}
type SettingsMonitor struct {
@ -64,13 +68,23 @@ func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error {
}
}
monitorGroups := []string{}
monitorsByGroup := map[string][]*MonitorWithWorkerGroupsAndStatus{}
for _, monitor := range monitorsWithStatus {
monitorsByGroup[monitor.Group] = append(monitorsByGroup[monitor.Group], monitor)
if slices.Contains(monitorGroups, monitor.Group) == false {
monitorGroups = append(monitorGroups, monitor.Group)
}
}
return c.Render(http.StatusOK, "settings_monitors.tmpl", &SettingsMonitors{
Settings: NewSettings(
cc.Principal.User,
GetPageByTitle(SettingsPages, "Monitors"),
[]*components.Page{GetPageByTitle(SettingsPages, "Monitors")},
),
Monitors: monitorsWithStatus,
Monitors: monitorsByGroup,
MonitorGroups: monitorGroups,
})
}
@ -174,6 +188,7 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
monitorId := c.Param("id")
update := UpdateMonitor{
Group: c.FormValue("group"),
WorkerGroups: strings.TrimSpace(c.FormValue("workergroups")),
Schedule: c.FormValue("schedule"),
Script: c.FormValue("script"),
@ -187,6 +202,7 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
if err != nil {
return err
}
monitor.Group = update.Group
monitor.Schedule = update.Schedule
monitor.Script = update.Script
@ -251,6 +267,7 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
create := CreateMonitor{
Name: c.FormValue("name"),
Group: c.FormValue("group"),
Schedule: c.FormValue("schedule"),
WorkerGroups: c.FormValue("workergroups"),
Script: c.FormValue("script"),
@ -282,6 +299,7 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
monitor := &models.Monitor{
Name: create.Name,
Group: create.Group,
Id: monitorId,
Schedule: create.Schedule,
Script: create.Script,

View file

@ -62,7 +62,7 @@ func SetMonitorStatus(ctx context.Context, temporal client.Client, id string, st
func CreateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error {
_, err := db.NamedExecContext(ctx,
"INSERT INTO monitors (id, name, script, schedule) VALUES (:id, :name, :script, :schedule)",
`INSERT INTO monitors (id, name, "group", script, schedule) VALUES (:id, :name, :group, :script, :schedule)`,
monitor,
)
return err
@ -70,7 +70,7 @@ func CreateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) er
func UpdateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error {
_, err := db.NamedExecContext(ctx,
"UPDATE monitors SET name=:name, script=:script, schedule=:schedule WHERE id=:id",
`UPDATE monitors SET "group"=:group, script=:script, schedule=:schedule WHERE id=:id`,
monitor,
)
return err
@ -126,6 +126,7 @@ func GetMonitorWithWorkerGroups(ctx context.Context, db *sqlx.DB, id string) (*m
SELECT
monitors.id,
monitors.name,
monitors."group",
monitors.script,
monitors.schedule,
monitors.created_at,
@ -150,6 +151,7 @@ WHERE monitors.id=$1
err = rows.Scan(
&monitor.Id,
&monitor.Name,
&monitor.Group,
&monitor.Script,
&monitor.Schedule,
&monitor.CreatedAt,
@ -181,6 +183,7 @@ func GetMonitorsWithWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.Mo
SELECT
monitors.id,
monitors.name,
monitors."group",
monitors.script,
monitors.schedule,
monitors.created_at,
@ -205,6 +208,7 @@ ORDER BY monitors.name
err = rows.Scan(
&monitor.Id,
&monitor.Name,
&monitor.Group,
&monitor.Script,
&monitor.Schedule,
&monitor.CreatedAt,

View file

@ -56,10 +56,10 @@ code {
@apply bg-blue-700 text-white;
}
.monitors {
@apply grid justify-items-stretch justify-stretch items-center mt-20 bg-white shadow-md p-5 rounded-lg;
.monitors .monitors-list {
@apply grid justify-items-stretch justify-stretch items-center bg-white shadow-md p-5 py-2 rounded-lg;
}
.monitors > div:not(:last-child) {
.monitors .monitors-list > div:not(:last-child) {
@apply mb-3;
}
.monitors .time-range > a {
@ -113,9 +113,16 @@ code {
.settings section table thead th {
@apply px-6 py-4 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider;
}
.settings section table tbody tr {
@apply odd:bg-white even:bg-gray-50;
}
.settings section table tbody tr th {
@apply px-6 py-4 font-medium text-gray-900 whitespace-nowrap text-center;
}
.settings section table tbody tr td {
@apply px-6 py-4 text-center whitespace-nowrap;
}
.settings section table tbody tr.row-special {
@apply bg-gray-100;
@apply font-semibold text-xs uppercase tracking-wider;
}

View file

@ -697,6 +697,10 @@ video {
height: 1.25rem;
}
.h-6 {
height: 1.5rem;
}
.h-8 {
height: 2rem;
}
@ -731,6 +735,10 @@ video {
width: 1.25rem;
}
.w-6 {
width: 1.5rem;
}
.w-fit {
width: -moz-fit-content;
width: fit-content;
@ -796,6 +804,10 @@ video {
gap: 0.5rem;
}
.gap-20 {
gap: 5rem;
}
.gap-4 {
gap: 1rem;
}
@ -1039,11 +1051,21 @@ 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;
@ -1322,8 +1344,7 @@ code {
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.monitors {
margin-top: 5rem;
.monitors .monitors-list {
display: grid;
align-items: center;
justify-content: stretch;
@ -1332,12 +1353,14 @@ code {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
padding: 1.25rem;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
}
.monitors > div:not(:last-child) {
.monitors .monitors-list > div:not(:last-child) {
margin-bottom: 0.75rem;
}
@ -1581,6 +1604,16 @@ code {
color: rgb(75 85 99 / var(--tw-text-opacity));
}
.settings section table tbody tr:nth-child(odd) {
--tw-bg-opacity: 1;
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
}
.settings section table tbody tr:nth-child(even) {
--tw-bg-opacity: 1;
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
}
.settings section table tbody tr th {
white-space: nowrap;
padding-left: 1.5rem;
@ -1602,6 +1635,16 @@ code {
text-align: center;
}
.settings section table tbody tr.row-special {
--tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity));
font-size: 0.75rem;
line-height: 1rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.05em;
}
.hover\:bg-blue-800:hover {
--tw-bg-opacity: 1;
background-color: rgb(30 64 175 / var(--tw-bg-opacity));
@ -1703,6 +1746,11 @@ code {
justify-self: end;
}
.sm\:px-0 {
padding-left: 0px;
padding-right: 0px;
}
.sm\:px-8 {
padding-left: 2rem;
padding-right: 2rem;

View file

@ -1,6 +1,7 @@
{{ define "main" }}
<div class="container max-w-screen-md flex flex-col mt-20">
{{ if eq .MonitorsLength 0 }}
<div class="container max-w-screen-md flex flex-col mt-20 gap-20">
{{ $length := len .Monitors }}
{{ if eq $length 0 }}
<section>
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
<h1
@ -57,13 +58,15 @@
</p>
</div>
{{ end }}
<div class="monitors">
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<p class="text-l font-normal text-gray-800 text-center sm:text-left">
<div class="monitors flex flex-col gap-4">
<div class="grid grid-cols-1 sm:grid-cols-2 px-4 sm:px-0">
<h2
class="text-xl font-normal text-gray-800 text-center sm:text-left"
>
Monitors
</p>
</h2>
<div
class="inline-flex gap-1 rounded-md shadow-sm justify-self-center sm:justify-self-end time-range py-1 px-1 bg-gray-100"
class="inline-flex gap-1 rounded-md shadow-sm justify-self-center sm:justify-self-end time-range py-1 px-1 bg-white"
role="group"
>
<a
@ -86,46 +89,61 @@
>
</div>
</div>
{{ range .HealthChecks }}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div class="flex items-center">
{{ if eq .Status "SUCCESS" }}
<span
class="flex w-3 h-3 me-2 bg-green-400 rounded-full"
></span>
{{ else if eq .Status "FAILURE" }}
<span class="flex w-3 h-3 me-2 bg-red-400 rounded-full"></span>
{{ else }}
<span class="flex w-3 h-3 me-2 bg-gray-200 rounded-full"></span>
{{ end }}
<p>{{ .Name }}</p>
</div>
<div class="justify-self-end text-sm">
{{ .History.Uptime }}% uptime
</div>
<div
class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden"
>
{{ range .History.List }}
{{ if eq . "SUCCESS" }}
<div class="bg-green-400 hover:bg-green-500 flex-auto"></div>
{{ else if eq . "FAILURE" }}
<div class="bg-red-400 hover:bg-red-500 flex-auto"></div>
{{ else }}
<div class="bg-gray-200 hover:bg-gray-300 flex-auto"></div>
{{ end }}
{{ end }}
</div>
<div class="text-slate-500 justify-self-start text-sm">
{{ if eq $.TimeRange "90days" }}
90 days ago
{{ else if eq $.TimeRange "48hours" }}
48 hours ago
{{ else if eq $.TimeRange "90minutes" }}
90 minutes ago
{{ end }}
</div>
<div class="text-slate-500 justify-self-end text-sm">Now</div>
{{ range $group, $monitors := .Monitors }}
<div class="monitors-list">
<h3 class="text-lg font-semibold flex flex-row gap-2">
<!--<span
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
></span>-->
<span class="flex-1">{{ $group }}</span>
<svg class="feather h-6 w-6 overflow-visible self-center">
<use href="/static/icons/feather-sprite.svg#chevron-down" />
</svg>
</h3>
{{ range $monitors }}
<div class="grid grid-cols-1 sm:grid-cols-2 gap-2">
<div class="flex items-center gap-2">
{{ if eq .Status "SUCCESS" }}
<span class="flex w-3 h-3 bg-green-400 rounded-full"></span>
{{ else if eq .Status "FAILURE" }}
<span class="flex w-3 h-3 bg-red-400 rounded-full"></span>
{{ else }}
<span class="flex w-3 h-3 bg-gray-200 rounded-full"></span>
{{ end }}
<h4>{{ .Name }}</h4>
</div>
<div class="justify-self-end text-sm">
{{ .History.Uptime }}% uptime
</div>
<div
class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden"
>
{{ range .History.List }}
{{ if eq . "SUCCESS" }}
<div
class="bg-green-400 hover:bg-green-500 flex-auto"
></div>
{{ else if eq . "FAILURE" }}
<div class="bg-red-400 hover:bg-red-500 flex-auto"></div>
{{ else }}
<div
class="bg-gray-200 hover:bg-gray-300 flex-auto"
></div>
{{ end }}
{{ end }}
</div>
<div class="text-slate-500 justify-self-start text-sm">
{{ if eq $.TimeRange "90days" }}
90 days ago
{{ else if eq $.TimeRange "48hours" }}
48 hours ago
{{ else if eq $.TimeRange "90minutes" }}
90 minutes ago
{{ end }}
</div>
<div class="text-slate-500 justify-self-end text-sm">Now</div>
</div>
{{ end }}
</div>
{{ end }}
</div>

View file

@ -50,6 +50,7 @@
</caption>
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr>
<th scope="col">Group</th>
<th scope="col">Name</th>
<th scope="col">Worker Groups</th>
<th scope="col">Status</th>
@ -57,51 +58,71 @@
<th scope="col">Action</th>
</tr>
</thead>
{{ range .Monitors }}
<tbody>
<tr>
<tbody>
{{ range .MonitorGroups }}
{{ $currentGroup := . }}
<tr class="row-special">
<th scope="row">
{{ .Name }}
{{ . }}
</th>
<td>
{{ range .WorkerGroups }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
>
{{ . }}
</span>
{{ end }}
</td>
<td>
{{ if eq .Status "ACTIVE" }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>
ACTIVE
</span>
{{ else if eq .Status "PAUSED" }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800"
>
PAUSED
</span>
{{ else }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>
UNKNOWN
</span>
{{ end }}
</td>
<td>
{{ .Schedule }}
</td>
<td>
<a href="/settings/monitors/{{ .Id }}" class="link">Details</a>
</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
</tbody>
{{ end }}
{{ range $group, $monitors := $.Monitors }}
{{ if eq $group $currentGroup }}
{{ range $monitors }}
<tr>
<th scope="row"></th>
<th scope="row">
{{ .Name }}
</th>
<td>
{{ range .WorkerGroups }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
>
{{ . }}
</span>
{{ end }}
</td>
<td>
{{ if eq .Status "ACTIVE" }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
>
ACTIVE
</span>
{{ else if eq .Status "PAUSED" }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800"
>
PAUSED
</span>
{{ else }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
>
UNKNOWN
</span>
{{ end }}
</td>
<td>
{{ .Schedule }}
</td>
<td>
<a href="/settings/monitors/{{ .Id }}" class="link"
>Details</a
>
</td>
</tr>
{{ end }}
{{ end }}
{{ end }}
{{ end }}
</tbody>
</table>
</section>
{{ end }}

View file

@ -2,8 +2,20 @@
<section class="p-5">
<form action="/settings/monitors/create" method="post">
<label for="name">Name</label>
<input type="name" name="name" id="name" placeholder="Github.com" />
<input type="text" name="name" id="name" placeholder="Github.com" />
<p>Name of the monitor can be anything.</p>
<label for="group">Group</label>
<input
type="text"
name="group"
id="group"
placeholder="default"
value="default"
/>
<p>
Group monitors together. This affects how they are presented on the
homepage.
</p>
<label for="workergroups">Worker Groups</label>
<input
type="text"

View file

@ -2,6 +2,12 @@
<section class="p-5">
<form action="/settings/monitors/{{ .Monitor.Id }}" method="post">
<h2>Configuration</h2>
<label for="group">Group</label>
<input type="text" name="group" id="group" value="{{ .Monitor.Group }}" />
<p>
Group monitors together. This affects how they are presented on the
homepage.
</p>
<label for="workergroups">Worker Groups</label>
<input
type="text"

View file

@ -56,8 +56,8 @@
<th>Action</th>
</tr>
</thead>
{{ range .WorkerGroups }}
<tbody>
<tbody>
{{ range .WorkerGroups }}
<tr>
<th scope="row">
{{ .Name }}
@ -86,8 +86,8 @@
>
</td>
</tr>
</tbody>
{{ end }}
{{ end }}
</tbody>
</table>
</section>
{{ end }}