From 07c7c716c50618cff8ec7f0a6bc2cd9a12c298cb Mon Sep 17 00:00:00 2001 From: Tine Date: Thu, 29 Feb 2024 23:42:56 +0100 Subject: [PATCH] refactor: schema changes etc. --- .gitignore | 5 +- database/database.go | 3 +- database/models/models.go | 77 ++++++++--- .../sqlite/migrations/2024-02-27-initial.sql | 74 +++++----- deploy/fly.toml | 4 +- internal/activities/monitor.go | 17 +-- internal/handlers/api.go | 14 +- internal/handlers/index.go | 129 ++++++------------ internal/handlers/oauth2.go | 7 +- internal/handlers/settings.go | 41 +++++- internal/handlers/settingsmonitors.go | 30 ++-- internal/handlers/settingsworkergroups.go | 21 ++- internal/jwt/jwt.go | 2 +- internal/services/monitor.go | 97 ++++++------- internal/services/monitor_history.go | 54 +++++++- internal/services/worker_group.go | 65 ++++----- internal/workflows/monitor.go | 30 ++-- pkg/api/monitors.go | 8 +- pkg/server/routes.go | 16 +-- web/static/css/tailwind.css | 8 -- web/templates/pages/index.tmpl | 67 ++++----- web/templates/pages/settings_monitors.tmpl | 4 +- .../pages/settings_monitors_describe.tmpl | 18 ++- web/templates/pages/settings_overview.tmpl | 75 ++++++++-- .../pages/settings_worker_groups.tmpl | 2 +- .../settings_worker_groups_describe.tmpl | 2 +- 26 files changed, 495 insertions(+), 375 deletions(-) diff --git a/.gitignore b/.gitignore index c428055..20281a1 100644 --- a/.gitignore +++ b/.gitignore @@ -6,9 +6,8 @@ package.json node_modules/ # Database -zdravko.db -temporal.db -temporal.db-journal +zdravko.db* +temporal.db* # Keys *.pem diff --git a/database/database.go b/database/database.go index da84362..de52fa7 100644 --- a/database/database.go +++ b/database/database.go @@ -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 } diff --git a/database/models/models.go b/database/models/models.go index 2316b80..a4c9db9 100644 --- a/database/models/models.go +++ b/database/models/models.go @@ -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"` } diff --git a/database/sqlite/migrations/2024-02-27-initial.sql b/database/sqlite/migrations/2024-02-27-initial.sql index 3a4e305..9c9db35 100644 --- a/database/sqlite/migrations/2024-02-27-initial.sql +++ b/database/sqlite/migrations/2024-02-27-initial.sql @@ -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; diff --git a/deploy/fly.toml b/deploy/fly.toml index e365c7a..ea13837 100644 --- a/deploy/fly.toml +++ b/deploy/fly.toml @@ -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' diff --git a/internal/activities/monitor.go b/internal/activities/monitor.go index 502fbeb..4092752 100644 --- a/internal/activities/monitor.go +++ b/internal/activities/monitor.go @@ -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) diff --git a/internal/handlers/api.go b/internal/handlers/api.go index a08c79f..bac6673 100644 --- a/internal/handlers/api.go +++ b/internal/handlers/api.go @@ -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 diff --git a/internal/handlers/index.go b/internal/handlers/index.go index 048f5ed..8d1f697 100644 --- a/internal/handlers/index.go +++ b/internal/handlers/index.go @@ -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, } } diff --git a/internal/handlers/oauth2.go b/internal/handlers/oauth2.go index 7218932..e123e7c 100644 --- a/internal/handlers/oauth2.go +++ b/internal/handlers/oauth2.go @@ -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") } diff --git a/internal/handlers/settings.go b/internal/handlers/settings.go index 2d73b0b..48f2433 100644 --- a/internal/handlers/settings.go +++ b/internal/handlers/settings.go @@ -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, + }) } diff --git a/internal/handlers/settingsmonitors.go b/internal/handlers/settingsmonitors.go index 985e5b4..5693a3b 100644 --- a/internal/handlers/settingsmonitors.go +++ b/internal/handlers/settingsmonitors.go @@ -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, } diff --git a/internal/handlers/settingsworkergroups.go b/internal/handlers/settingsworkergroups.go index 118cad8..7147483 100644 --- a/internal/handlers/settingsworkergroups.go +++ b/internal/handlers/settingsworkergroups.go @@ -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)) } diff --git a/internal/jwt/jwt.go b/internal/jwt/jwt.go index 3601bc2..2e7ad6d 100644 --- a/internal/jwt/jwt.go +++ b/internal/jwt/jwt.go @@ -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"}, diff --git a/internal/services/monitor.go b/internal/services/monitor.go index 8ee7fef..2d41892 100644 --- a/internal/services/monitor.go +++ b/internal/services/monitor.go @@ -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) diff --git a/internal/services/monitor_history.go b/internal/services/monitor_history.go index 57410c8..29f7940 100644 --- a/internal/services/monitor_history.go +++ b/internal/services/monitor_history.go @@ -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 diff --git a/internal/services/worker_group.go b/internal/services/worker_group.go index fc1508a..7ebaa73 100644 --- a/internal/services/worker_group.go +++ b/internal/services/worker_group.go @@ -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 { diff --git a/internal/workflows/monitor.go b/internal/workflows/monitor.go index 5424ea1..a87cc6c 100644 --- a/internal/workflows/monitor.go +++ b/internal/workflows/monitor.go @@ -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 } diff --git a/pkg/api/monitors.go b/pkg/api/monitors.go index a2689f2..726f9ce 100644 --- a/pkg/api/monitors.go +++ b/pkg/api/monitors.go @@ -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"` } diff --git a/pkg/server/routes.go b/pkg/server/routes.go index e069d99..ceea675 100644 --- a/pkg/server/routes.go +++ b/pkg/server/routes.go @@ -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) { diff --git a/web/static/css/tailwind.css b/web/static/css/tailwind.css index a643897..1a792d9 100644 --- a/web/static/css/tailwind.css +++ b/web/static/css/tailwind.css @@ -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; } diff --git a/web/templates/pages/index.tmpl b/web/templates/pages/index.tmpl index 4ed517c..2fa2540 100644 --- a/web/templates/pages/index.tmpl +++ b/web/templates/pages/index.tmpl @@ -1,39 +1,3 @@ -{{ define "daily" }} -
{{ .HistoryDaily.Uptime }}% uptime
-
- {{ range .HistoryDaily.History }} - {{ if eq . "SUCCESS" }} -
- {{ else if eq . "FAILURE" }} -
- {{ else }} -
- {{ end }} - {{ end }} -
-
90 days ago
-
Today
-{{ end }} - -{{ define "hourly" }} -
- {{ .HistoryHourly.Uptime }}% uptime -
-
- {{ range .HistoryHourly.History }} - {{ if eq . "SUCCESS" }} -
- {{ else if eq . "FAILURE" }} -
- {{ else }} -
- {{ end }} - {{ end }} -
-
48 hours ago
-
Now
-{{ end }} - {{ define "main" }}
{{ if eq .MonitorsLength 0 }} @@ -136,11 +100,32 @@ {{ end }}

{{ .Name }}

- {{ if eq $.TimeRange "90days" }} - {{ template "daily" . }} - {{ else }} - {{ template "hourly" . }} - {{ end }} +
+ {{ .History.Uptime }}% uptime +
+
+ {{ range .History.List }} + {{ if eq . "SUCCESS" }} +
+ {{ else if eq . "FAILURE" }} +
+ {{ else }} +
+ {{ end }} + {{ end }} +
+
+ {{ if eq $.TimeRange "90days" }} + 90 days ago + {{ else if eq $.TimeRange "48hours" }} + 48 hours ago + {{ else if eq $.TimeRange "90minutes" }} + 90 minutes ago + {{ end }} +
+
Now
{{ end }} diff --git a/web/templates/pages/settings_monitors.tmpl b/web/templates/pages/settings_monitors.tmpl index 9f80ff5..f1f2bb8 100644 --- a/web/templates/pages/settings_monitors.tmpl +++ b/web/templates/pages/settings_monitors.tmpl @@ -96,9 +96,7 @@ {{ .Schedule }} - Details + Details diff --git a/web/templates/pages/settings_monitors_describe.tmpl b/web/templates/pages/settings_monitors_describe.tmpl index e87eef1..397e441 100644 --- a/web/templates/pages/settings_monitors_describe.tmpl +++ b/web/templates/pages/settings_monitors_describe.tmpl @@ -1,6 +1,6 @@ {{ define "settings" }}
-
+

Configuration

Pause {{ else if eq .Monitor.Status "PAUSED" }} Resume {{ end }} @@ -87,7 +87,7 @@

Permanently delete this monitor.

Delete
@@ -102,6 +102,7 @@ Status + Worker Group Created At Duration Note @@ -122,7 +123,14 @@ - {{ .CreatedAt.Format "2006-01-02 15:04:05" }} + + {{ .WorkerGroupName }} + + + + {{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }} { .Duration } diff --git a/web/templates/pages/settings_overview.tmpl b/web/templates/pages/settings_overview.tmpl index 9c2548e..7968e7a 100644 --- a/web/templates/pages/settings_overview.tmpl +++ b/web/templates/pages/settings_overview.tmpl @@ -6,33 +6,90 @@ Hi there, {{ .User.Email }}.

- 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.

-

Total Workers

-

42

+

+ Total Worker Groups +

+

{{ .WorkerGroupsCount }}

Total Monitors

-

42

+

{{ .MonitorsCount }}

Total Notifications

-

42

+

{{ .NotificationsCount }}

+ +
+ + + + + + + + + + + + + {{ range .History }} + + + + + + + + {{ end }} + +
+ Execution History +

Last 10 executions for all monitors and worker groups.

+
MonitorWorker GroupStatusExecuted AtNote
+ {{ .MonitorName }} + + + {{ .WorkerGroupName }} + + + + {{ .Status }} + + + {{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }} + + {{ .Note }} +
+
{{ end }} diff --git a/web/templates/pages/settings_worker_groups.tmpl b/web/templates/pages/settings_worker_groups.tmpl index 64c24b2..7fda6fb 100644 --- a/web/templates/pages/settings_worker_groups.tmpl +++ b/web/templates/pages/settings_worker_groups.tmpl @@ -80,7 +80,7 @@ {{ len .Monitors }} - Details diff --git a/web/templates/pages/settings_worker_groups_describe.tmpl b/web/templates/pages/settings_worker_groups_describe.tmpl index 3679a43..8d49922 100644 --- a/web/templates/pages/settings_worker_groups_describe.tmpl +++ b/web/templates/pages/settings_worker_groups_describe.tmpl @@ -82,7 +82,7 @@

Delete