feat: renaming things and thus breaking existing database schema

This commit is contained in:
Tine 2024-05-16 22:15:14 +02:00
parent 4603f7a79a
commit 1afde077a6
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
39 changed files with 890 additions and 786 deletions

4
.gitignore vendored
View file

@ -6,9 +6,7 @@ package.json
node_modules/ node_modules/
# Database # Database
zdravko.db* store/
zdravko_kv.db*
temporal.db*
# Keys # Keys
*.pem *.pem

View file

@ -45,16 +45,16 @@ type OAuth2State struct {
ExpiresAt *Time `db:"expires_at"` ExpiresAt *Time `db:"expires_at"`
} }
type MonitorStatus string type CheckStatus string
const ( const (
MonitorSuccess MonitorStatus = "SUCCESS" CheckSuccess CheckStatus = "SUCCESS"
MonitorFailure MonitorStatus = "FAILURE" CheckFailure CheckStatus = "FAILURE"
MonitorError MonitorStatus = "ERROR" CheckError CheckStatus = "ERROR"
MonitorUnknown MonitorStatus = "UNKNOWN" CheckUnknown CheckStatus = "UNKNOWN"
) )
type Monitor struct { type Check struct {
CreatedAt *Time `db:"created_at"` CreatedAt *Time `db:"created_at"`
UpdatedAt *Time `db:"updated_at"` UpdatedAt *Time `db:"updated_at"`
@ -66,18 +66,18 @@ type Monitor struct {
Script string `db:"script"` Script string `db:"script"`
} }
type MonitorWithWorkerGroups struct { type CheckWithWorkerGroups struct {
Monitor Check
// List of worker group names // List of worker group names
WorkerGroups []string WorkerGroups []string
} }
type MonitorHistory struct { type CheckHistory struct {
CreatedAt *Time `db:"created_at"` CreatedAt *Time `db:"created_at"`
MonitorId string `db:"monitor_id"` CheckId string `db:"check_id"`
Status MonitorStatus `db:"status"` Status CheckStatus `db:"status"`
Note string `db:"note"` Note string `db:"note"`
WorkerGroupId string `db:"worker_group_id"` WorkerGroupId string `db:"worker_group_id"`
@ -92,11 +92,11 @@ type WorkerGroup struct {
Name string `db:"name"` Name string `db:"name"`
} }
type WorkerGroupWithMonitors struct { type WorkerGroupWithChecks struct {
WorkerGroup WorkerGroup
// List of worker group names // List of worker group names
Monitors []string Checks []string
} }
type TriggerStatus string type TriggerStatus string

View file

@ -6,7 +6,7 @@ CREATE TABLE oauth2_states (
PRIMARY KEY (state) PRIMARY KEY (state)
) STRICT; ) STRICT;
CREATE TABLE monitors ( CREATE TABLE checks (
id TEXT NOT NULL, id TEXT NOT NULL,
name TEXT NOT NULL, name TEXT NOT NULL,
"group" TEXT NOT NULL DEFAULT 'default', "group" TEXT NOT NULL DEFAULT 'default',
@ -17,12 +17,12 @@ CREATE TABLE monitors (
updated_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 (id), PRIMARY KEY (id),
CONSTRAINT unique_monitors_name UNIQUE (name) CONSTRAINT unique_checks_name UNIQUE (name)
) STRICT; ) STRICT;
--CREATE TRIGGER monitors_updated_timestamp AFTER UPDATE ON monitors BEGIN --CREATE TRIGGER checks_updated_timestamp AFTER UPDATE ON checks BEGIN
-- update monitors set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id; -- update checks set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id;
--END; --END;
CREATE TABLE worker_groups ( CREATE TABLE worker_groups (
@ -40,17 +40,17 @@ CREATE TABLE worker_groups (
-- update worker_groups set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id; -- update worker_groups set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id;
--END; --END;
CREATE TABLE monitor_worker_groups ( CREATE TABLE check_worker_groups (
worker_group_id TEXT NOT NULL, worker_group_id TEXT NOT NULL,
monitor_id TEXT NOT NULL, check_id TEXT NOT NULL,
PRIMARY KEY (worker_group_id,monitor_id), PRIMARY KEY (worker_group_id,check_id),
CONSTRAINT fk_monitor_worker_groups_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE, CONSTRAINT fk_check_worker_groups_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE,
CONSTRAINT fk_monitor_worker_groups_monitor FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE CONSTRAINT fk_check_worker_groups_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE
) STRICT; ) STRICT;
CREATE TABLE monitor_histories ( CREATE TABLE check_histories (
monitor_id TEXT NOT NULL, check_id TEXT NOT NULL,
worker_group_id TEXT NOT NULL, worker_group_id TEXT NOT NULL,
status TEXT NOT NULL, status TEXT NOT NULL,
@ -58,14 +58,41 @@ CREATE TABLE monitor_histories (
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')), created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (monitor_id, worker_group_id, created_at), PRIMARY KEY (check_id, worker_group_id, created_at),
CONSTRAINT fk_monitor_histories_monitor FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE, CONSTRAINT fk_check_histories_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE,
CONSTRAINT fk_monitor_histories_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE CONSTRAINT fk_check_histories_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE
) STRICT;
CREATE TABLE triggers (
id TEXT NOT NULL,
name TEXT NOT NULL,
script TEXT NOT NULL,
status TEXT NOT NULL,
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 (id),
CONSTRAINT unique_triggers_name UNIQUE (name)
) STRICT;
CREATE TABLE trigger_histories (
trigger_id TEXT NOT NULL,
status TEXT NOT NULL,
note TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (trigger_id, created_at),
CONSTRAINT fk_trigger_histories_trigger FOREIGN KEY (trigger_id) REFERENCES triggers(id) ON DELETE CASCADE
) STRICT; ) STRICT;
-- +migrate Down -- +migrate Down
DROP TABLE oauth2_states; DROP TABLE oauth2_states;
DROP TABLE monitor_worker_groups; DROP TABLE check_worker_groups;
DROP TABLE worker_groups; DROP TABLE worker_groups;
DROP TABLE monitor_histories; DROP TABLE check_histories;
DROP TABLE monitors; DROP TABLE checks;
DROP TABLE triggers;
DROP TABLE trigger_histories;

View file

@ -1,29 +0,0 @@
-- +migrate Up
CREATE TABLE triggers (
id TEXT NOT NULL,
name TEXT NOT NULL,
script TEXT NOT NULL,
status TEXT NOT NULL,
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 (id),
CONSTRAINT unique_triggers_name UNIQUE (name)
) STRICT;
CREATE TABLE trigger_histories (
trigger_id TEXT NOT NULL,
status TEXT NOT NULL,
note TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
PRIMARY KEY (trigger_id, created_at),
CONSTRAINT fk_trigger_histories_trigger FOREIGN KEY (trigger_id) REFERENCES triggers(id) ON DELETE CASCADE
) STRICT;
-- +migrate Down
DROP TABLE triggers;
DROP TABLE trigger_histories;

View file

@ -17,9 +17,9 @@ primary_region = 'waw'
ROOT_URL = 'https://zdravko.mnts.dev' ROOT_URL = 'https://zdravko.mnts.dev'
TEMPORAL_SERVER_HOST = 'server.process.zdravko.internal:7233' TEMPORAL_SERVER_HOST = 'server.process.zdravko.internal:7233'
TEMPORAL_DATABASE_PATH = '/data/temporal-9.db' TEMPORAL_DATABASE_PATH = '/data/temporal-10.db'
SQLITE_DATABASE_PATH = '/data/zdravko-9.db' SQLITE_DATABASE_PATH = '/data/zdravko-10.db'
KEYVALUE_DATABASE_PATH = '/data/zdravko_kv.db' KEYVALUE_DATABASE_PATH = '/data/zdravko_kv-10.db'
[processes] [processes]
server = '--temporal --server' server = '--temporal --server'

View file

@ -18,12 +18,12 @@ type HealtcheckParam struct {
Script string Script string
} }
type MonitorResult struct { type CheckResult struct {
Success bool Success bool
Note string Note string
} }
func (a *Activities) Monitor(ctx context.Context, param HealtcheckParam) (*MonitorResult, error) { func (a *Activities) Check(ctx context.Context, param HealtcheckParam) (*CheckResult, error) {
execution := k6.NewExecution(slog.Default(), script.UnescapeString(param.Script)) execution := k6.NewExecution(slog.Default(), script.UnescapeString(param.Script))
result, err := execution.Run(ctx) result, err := execution.Run(ctx)
@ -31,23 +31,23 @@ func (a *Activities) Monitor(ctx context.Context, param HealtcheckParam) (*Monit
return nil, err return nil, err
} }
return &MonitorResult{Success: result.Success, Note: result.Note}, nil return &CheckResult{Success: result.Success, Note: result.Note}, nil
} }
type HealtcheckAddToHistoryParam struct { type HealtcheckAddToHistoryParam struct {
MonitorId string CheckId string
Status models.MonitorStatus Status models.CheckStatus
Note string Note string
WorkerGroupId string WorkerGroupId string
} }
type MonitorAddToHistoryResult struct { type CheckAddToHistoryResult struct {
} }
func (a *Activities) MonitorAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*MonitorAddToHistoryResult, error) { func (a *Activities) CheckAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*CheckAddToHistoryResult, error) {
url := fmt.Sprintf("%s/api/v1/monitors/%s/history", a.config.ApiUrl, param.MonitorId) url := fmt.Sprintf("%s/api/v1/checks/%s/history", a.config.ApiUrl, param.CheckId)
body := api.ApiV1MonitorsHistoryPOSTBody{ body := api.ApiV1ChecksHistoryPOSTBody{
Status: param.Status, Status: param.Status,
Note: param.Note, Note: param.Note,
WorkerGroupId: param.WorkerGroupId, WorkerGroupId: param.WorkerGroupId,
@ -73,5 +73,5 @@ func (a *Activities) MonitorAddToHistory(ctx context.Context, param HealtcheckAd
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode) return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
} }
return &MonitorAddToHistoryResult{}, nil return &CheckAddToHistoryResult{}, nil
} }

View file

@ -48,8 +48,8 @@ func NewServerConfig() *ServerConfig {
// Set defaults // Set defaults
v.SetDefault("port", GetEnvOrDefault("PORT", "8000")) v.SetDefault("port", GetEnvOrDefault("PORT", "8000"))
v.SetDefault("rooturl", GetEnvOrDefault("ROOT_URL", "http://localhost:8000")) v.SetDefault("rooturl", GetEnvOrDefault("ROOT_URL", "http://localhost:8000"))
v.SetDefault("sqlitedatabasepath", GetEnvOrDefault("SQLITE_DATABASE_PATH", "zdravko.db")) v.SetDefault("sqlitedatabasepath", GetEnvOrDefault("SQLITE_DATABASE_PATH", "store/zdravko.db"))
v.SetDefault("keyvaluedatabasepath", GetEnvOrDefault("KEYVALUE_DATABASE_PATH", "zdravko_kv.db")) v.SetDefault("keyvaluedatabasepath", GetEnvOrDefault("KEYVALUE_DATABASE_PATH", "store/zdravko_kv.db"))
v.SetDefault("sessionsecret", os.Getenv("SESSION_SECRET")) v.SetDefault("sessionsecret", os.Getenv("SESSION_SECRET"))
v.SetDefault("temporal.uihost", GetEnvOrDefault("TEMPORAL_UI_HOST", "127.0.0.1:8223")) v.SetDefault("temporal.uihost", GetEnvOrDefault("TEMPORAL_UI_HOST", "127.0.0.1:8223"))
v.SetDefault("temporal.serverhost", GetEnvOrDefault("TEMPORAL_SERVER_HOST", "127.0.0.1:7233")) v.SetDefault("temporal.serverhost", GetEnvOrDefault("TEMPORAL_SERVER_HOST", "127.0.0.1:7233"))

View file

@ -23,7 +23,7 @@ func NewTemporalConfig() *TemporalConfig {
v := newViper() v := newViper()
// Set defaults // Set defaults
v.SetDefault("databasepath", GetEnvOrDefault("TEMPORAL_DATABASE_PATH", "temporal.db")) v.SetDefault("databasepath", GetEnvOrDefault("TEMPORAL_DATABASE_PATH", "store/temporal.db"))
v.SetDefault("listenaddress", GetEnvOrDefault("TEMPORAL_LISTEN_ADDRESS", "0.0.0.0")) v.SetDefault("listenaddress", GetEnvOrDefault("TEMPORAL_LISTEN_ADDRESS", "0.0.0.0"))
v.SetDefault("jwt.publickey", os.Getenv("JWT_PUBLIC_KEY")) v.SetDefault("jwt.publickey", os.Getenv("JWT_PUBLIC_KEY"))

View file

@ -40,26 +40,26 @@ func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
// TODO: Can we instead get this from the Workflow outcome? // TODO: Can we instead get this from the Workflow outcome?
// //
// To somehow listen for the outcomes and then store them automatically. // To somehow listen for the outcomes and then store them automatically.
func (h *BaseHandler) ApiV1MonitorsHistoryPOST(c echo.Context) error { func (h *BaseHandler) ApiV1ChecksHistoryPOST(c echo.Context) error {
ctx := context.Background() ctx := context.Background()
id := c.Param("id") id := c.Param("id")
var body api.ApiV1MonitorsHistoryPOSTBody var body api.ApiV1ChecksHistoryPOSTBody
err := (&echo.DefaultBinder{}).BindBody(c, &body) err := (&echo.DefaultBinder{}).BindBody(c, &body)
if err != nil { if err != nil {
return err return err
} }
_, err = services.GetMonitor(ctx, h.db, id) _, err = services.GetCheck(ctx, h.db, id)
if err != nil { if err != nil {
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return echo.NewHTTPError(http.StatusNotFound, "Monitor not found") return echo.NewHTTPError(http.StatusNotFound, "Check not found")
} }
return err return err
} }
err = services.AddHistoryForMonitor(ctx, h.db, &models.MonitorHistory{ err = services.AddHistoryForCheck(ctx, h.db, &models.CheckHistory{
MonitorId: id, CheckId: id,
WorkerGroupId: body.WorkerGroupId, WorkerGroupId: body.WorkerGroupId,
Status: body.Status, Status: body.Status,
Note: body.Note, Note: body.Note,

View file

@ -18,7 +18,7 @@ import (
var examplesYaml embed.FS var examplesYaml embed.FS
type examples struct { type examples struct {
Monitor string `yaml:"monitor"` Check string `yaml:"check"`
Trigger string `yaml:"trigger"` Trigger string `yaml:"trigger"`
} }
@ -63,7 +63,7 @@ func NewBaseHandler(db *sqlx.DB, kvStore kv.KeyValueStore, temporal client.Clien
panic(err) panic(err)
} }
examples.Monitor = script.EscapeString(examples.Monitor) examples.Check = script.EscapeString(examples.Check)
examples.Trigger = script.EscapeString(examples.Trigger) examples.Trigger = script.EscapeString(examples.Trigger)
return &BaseHandler{ return &BaseHandler{

View file

@ -13,21 +13,21 @@ import (
type IndexData struct { type IndexData struct {
*components.Base *components.Base
Monitors map[string]MonitorsAndStatus Checks map[string]ChecksAndStatus
MonitorsLength int ChecksLength int
TimeRange string TimeRange string
Status models.MonitorStatus Status models.CheckStatus
} }
type Monitor struct { type Check struct {
Name string Name string
Group string Group string
Status models.MonitorStatus Status models.CheckStatus
History *History History *History
} }
type HistoryItem struct { type HistoryItem struct {
Status models.MonitorStatus Status models.CheckStatus
Date time.Time Date time.Time
} }
@ -36,23 +36,23 @@ type History struct {
Uptime int Uptime int
} }
type MonitorsAndStatus struct { type ChecksAndStatus struct {
Status models.MonitorStatus Status models.CheckStatus
Monitors []*Monitor Checks []*Check
} }
func getDateString(date time.Time) string { func getDateString(date time.Time) string {
return date.UTC().Format("2006-01-02T15:04:05") return date.UTC().Format("2006-01-02T15:04:05")
} }
func getHistory(history []*models.MonitorHistory, period time.Duration, buckets int) *History { func getHistory(history []*models.CheckHistory, period time.Duration, buckets int) *History {
historyMap := map[string]models.MonitorStatus{} historyMap := map[string]models.CheckStatus{}
numOfSuccess := 0 numOfSuccess := 0
numTotal := 0 numTotal := 0
for i := 0; i < buckets; i++ { for i := 0; i < buckets; i++ {
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period)) dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
historyMap[dateString] = models.MonitorUnknown historyMap[dateString] = models.CheckUnknown
} }
for _, _history := range history { for _, _history := range history {
@ -64,12 +64,12 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
} }
numTotal++ numTotal++
if _history.Status == models.MonitorSuccess { if _history.Status == models.CheckSuccess {
numOfSuccess++ numOfSuccess++
} }
// skip if it is already set to failure // skip if it is already set to failure
if historyMap[dateString] == models.MonitorFailure { if historyMap[dateString] == models.CheckFailure {
continue continue
} }
@ -101,7 +101,7 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
func (h *BaseHandler) Index(c echo.Context) error { func (h *BaseHandler) Index(c echo.Context) error {
ctx := context.Background() ctx := context.Background()
monitors, err := services.GetMonitors(ctx, h.db) checks, err := services.GetChecks(ctx, h.db)
if err != nil { if err != nil {
return err return err
} }
@ -111,12 +111,12 @@ func (h *BaseHandler) Index(c echo.Context) error {
timeRange = "90days" timeRange = "90days"
} }
overallStatus := models.MonitorUnknown overallStatus := models.CheckUnknown
statusByGroup := make(map[string]models.MonitorStatus) statusByGroup := make(map[string]models.CheckStatus)
monitorsWithHistory := make([]*Monitor, len(monitors)) checksWithHistory := make([]*Check, len(checks))
for i, monitor := range monitors { for i, check := range checks {
history, err := services.GetMonitorHistoryForMonitor(ctx, h.db, monitor.Id) history, err := services.GetCheckHistoryForCheck(ctx, h.db, check.Id)
if err != nil { if err != nil {
return err return err
} }
@ -131,37 +131,37 @@ func (h *BaseHandler) Index(c echo.Context) error {
historyResult = getHistory(history, time.Minute, 90) historyResult = getHistory(history, time.Minute, 90)
} }
if statusByGroup[monitor.Group] == "" { if statusByGroup[check.Group] == "" {
statusByGroup[monitor.Group] = models.MonitorUnknown statusByGroup[check.Group] = models.CheckUnknown
} }
status := historyResult.List[len(historyResult.List)-1] status := historyResult.List[len(historyResult.List)-1]
if status.Status == models.MonitorSuccess { if status.Status == models.CheckSuccess {
if overallStatus == models.MonitorUnknown { if overallStatus == models.CheckUnknown {
overallStatus = status.Status overallStatus = status.Status
} }
if statusByGroup[monitor.Group] == models.MonitorUnknown { if statusByGroup[check.Group] == models.CheckUnknown {
statusByGroup[monitor.Group] = status.Status statusByGroup[check.Group] = status.Status
} }
} }
if status.Status != models.MonitorSuccess && status.Status != models.MonitorUnknown { if status.Status != models.CheckSuccess && status.Status != models.CheckUnknown {
overallStatus = status.Status overallStatus = status.Status
statusByGroup[monitor.Group] = status.Status statusByGroup[check.Group] = status.Status
} }
monitorsWithHistory[i] = &Monitor{ checksWithHistory[i] = &Check{
Name: monitor.Name, Name: check.Name,
Group: monitor.Group, Group: check.Group,
Status: status.Status, Status: status.Status,
History: historyResult, History: historyResult,
} }
} }
monitorsByGroup := map[string]MonitorsAndStatus{} checksByGroup := map[string]ChecksAndStatus{}
for _, monitor := range monitorsWithHistory { for _, check := range checksWithHistory {
monitorsByGroup[monitor.Group] = MonitorsAndStatus{ checksByGroup[check.Group] = ChecksAndStatus{
Status: statusByGroup[monitor.Group], Status: statusByGroup[check.Group],
Monitors: append(monitorsByGroup[monitor.Group].Monitors, monitor), Checks: append(checksByGroup[check.Group].Checks, check),
} }
} }
@ -172,7 +172,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
NavbarActive: GetPageByTitle(Pages, "Status"), NavbarActive: GetPageByTitle(Pages, "Status"),
Navbar: Pages, Navbar: Pages,
}, },
Monitors: monitorsByGroup, Checks: checksByGroup,
TimeRange: timeRange, TimeRange: timeRange,
Status: overallStatus, Status: overallStatus,
}) })

View file

@ -36,11 +36,13 @@ func NewSettings(user *AuthenticatedUser, page *components.Page, breadCrumbs []*
var SettingsPages = []*components.Page{ var SettingsPages = []*components.Page{
{Path: "/settings", Title: "Overview", Breadcrumb: "Overview"}, {Path: "/settings", Title: "Overview", Breadcrumb: "Overview"},
{Path: "/settings/targets", Title: "Incidents", Breadcrumb: "Incidents"}, {Path: "/settings/incidents", Title: "Incidents", Breadcrumb: "Incidents"},
{Path: "/settings/targets", Title: "Targets", Breadcrumb: "Targets"}, {Path: "/settings/targets", Title: "Targets", Breadcrumb: "Targets"},
{Path: "/settings/targets/create", Title: "Targets Create", Breadcrumb: "Create"}, {Path: "/settings/targets/create", Title: "Targets Create", Breadcrumb: "Create"},
{Path: "/settings/monitors", Title: "Checks", Breadcrumb: "Checks"}, {Path: "/settings/hooks", Title: "Hooks", Breadcrumb: "Hooks"},
{Path: "/settings/monitors/create", Title: "Checks Create", Breadcrumb: "Create"}, {Path: "/settings/hooks/create", Title: "Hooks Create", Breadcrumb: "Create"},
{Path: "/settings/checks", Title: "Checks", Breadcrumb: "Checks"},
{Path: "/settings/checks/create", Title: "Checks Create", Breadcrumb: "Create"},
{Path: "/settings/worker-groups", Title: "Worker Groups", Breadcrumb: "Worker Groups"}, {Path: "/settings/worker-groups", Title: "Worker Groups", Breadcrumb: "Worker Groups"},
{Path: "/settings/worker-groups/create", Title: "Worker Groups Create", Breadcrumb: "Create"}, {Path: "/settings/worker-groups/create", Title: "Worker Groups Create", Breadcrumb: "Create"},
{Path: "/settings/notifications", Title: "Notifications", Breadcrumb: "Notifications"}, {Path: "/settings/notifications", Title: "Notifications", Breadcrumb: "Notifications"},
@ -63,10 +65,11 @@ var SettingsSidebar = []SettingsSidebarGroup{
Pages: []*components.Page{ Pages: []*components.Page{
GetPageByTitle(SettingsPages, "Targets"), GetPageByTitle(SettingsPages, "Targets"),
GetPageByTitle(SettingsPages, "Checks"), GetPageByTitle(SettingsPages, "Checks"),
GetPageByTitle(SettingsPages, "Hooks"),
}, },
}, },
{ {
Group: "Decide", Group: "Alert",
Pages: []*components.Page{ Pages: []*components.Page{
GetPageByTitle(SettingsPages, "Triggers"), GetPageByTitle(SettingsPages, "Triggers"),
}, },
@ -91,9 +94,9 @@ var SettingsSidebar = []SettingsSidebarGroup{
type SettingsOverview struct { type SettingsOverview struct {
*Settings *Settings
WorkerGroupsCount int WorkerGroupsCount int
MonitorsCount int ChecksCount int
NotificationsCount int NotificationsCount int
History []*services.MonitorHistoryWithMonitor History []*services.CheckHistoryWithCheck
} }
func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error { func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
@ -105,12 +108,12 @@ func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
return err return err
} }
monitors, err := services.CountMonitors(ctx, h.db) checks, err := services.CountChecks(ctx, h.db)
if err != nil { if err != nil {
return err return err
} }
history, err := services.GetLastNMonitorHistory(ctx, h.db, 10) history, err := services.GetLastNCheckHistory(ctx, h.db, 10)
if err != nil { if err != nil {
return err return err
} }
@ -122,7 +125,7 @@ func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
[]*components.Page{GetPageByTitle(SettingsPages, "Overview")}, []*components.Page{GetPageByTitle(SettingsPages, "Overview")},
), ),
WorkerGroupsCount: workerGroups, WorkerGroupsCount: workerGroups,
MonitorsCount: monitors, ChecksCount: checks,
NotificationsCount: 42, NotificationsCount: 42,
History: history, History: history,
}) })

View file

@ -0,0 +1,30 @@
package handlers
import (
"net/http"
"code.tjo.space/mentos1386/zdravko/web/templates/components"
"github.com/labstack/echo/v4"
)
type Incident struct{}
type SettingsIncidents struct {
*Settings
Incidents []*Incident
}
func (h *BaseHandler) SettingsIncidentsGET(c echo.Context) error {
cc := c.(AuthenticatedContext)
incidents := make([]*Incident, 0)
return c.Render(http.StatusOK, "settings_incidents.tmpl", &SettingsIncidents{
Settings: NewSettings(
cc.Principal.User,
GetPageByTitle(SettingsPages, "Incidents"),
[]*components.Page{GetPageByTitle(SettingsPages, "Incidents")},
),
Incidents: incidents,
})
}

View file

@ -14,13 +14,6 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
type Incident struct{}
type SettingsIncidents struct {
*Settings
Incidents []*Incident
}
type CreateTrigger struct { type CreateTrigger struct {
Name string `validate:"required"` Name string `validate:"required"`
Script string `validate:"required"` Script string `validate:"required"`

View file

@ -17,7 +17,7 @@ import (
"github.com/labstack/echo/v4" "github.com/labstack/echo/v4"
) )
type CreateMonitor struct { type CreateCheck struct {
Name string `validate:"required"` Name string `validate:"required"`
Group string `validate:"required"` Group string `validate:"required"`
WorkerGroups string `validate:"required"` WorkerGroups string `validate:"required"`
@ -25,96 +25,96 @@ type CreateMonitor struct {
Script string `validate:"required"` Script string `validate:"required"`
} }
type UpdateMonitor struct { type UpdateCheck struct {
Group string `validate:"required"` Group string `validate:"required"`
WorkerGroups string `validate:"required"` WorkerGroups string `validate:"required"`
Schedule string `validate:"required,cron"` Schedule string `validate:"required,cron"`
Script string `validate:"required"` Script string `validate:"required"`
} }
type MonitorWithWorkerGroupsAndStatus struct { type CheckWithWorkerGroupsAndStatus struct {
*models.MonitorWithWorkerGroups *models.CheckWithWorkerGroups
Status services.MonitorStatus Status services.CheckStatus
} }
type SettingsMonitors struct { type SettingsChecks struct {
*Settings *Settings
Monitors map[string][]*MonitorWithWorkerGroupsAndStatus Checks map[string][]*CheckWithWorkerGroupsAndStatus
MonitorGroups []string CheckGroups []string
} }
type SettingsMonitor struct { type SettingsCheck struct {
*Settings *Settings
Monitor *MonitorWithWorkerGroupsAndStatus Check *CheckWithWorkerGroupsAndStatus
History []*models.MonitorHistory History []*models.CheckHistory
} }
type SettingsMonitorCreate struct { type SettingsCheckCreate struct {
*Settings *Settings
Example string Example string
} }
func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error { func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
cc := c.(AuthenticatedContext) cc := c.(AuthenticatedContext)
monitors, err := services.GetMonitorsWithWorkerGroups(context.Background(), h.db) checks, err := services.GetChecksWithWorkerGroups(context.Background(), h.db)
if err != nil { if err != nil {
return err return err
} }
monitorsWithStatus := make([]*MonitorWithWorkerGroupsAndStatus, len(monitors)) checksWithStatus := make([]*CheckWithWorkerGroupsAndStatus, len(checks))
for i, monitor := range monitors { for i, check := range checks {
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Id) status, err := services.GetCheckStatus(context.Background(), h.temporal, check.Id)
if err != nil { if err != nil {
return err return err
} }
monitorsWithStatus[i] = &MonitorWithWorkerGroupsAndStatus{ checksWithStatus[i] = &CheckWithWorkerGroupsAndStatus{
MonitorWithWorkerGroups: monitor, CheckWithWorkerGroups: check,
Status: status, Status: status,
} }
} }
monitorGroups := []string{} checkGroups := []string{}
monitorsByGroup := map[string][]*MonitorWithWorkerGroupsAndStatus{} checksByGroup := map[string][]*CheckWithWorkerGroupsAndStatus{}
for _, monitor := range monitorsWithStatus { for _, check := range checksWithStatus {
monitorsByGroup[monitor.Group] = append(monitorsByGroup[monitor.Group], monitor) checksByGroup[check.Group] = append(checksByGroup[check.Group], check)
if slices.Contains(monitorGroups, monitor.Group) == false { if slices.Contains(checkGroups, check.Group) == false {
monitorGroups = append(monitorGroups, monitor.Group) checkGroups = append(checkGroups, check.Group)
} }
} }
return c.Render(http.StatusOK, "settings_monitors.tmpl", &SettingsMonitors{ return c.Render(http.StatusOK, "settings_checks.tmpl", &SettingsChecks{
Settings: NewSettings( Settings: NewSettings(
cc.Principal.User, cc.Principal.User,
GetPageByTitle(SettingsPages, "Checks"), GetPageByTitle(SettingsPages, "Checks"),
[]*components.Page{GetPageByTitle(SettingsPages, "Checks")}, []*components.Page{GetPageByTitle(SettingsPages, "Checks")},
), ),
Monitors: monitorsByGroup, Checks: checksByGroup,
MonitorGroups: monitorGroups, CheckGroups: checkGroups,
}) })
} }
func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error { func (h *BaseHandler) SettingsChecksDescribeGET(c echo.Context) error {
cc := c.(AuthenticatedContext) cc := c.(AuthenticatedContext)
slug := c.Param("id") slug := c.Param("id")
monitor, err := services.GetMonitorWithWorkerGroups(context.Background(), h.db, slug) check, err := services.GetCheckWithWorkerGroups(context.Background(), h.db, slug)
if err != nil { if err != nil {
return err return err
} }
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Id) status, err := services.GetCheckStatus(context.Background(), h.temporal, check.Id)
if err != nil { if err != nil {
return err return err
} }
monitorWithStatus := &MonitorWithWorkerGroupsAndStatus{ checkWithStatus := &CheckWithWorkerGroupsAndStatus{
MonitorWithWorkerGroups: monitor, CheckWithWorkerGroups: check,
Status: status, Status: status,
} }
history, err := services.GetMonitorHistoryForMonitor(context.Background(), h.db, slug) history, err := services.GetCheckHistoryForCheck(context.Background(), h.db, slug)
if err != nil { if err != nil {
return err return err
} }
@ -124,76 +124,76 @@ func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error {
maxElements = len(history) maxElements = len(history)
} }
return c.Render(http.StatusOK, "settings_monitors_describe.tmpl", &SettingsMonitor{ return c.Render(http.StatusOK, "settings_checks_describe.tmpl", &SettingsCheck{
Settings: NewSettings( Settings: NewSettings(
cc.Principal.User, cc.Principal.User,
GetPageByTitle(SettingsPages, "Checks"), GetPageByTitle(SettingsPages, "Checks"),
[]*components.Page{ []*components.Page{
GetPageByTitle(SettingsPages, "Checks"), GetPageByTitle(SettingsPages, "Checks"),
{ {
Path: fmt.Sprintf("/settings/monitors/%s", slug), Path: fmt.Sprintf("/settings/checks/%s", slug),
Title: "Describe", Title: "Describe",
Breadcrumb: monitor.Name, Breadcrumb: check.Name,
}, },
}), }),
Monitor: monitorWithStatus, Check: checkWithStatus,
History: history[:maxElements], History: history[:maxElements],
}) })
} }
func (h *BaseHandler) SettingsMonitorsDescribeDELETE(c echo.Context) error { func (h *BaseHandler) SettingsChecksDescribeDELETE(c echo.Context) error {
slug := c.Param("id") slug := c.Param("id")
err := services.DeleteMonitor(context.Background(), h.db, slug) err := services.DeleteCheck(context.Background(), h.db, slug)
if err != nil { if err != nil {
return err return err
} }
err = services.DeleteMonitorSchedule(context.Background(), h.temporal, slug) err = services.DeleteCheckSchedule(context.Background(), h.temporal, slug)
if err != nil { if err != nil {
return err return err
} }
return c.Redirect(http.StatusSeeOther, "/settings/monitors") return c.Redirect(http.StatusSeeOther, "/settings/checks")
} }
func (h *BaseHandler) SettingsMonitorsDisableGET(c echo.Context) error { func (h *BaseHandler) SettingsChecksDisableGET(c echo.Context) error {
slug := c.Param("id") slug := c.Param("id")
monitor, err := services.GetMonitor(context.Background(), h.db, slug) check, err := services.GetCheck(context.Background(), h.db, slug)
if err != nil { if err != nil {
return err return err
} }
err = services.SetMonitorStatus(context.Background(), h.temporal, monitor.Id, services.MonitorStatusPaused) err = services.SetCheckStatus(context.Background(), h.temporal, check.Id, services.CheckStatusPaused)
if err != nil { if err != nil {
return err return err
} }
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/monitors/%s", slug)) return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/checks/%s", slug))
} }
func (h *BaseHandler) SettingsMonitorsEnableGET(c echo.Context) error { func (h *BaseHandler) SettingsChecksEnableGET(c echo.Context) error {
slug := c.Param("id") slug := c.Param("id")
monitor, err := services.GetMonitor(context.Background(), h.db, slug) check, err := services.GetCheck(context.Background(), h.db, slug)
if err != nil { if err != nil {
return err return err
} }
err = services.SetMonitorStatus(context.Background(), h.temporal, monitor.Id, services.MonitorStatusActive) err = services.SetCheckStatus(context.Background(), h.temporal, check.Id, services.CheckStatusActive)
if err != nil { if err != nil {
return err return err
} }
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/monitors/%s", slug)) return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/checks/%s", slug))
} }
func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error { func (h *BaseHandler) SettingsChecksDescribePOST(c echo.Context) error {
ctx := context.Background() ctx := context.Background()
monitorId := c.Param("id") checkId := c.Param("id")
update := UpdateMonitor{ update := UpdateCheck{
Group: strings.ToLower(c.FormValue("group")), Group: strings.ToLower(c.FormValue("group")),
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))), WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
Schedule: c.FormValue("schedule"), Schedule: c.FormValue("schedule"),
@ -204,18 +204,18 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
return err return err
} }
monitor, err := services.GetMonitor(ctx, h.db, monitorId) check, err := services.GetCheck(ctx, h.db, checkId)
if err != nil { if err != nil {
return err return err
} }
monitor.Group = update.Group check.Group = update.Group
monitor.Schedule = update.Schedule check.Schedule = update.Schedule
monitor.Script = update.Script check.Script = update.Script
err = services.UpdateMonitor( err = services.UpdateCheck(
ctx, ctx,
h.db, h.db,
monitor, check,
) )
if err != nil { if err != nil {
return err return err
@ -241,23 +241,23 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
workerGroups = append(workerGroups, workerGroup) workerGroups = append(workerGroups, workerGroup)
} }
err = services.UpdateMonitorWorkerGroups(ctx, h.db, monitor, workerGroups) err = services.UpdateCheckWorkerGroups(ctx, h.db, check, workerGroups)
if err != nil { if err != nil {
return err return err
} }
err = services.CreateOrUpdateMonitorSchedule(ctx, h.temporal, monitor, workerGroups) err = services.CreateOrUpdateCheckSchedule(ctx, h.temporal, check, workerGroups)
if err != nil { if err != nil {
return err return err
} }
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/monitors/%s", monitorId)) return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/checks/%s", checkId))
} }
func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error { func (h *BaseHandler) SettingsChecksCreateGET(c echo.Context) error {
cc := c.(AuthenticatedContext) cc := c.(AuthenticatedContext)
return c.Render(http.StatusOK, "settings_monitors_create.tmpl", &SettingsMonitorCreate{ return c.Render(http.StatusOK, "settings_checks_create.tmpl", &SettingsCheckCreate{
Settings: NewSettings( Settings: NewSettings(
cc.Principal.User, cc.Principal.User,
GetPageByTitle(SettingsPages, "Checks"), GetPageByTitle(SettingsPages, "Checks"),
@ -266,15 +266,15 @@ func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error {
GetPageByTitle(SettingsPages, "Checks Create"), GetPageByTitle(SettingsPages, "Checks Create"),
}, },
), ),
Example: h.examples.Monitor, Example: h.examples.Check,
}) })
} }
func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error { func (h *BaseHandler) SettingsChecksCreatePOST(c echo.Context) error {
ctx := context.Background() ctx := context.Background()
monitorId := slug.Make(c.FormValue("name")) checkId := slug.Make(c.FormValue("name"))
create := CreateMonitor{ create := CreateCheck{
Name: c.FormValue("name"), Name: c.FormValue("name"),
Group: strings.ToLower(c.FormValue("group")), Group: strings.ToLower(c.FormValue("group")),
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))), WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
@ -306,32 +306,32 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
workerGroups = append(workerGroups, workerGroup) workerGroups = append(workerGroups, workerGroup)
} }
monitor := &models.Monitor{ check := &models.Check{
Name: create.Name, Name: create.Name,
Group: create.Group, Group: create.Group,
Id: monitorId, Id: checkId,
Schedule: create.Schedule, Schedule: create.Schedule,
Script: create.Script, Script: create.Script,
} }
err = services.CreateMonitor( err = services.CreateCheck(
ctx, ctx,
h.db, h.db,
monitor, check,
) )
if err != nil { if err != nil {
return err return err
} }
err = services.UpdateMonitorWorkerGroups(ctx, h.db, monitor, workerGroups) err = services.UpdateCheckWorkerGroups(ctx, h.db, check, workerGroups)
if err != nil { if err != nil {
return err return err
} }
err = services.CreateOrUpdateMonitorSchedule(ctx, h.temporal, monitor, workerGroups) err = services.CreateOrUpdateCheckSchedule(ctx, h.temporal, check, workerGroups)
if err != nil { if err != nil {
return err return err
} }
return c.Redirect(http.StatusSeeOther, "/settings/monitors") return c.Redirect(http.StatusSeeOther, "/settings/checks")
} }

View file

@ -22,7 +22,7 @@ type WorkerWithTokenAndActiveWorkers struct {
} }
type WorkerGroupWithActiveWorkers struct { type WorkerGroupWithActiveWorkers struct {
*models.WorkerGroupWithMonitors *models.WorkerGroupWithChecks
ActiveWorkers []string ActiveWorkers []string
} }
@ -39,7 +39,7 @@ type SettingsWorker struct {
func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error { func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
cc := c.(AuthenticatedContext) cc := c.(AuthenticatedContext)
workerGroups, err := services.GetWorkerGroupsWithMonitors(context.Background(), h.db) workerGroups, err := services.GetWorkerGroupsWithChecks(context.Background(), h.db)
if err != nil { if err != nil {
return err return err
} }
@ -51,7 +51,7 @@ func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
return err return err
} }
workerGroupsWithActiveWorkers[i] = &WorkerGroupWithActiveWorkers{ workerGroupsWithActiveWorkers[i] = &WorkerGroupWithActiveWorkers{
WorkerGroupWithMonitors: workerGroup, WorkerGroupWithChecks: workerGroup,
ActiveWorkers: activeWorkers, ActiveWorkers: activeWorkers,
} }
} }

316
internal/services/check.go Normal file
View file

@ -0,0 +1,316 @@
package services
import (
"context"
"log"
"sort"
"time"
"code.tjo.space/mentos1386/zdravko/database/models"
"code.tjo.space/mentos1386/zdravko/internal/workflows"
"github.com/jmoiron/sqlx"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporal"
"golang.org/x/exp/maps"
)
type CheckStatus string
const (
CheckStatusUnknown CheckStatus = "UNKNOWN"
CheckStatusPaused CheckStatus = "PAUSED"
CheckStatusActive CheckStatus = "ACTIVE"
)
func getScheduleId(id string) string {
return "check-" + id
}
func CountChecks(ctx context.Context, db *sqlx.DB) (int, error) {
var count int
err := db.GetContext(ctx, &count, "SELECT COUNT(*) FROM checks")
return count, err
}
func GetCheckStatus(ctx context.Context, temporal client.Client, id string) (CheckStatus, error) {
schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(id))
description, err := schedule.Describe(ctx)
if err != nil {
return CheckStatusUnknown, err
}
if description.Schedule.State.Paused {
return CheckStatusPaused, nil
}
return CheckStatusActive, nil
}
func SetCheckStatus(ctx context.Context, temporal client.Client, id string, status CheckStatus) error {
schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(id))
if status == CheckStatusActive {
return schedule.Unpause(ctx, client.ScheduleUnpauseOptions{Note: "Unpaused by user"})
}
if status == CheckStatusPaused {
return schedule.Pause(ctx, client.SchedulePauseOptions{Note: "Paused by user"})
}
return nil
}
func CreateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
_, err := db.NamedExecContext(ctx,
`INSERT INTO checks (id, name, "group", script, schedule) VALUES (:id, :name, :group, :script, :schedule)`,
check,
)
return err
}
func UpdateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
_, err := db.NamedExecContext(ctx,
`UPDATE checks SET "group"=:group, script=:script, schedule=:schedule WHERE id=:id`,
check,
)
return err
}
func DeleteCheck(ctx context.Context, db *sqlx.DB, id string) error {
_, err := db.ExecContext(ctx,
"DELETE FROM checks WHERE id=$1",
id,
)
return err
}
func UpdateCheckWorkerGroups(ctx context.Context, db *sqlx.DB, check *models.Check, workerGroups []*models.WorkerGroup) error {
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx,
"DELETE FROM check_worker_groups WHERE check_id=$1",
check.Id,
)
if err != nil {
tx.Rollback()
return err
}
for _, group := range workerGroups {
_, err = tx.ExecContext(ctx,
"INSERT INTO check_worker_groups (check_id, worker_group_id) VALUES ($1, $2)",
check.Id,
group.Id,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
func GetCheck(ctx context.Context, db *sqlx.DB, id string) (*models.Check, error) {
check := &models.Check{}
err := db.GetContext(ctx, check,
"SELECT * FROM checks WHERE id=$1",
id,
)
return check, err
}
func GetCheckWithWorkerGroups(ctx context.Context, db *sqlx.DB, id string) (*models.CheckWithWorkerGroups, error) {
rows, err := db.QueryContext(ctx,
`
SELECT
checks.id,
checks.name,
checks."group",
checks.script,
checks.schedule,
checks.created_at,
checks.updated_at,
worker_groups.name as worker_group_name
FROM checks
LEFT OUTER JOIN check_worker_groups ON checks.id = check_worker_groups.check_id
LEFT OUTER JOIN worker_groups ON check_worker_groups.worker_group_id = worker_groups.id
WHERE checks.id=$1
ORDER BY checks.name
`,
id,
)
if err != nil {
return nil, err
}
defer rows.Close()
check := &models.CheckWithWorkerGroups{}
for rows.Next() {
var workerGroupName *string
err = rows.Scan(
&check.Id,
&check.Name,
&check.Group,
&check.Script,
&check.Schedule,
&check.CreatedAt,
&check.UpdatedAt,
&workerGroupName,
)
if err != nil {
return nil, err
}
if workerGroupName != nil {
check.WorkerGroups = append(check.WorkerGroups, *workerGroupName)
}
}
return check, err
}
func GetChecks(ctx context.Context, db *sqlx.DB) ([]*models.Check, error) {
checks := []*models.Check{}
err := db.SelectContext(ctx, &checks,
"SELECT * FROM checks ORDER BY name",
)
return checks, err
}
func GetChecksWithWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.CheckWithWorkerGroups, error) {
rows, err := db.QueryContext(ctx,
`
SELECT
checks.id,
checks.name,
checks."group",
checks.script,
checks.schedule,
checks.created_at,
checks.updated_at,
worker_groups.name as worker_group_name
FROM checks
LEFT OUTER JOIN check_worker_groups ON checks.id = check_worker_groups.check_id
LEFT OUTER JOIN worker_groups ON check_worker_groups.worker_group_id = worker_groups.id
ORDER BY checks.name
`)
if err != nil {
return nil, err
}
defer rows.Close()
checks := map[string]*models.CheckWithWorkerGroups{}
for rows.Next() {
check := &models.CheckWithWorkerGroups{}
var workerGroupName *string
err = rows.Scan(
&check.Id,
&check.Name,
&check.Group,
&check.Script,
&check.Schedule,
&check.CreatedAt,
&check.UpdatedAt,
&workerGroupName,
)
if err != nil {
return nil, err
}
if workerGroupName != nil {
workerGroups := []string{}
if checks[check.Id] != nil {
workerGroups = checks[check.Id].WorkerGroups
}
check.WorkerGroups = append(workerGroups, *workerGroupName)
}
checks[check.Id] = check
}
checksWithWorkerGroups := maps.Values(checks)
sort.SliceStable(checksWithWorkerGroups, func(i, j int) bool {
return checksWithWorkerGroups[i].Name < checksWithWorkerGroups[j].Name
})
return checksWithWorkerGroups, err
}
func DeleteCheckSchedule(ctx context.Context, t client.Client, id string) error {
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(id))
return schedule.Delete(ctx)
}
func CreateOrUpdateCheckSchedule(
ctx context.Context,
t client.Client,
check *models.Check,
workerGroups []*models.WorkerGroup,
) error {
log.Println("Creating or Updating Check Schedule")
workerGroupStrings := make([]string, len(workerGroups))
for i, group := range workerGroups {
workerGroupStrings[i] = group.Id
}
args := make([]interface{}, 1)
args[0] = workflows.CheckWorkflowParam{
Script: check.Script,
CheckId: check.Id,
WorkerGroupIds: workerGroupStrings,
}
options := client.ScheduleOptions{
ID: getScheduleId(check.Id),
Spec: client.ScheduleSpec{
CronExpressions: []string{check.Schedule},
Jitter: time.Second * 10,
},
Action: &client.ScheduleWorkflowAction{
ID: getScheduleId(check.Id),
Workflow: workflows.NewWorkflows(nil).CheckWorkflowDefinition,
Args: args,
TaskQueue: "default",
RetryPolicy: &temporal.RetryPolicy{
MaximumAttempts: 3,
},
},
}
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(check.Id))
// If exists, we update
_, err := schedule.Describe(ctx)
if err == nil {
err = schedule.Update(ctx, client.ScheduleUpdateOptions{
DoUpdate: func(input client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
return &client.ScheduleUpdate{
Schedule: &client.Schedule{
Spec: &options.Spec,
Action: options.Action,
Policy: input.Description.Schedule.Policy,
State: input.Description.Schedule.State,
},
}, nil
},
})
if err != nil {
return err
}
} else {
schedule, err = t.ScheduleClient().Create(ctx, options)
if err != nil {
return err
}
}
err = schedule.Trigger(ctx, client.ScheduleTriggerOptions{})
if err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,67 @@
package services
import (
"context"
"code.tjo.space/mentos1386/zdravko/database/models"
"github.com/jmoiron/sqlx"
)
type CheckHistoryWithCheck struct {
*models.CheckHistory
CheckName string `db:"check_name"`
CheckId string `db:"check_id"`
}
func GetLastNCheckHistory(ctx context.Context, db *sqlx.DB, n int) ([]*CheckHistoryWithCheck, error) {
var checkHistory []*CheckHistoryWithCheck
err := db.SelectContext(ctx, &checkHistory, `
SELECT
mh.*,
wg.name AS worker_group_name,
m.name AS check_name,
m.id AS check_id
FROM check_histories mh
LEFT JOIN worker_groups wg ON mh.worker_group_id = wg.id
LEFT JOIN check_worker_groups mwg ON mh.check_id = mwg.check_id
LEFT JOIN checks m ON mwg.check_id = m.id
ORDER BY mh.created_at DESC
LIMIT $1
`, n)
return checkHistory, err
}
func GetCheckHistoryForCheck(ctx context.Context, db *sqlx.DB, checkId string) ([]*models.CheckHistory, error) {
var checkHistory []*models.CheckHistory
err := db.SelectContext(ctx, &checkHistory, `
SELECT
mh.*,
wg.name AS worker_group_name,
wg.id AS worker_group_id
FROM check_histories as mh
LEFT JOIN worker_groups wg ON mh.worker_group_id = wg.id
LEFT JOIN check_worker_groups mwg ON mh.check_id = mwg.check_id
WHERE mh.check_id = $1
ORDER BY mh.created_at DESC
`, checkId)
return checkHistory, err
}
func AddHistoryForCheck(ctx context.Context, db *sqlx.DB, history *models.CheckHistory) error {
_, err := db.NamedExecContext(ctx,
`
INSERT INTO check_histories (
check_id,
worker_group_id,
status,
note
) VALUES (
:check_id,
:worker_group_id,
:status,
:note
)`,
history,
)
return err
}

View file

@ -1,316 +0,0 @@
package services
import (
"context"
"log"
"sort"
"time"
"code.tjo.space/mentos1386/zdravko/database/models"
"code.tjo.space/mentos1386/zdravko/internal/workflows"
"github.com/jmoiron/sqlx"
"go.temporal.io/sdk/client"
"go.temporal.io/sdk/temporal"
"golang.org/x/exp/maps"
)
type MonitorStatus string
const (
MonitorStatusUnknown MonitorStatus = "UNKNOWN"
MonitorStatusPaused MonitorStatus = "PAUSED"
MonitorStatusActive MonitorStatus = "ACTIVE"
)
func getScheduleId(id string) string {
return "monitor-" + id
}
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 {
return MonitorStatusUnknown, err
}
if description.Schedule.State.Paused {
return MonitorStatusPaused, nil
}
return MonitorStatusActive, nil
}
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"})
}
if status == MonitorStatusPaused {
return schedule.Pause(ctx, client.SchedulePauseOptions{Note: "Paused by user"})
}
return nil
}
func CreateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error {
_, err := db.NamedExecContext(ctx,
`INSERT INTO monitors (id, name, "group", script, schedule) VALUES (:id, :name, :group, :script, :schedule)`,
monitor,
)
return err
}
func UpdateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error {
_, err := db.NamedExecContext(ctx,
`UPDATE monitors SET "group"=:group, script=:script, schedule=:schedule WHERE id=:id`,
monitor,
)
return err
}
func DeleteMonitor(ctx context.Context, db *sqlx.DB, id string) error {
_, err := db.ExecContext(ctx,
"DELETE FROM monitors WHERE id=$1",
id,
)
return err
}
func UpdateMonitorWorkerGroups(ctx context.Context, db *sqlx.DB, monitor *models.Monitor, workerGroups []*models.WorkerGroup) error {
tx, err := db.BeginTxx(ctx, nil)
if err != nil {
return err
}
_, err = tx.ExecContext(ctx,
"DELETE FROM monitor_worker_groups WHERE monitor_id=$1",
monitor.Id,
)
if err != nil {
tx.Rollback()
return err
}
for _, group := range workerGroups {
_, err = tx.ExecContext(ctx,
"INSERT INTO monitor_worker_groups (monitor_id, worker_group_id) VALUES ($1, $2)",
monitor.Id,
group.Id,
)
if err != nil {
tx.Rollback()
return err
}
}
return tx.Commit()
}
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 id=$1",
id,
)
return monitor, err
}
func GetMonitorWithWorkerGroups(ctx context.Context, db *sqlx.DB, id string) (*models.MonitorWithWorkerGroups, error) {
rows, err := db.QueryContext(ctx,
`
SELECT
monitors.id,
monitors.name,
monitors."group",
monitors.script,
monitors.schedule,
monitors.created_at,
monitors.updated_at,
worker_groups.name as worker_group_name
FROM monitors
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
ORDER BY monitors.name
`,
id,
)
if err != nil {
return nil, err
}
defer rows.Close()
monitor := &models.MonitorWithWorkerGroups{}
for rows.Next() {
var workerGroupName *string
err = rows.Scan(
&monitor.Id,
&monitor.Name,
&monitor.Group,
&monitor.Script,
&monitor.Schedule,
&monitor.CreatedAt,
&monitor.UpdatedAt,
&workerGroupName,
)
if err != nil {
return nil, err
}
if workerGroupName != nil {
monitor.WorkerGroups = append(monitor.WorkerGroups, *workerGroupName)
}
}
return monitor, err
}
func GetMonitors(ctx context.Context, db *sqlx.DB) ([]*models.Monitor, error) {
monitors := []*models.Monitor{}
err := db.SelectContext(ctx, &monitors,
"SELECT * FROM monitors ORDER BY name",
)
return monitors, err
}
func GetMonitorsWithWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.MonitorWithWorkerGroups, error) {
rows, err := db.QueryContext(ctx,
`
SELECT
monitors.id,
monitors.name,
monitors."group",
monitors.script,
monitors.schedule,
monitors.created_at,
monitors.updated_at,
worker_groups.name as worker_group_name
FROM monitors
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 {
return nil, err
}
defer rows.Close()
monitors := map[string]*models.MonitorWithWorkerGroups{}
for rows.Next() {
monitor := &models.MonitorWithWorkerGroups{}
var workerGroupName *string
err = rows.Scan(
&monitor.Id,
&monitor.Name,
&monitor.Group,
&monitor.Script,
&monitor.Schedule,
&monitor.CreatedAt,
&monitor.UpdatedAt,
&workerGroupName,
)
if err != nil {
return nil, err
}
if workerGroupName != nil {
workerGroups := []string{}
if monitors[monitor.Id] != nil {
workerGroups = monitors[monitor.Id].WorkerGroups
}
monitor.WorkerGroups = append(workerGroups, *workerGroupName)
}
monitors[monitor.Id] = monitor
}
monitorsWithWorkerGroups := maps.Values(monitors)
sort.SliceStable(monitorsWithWorkerGroups, func(i, j int) bool {
return monitorsWithWorkerGroups[i].Name < monitorsWithWorkerGroups[j].Name
})
return monitorsWithWorkerGroups, err
}
func DeleteMonitorSchedule(ctx context.Context, t client.Client, id string) error {
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(id))
return schedule.Delete(ctx)
}
func CreateOrUpdateMonitorSchedule(
ctx context.Context,
t client.Client,
monitor *models.Monitor,
workerGroups []*models.WorkerGroup,
) error {
log.Println("Creating or Updating Monitor Schedule")
workerGroupStrings := make([]string, len(workerGroups))
for i, group := range workerGroups {
workerGroupStrings[i] = group.Id
}
args := make([]interface{}, 1)
args[0] = workflows.MonitorWorkflowParam{
Script: monitor.Script,
MonitorId: monitor.Id,
WorkerGroupIds: workerGroupStrings,
}
options := client.ScheduleOptions{
ID: getScheduleId(monitor.Id),
Spec: client.ScheduleSpec{
CronExpressions: []string{monitor.Schedule},
Jitter: time.Second * 10,
},
Action: &client.ScheduleWorkflowAction{
ID: getScheduleId(monitor.Id),
Workflow: workflows.NewWorkflows(nil).MonitorWorkflowDefinition,
Args: args,
TaskQueue: "default",
RetryPolicy: &temporal.RetryPolicy{
MaximumAttempts: 3,
},
},
}
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor.Id))
// If exists, we update
_, err := schedule.Describe(ctx)
if err == nil {
err = schedule.Update(ctx, client.ScheduleUpdateOptions{
DoUpdate: func(input client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
return &client.ScheduleUpdate{
Schedule: &client.Schedule{
Spec: &options.Spec,
Action: options.Action,
Policy: input.Description.Schedule.Policy,
State: input.Description.Schedule.State,
},
}, nil
},
})
if err != nil {
return err
}
} else {
schedule, err = t.ScheduleClient().Create(ctx, options)
if err != nil {
return err
}
}
err = schedule.Trigger(ctx, client.ScheduleTriggerOptions{})
if err != nil {
return err
}
return nil
}

View file

@ -1,67 +0,0 @@
package services
import (
"context"
"code.tjo.space/mentos1386/zdravko/database/models"
"github.com/jmoiron/sqlx"
)
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
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_id,
worker_group_id,
status,
note
) VALUES (
:monitor_id,
:worker_group_id,
:status,
:note
)`,
history,
)
return err
}

View file

@ -54,7 +54,7 @@ func GetWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.WorkerGroup, e
return workerGroups, err return workerGroups, err
} }
func GetWorkerGroupsWithMonitors(ctx context.Context, db *sqlx.DB) ([]*models.WorkerGroupWithMonitors, error) { func GetWorkerGroupsWithChecks(ctx context.Context, db *sqlx.DB) ([]*models.WorkerGroupWithChecks, error) {
rows, err := db.QueryContext(ctx, rows, err := db.QueryContext(ctx,
` `
SELECT SELECT
@ -62,10 +62,10 @@ SELECT
worker_groups.name, worker_groups.name,
worker_groups.created_at, worker_groups.created_at,
worker_groups.updated_at, worker_groups.updated_at,
monitors.name as monitor_name checks.name as check_name
FROM worker_groups FROM worker_groups
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.id = monitor_worker_groups.worker_group_id LEFT OUTER JOIN check_worker_groups ON worker_groups.id = check_worker_groups.worker_group_id
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_id = monitors.id LEFT OUTER JOIN checks ON check_worker_groups.check_id = checks.id
ORDER BY worker_groups.name ORDER BY worker_groups.name
`) `)
if err != nil { if err != nil {
@ -73,29 +73,29 @@ ORDER BY worker_groups.name
} }
defer rows.Close() defer rows.Close()
workerGroups := map[string]*models.WorkerGroupWithMonitors{} workerGroups := map[string]*models.WorkerGroupWithChecks{}
for rows.Next() { for rows.Next() {
workerGroup := &models.WorkerGroupWithMonitors{} workerGroup := &models.WorkerGroupWithChecks{}
var monitorName *string var checkName *string
err = rows.Scan( err = rows.Scan(
&workerGroup.Id, &workerGroup.Id,
&workerGroup.Name, &workerGroup.Name,
&workerGroup.CreatedAt, &workerGroup.CreatedAt,
&workerGroup.UpdatedAt, &workerGroup.UpdatedAt,
&monitorName, &checkName,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
if monitorName != nil { if checkName != nil {
monitors := []string{} checks := []string{}
if workerGroups[workerGroup.Id] != nil { if workerGroups[workerGroup.Id] != nil {
monitors = workerGroups[workerGroup.Id].Monitors checks = workerGroups[workerGroup.Id].Checks
} }
workerGroup.Monitors = append(monitors, *monitorName) workerGroup.Checks = append(checks, *checkName)
} }
workerGroups[workerGroup.Id] = workerGroup workerGroups[workerGroup.Id] = workerGroup
@ -122,7 +122,7 @@ func GetWorkerGroup(ctx context.Context, db *sqlx.DB, id string) (*models.Worker
return &workerGroup, err return &workerGroup, err
} }
func GetWorkerGroupWithMonitors(ctx context.Context, db *sqlx.DB, id string) (*models.WorkerGroupWithMonitors, error) { func GetWorkerGroupWithChecks(ctx context.Context, db *sqlx.DB, id string) (*models.WorkerGroupWithChecks, error) {
rows, err := db.QueryContext(ctx, rows, err := db.QueryContext(ctx,
` `
SELECT SELECT
@ -130,10 +130,10 @@ SELECT
worker_groups.name, worker_groups.name,
worker_groups.created_at, worker_groups.created_at,
worker_groups.updated_at, worker_groups.updated_at,
monitors.name as monitor_name checks.name as check_name
FROM worker_groups FROM worker_groups
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.id = monitor_worker_groups.worker_group_id LEFT OUTER JOIN check_worker_groups ON worker_groups.id = check_worker_groups.worker_group_id
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_id = monitors.id LEFT OUTER JOIN checks ON check_worker_groups.check_id = checks.id
WHERE worker_groups.id=$1 WHERE worker_groups.id=$1
`, `,
id, id,
@ -143,22 +143,22 @@ WHERE worker_groups.id=$1
} }
defer rows.Close() defer rows.Close()
workerGroup := &models.WorkerGroupWithMonitors{} workerGroup := &models.WorkerGroupWithChecks{}
for rows.Next() { for rows.Next() {
var monitorName *string var checkName *string
err = rows.Scan( err = rows.Scan(
&workerGroup.Id, &workerGroup.Id,
&workerGroup.Name, &workerGroup.Name,
&workerGroup.CreatedAt, &workerGroup.CreatedAt,
&workerGroup.UpdatedAt, &workerGroup.UpdatedAt,
&monitorName, &checkName,
) )
if err != nil { if err != nil {
return nil, err return nil, err
} }
if monitorName != nil { if checkName != nil {
workerGroup.Monitors = append(workerGroup.Monitors, *monitorName) workerGroup.Checks = append(workerGroup.Checks, *checkName)
} }
} }

View file

@ -0,0 +1,58 @@
package workflows
import (
"sort"
"time"
"code.tjo.space/mentos1386/zdravko/database/models"
"code.tjo.space/mentos1386/zdravko/internal/activities"
"go.temporal.io/sdk/workflow"
)
type CheckWorkflowParam struct {
Script string
CheckId string
WorkerGroupIds []string
}
func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param CheckWorkflowParam) (models.CheckStatus, error) {
workerGroupIds := param.WorkerGroupIds
sort.Strings(workerGroupIds)
for _, workerGroupId := range workerGroupIds {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: workerGroupId,
})
heatlcheckParam := activities.HealtcheckParam{
Script: param.Script,
}
var checkResult *activities.CheckResult
err := workflow.ExecuteActivity(ctx, w.activities.Check, heatlcheckParam).Get(ctx, &checkResult)
if err != nil {
return models.CheckUnknown, err
}
status := models.CheckFailure
if checkResult.Success {
status = models.CheckSuccess
}
historyParam := activities.HealtcheckAddToHistoryParam{
CheckId: param.CheckId,
Status: status,
Note: checkResult.Note,
WorkerGroupId: workerGroupId,
}
var historyResult *activities.CheckAddToHistoryResult
err = workflow.ExecuteActivity(ctx, w.activities.CheckAddToHistory, historyParam).Get(ctx, &historyResult)
if err != nil {
return models.CheckUnknown, err
}
}
return models.CheckSuccess, nil
}

View file

@ -1,58 +0,0 @@
package workflows
import (
"sort"
"time"
"code.tjo.space/mentos1386/zdravko/database/models"
"code.tjo.space/mentos1386/zdravko/internal/activities"
"go.temporal.io/sdk/workflow"
)
type MonitorWorkflowParam struct {
Script string
MonitorId string
WorkerGroupIds []string
}
func (w *Workflows) MonitorWorkflowDefinition(ctx workflow.Context, param MonitorWorkflowParam) (models.MonitorStatus, error) {
workerGroupIds := param.WorkerGroupIds
sort.Strings(workerGroupIds)
for _, workerGroupId := range workerGroupIds {
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
StartToCloseTimeout: 60 * time.Second,
TaskQueue: workerGroupId,
})
heatlcheckParam := activities.HealtcheckParam{
Script: param.Script,
}
var monitorResult *activities.MonitorResult
err := workflow.ExecuteActivity(ctx, w.activities.Monitor, heatlcheckParam).Get(ctx, &monitorResult)
if err != nil {
return models.MonitorUnknown, err
}
status := models.MonitorFailure
if monitorResult.Success {
status = models.MonitorSuccess
}
historyParam := activities.HealtcheckAddToHistoryParam{
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 models.MonitorUnknown, err
}
}
return models.MonitorSuccess, nil
}

View file

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

View file

@ -72,14 +72,16 @@ func Routes(
//settings.GET("/targets/:id/disable", h.SettingsTargetsDisableGET) //settings.GET("/targets/:id/disable", h.SettingsTargetsDisableGET)
//settings.GET("/targets/:id/enable", h.SettingsTargetsEnableGET) //settings.GET("/targets/:id/enable", h.SettingsTargetsEnableGET)
settings.GET("/monitors", h.SettingsMonitorsGET) settings.GET("/incidents", h.SettingsIncidentsGET)
settings.GET("/monitors/create", h.SettingsMonitorsCreateGET)
settings.POST("/monitors/create", h.SettingsMonitorsCreatePOST) settings.GET("/checks", h.SettingsChecksGET)
settings.GET("/monitors/:id", h.SettingsMonitorsDescribeGET) settings.GET("/checks/create", h.SettingsChecksCreateGET)
settings.POST("/monitors/:id", h.SettingsMonitorsDescribePOST) settings.POST("/checks/create", h.SettingsChecksCreatePOST)
settings.GET("/monitors/:id/delete", h.SettingsMonitorsDescribeDELETE) settings.GET("/checks/:id", h.SettingsChecksDescribeGET)
settings.GET("/monitors/:id/disable", h.SettingsMonitorsDisableGET) settings.POST("/checks/:id", h.SettingsChecksDescribePOST)
settings.GET("/monitors/:id/enable", h.SettingsMonitorsEnableGET) settings.GET("/checks/:id/delete", h.SettingsChecksDescribeDELETE)
settings.GET("/checks/:id/disable", h.SettingsChecksDisableGET)
settings.GET("/checks/:id/enable", h.SettingsChecksEnableGET)
settings.GET("/notifications", h.SettingsNotificationsGET) settings.GET("/notifications", h.SettingsNotificationsGET)
@ -101,7 +103,6 @@ func Routes(
apiv1 := e.Group("/api/v1") apiv1 := e.Group("/api/v1")
apiv1.Use(h.Authenticated) apiv1.Use(h.Authenticated)
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET) apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
apiv1.POST("/monitors/:id/history", h.ApiV1MonitorsHistoryPOST)
// Error handler // Error handler
e.HTTPErrorHandler = func(err error, c echo.Context) { e.HTTPErrorHandler = func(err error, c echo.Context) {

View file

@ -20,7 +20,7 @@ func NewWorker(temporalClient client.Client, cfg *config.ServerConfig) *Worker {
workerWorkflows := workflows.NewWorkflows(workerActivities) workerWorkflows := workflows.NewWorkflows(workerActivities)
// Register Workflows // Register Workflows
w.RegisterWorkflow(workerWorkflows.MonitorWorkflowDefinition) w.RegisterWorkflow(workerWorkflows.CheckWorkflowDefinition)
return &Worker{ return &Worker{
worker: w, worker: w,

View file

@ -92,11 +92,11 @@ func (w *Worker) Start() error {
workerWorkflows := workflows.NewWorkflows(workerActivities) workerWorkflows := workflows.NewWorkflows(workerActivities)
// Register Workflows // Register Workflows
w.worker.RegisterWorkflow(workerWorkflows.MonitorWorkflowDefinition) w.worker.RegisterWorkflow(workerWorkflows.CheckWorkflowDefinition)
// Register Activities // Register Activities
w.worker.RegisterActivity(workerActivities.Monitor) w.worker.RegisterActivity(workerActivities.Check)
w.worker.RegisterActivity(workerActivities.MonitorAddToHistory) w.worker.RegisterActivity(workerActivities.CheckAddToHistory)
return w.worker.Run(worker.InterruptCh()) return w.worker.Run(worker.InterruptCh())
} }

View file

@ -51,9 +51,6 @@ code {
@apply shadow; @apply shadow;
} }
.sidebar {
@apply flex flex-row flex-wrap justify-center lg:flex-col lg:w-48 gap-2 h-fit text-sm font-medium text-gray-900;
}
.sidebar a { .sidebar a {
@apply w-full block rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:text-blue-700; @apply w-full block rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:text-blue-700;
} }

View file

@ -825,6 +825,10 @@ video {
flex-direction: column; flex-direction: column;
} }
.flex-wrap {
flex-wrap: wrap;
}
.items-center { .items-center {
align-items: center; align-items: center;
} }
@ -1361,28 +1365,6 @@ code {
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow); box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
} }
.sidebar {
display: flex;
height: -moz-fit-content;
height: fit-content;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
gap: 0.5rem;
font-size: 0.875rem;
line-height: 1.25rem;
font-weight: 500;
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity));
}
@media (min-width: 1024px) {
.sidebar {
width: 12rem;
flex-direction: column;
}
}
.sidebar a { .sidebar a {
display: block; display: block;
width: 100%; width: 100%;
@ -1856,10 +1838,22 @@ code {
margin-top: 5rem; margin-top: 5rem;
} }
.lg\:w-48 {
width: 12rem;
}
.lg\:grid-cols-\[min-content_minmax\(0\2c 1fr\)\] { .lg\:grid-cols-\[min-content_minmax\(0\2c 1fr\)\] {
grid-template-columns: min-content minmax(0,1fr); grid-template-columns: min-content minmax(0,1fr);
} }
.lg\:flex-col {
flex-direction: column;
}
.lg\:items-start {
align-items: flex-start;
}
.lg\:justify-start { .lg\:justify-start {
justify-content: flex-start; justify-content: flex-start;
} }

View file

@ -10,15 +10,17 @@
<div <div
class="md:px-4 lg:px-8 mx-auto mt-8 w-full max-w-screen-xl lg:mt-20 grid grid-cols-1 lg:grid-cols-[min-content_minmax(0,1fr)] gap-8" class="md:px-4 lg:px-8 mx-auto mt-8 w-full max-w-screen-xl lg:mt-20 grid grid-cols-1 lg:grid-cols-[min-content_minmax(0,1fr)] gap-8"
> >
<ul class="sidebar gap-4"> <ul
class="sidebar gap-2 flex flex-row flex-wrap justify-center lg:flex-col lg:w-48 h-fit text-sm font-medium text-gray-900"
>
{{ range .SettingsSidebar }} {{ range .SettingsSidebar }}
<li> <li class="flex items-center gap-1 lg:flex-col lg:items-start">
<p <p
class="mb-2 text-xs font-semibold text-gray-600 uppercase tracking-wider" class="text-xs font-semibold text-gray-600 uppercase tracking-wider"
> >
{{ .Group }} {{ .Group }}
</p> </p>
<ul> <ul class="flex flex-row flex-wrap gap-1 lg:flex-col">
{{ range .Pages }} {{ range .Pages }}
<li> <li>
<a <a
@ -40,6 +42,9 @@
{{ if eq .Title "Checks" }} {{ if eq .Title "Checks" }}
<span class="text-slate-400">(3)</span> <span class="text-slate-400">(3)</span>
{{ end }} {{ end }}
{{ if eq .Title "Hooks" }}
<span class="text-slate-400">(3)</span>
{{ end }}
{{ if eq .Title "Triggers" }} {{ if eq .Title "Triggers" }}
<span class="text-slate-400">(3)</span> <span class="text-slate-400">(3)</span>
{{ end }} {{ end }}

View file

@ -1,26 +1,26 @@
{{ define "main" }} {{ define "main" }}
<div class="container max-w-screen-md flex flex-col mt-20 gap-20"> <div class="container max-w-screen-md flex flex-col mt-20 gap-20">
{{ $length := len .Monitors }} {{ $length := len .Checks }}
{{ if eq $length 0 }} {{ if eq $length 0 }}
<section> <section>
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16"> <div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
<h1 <h1
class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl" class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl"
> >
There are no monitors yet. There are no checks yet.
</h1> </h1>
<p <p
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40" class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
> >
Create a monitor to monitor your services and get notified when they Create a check to check your services and get notified when they
are down. are down.
</p> </p>
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center"> <div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
<a <a
href="/settings/monitors/create" href="/settings/checks/create"
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300" class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
> >
Create First Monitor Create First Check
<svg class="feather ml-1 h-5 w-5 overflow-visible"> <svg class="feather ml-1 h-5 w-5 overflow-visible">
<use href="/static/icons/feather-sprite.svg#plus" /> <use href="/static/icons/feather-sprite.svg#plus" />
</svg> </svg>
@ -69,7 +69,7 @@
</p> </p>
</div> </div>
{{ end }} {{ end }}
<div class="monitors flex flex-col gap-4"> <div class="checks flex flex-col gap-4">
<div <div
class="inline-flex gap-1 justify-center md:justify-end time-range" class="inline-flex gap-1 justify-center md:justify-end time-range"
role="group" role="group"
@ -93,7 +93,7 @@
>90 Minutes</a >90 Minutes</a
> >
</div> </div>
{{ range $group, $monitorsAndStatus := .Monitors }} {{ range $group, $checksAndStatus := .Checks }}
<details <details
open open
class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:rotate-90" class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:rotate-90"
@ -101,11 +101,11 @@
<summary <summary
class="flex flex-row gap-2 p-3 py-2 -mx-3 cursor-pointer hover:bg-blue-50 rounded-lg" class="flex flex-row gap-2 p-3 py-2 -mx-3 cursor-pointer hover:bg-blue-50 rounded-lg"
> >
{{ if eq $monitorsAndStatus.Status "SUCCESS" }} {{ if eq $checksAndStatus.Status "SUCCESS" }}
<span <span
class="flex w-3 h-3 bg-green-400 rounded-full self-center" class="flex w-3 h-3 bg-green-400 rounded-full self-center"
></span> ></span>
{{ else if eq $monitorsAndStatus.Status "FAILURE" }} {{ else if eq $checksAndStatus.Status "FAILURE" }}
<span <span
class="flex w-3 h-3 bg-red-400 rounded-full self-center" class="flex w-3 h-3 bg-red-400 rounded-full self-center"
></span> ></span>
@ -123,7 +123,7 @@
<use href="/static/icons/feather-sprite.svg#chevron-right" /> <use href="/static/icons/feather-sprite.svg#chevron-right" />
</svg> </svg>
</summary> </summary>
{{ range $monitorsAndStatus.Monitors }} {{ range $checksAndStatus.Checks }}
<div <div
class="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-2 pb-2 border-b last-of-type:pb-0 last-of-type:border-0 border-gray-100" class="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-2 pb-2 border-b last-of-type:pb-0 last-of-type:border-0 border-gray-100"
> >

View file

@ -1,13 +1,13 @@
{{ define "settings" }} {{ define "settings" }}
{{ $description := "Monitors are constantly determining if targets are healthy or not." }} {{ $description := "Checks are constantly determining if targets are healthy or not." }}
{{ $length := len .Monitors }} {{ $length := len .Checks }}
{{ if eq $length 0 }} {{ if eq $length 0 }}
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16"> <div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
<h1 <h1
class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl" class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl"
> >
There are no monitors yet. There are no checks yet.
</h1> </h1>
<p <p
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40" class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
@ -16,10 +16,10 @@
</p> </p>
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center"> <div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
<a <a
href="/settings/monitors/create" href="/settings/checks/create"
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300" class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
> >
Create First Monitor Create First Check
<svg class="feather ml-1 h-5 w-5 overflow-visible"> <svg class="feather ml-1 h-5 w-5 overflow-visible">
<use href="/static/icons/feather-sprite.svg#plus" /> <use href="/static/icons/feather-sprite.svg#plus" />
</svg> </svg>
@ -30,13 +30,13 @@
<section> <section>
<table> <table>
<caption> <caption>
List of Monitors List of Checks
<div class="mt-1 gap-4 grid grid-cols-1 md:grid-cols-[1fr,20%]"> <div class="mt-1 gap-4 grid grid-cols-1 md:grid-cols-[1fr,20%]">
<p> <p>
{{ $description }} {{ $description }}
</p> </p>
<a <a
href="/settings/monitors/create" href="/settings/checks/create"
class="h-min inline-flex justify-center items-center py-2 px-4 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300" class="h-min inline-flex justify-center items-center py-2 px-4 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
> >
Create New Create New
@ -48,7 +48,7 @@
</caption> </caption>
<thead class="text-xs text-gray-700 uppercase bg-gray-50"> <thead class="text-xs text-gray-700 uppercase bg-gray-50">
<tr> <tr>
<th scope="col">Monitor Group</th> <th scope="col">Check Group</th>
<th scope="col">Name</th> <th scope="col">Name</th>
<th scope="col">Visibility</th> <th scope="col">Visibility</th>
<th scope="col">Worker Groups</th> <th scope="col">Worker Groups</th>
@ -58,7 +58,7 @@
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{{ range .MonitorGroups }} {{ range .CheckGroups }}
{{ $currentGroup := . }} {{ $currentGroup := . }}
<tr class="row-special"> <tr class="row-special">
<th scope="row"> <th scope="row">
@ -71,9 +71,9 @@
<td></td> <td></td>
<td></td> <td></td>
</tr> </tr>
{{ range $group, $monitors := $.Monitors }} {{ range $group, $checks := $.Checks }}
{{ if eq $group $currentGroup }} {{ if eq $group $currentGroup }}
{{ range $monitors }} {{ range $checks }}
<tr> <tr>
<th scope="row">└─</th> <th scope="row">└─</th>
<th scope="row"> <th scope="row">
@ -125,7 +125,7 @@
{{ .Schedule }} {{ .Schedule }}
</td> </td>
<td> <td>
<a href="/settings/monitors/{{ .Id }}" class="link" <a href="/settings/checks/{{ .Id }}" class="link"
>Details</a >Details</a
> >
</td> </td>

View file

@ -1,10 +1,10 @@
{{ define "settings" }} {{ define "settings" }}
<section class="p-5"> <section class="p-5">
<form action="/settings/monitors/create" method="post"> <form action="/settings/checks/create" method="post">
<label for="name">Name</label> <label for="name">Name</label>
<input type="text" name="name" id="name" placeholder="Github.com" /> <input type="text" name="name" id="name" placeholder="Github.com" />
<p>Name of the monitor can be anything.</p> <p>Name of the check can be anything.</p>
<label list="existing-groups" for="group">Monitor Group</label> <label list="existing-groups" for="group">Check Group</label>
<input <input
type="text" type="text"
name="group" name="group"
@ -17,7 +17,7 @@
<option value="default"></option> <option value="default"></option>
</datalist> </datalist>
<p> <p>
Group monitors together. This affects how they are presented on the Group checks together. This affects how they are presented on the
homepage. homepage.
</p> </p>
<label for="workergroups">Worker Groups</label> <label for="workergroups">Worker Groups</label>
@ -29,7 +29,7 @@
required required
/> />
<p> <p>
Worker groups are used to distribute the monitor to specific workers. Worker groups are used to distribute the check to specific workers.
</p> </p>
<label for="schedule">Schedule</label> <label for="schedule">Schedule</label>
<input <input
@ -41,7 +41,7 @@
required required
/> />
<p> <p>
Schedule is a cron expression that defines when the monitor should be Schedule is a cron expression that defines when the check should be
executed. executed.
<br /> <br />
You can also use <code>@every [interval]</code> where interval is a You can also use <code>@every [interval]</code> where interval is a

View file

@ -1,17 +1,17 @@
{{ define "settings" }} {{ define "settings" }}
<section class="p-5"> <section class="p-5">
<form action="/settings/monitors/{{ .Monitor.Id }}" method="post"> <form action="/settings/checks/{{ .Check.Id }}" method="post">
<h2>Configuration</h2> <h2>Configuration</h2>
<label for="group">Monitor Group</label> <label for="group">Check Group</label>
<input <input
type="text" type="text"
name="group" name="group"
id="group" id="group"
value="{{ .Monitor.Group }}" value="{{ .Check.Group }}"
required required
/> />
<p> <p>
Group monitors together. This affects how they are presented on the Group checks together. This affects how they are presented on the
homepage. homepage.
</p> </p>
<label for="workergroups">Worker Groups</label> <label for="workergroups">Worker Groups</label>
@ -19,22 +19,22 @@
type="text" type="text"
name="workergroups" name="workergroups"
id="workergroups" id="workergroups"
value="{{ range .Monitor.WorkerGroups }}{{ . }}{{ end }}" value="{{ range .Check.WorkerGroups }}{{ . }}{{ end }}"
required required
/> />
<p> <p>
Worker groups are used to distribute the monitor to specific workers. Worker groups are used to distribute the check to specific workers.
</p> </p>
<label for="schedule">Schedule</label> <label for="schedule">Schedule</label>
<input <input
type="text" type="text"
name="schedule" name="schedule"
id="schedule" id="schedule"
value="{{ .Monitor.Schedule }}" value="{{ .Check.Schedule }}"
required required
/> />
<p> <p>
Schedule is a cron expression that defines when the monitor should be Schedule is a cron expression that defines when the check should be
executed. executed.
<br /> <br />
You can also use <code>@every [interval]</code> where interval is a You can also use <code>@every [interval]</code> where interval is a
@ -44,7 +44,7 @@
</p> </p>
<label for="script">Script</label> <label for="script">Script</label>
<textarea required id="script" name="script" class="h-96"> <textarea required id="script" name="script" class="h-96">
{{ ScriptUnescapeString .Monitor.Script }}</textarea {{ ScriptUnescapeString .Check.Script }}</textarea
> >
<div <div
id="editor" id="editor"
@ -65,13 +65,13 @@
<section class="p-5 flex-1"> <section class="p-5 flex-1">
<h2 class="mb-2 flex flex-row gap-2"> <h2 class="mb-2 flex flex-row gap-2">
Status Status
{{ if eq .Monitor.Status "ACTIVE" }} {{ if eq .Check.Status "ACTIVE" }}
<span <span
class="self-center h-fit w-fit px-2 text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800" class="self-center h-fit w-fit px-2 text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
> >
ACTIVE ACTIVE
</span> </span>
{{ else if eq .Monitor.Status "PAUSED" }} {{ else if eq .Check.Status "PAUSED" }}
<span <span
class="self-center h-fit w-fit px-2 text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800" class="self-center h-fit w-fit px-2 text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800"
> >
@ -80,19 +80,19 @@
{{ end }} {{ end }}
</h2> </h2>
<p class="text-sm mb-2"> <p class="text-sm mb-2">
Pausing the monitor will stop it from executing. This can be useful in Pausing the check will stop it from executing. This can be useful in
cases of expected downtime. Or when the monitor is not needed anymore. cases of expected downtime. Or when the check is not needed anymore.
</p> </p>
{{ if eq .Monitor.Status "ACTIVE" }} {{ if eq .Check.Status "ACTIVE" }}
<a <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" 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.Id }}/disable" href="/settings/checks/{{ .Check.Id }}/disable"
>Pause</a >Pause</a
> >
{{ else if eq .Monitor.Status "PAUSED" }} {{ else if eq .Check.Status "PAUSED" }}
<a <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" 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.Id }}/enable" href="/settings/checks/{{ .Check.Id }}/enable"
>Resume</a >Resume</a
> >
{{ end }} {{ end }}
@ -100,10 +100,10 @@
<section class="p-2 flex-1 border-4 border-red-300"> <section class="p-2 flex-1 border-4 border-red-300">
<h2 class="mb-2">Danger Zone</h2> <h2 class="mb-2">Danger Zone</h2>
<p class="text-sm mb-2">Permanently delete this monitor.</p> <p class="text-sm mb-2">Permanently delete this check.</p>
<a <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" 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.Id }}/delete" href="/settings/checks/{{ .Check.Id }}/delete"
>Delete</a >Delete</a
> >
</section> </section>
@ -113,7 +113,7 @@
<table> <table>
<caption> <caption>
History History
<p>Last 10 executions of monitor script.</p> <p>Last 10 executions of check script.</p>
</caption> </caption>
<thead> <thead>
<tr> <tr>
@ -172,7 +172,7 @@
var doc = new DOMParser().parseFromString(input, "text/html"); var doc = new DOMParser().parseFromString(input, "text/html");
return doc.documentElement.textContent; return doc.documentElement.textContent;
} }
script = htmlDecode("{{ .Monitor.Script }}") script = htmlDecode("{{ .Check.Script }}")
require.config({ paths: { vs: '/static/monaco/vs' } }); require.config({ paths: { vs: '/static/monaco/vs' } });
require(['vs/editor/editor.main'], function () { require(['vs/editor/editor.main'], function () {

View file

@ -0,0 +1,84 @@
{{ define "settings" }}
{{ $description := "Incidents represent an event where some services were unavailable. Incidents can either be created automatically via the Triggers or manually. Incidents can be resolved manually or via triggers as well, once services are healthy again." }}
{{ $length := len .Incidents }}
{{ if eq $length 0 }}
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
<h1
class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl"
>
There are no incidents yet.
</h1>
<p
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
>
{{ $description }}
</p>
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
<a
href="/settings/incidents/create"
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
>
Add Manual Incident
<svg class="feather ml-1 h-5 w-5 overflow-visible">
<use href="/static/icons/feather-sprite.svg#plus" />
</svg>
</a>
<a
href="/settings/triggers"
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
>
Go to Triggers
</a>
</div>
</div>
{{ else }}
<section>
<table>
<caption>
List of Incidents
<div class="mt-1 gap-4 grid grid-cols-1 md:grid-cols-[1fr,20%]">
<p>
{{ $description }}
</p>
<a
href="/settings/incidents/create"
class="h-min inline-flex justify-center items-center py-2 px-4 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
>
Create New
<svg class="feather h-5 w-5 overflow-visible">
<use href="/static/icons/feather-sprite.svg#plus" />
</svg>
</a>
</div>
</caption>
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th>Action</th>
</tr>
</thead>
{{ range .Incidents }}
<tbody>
<tr>
<th scope="row">
{{ .Name }}
</th>
<td>
<span
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
>
{{ .Type }}
</span>
</td>
<td>
<a href="/settings/incidents/{{ .Id }}" class="link">Details</a>
</td>
</tr>
</tbody>
{{ end }}
</table>
</section>
{{ end }}
{{ end }}

View file

@ -7,7 +7,7 @@
</h1> </h1>
<p class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 md:px-40"> <p class="mb-8 text-l font-normal text-gray-500 lg:text-l sm:px-8 md:px-40">
Welcome to the settings page. Here you can manage your worker groups, Welcome to the settings page. Here you can manage your worker groups,
monitors, and notifications. checks, and notifications.
</p> </p>
</div> </div>
@ -16,15 +16,15 @@
class="inline-block bg-white rounded-lg shadow p-5 text-center 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 Targets</h3> <h3 class="text-sm leading-6 font-medium text-gray-400">Total Targets</h3>
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p> <p class="text-3xl font-bold text-black">{{ .ChecksCount }}</p>
</div> </div>
<div <div
class="inline-block bg-white rounded-lg shadow p-5 text-center 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"> <h3 class="text-sm leading-6 font-medium text-gray-400">
Total Monitors Total Checks
</h3> </h3>
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p> <p class="text-3xl font-bold text-black">{{ .ChecksCount }}</p>
</div> </div>
<div <div
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left" class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
@ -48,11 +48,11 @@
<table> <table>
<caption> <caption>
Execution History Execution History
<p>Last 10 executions for all monitors and worker groups.</p> <p>Last 10 executions for all checks and worker groups.</p>
</caption> </caption>
<thead> <thead>
<tr> <tr>
<th>Monitor</th> <th>Check</th>
<th>Worker Group</th> <th>Worker Group</th>
<th>Status</th> <th>Status</th>
<th>Executed At</th> <th>Executed At</th>
@ -65,8 +65,8 @@
<th> <th>
<a <a
class="underline hover:text-blue-600" class="underline hover:text-blue-600"
href="/settings/monitors/{{ .MonitorId }}" href="/settings/checks/{{ .CheckId }}"
>{{ .MonitorName }}</a >{{ .CheckName }}</a
> >
</th> </th>
<td> <td>

View file

@ -1,5 +1,5 @@
{{ define "settings" }} {{ define "settings" }}
{{ $description := "Triggers process monitor outcomes and determine if an incident should be created, updated or closed." }} {{ $description := "Triggers process check outcomes and determine if an incident should be created, updated or closed." }}
{{ $length := len .Triggers }} {{ $length := len .Triggers }}
{{ if eq $length 0 }} {{ if eq $length 0 }}

View file

@ -50,7 +50,7 @@
<tr> <tr>
<th>Name</th> <th>Name</th>
<th>Workers</th> <th>Workers</th>
<th>Monitors</th> <th>Checks</th>
<th>Action</th> <th>Action</th>
</tr> </tr>
</thead> </thead>
@ -76,7 +76,7 @@
{{ end }} {{ end }}
</td> </td>
<td> <td>
{{ len .Monitors }} {{ len .Checks }}
</td> </td>
<td> <td>
<a href="/settings/worker-groups/{{ .Id }}" class="link" <a href="/settings/worker-groups/{{ .Id }}" class="link"

View file

@ -51,13 +51,14 @@ func NewTemplates() *Templates {
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"), "settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"), "settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
"settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"), "settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"),
"settings_incidents.tmpl": loadSettings("pages/settings_incidents.tmpl"),
"settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"), "settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"),
"settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"), "settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"),
"settings_worker_groups_create.tmpl": loadSettings("pages/settings_worker_groups_create.tmpl"), "settings_worker_groups_create.tmpl": loadSettings("pages/settings_worker_groups_create.tmpl"),
"settings_worker_groups_describe.tmpl": loadSettings("pages/settings_worker_groups_describe.tmpl"), "settings_worker_groups_describe.tmpl": loadSettings("pages/settings_worker_groups_describe.tmpl"),
"settings_monitors.tmpl": loadSettings("pages/settings_monitors.tmpl"), "settings_checks.tmpl": loadSettings("pages/settings_checks.tmpl"),
"settings_monitors_create.tmpl": loadSettings("pages/settings_monitors_create.tmpl"), "settings_checks_create.tmpl": loadSettings("pages/settings_checks_create.tmpl"),
"settings_monitors_describe.tmpl": loadSettings("pages/settings_monitors_describe.tmpl"), "settings_checks_describe.tmpl": loadSettings("pages/settings_checks_describe.tmpl"),
}, },
} }
} }