refactor: schema changes etc.

This commit is contained in:
Tine 2024-02-29 23:42:56 +01:00
parent 5223aef1ca
commit 07c7c716c5
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
26 changed files with 495 additions and 375 deletions

5
.gitignore vendored
View file

@ -6,9 +6,8 @@ package.json
node_modules/
# Database
zdravko.db
temporal.db
temporal.db-journal
zdravko.db*
temporal.db*
# Keys
*.pem

View file

@ -2,6 +2,7 @@ package database
import (
"embed"
"fmt"
"log/slog"
"github.com/jmoiron/sqlx"
@ -13,7 +14,7 @@ import (
var sqliteMigrations embed.FS
func ConnectToDatabase(logger *slog.Logger, path string) (*sqlx.DB, error) {
db, err := sqlx.Connect("sqlite3", path)
db, err := sqlx.Connect("sqlite3", fmt.Sprintf("%s?_journal=WAL&_timeout=5000&_fk=true", path))
if err != nil {
return nil, err
}

View file

@ -1,27 +1,64 @@
package models
import (
"database/sql/driver"
"fmt"
"time"
)
type OAuth2State struct {
State string `db:"state"`
ExpiresAt time.Time `db:"expires_at"`
State string `db:"state"`
ExpiresAt Time `db:"expires_at"`
}
type MonitorStatus string
const (
MonitorSuccess string = "SUCCESS"
MonitorFailure string = "FAILURE"
MonitorError string = "ERROR"
MonitorUnknown string = "UNKNOWN"
MonitorSuccess MonitorStatus = "SUCCESS"
MonitorFailure MonitorStatus = "FAILURE"
MonitorError MonitorStatus = "ERROR"
MonitorUnknown MonitorStatus = "UNKNOWN"
)
type Monitor struct {
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DeletedAt *time.Time `db:"deleted_at"`
type Time struct {
Time time.Time
}
Slug string `db:"slug"`
// rfc3339Milli is like time.RFC3339Nano, but with millisecond precision, and fractional seconds do not have trailing
// zeros removed.
const rfc3339Milli = "2006-01-02T15:04:05.000Z07:00"
// Value satisfies driver.Valuer interface.
func (t *Time) Value() (driver.Value, error) {
return t.Time.UTC().Format(rfc3339Milli), nil
}
// Scan satisfies sql.Scanner interface.
func (t *Time) Scan(src any) error {
if src == nil {
return nil
}
s, ok := src.(string)
if !ok {
return fmt.Errorf("error scanning time, got %+v", src)
}
parsedT, err := time.Parse(rfc3339Milli, s)
if err != nil {
return err
}
t.Time = parsedT.UTC()
return nil
}
type Monitor struct {
CreatedAt Time `db:"created_at"`
UpdatedAt Time `db:"updated_at"`
Id string `db:"id"`
Name string `db:"name"`
Schedule string `db:"schedule"`
@ -36,19 +73,21 @@ type MonitorWithWorkerGroups struct {
}
type MonitorHistory struct {
CreatedAt time.Time `db:"created_at"`
CreatedAt Time `db:"created_at"`
MonitorSlug string `db:"monitor_slug"`
Status string `db:"status"`
Note string `db:"note"`
MonitorId string `db:"monitor_id"`
Status MonitorStatus `db:"status"`
Note string `db:"note"`
WorkerGroupId string `db:"worker_group_id"`
WorkerGroupName string `db:"worker_group_name"`
}
type WorkerGroup struct {
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
DeletedAt *time.Time `db:"deleted_at"`
CreatedAt Time `db:"created_at"`
UpdatedAt Time `db:"updated_at"`
Slug string `db:"slug"`
Id string `db:"id"`
Name string `db:"name"`
}

View file

@ -1,56 +1,66 @@
-- +migrate Up
CREATE TABLE oauth2_states (
state TEXT,
expires_at DATETIME DEFAULT CURRENT_TIMESTAMP,
state TEXT NOT NULL,
expires_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (state)
);
) STRICT;
CREATE TABLE monitors (
slug TEXT,
name TEXT,
schedule TEXT,
script TEXT,
id TEXT NOT NULL,
name TEXT NOT NULL,
schedule TEXT NOT NULL,
script TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (slug),
PRIMARY KEY (id),
CONSTRAINT unique_monitors_name UNIQUE (name)
);
) STRICT;
--CREATE TRIGGER monitors_updated_timestamp AFTER UPDATE ON monitors BEGIN
-- update monitors set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id;
--END;
CREATE TABLE worker_groups (
slug TEXT,
name TEXT,
id TEXT NOT NULL,
name TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
deleted_at DATETIME,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (slug),
PRIMARY KEY (id),
CONSTRAINT unique_worker_groups_name UNIQUE (name)
);
) STRICT;
--CREATE TRIGGER worker_groups_updated_timestamp AFTER UPDATE ON worker_groups BEGIN
-- update worker_groups set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id;
--END;
CREATE TABLE monitor_worker_groups (
worker_group_slug TEXT,
monitor_slug TEXT,
worker_group_id TEXT NOT NULL,
monitor_id TEXT NOT NULL,
PRIMARY KEY (worker_group_slug,monitor_slug),
CONSTRAINT fk_monitor_worker_groups_worker_group FOREIGN KEY (worker_group_slug) REFERENCES worker_groups(slug),
CONSTRAINT fk_monitor_worker_groups_monitor FOREIGN KEY (monitor_slug) REFERENCES monitors(slug)
);
PRIMARY KEY (worker_group_id,monitor_id),
CONSTRAINT fk_monitor_worker_groups_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id),
CONSTRAINT fk_monitor_worker_groups_monitor FOREIGN KEY (monitor_id) REFERENCES monitors(id)
) STRICT;
CREATE TABLE monitor_histories (
monitor_slug TEXT,
status TEXT,
note TEXT,
monitor_id TEXT NOT NULL,
worker_group_id TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
status TEXT NOT NULL,
note TEXT NOT NULL,
PRIMARY KEY (monitor_slug, created_at),
CONSTRAINT fk_monitors_history FOREIGN KEY (monitor_slug) REFERENCES monitors(slug)
);
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (monitor_id, worker_group_id, created_at),
CONSTRAINT fk_monitor_histories_monitor FOREIGN KEY (monitor_id) REFERENCES monitors(id),
CONSTRAINT fk_monitor_histories_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id)
) STRICT;
-- +migrate Down
DROP TABLE oauth2_states;

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-6.db'
DATABASE_PATH = '/data/zdravko-6.db'
TEMPORAL_DATABASE_PATH = '/data/temporal-7.db'
DATABASE_PATH = '/data/zdravko-7.db'
[processes]
server = '--temporal --server'

View file

@ -8,6 +8,7 @@ import (
"log/slog"
"net/http"
"code.tjo.space/mentos1386/zdravko/database/models"
"code.tjo.space/mentos1386/zdravko/pkg/api"
"code.tjo.space/mentos1386/zdravko/pkg/k6"
)
@ -33,22 +34,22 @@ func (a *Activities) Monitor(ctx context.Context, param HealtcheckParam) (*Monit
}
type HealtcheckAddToHistoryParam struct {
Slug string
Status string
Note string
WorkerGroup string
MonitorId string
Status models.MonitorStatus
Note string
WorkerGroupId string
}
type MonitorAddToHistoryResult struct {
}
func (a *Activities) MonitorAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*MonitorAddToHistoryResult, error) {
url := fmt.Sprintf("%s/api/v1/monitors/%s/history", a.config.ApiUrl, param.Slug)
url := fmt.Sprintf("%s/api/v1/monitors/%s/history", a.config.ApiUrl, param.MonitorId)
body := api.ApiV1MonitorsHistoryPOSTBody{
Status: param.Status,
Note: param.Note,
WorkerGroup: param.WorkerGroup,
Status: param.Status,
Note: param.Note,
WorkerGroupId: param.WorkerGroupId,
}
jsonBody, err := json.Marshal(body)

View file

@ -31,7 +31,7 @@ func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
response := ApiV1WorkersConnectGETResponse{
Endpoint: h.config.Temporal.ServerHost,
Group: workerGroup.Slug,
Group: workerGroup.Id,
}
return c.JSON(http.StatusOK, response)
@ -42,8 +42,7 @@ func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
// To somehow listen for the outcomes and then store them automatically.
func (h *BaseHandler) ApiV1MonitorsHistoryPOST(c echo.Context) error {
ctx := context.Background()
slug := c.Param("slug")
id := c.Param("id")
var body api.ApiV1MonitorsHistoryPOSTBody
err := (&echo.DefaultBinder{}).BindBody(c, &body)
@ -51,7 +50,7 @@ func (h *BaseHandler) ApiV1MonitorsHistoryPOST(c echo.Context) error {
return err
}
_, err = services.GetMonitor(ctx, h.db, slug)
_, err = services.GetMonitor(ctx, h.db, id)
if err != nil {
if err == sql.ErrNoRows {
return echo.NewHTTPError(http.StatusNotFound, "Monitor not found")
@ -60,9 +59,10 @@ func (h *BaseHandler) ApiV1MonitorsHistoryPOST(c echo.Context) error {
}
err = services.AddHistoryForMonitor(ctx, h.db, &models.MonitorHistory{
MonitorSlug: slug,
Status: body.Status,
Note: body.Note,
MonitorId: id,
WorkerGroupId: body.WorkerGroupId,
Status: body.Status,
Note: body.Note,
})
if err != nil {
return err

View file

@ -16,45 +16,39 @@ type IndexData struct {
HealthChecks []*HealthCheck
MonitorsLength int
TimeRange string
Status string
Status models.MonitorStatus
}
type HealthCheck struct {
Name string
Status string
HistoryDaily *History
HistoryHourly *History
Name string
Status models.MonitorStatus
History *History
}
type History struct {
History []string
Uptime int
}
func getDay(date time.Time) string {
return date.Format("2006-01-02")
List []models.MonitorStatus
Uptime int
}
func getHour(date time.Time) string {
return date.Format("2006-01-02T15:04")
return date.UTC().Format("2006-01-02T15:04")
}
func getDailyHistory(history []*models.MonitorHistory) *History {
numDays := 90
historyDailyMap := map[string]string{}
func getHistory(history []*models.MonitorHistory, period time.Duration, buckets int) *History {
historyMap := map[string]models.MonitorStatus{}
numOfSuccess := 0
numTotal := 0
for i := 0; i < numDays; i++ {
day := getDay(time.Now().AddDate(0, 0, -i).Truncate(time.Hour * 24))
historyDailyMap[day] = models.MonitorUnknown
for i := 0; i < buckets; i++ {
datetime := getHour(time.Now().Add(period * time.Duration(-i)).Truncate(period))
historyMap[datetime] = models.MonitorUnknown
}
for _, _history := range history {
day := getDay(_history.CreatedAt.Truncate(time.Hour * 24))
hour := getHour(_history.CreatedAt.Time.Truncate(time.Hour))
// skip if day is not in the last 90 days
if _, ok := historyDailyMap[day]; !ok {
// Skip if not part of the "buckets"
if _, ok := historyMap[hour]; !ok {
continue
}
@ -63,18 +57,18 @@ func getDailyHistory(history []*models.MonitorHistory) *History {
numOfSuccess++
}
// skip if day is already set to failure
if historyDailyMap[day] == models.MonitorFailure {
// skip if it is already set to failure
if historyMap[hour] == models.MonitorFailure {
continue
}
historyDailyMap[day] = _history.Status
historyMap[hour] = _history.Status
}
historyDaily := make([]string, numDays)
for i := 0; i < numDays; i++ {
day := getDay(time.Now().AddDate(0, 0, -numDays+i+1).Truncate(time.Hour * 24))
historyDaily[i] = historyDailyMap[day]
historyHourly := make([]models.MonitorStatus, buckets)
for i := 0; i < buckets; i++ {
datetime := getHour(time.Now().Add(period * time.Duration(-buckets+i+1)).Truncate(period))
historyHourly[i] = historyMap[datetime]
}
uptime := 0
@ -83,57 +77,8 @@ func getDailyHistory(history []*models.MonitorHistory) *History {
}
return &History{
History: historyDaily,
Uptime: uptime,
}
}
func getHourlyHistory(history []*models.MonitorHistory) *History {
numHours := 48
historyHourlyMap := map[string]string{}
numOfSuccess := 0
numTotal := 0
for i := 0; i < numHours; i++ {
hour := getHour(time.Now().Add(time.Hour * time.Duration(-i)).Truncate(time.Hour))
historyHourlyMap[hour] = models.MonitorUnknown
}
for _, _history := range history {
hour := getHour(_history.CreatedAt.Truncate(time.Hour))
// skip if day is not in the last 90 days
if _, ok := historyHourlyMap[hour]; !ok {
continue
}
numTotal++
if _history.Status == models.MonitorSuccess {
numOfSuccess++
}
// skip if day is already set to failure
if historyHourlyMap[hour] == models.MonitorFailure {
continue
}
historyHourlyMap[hour] = _history.Status
}
historyHourly := make([]string, numHours)
for i := 0; i < numHours; i++ {
hour := getHour(time.Now().Add(time.Hour * time.Duration(-numHours+i+1)).Truncate(time.Hour))
historyHourly[i] = historyHourlyMap[hour]
}
uptime := 0
if numTotal > 0 {
uptime = 100 * numOfSuccess / numTotal
}
return &History{
History: historyHourly,
Uptime: uptime,
List: historyHourly,
Uptime: uptime,
}
}
@ -145,32 +90,38 @@ func (h *BaseHandler) Index(c echo.Context) error {
}
timeRange := c.QueryParam("time-range")
if timeRange != "48hours" && timeRange != "90days" {
if timeRange != "48hours" && timeRange != "90days" && timeRange != "90minutes" {
timeRange = "90days"
}
overallStatus := "SUCCESS"
overallStatus := models.MonitorSuccess
monitorsWithHistory := make([]*HealthCheck, len(monitors))
for i, monitor := range monitors {
history, err := services.GetMonitorHistoryForMonitor(ctx, h.db, monitor.Slug)
history, err := services.GetMonitorHistoryForMonitor(ctx, h.db, monitor.Id)
if err != nil {
return err
}
historyDaily := getDailyHistory(history)
historyHourly := getHourlyHistory(history)
var historyResult *History
switch timeRange {
case "48hours":
historyResult = getHistory(history, time.Hour, 48)
case "90days":
historyResult = getHistory(history, time.Hour*24, 90)
case "90minutes":
historyResult = getHistory(history, time.Minute, 90)
}
status := historyDaily.History[89]
status := historyResult.List[len(historyResult.List)-1]
if status != models.MonitorSuccess {
overallStatus = status
}
monitorsWithHistory[i] = &HealthCheck{
Name: monitor.Name,
Status: status,
HistoryDaily: historyDaily,
HistoryHourly: historyHourly,
Name: monitor.Name,
Status: status,
History: historyResult,
}
}

View file

@ -87,7 +87,10 @@ func (h *BaseHandler) OAuth2LoginGET(c echo.Context) error {
conf := newOAuth2(h.config)
state := newRandomState()
err := services.CreateOAuth2State(ctx, h.db, &models.OAuth2State{State: state, ExpiresAt: time.Now().Add(5 * time.Minute)})
err := services.CreateOAuth2State(ctx, h.db, &models.OAuth2State{
State: state,
ExpiresAt: models.Time{Time: time.Now().Add(5 * time.Minute)},
})
if err != nil {
return err
}
@ -108,7 +111,7 @@ func (h *BaseHandler) OAuth2CallbackGET(c echo.Context) error {
if err != nil {
return err
}
if deleted == false {
if !deleted {
return errors.New("invalid state")
}

View file

@ -3,6 +3,7 @@ package handlers
import (
"net/http"
"code.tjo.space/mentos1386/zdravko/internal/services"
"code.tjo.space/mentos1386/zdravko/web/templates/components"
"github.com/labstack/echo/v4"
)
@ -48,12 +49,42 @@ var SettingsNavbar = []*components.Page{
GetPageByTitle(SettingsPages, "Logout"),
}
type SettingsOverview struct {
*Settings
WorkerGroupsCount int
MonitorsCount int
NotificationsCount int
History []*services.MonitorHistoryWithMonitor
}
func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
cc := c.(AuthenticatedContext)
ctx := c.Request().Context()
return c.Render(http.StatusOK, "settings_overview.tmpl", NewSettings(
cc.Principal.User,
GetPageByTitle(SettingsPages, "Overview"),
[]*components.Page{GetPageByTitle(SettingsPages, "Overview")},
))
workerGroups, err := services.CountWorkerGroups(ctx, h.db)
if err != nil {
return err
}
monitors, err := services.CountMonitors(ctx, h.db)
if err != nil {
return err
}
history, err := services.GetLastNMonitorHistory(ctx, h.db, 10)
if err != nil {
return err
}
return c.Render(http.StatusOK, "settings_overview.tmpl", SettingsOverview{
Settings: NewSettings(
cc.Principal.User,
GetPageByTitle(SettingsPages, "Overview"),
[]*components.Page{GetPageByTitle(SettingsPages, "Overview")},
),
WorkerGroupsCount: workerGroups,
MonitorsCount: monitors,
NotificationsCount: 42,
History: history,
})
}

View file

@ -55,7 +55,7 @@ func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error {
monitorsWithStatus := make([]*MonitorWithWorkerGroupsAndStatus, len(monitors))
for i, monitor := range monitors {
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Slug)
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Id)
if err != nil {
return err
}
@ -79,14 +79,14 @@ func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error {
func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error {
cc := c.(AuthenticatedContext)
slug := c.Param("slug")
slug := c.Param("id")
monitor, err := services.GetMonitorWithWorkerGroups(context.Background(), h.db, slug)
if err != nil {
return err
}
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Slug)
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Id)
if err != nil {
return err
}
@ -124,7 +124,7 @@ func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error {
}
func (h *BaseHandler) SettingsMonitorsDescribeDELETE(c echo.Context) error {
slug := c.Param("slug")
slug := c.Param("id")
err := services.DeleteMonitor(context.Background(), h.db, slug)
if err != nil {
@ -140,14 +140,14 @@ func (h *BaseHandler) SettingsMonitorsDescribeDELETE(c echo.Context) error {
}
func (h *BaseHandler) SettingsMonitorsDisableGET(c echo.Context) error {
slug := c.Param("slug")
slug := c.Param("id")
monitor, err := services.GetMonitor(context.Background(), h.db, slug)
if err != nil {
return err
}
err = services.SetMonitorStatus(context.Background(), h.temporal, monitor.Slug, services.MonitorStatusPaused)
err = services.SetMonitorStatus(context.Background(), h.temporal, monitor.Id, services.MonitorStatusPaused)
if err != nil {
return err
}
@ -156,14 +156,14 @@ func (h *BaseHandler) SettingsMonitorsDisableGET(c echo.Context) error {
}
func (h *BaseHandler) SettingsMonitorsEnableGET(c echo.Context) error {
slug := c.Param("slug")
slug := c.Param("id")
monitor, err := services.GetMonitor(context.Background(), h.db, slug)
if err != nil {
return err
}
err = services.SetMonitorStatus(context.Background(), h.temporal, monitor.Slug, services.MonitorStatusActive)
err = services.SetMonitorStatus(context.Background(), h.temporal, monitor.Id, services.MonitorStatusActive)
if err != nil {
return err
}
@ -173,7 +173,7 @@ func (h *BaseHandler) SettingsMonitorsEnableGET(c echo.Context) error {
func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
ctx := context.Background()
monitorSlug := c.Param("slug")
monitorId := c.Param("id")
update := UpdateMonitor{
WorkerGroups: strings.TrimSpace(c.FormValue("workergroups")),
@ -185,7 +185,7 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
return err
}
monitor, err := services.GetMonitor(ctx, h.db, monitorSlug)
monitor, err := services.GetMonitor(ctx, h.db, monitorId)
if err != nil {
return err
}
@ -209,7 +209,7 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
workerGroup, err := services.GetWorkerGroup(ctx, h.db, slug.Make(group))
if err != nil {
if err == sql.ErrNoRows {
workerGroup = &models.WorkerGroup{Name: group, Slug: slug.Make(group)}
workerGroup = &models.WorkerGroup{Name: group, Id: slug.Make(group)}
err = services.CreateWorkerGroup(ctx, h.db, workerGroup)
if err != nil {
return err
@ -231,7 +231,7 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
return err
}
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/monitors/%s", monitorSlug))
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/monitors/%s", monitorId))
}
func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error {
@ -249,7 +249,7 @@ func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error {
func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
ctx := context.Background()
monitorSlug := slug.Make(c.FormValue("name"))
monitorId := slug.Make(c.FormValue("name"))
create := CreateMonitor{
Name: c.FormValue("name"),
@ -270,7 +270,7 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
workerGroup, err := services.GetWorkerGroup(ctx, h.db, slug.Make(group))
if err != nil {
if err == sql.ErrNoRows {
workerGroup = &models.WorkerGroup{Name: group, Slug: slug.Make(group)}
workerGroup = &models.WorkerGroup{Name: group, Id: slug.Make(group)}
err = services.CreateWorkerGroup(ctx, h.db, workerGroup)
if err != nil {
return err
@ -284,7 +284,7 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
monitor := &models.Monitor{
Name: create.Name,
Slug: monitorSlug,
Id: monitorId,
Schedule: create.Schedule,
Script: create.Script,
}

View file

@ -46,7 +46,7 @@ func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
workerGroupsWithActiveWorkers := make([]*WorkerGroupWithActiveWorkers, len(workerGroups))
for i, workerGroup := range workerGroups {
activeWorkers, err := services.GetActiveWorkers(context.Background(), workerGroup.Slug, h.temporal)
activeWorkers, err := services.GetActiveWorkers(context.Background(), workerGroup.Id, h.temporal)
if err != nil {
return err
}
@ -69,10 +69,9 @@ func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
func (h *BaseHandler) SettingsWorkerGroupsDescribeGET(c echo.Context) error {
cc := c.(AuthenticatedContext)
id := c.Param("id")
slug := c.Param("slug")
worker, err := services.GetWorkerGroup(context.Background(), h.db, slug)
worker, err := services.GetWorkerGroup(context.Background(), h.db, id)
if err != nil {
return err
}
@ -83,7 +82,7 @@ func (h *BaseHandler) SettingsWorkerGroupsDescribeGET(c echo.Context) error {
return err
}
activeWorkers, err := services.GetActiveWorkers(context.Background(), worker.Slug, h.temporal)
activeWorkers, err := services.GetActiveWorkers(context.Background(), worker.Id, h.temporal)
if err != nil {
return err
}
@ -95,7 +94,7 @@ func (h *BaseHandler) SettingsWorkerGroupsDescribeGET(c echo.Context) error {
[]*components.Page{
GetPageByTitle(SettingsPages, "Worker Groups"),
{
Path: fmt.Sprintf("/settings/worker-groups/%s", slug),
Path: fmt.Sprintf("/settings/worker-groups/%s", id),
Title: "Describe",
Breadcrumb: worker.Name,
},
@ -109,9 +108,9 @@ func (h *BaseHandler) SettingsWorkerGroupsDescribeGET(c echo.Context) error {
}
func (h *BaseHandler) SettingsWorkerGroupsDescribeDELETE(c echo.Context) error {
slug := c.Param("slug")
id := c.Param("id")
err := services.DeleteWorkerGroup(context.Background(), h.db, slug)
err := services.DeleteWorkerGroup(context.Background(), h.db, id)
if err != nil {
return err
}
@ -134,11 +133,11 @@ func (h *BaseHandler) SettingsWorkerGroupsCreateGET(c echo.Context) error {
func (h *BaseHandler) SettingsWorkerGroupsCreatePOST(c echo.Context) error {
ctx := context.Background()
slug := slug.Make(c.FormValue("name"))
id := slug.Make(c.FormValue("name"))
workerGroup := &models.WorkerGroup{
Name: c.FormValue("name"),
Slug: slug,
Id: id,
}
err := validator.New(validator.WithRequiredStructEnabled()).Struct(workerGroup)
@ -155,5 +154,5 @@ func (h *BaseHandler) SettingsWorkerGroupsCreatePOST(c echo.Context) error {
return err
}
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/worker-groups/%s", slug))
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/worker-groups/%s", id))
}

View file

@ -79,7 +79,7 @@ func NewTokenForWorker(privateKey string, publicKey string, workerGroup *models.
IssuedAt: jwt.NewNumericDate(time.Now()),
NotBefore: jwt.NewNumericDate(time.Now()),
Issuer: "zdravko",
Subject: "worker-group:" + workerGroup.Slug,
Subject: "worker-group:" + workerGroup.Id,
},
// Ref: https://docs.temporal.io/self-hosted-guide/security#authorization
[]string{"default:read", "default:write", "default:worker"},

View file

@ -21,12 +21,18 @@ const (
MonitorStatusActive MonitorStatus = "ACTIVE"
)
func getScheduleId(slug string) string {
return "monitor-" + slug
func getScheduleId(id string) string {
return "monitor-" + id
}
func GetMonitorStatus(ctx context.Context, temporal client.Client, slug string) (MonitorStatus, error) {
schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(slug))
func CountMonitors(ctx context.Context, db *sqlx.DB) (int, error) {
var count int
err := db.GetContext(ctx, &count, "SELECT COUNT(*) FROM monitors")
return count, err
}
func GetMonitorStatus(ctx context.Context, temporal client.Client, id string) (MonitorStatus, error) {
schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(id))
description, err := schedule.Describe(ctx)
if err != nil {
@ -40,8 +46,8 @@ func GetMonitorStatus(ctx context.Context, temporal client.Client, slug string)
return MonitorStatusActive, nil
}
func SetMonitorStatus(ctx context.Context, temporal client.Client, slug string, status MonitorStatus) error {
schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(slug))
func SetMonitorStatus(ctx context.Context, temporal client.Client, id string, status MonitorStatus) error {
schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(id))
if status == MonitorStatusActive {
return schedule.Unpause(ctx, client.ScheduleUnpauseOptions{Note: "Unpaused by user"})
@ -56,7 +62,7 @@ func SetMonitorStatus(ctx context.Context, temporal client.Client, slug string,
func CreateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error {
_, err := db.NamedExecContext(ctx,
"INSERT INTO monitors (slug, name, script, schedule) VALUES (:slug, :name, :script, :schedule)",
"INSERT INTO monitors (id, name, script, schedule) VALUES (:id, :name, :script, :schedule)",
monitor,
)
return err
@ -64,16 +70,16 @@ 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 slug=:slug",
"UPDATE monitors SET name=:name, script=:script, schedule=:schedule WHERE id=:id",
monitor,
)
return err
}
func DeleteMonitor(ctx context.Context, db *sqlx.DB, slug string) error {
func DeleteMonitor(ctx context.Context, db *sqlx.DB, id string) error {
_, err := db.ExecContext(ctx,
"UPDATE monitors SET deleted_at = datetime('now') WHERE slug=$1",
slug,
"DELETE FROM monitors WHERE id=$1",
id,
)
return err
}
@ -84,8 +90,8 @@ func UpdateMonitorWorkerGroups(ctx context.Context, db *sqlx.DB, monitor *models
return err
}
_, err = tx.ExecContext(ctx,
"DELETE FROM monitor_worker_groups WHERE monitor_slug=$1",
monitor.Slug,
"DELETE FROM monitor_worker_groups WHERE monitor_id=$1",
monitor.Id,
)
if err != nil {
tx.Rollback()
@ -93,9 +99,9 @@ func UpdateMonitorWorkerGroups(ctx context.Context, db *sqlx.DB, monitor *models
}
for _, group := range workerGroups {
_, err = tx.ExecContext(ctx,
"INSERT INTO monitor_worker_groups (monitor_slug, worker_group_slug) VALUES ($1, $2)",
monitor.Slug,
group.Slug,
"INSERT INTO monitor_worker_groups (monitor_id, worker_group_id) VALUES ($1, $2)",
monitor.Id,
group.Id,
)
if err != nil {
tx.Rollback()
@ -105,33 +111,32 @@ func UpdateMonitorWorkerGroups(ctx context.Context, db *sqlx.DB, monitor *models
return tx.Commit()
}
func GetMonitor(ctx context.Context, db *sqlx.DB, slug string) (*models.Monitor, error) {
func GetMonitor(ctx context.Context, db *sqlx.DB, id string) (*models.Monitor, error) {
monitor := &models.Monitor{}
err := db.GetContext(ctx, monitor,
"SELECT * FROM monitors WHERE slug=$1 AND deleted_at IS NULL",
slug,
"SELECT * FROM monitors WHERE id=$1",
id,
)
return monitor, err
}
func GetMonitorWithWorkerGroups(ctx context.Context, db *sqlx.DB, slug string) (*models.MonitorWithWorkerGroups, error) {
func GetMonitorWithWorkerGroups(ctx context.Context, db *sqlx.DB, id string) (*models.MonitorWithWorkerGroups, error) {
rows, err := db.QueryContext(ctx,
`
SELECT
monitors.slug,
monitors.id,
monitors.name,
monitors.script,
monitors.schedule,
monitors.created_at,
monitors.updated_at,
monitors.deleted_at,
worker_groups.name as worker_group_name
FROM monitors
LEFT OUTER JOIN monitor_worker_groups ON monitors.slug = monitor_worker_groups.monitor_slug
LEFT OUTER JOIN worker_groups ON monitor_worker_groups.worker_group_slug = worker_groups.slug
WHERE monitors.slug=$1 AND monitors.deleted_at IS NULL
LEFT OUTER JOIN monitor_worker_groups ON monitors.id = monitor_worker_groups.monitor_id
LEFT OUTER JOIN worker_groups ON monitor_worker_groups.worker_group_id = worker_groups.id
WHERE monitors.id=$1
`,
slug,
id,
)
if err != nil {
return nil, err
@ -143,13 +148,12 @@ WHERE monitors.slug=$1 AND monitors.deleted_at IS NULL
for rows.Next() {
var workerGroupName *string
err = rows.Scan(
&monitor.Slug,
&monitor.Id,
&monitor.Name,
&monitor.Script,
&monitor.Schedule,
&monitor.CreatedAt,
&monitor.UpdatedAt,
&monitor.DeletedAt,
&workerGroupName,
)
if err != nil {
@ -166,7 +170,7 @@ WHERE monitors.slug=$1 AND monitors.deleted_at IS NULL
func GetMonitors(ctx context.Context, db *sqlx.DB) ([]*models.Monitor, error) {
monitors := []*models.Monitor{}
err := db.SelectContext(ctx, &monitors,
"SELECT * FROM monitors WHERE deleted_at IS NULL ORDER BY name",
"SELECT * FROM monitors ORDER BY name",
)
return monitors, err
}
@ -175,18 +179,16 @@ func GetMonitorsWithWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.Mo
rows, err := db.QueryContext(ctx,
`
SELECT
monitors.slug,
monitors.id,
monitors.name,
monitors.script,
monitors.schedule,
monitors.created_at,
monitors.updated_at,
monitors.deleted_at,
worker_groups.name as worker_group_name
FROM monitors
LEFT OUTER JOIN monitor_worker_groups ON monitors.slug = monitor_worker_groups.monitor_slug
LEFT OUTER JOIN worker_groups ON monitor_worker_groups.worker_group_slug = worker_groups.slug
WHERE monitors.deleted_at IS NULL
LEFT OUTER JOIN monitor_worker_groups ON monitors.id = monitor_worker_groups.monitor_id
LEFT OUTER JOIN worker_groups ON monitor_worker_groups.worker_group_id = worker_groups.id
ORDER BY monitors.name
`)
if err != nil {
@ -201,13 +203,12 @@ ORDER BY monitors.name
var workerGroupName *string
err = rows.Scan(
&monitor.Slug,
&monitor.Id,
&monitor.Name,
&monitor.Script,
&monitor.Schedule,
&monitor.CreatedAt,
&monitor.UpdatedAt,
&monitor.DeletedAt,
&workerGroupName,
)
if err != nil {
@ -215,19 +216,19 @@ ORDER BY monitors.name
}
if workerGroupName != nil {
workerGroups := []string{}
if monitors[monitor.Slug] != nil {
workerGroups = monitors[monitor.Slug].WorkerGroups
if monitors[monitor.Id] != nil {
workerGroups = monitors[monitor.Id].WorkerGroups
}
monitor.WorkerGroups = append(workerGroups, *workerGroupName)
}
monitors[monitor.Slug] = monitor
monitors[monitor.Id] = monitor
}
return maps.Values(monitors), err
}
func DeleteMonitorSchedule(ctx context.Context, t client.Client, slug string) error {
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(slug))
func DeleteMonitorSchedule(ctx context.Context, t client.Client, id string) error {
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(id))
return schedule.Delete(ctx)
}
@ -241,24 +242,24 @@ func CreateOrUpdateMonitorSchedule(
workerGroupStrings := make([]string, len(workerGroups))
for i, group := range workerGroups {
workerGroupStrings[i] = group.Slug
workerGroupStrings[i] = group.Id
}
args := make([]interface{}, 1)
args[0] = workflows.MonitorWorkflowParam{
Script: monitor.Script,
Slug: monitor.Slug,
WorkerGroups: workerGroupStrings,
Script: monitor.Script,
MonitorId: monitor.Id,
WorkerGroupIds: workerGroupStrings,
}
options := client.ScheduleOptions{
ID: getScheduleId(monitor.Slug),
ID: getScheduleId(monitor.Id),
Spec: client.ScheduleSpec{
CronExpressions: []string{monitor.Schedule},
Jitter: time.Second * 10,
},
Action: &client.ScheduleWorkflowAction{
ID: getScheduleId(monitor.Slug),
ID: getScheduleId(monitor.Id),
Workflow: workflows.NewWorkflows(nil).MonitorWorkflowDefinition,
Args: args,
TaskQueue: "default",
@ -268,7 +269,7 @@ func CreateOrUpdateMonitorSchedule(
},
}
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor.Slug))
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor.Id))
// If exists, we update
_, err := schedule.Describe(ctx)

View file

@ -7,18 +7,60 @@ import (
"github.com/jmoiron/sqlx"
)
func GetMonitorHistoryForMonitor(ctx context.Context, db *sqlx.DB, monitorSlug string) ([]*models.MonitorHistory, error) {
type MonitorHistoryWithMonitor struct {
*models.MonitorHistory
MonitorName string `db:"monitor_name"`
MonitorId string `db:"monitor_id"`
}
func GetLastNMonitorHistory(ctx context.Context, db *sqlx.DB, n int) ([]*MonitorHistoryWithMonitor, error) {
var monitorHistory []*MonitorHistoryWithMonitor
err := db.SelectContext(ctx, &monitorHistory, `
SELECT
mh.*,
wg.name AS worker_group_name,
m.name AS monitor_name,
m.id AS monitor_id
FROM monitor_histories mh
LEFT JOIN worker_groups wg ON mh.worker_group_id = wg.id
LEFT JOIN monitor_worker_groups mwg ON mh.monitor_id = mwg.monitor_id
LEFT JOIN monitors m ON mwg.monitor_id = m.id
ORDER BY mh.created_at DESC
LIMIT $1
`, n)
return monitorHistory, err
}
func GetMonitorHistoryForMonitor(ctx context.Context, db *sqlx.DB, monitorId string) ([]*models.MonitorHistory, error) {
var monitorHistory []*models.MonitorHistory
err := db.SelectContext(ctx, &monitorHistory,
"SELECT * FROM monitor_histories WHERE monitor_slug = $1 ORDER BY created_at DESC",
monitorSlug,
)
err := db.SelectContext(ctx, &monitorHistory, `
SELECT
mh.*,
wg.name AS worker_group_name,
wg.id AS worker_group_id
FROM monitor_histories as mh
LEFT JOIN worker_groups wg ON mh.worker_group_id = wg.id
LEFT JOIN monitor_worker_groups mwg ON mh.monitor_id = mwg.monitor_id
WHERE mh.monitor_id = $1
ORDER BY mh.created_at DESC
`, monitorId)
return monitorHistory, err
}
func AddHistoryForMonitor(ctx context.Context, db *sqlx.DB, history *models.MonitorHistory) error {
_, err := db.NamedExecContext(ctx,
"INSERT INTO monitor_histories (monitor_slug, status, note) VALUES (:monitor_slug, :status, :note)",
`
INSERT INTO monitor_histories (
monitor_id,
worker_group_id,
status,
note
) VALUES (
:monitor_id,
:worker_group_id,
:status,
:note
)`,
history,
)
return err

View file

@ -10,8 +10,14 @@ import (
"golang.org/x/exp/maps"
)
func GetActiveWorkers(ctx context.Context, workerGroupSlug string, temporal client.Client) ([]string, error) {
response, err := temporal.DescribeTaskQueue(ctx, workerGroupSlug, enums.TASK_QUEUE_TYPE_ACTIVITY)
func CountWorkerGroups(ctx context.Context, db *sqlx.DB) (int, error) {
var count int
err := db.GetContext(ctx, &count, "SELECT COUNT(*) FROM worker_groups")
return count, err
}
func GetActiveWorkers(ctx context.Context, workerGroupId string, temporal client.Client) ([]string, error) {
response, err := temporal.DescribeTaskQueue(ctx, workerGroupId, enums.TASK_QUEUE_TYPE_ACTIVITY)
if err != nil {
return make([]string, 0), err
}
@ -26,16 +32,16 @@ func GetActiveWorkers(ctx context.Context, workerGroupSlug string, temporal clie
func CreateWorkerGroup(ctx context.Context, db *sqlx.DB, workerGroup *models.WorkerGroup) error {
_, err := db.NamedExecContext(ctx,
"INSERT INTO worker_groups (slug, name) VALUES (:slug, :name)",
"INSERT INTO worker_groups (id, name) VALUES (:id, :name)",
workerGroup,
)
return err
}
func DeleteWorkerGroup(ctx context.Context, db *sqlx.DB, slug string) error {
func DeleteWorkerGroup(ctx context.Context, db *sqlx.DB, id string) error {
_, err := db.ExecContext(ctx,
"UPDATE worker_groups SET deleted_at = datetime('now') WHERE slug = $1",
slug,
"DELETE FROM worker_groups WHERE id = $1",
id,
)
return err
}
@ -43,7 +49,7 @@ func DeleteWorkerGroup(ctx context.Context, db *sqlx.DB, slug string) error {
func GetWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.WorkerGroup, error) {
var workerGroups []*models.WorkerGroup
err := db.SelectContext(ctx, &workerGroups,
"SELECT * FROM worker_groups WHERE deleted_at IS NULL ORDER BY name",
"SELECT * FROM worker_groups ORDER BY name",
)
return workerGroups, err
}
@ -52,16 +58,14 @@ func GetWorkerGroupsWithMonitors(ctx context.Context, db *sqlx.DB) ([]*models.Wo
rows, err := db.QueryContext(ctx,
`
SELECT
worker_groups.slug,
worker_groups.id,
worker_groups.name,
worker_groups.created_at,
worker_groups.updated_at,
worker_groups.deleted_at,
monitors.name as monitor_name
FROM worker_groups
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.slug = monitor_worker_groups.worker_group_slug
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_slug = monitors.slug
WHERE worker_groups.deleted_at IS NULL AND monitors.deleted_at IS NULL
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.id = monitor_worker_groups.worker_group_id
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_id = monitors.id
ORDER BY worker_groups.name
`)
if err != nil {
@ -76,11 +80,10 @@ ORDER BY worker_groups.name
var monitorName *string
err = rows.Scan(
&workerGroup.Slug,
&workerGroup.Id,
&workerGroup.Name,
&workerGroup.CreatedAt,
&workerGroup.UpdatedAt,
&workerGroup.DeletedAt,
&monitorName,
)
if err != nil {
@ -89,52 +92,51 @@ ORDER BY worker_groups.name
if monitorName != nil {
monitors := []string{}
if workerGroups[workerGroup.Slug] != nil {
monitors = workerGroups[workerGroup.Slug].Monitors
if workerGroups[workerGroup.Id] != nil {
monitors = workerGroups[workerGroup.Id].Monitors
}
workerGroup.Monitors = append(monitors, *monitorName)
}
workerGroups[workerGroup.Slug] = workerGroup
workerGroups[workerGroup.Id] = workerGroup
}
return maps.Values(workerGroups), err
}
func GetWorkerGroupsBySlug(ctx context.Context, db *sqlx.DB, slugs []string) ([]*models.WorkerGroup, error) {
func GetWorkerGroupsById(ctx context.Context, db *sqlx.DB, ids []string) ([]*models.WorkerGroup, error) {
var workerGroups []*models.WorkerGroup
err := db.SelectContext(ctx, &workerGroups,
"SELECT * FROM worker_groups WHERE slug = ANY($1) AND deleted_at IS NULL",
slugs,
"SELECT * FROM worker_groups WHERE id = ANY($1)",
ids,
)
return workerGroups, err
}
func GetWorkerGroup(ctx context.Context, db *sqlx.DB, slug string) (*models.WorkerGroup, error) {
func GetWorkerGroup(ctx context.Context, db *sqlx.DB, id string) (*models.WorkerGroup, error) {
var workerGroup models.WorkerGroup
err := db.GetContext(ctx, &workerGroup,
"SELECT * FROM worker_groups WHERE slug = $1 AND deleted_at IS NULL",
slug,
"SELECT * FROM worker_groups WHERE id = $1",
id,
)
return &workerGroup, err
}
func GetWorkerGroupWithMonitors(ctx context.Context, db *sqlx.DB, slug string) (*models.WorkerGroupWithMonitors, error) {
func GetWorkerGroupWithMonitors(ctx context.Context, db *sqlx.DB, id string) (*models.WorkerGroupWithMonitors, error) {
rows, err := db.QueryContext(ctx,
`
SELECT
worker_groups.slug,
worker_groups.id,
worker_groups.name,
worker_groups.created_at,
worker_groups.updated_at,
worker_groups.deleted_at,
monitors.name as monitor_name
FROM worker_groups
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.slug = monitor_worker_groups.worker_group_slug
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_slug = monitors.slug
WHERE worker_groups.slug=$1 AND worker_groups.deleted_at IS NULL AND monitors.deleted_at IS NULL
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.id = monitor_worker_groups.worker_group_id
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_id = monitors.id
WHERE worker_groups.id=$1
`,
slug,
id,
)
if err != nil {
return nil, err
@ -146,11 +148,10 @@ WHERE worker_groups.slug=$1 AND worker_groups.deleted_at IS NULL AND monitors.de
for rows.Next() {
var monitorName *string
err = rows.Scan(
&workerGroup.Slug,
&workerGroup.Id,
&workerGroup.Name,
&workerGroup.CreatedAt,
&workerGroup.UpdatedAt,
&workerGroup.DeletedAt,
&monitorName,
)
if err != nil {

View file

@ -10,19 +10,19 @@ import (
)
type MonitorWorkflowParam struct {
Script string
Slug string
WorkerGroups []string
Script string
MonitorId string
WorkerGroupIds []string
}
func (w *Workflows) MonitorWorkflowDefinition(ctx workflow.Context, param MonitorWorkflowParam) error {
workerGroups := param.WorkerGroups
sort.Strings(workerGroups)
func (w *Workflows) MonitorWorkflowDefinition(ctx workflow.Context, param MonitorWorkflowParam) (models.MonitorStatus, error) {
workerGroupIds := param.WorkerGroupIds
sort.Strings(workerGroupIds)
for _, workerGroup := range workerGroups {
for _, workerGroupId := range workerGroupIds {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: workerGroup,
TaskQueue: workerGroupId,
})
heatlcheckParam := activities.HealtcheckParam{
@ -32,7 +32,7 @@ func (w *Workflows) MonitorWorkflowDefinition(ctx workflow.Context, param Monito
var monitorResult *activities.MonitorResult
err := workflow.ExecuteActivity(ctx, w.activities.Monitor, heatlcheckParam).Get(ctx, &monitorResult)
if err != nil {
return err
return models.MonitorUnknown, err
}
status := models.MonitorFailure
@ -41,18 +41,18 @@ func (w *Workflows) MonitorWorkflowDefinition(ctx workflow.Context, param Monito
}
historyParam := activities.HealtcheckAddToHistoryParam{
Slug: param.Slug,
Status: status,
Note: monitorResult.Note,
WorkerGroup: workerGroup,
MonitorId: param.MonitorId,
Status: status,
Note: monitorResult.Note,
WorkerGroupId: workerGroupId,
}
var historyResult *activities.MonitorAddToHistoryResult
err = workflow.ExecuteActivity(ctx, w.activities.MonitorAddToHistory, historyParam).Get(ctx, &historyResult)
if err != nil {
return err
return models.MonitorUnknown, err
}
}
return nil
return models.MonitorSuccess, nil
}

View file

@ -1,7 +1,9 @@
package api
import "code.tjo.space/mentos1386/zdravko/database/models"
type ApiV1MonitorsHistoryPOSTBody struct {
Status string `json:"status"`
Note string `json:"note"`
WorkerGroup string `json:"worker_group"`
Status models.MonitorStatus `json:"status"`
Note string `json:"note"`
WorkerGroupId string `json:"worker_group"`
}

View file

@ -48,16 +48,16 @@ func Routes(
settings.GET("/monitors", h.SettingsMonitorsGET)
settings.GET("/monitors/create", h.SettingsMonitorsCreateGET)
settings.POST("/monitors/create", h.SettingsMonitorsCreatePOST)
settings.GET("/monitors/:slug", h.SettingsMonitorsDescribeGET)
settings.POST("/monitors/:slug", h.SettingsMonitorsDescribePOST)
settings.GET("/monitors/:slug/delete", h.SettingsMonitorsDescribeDELETE)
settings.GET("/monitors/:slug/disable", h.SettingsMonitorsDisableGET)
settings.GET("/monitors/:slug/enable", h.SettingsMonitorsEnableGET)
settings.GET("/monitors/:id", h.SettingsMonitorsDescribeGET)
settings.POST("/monitors/:id", h.SettingsMonitorsDescribePOST)
settings.GET("/monitors/:id/delete", h.SettingsMonitorsDescribeDELETE)
settings.GET("/monitors/:id/disable", h.SettingsMonitorsDisableGET)
settings.GET("/monitors/:id/enable", h.SettingsMonitorsEnableGET)
settings.GET("/worker-groups", h.SettingsWorkerGroupsGET)
settings.GET("/worker-groups/create", h.SettingsWorkerGroupsCreateGET)
settings.POST("/worker-groups/create", h.SettingsWorkerGroupsCreatePOST)
settings.GET("/worker-groups/:slug", h.SettingsWorkerGroupsDescribeGET)
settings.GET("/worker-groups/:slug/delete", h.SettingsWorkerGroupsDescribeDELETE)
settings.GET("/worker-groups/:id", h.SettingsWorkerGroupsDescribeGET)
settings.GET("/worker-groups/:id/delete", h.SettingsWorkerGroupsDescribeDELETE)
settings.Match([]string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"}, "/temporal*", h.Temporal)
@ -71,7 +71,7 @@ func Routes(
apiv1 := e.Group("/api/v1")
apiv1.Use(h.Authenticated)
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
apiv1.POST("/monitors/:slug/history", h.ApiV1MonitorsHistoryPOST)
apiv1.POST("/monitors/:id/history", h.ApiV1MonitorsHistoryPOST)
// Error handler
e.HTTPErrorHandler = func(err error, c echo.Context) {

View file

@ -1677,14 +1677,6 @@ code {
}
@media (min-width: 640px) {
.sm\:ml-2 {
margin-left: 0.5rem;
}
.sm\:mt-0 {
margin-top: 0px;
}
.sm\:w-auto {
width: auto;
}

View file

@ -1,39 +1,3 @@
{{ define "daily" }}
<div class="justify-self-end text-sm">{{ .HistoryDaily.Uptime }}% uptime</div>
<div class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden">
{{ range .HistoryDaily.History }}
{{ 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-400 hover:bg-gray-500 flex-auto"></div>
{{ end }}
{{ end }}
</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>
{{ end }}
{{ define "hourly" }}
<div class="justify-self-end text-sm">
{{ .HistoryHourly.Uptime }}% uptime
</div>
<div class="grid gap-px col-span-2 grid-flow-col h-8 rounded overflow-hidden">
{{ range .HistoryHourly.History }}
{{ 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-400 hover:bg-gray-500 flex-auto"></div>
{{ end }}
{{ end }}
</div>
<div class="text-slate-500 justify-self-start text-sm">48 hours ago</div>
<div class="text-slate-500 justify-self-end text-sm">Now</div>
{{ end }}
{{ define "main" }}
<div class="container max-w-screen-md flex flex-col mt-20">
{{ if eq .MonitorsLength 0 }}
@ -136,11 +100,32 @@
{{ end }}
<p>{{ .Name }}</p>
</div>
{{ if eq $.TimeRange "90days" }}
{{ template "daily" . }}
{{ else }}
{{ template "hourly" . }}
{{ end }}
<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-400 hover:bg-gray-500 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>

View file

@ -96,9 +96,7 @@
{{ .Schedule }}
</td>
<td>
<a href="/settings/monitors/{{ .Slug }}" class="link"
>Details</a
>
<a href="/settings/monitors/{{ .Id }}" class="link">Details</a>
</td>
</tr>
</tbody>

View file

@ -1,6 +1,6 @@
{{ define "settings" }}
<section class="p-5">
<form action="/settings/monitors/{{ .Monitor.Slug }}" method="post">
<form action="/settings/monitors/{{ .Monitor.Id }}" method="post">
<h2>Configuration</h2>
<label for="workergroups">Worker Groups</label>
<input
@ -70,13 +70,13 @@
{{ if eq .Monitor.Status "ACTIVE" }}
<a
class="block text-center py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100"
href="/settings/monitors/{{ .Monitor.Slug }}/disable"
href="/settings/monitors/{{ .Monitor.Id }}/disable"
>Pause</a
>
{{ else if eq .Monitor.Status "PAUSED" }}
<a
class="block text-center py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100"
href="/settings/monitors/{{ .Monitor.Slug }}/enable"
href="/settings/monitors/{{ .Monitor.Id }}/enable"
>Resume</a
>
{{ end }}
@ -87,7 +87,7 @@
<p class="text-sm mb-2">Permanently delete this monitor.</p>
<a
class="block text-center focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2"
href="/settings/monitors/{{ .Monitor.Slug }}/delete"
href="/settings/monitors/{{ .Monitor.Id }}/delete"
>Delete</a
>
</section>
@ -102,6 +102,7 @@
<thead>
<tr>
<th>Status</th>
<th>Worker Group</th>
<th>Created At</th>
<th>Duration</th>
<th>Note</th>
@ -122,7 +123,14 @@
</span>
</td>
<td>
{{ .CreatedAt.Format "2006-01-02 15:04:05" }}
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
>
{{ .WorkerGroupName }}
</span>
</td>
<td>
{{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }}
</td>
<td>{ .Duration }</td>
<td class="whitespace-normal">

View file

@ -6,33 +6,90 @@
Hi there, {{ .User.Email }}.
</h1>
<p class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 md:px-40">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
Welcome to the settings page. Here you can manage your worker groups,
monitors, and notifications.
</p>
</div>
<div class="mx-auto max-w-screen-xl flex flex-col sm:flex-row gap-4">
<div
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:mt-0 sm:ml-2 sm:text-left"
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
>
<h3 class="text-sm leading-6 font-medium text-gray-400">Total Workers</h3>
<p class="text-3xl font-bold text-black">42</p>
<h3 class="text-sm leading-6 font-medium text-gray-400">
Total Worker Groups
</h3>
<p class="text-3xl font-bold text-black">{{ .WorkerGroupsCount }}</p>
</div>
<div
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:mt-0 sm:ml-2 sm:text-left"
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
>
<h3 class="text-sm leading-6 font-medium text-gray-400">
Total Monitors
</h3>
<p class="text-3xl font-bold text-black">42</p>
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p>
</div>
<div
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:mt-0 sm:ml-2 sm:text-left"
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
>
<h3 class="text-sm leading-6 font-medium text-gray-400">
Total Notifications
</h3>
<p class="text-3xl font-bold text-black">42</p>
<p class="text-3xl font-bold text-black">{{ .NotificationsCount }}</p>
</div>
</div>
<section class="mt-4">
<table>
<caption>
Execution History
<p>Last 10 executions for all monitors and worker groups.</p>
</caption>
<thead>
<tr>
<th>Monitor</th>
<th>Worker Group</th>
<th>Status</th>
<th>Executed At</th>
<th>Note</th>
</tr>
</thead>
<tbody>
{{ range .History }}
<tr>
<th>
<a
class="underline hover:text-blue-600"
href="/settings/monitors/{{ .MonitorId }}"
>{{ .MonitorName }}</a
>
</th>
<td>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
>
{{ .WorkerGroupName }}
</span>
</td>
<td>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
bg-green-100 text-green-800
{{ else }}
bg-red-100 text-red-800
{{ end }}"
>
{{ .Status }}
</span>
</td>
<td>
{{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }}
</td>
<td class="whitespace-normal">
{{ .Note }}
</td>
</tr>
{{ end }}
</tbody>
</table>
</section>
{{ end }}

View file

@ -80,7 +80,7 @@
{{ len .Monitors }}
</td>
<td>
<a href="/settings/worker-groups/{{ .Slug }}" class="link"
<a href="/settings/worker-groups/{{ .Id }}" class="link"
>Details</a
>
</td>

View file

@ -82,7 +82,7 @@
</p>
<a
class="block text-center focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2"
href="/settings/worker-groups/{{ .Worker.Slug }}/delete"
href="/settings/worker-groups/{{ .Worker.Id }}/delete"
>Delete</a
>
</section>