mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-21 15:26:29 +00:00
feat: renaming things and thus breaking existing database schema
This commit is contained in:
parent
4603f7a79a
commit
1afde077a6
39 changed files with 890 additions and 786 deletions
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -6,9 +6,7 @@ package.json
|
|||
node_modules/
|
||||
|
||||
# Database
|
||||
zdravko.db*
|
||||
zdravko_kv.db*
|
||||
temporal.db*
|
||||
store/
|
||||
|
||||
# Keys
|
||||
*.pem
|
||||
|
|
|
@ -45,16 +45,16 @@ type OAuth2State struct {
|
|||
ExpiresAt *Time `db:"expires_at"`
|
||||
}
|
||||
|
||||
type MonitorStatus string
|
||||
type CheckStatus string
|
||||
|
||||
const (
|
||||
MonitorSuccess MonitorStatus = "SUCCESS"
|
||||
MonitorFailure MonitorStatus = "FAILURE"
|
||||
MonitorError MonitorStatus = "ERROR"
|
||||
MonitorUnknown MonitorStatus = "UNKNOWN"
|
||||
CheckSuccess CheckStatus = "SUCCESS"
|
||||
CheckFailure CheckStatus = "FAILURE"
|
||||
CheckError CheckStatus = "ERROR"
|
||||
CheckUnknown CheckStatus = "UNKNOWN"
|
||||
)
|
||||
|
||||
type Monitor struct {
|
||||
type Check struct {
|
||||
CreatedAt *Time `db:"created_at"`
|
||||
UpdatedAt *Time `db:"updated_at"`
|
||||
|
||||
|
@ -66,18 +66,18 @@ type Monitor struct {
|
|||
Script string `db:"script"`
|
||||
}
|
||||
|
||||
type MonitorWithWorkerGroups struct {
|
||||
Monitor
|
||||
type CheckWithWorkerGroups struct {
|
||||
Check
|
||||
|
||||
// List of worker group names
|
||||
WorkerGroups []string
|
||||
}
|
||||
|
||||
type MonitorHistory struct {
|
||||
type CheckHistory struct {
|
||||
CreatedAt *Time `db:"created_at"`
|
||||
|
||||
MonitorId string `db:"monitor_id"`
|
||||
Status MonitorStatus `db:"status"`
|
||||
CheckId string `db:"check_id"`
|
||||
Status CheckStatus `db:"status"`
|
||||
Note string `db:"note"`
|
||||
|
||||
WorkerGroupId string `db:"worker_group_id"`
|
||||
|
@ -92,11 +92,11 @@ type WorkerGroup struct {
|
|||
Name string `db:"name"`
|
||||
}
|
||||
|
||||
type WorkerGroupWithMonitors struct {
|
||||
type WorkerGroupWithChecks struct {
|
||||
WorkerGroup
|
||||
|
||||
// List of worker group names
|
||||
Monitors []string
|
||||
Checks []string
|
||||
}
|
||||
|
||||
type TriggerStatus string
|
||||
|
|
|
@ -6,7 +6,7 @@ CREATE TABLE oauth2_states (
|
|||
PRIMARY KEY (state)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE monitors (
|
||||
CREATE TABLE checks (
|
||||
id TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
"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')),
|
||||
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT unique_monitors_name UNIQUE (name)
|
||||
CONSTRAINT unique_checks_name UNIQUE (name)
|
||||
) STRICT;
|
||||
|
||||
|
||||
--CREATE TRIGGER monitors_updated_timestamp AFTER UPDATE ON monitors BEGIN
|
||||
-- update monitors set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id;
|
||||
--CREATE TRIGGER checks_updated_timestamp AFTER UPDATE ON checks BEGIN
|
||||
-- update checks set updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') where id = new.id;
|
||||
--END;
|
||||
|
||||
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;
|
||||
--END;
|
||||
|
||||
CREATE TABLE monitor_worker_groups (
|
||||
CREATE TABLE check_worker_groups (
|
||||
worker_group_id TEXT NOT NULL,
|
||||
monitor_id TEXT NOT NULL,
|
||||
check_id TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY (worker_group_id,monitor_id),
|
||||
CONSTRAINT fk_monitor_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
|
||||
PRIMARY KEY (worker_group_id,check_id),
|
||||
CONSTRAINT fk_check_worker_groups_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_check_worker_groups_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE monitor_histories (
|
||||
monitor_id TEXT NOT NULL,
|
||||
CREATE TABLE check_histories (
|
||||
check_id TEXT NOT NULL,
|
||||
worker_group_id 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')),
|
||||
|
||||
PRIMARY KEY (monitor_id, worker_group_id, created_at),
|
||||
CONSTRAINT fk_monitor_histories_monitor FOREIGN KEY (monitor_id) REFERENCES monitors(id) ON DELETE CASCADE,
|
||||
CONSTRAINT fk_monitor_histories_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE
|
||||
PRIMARY KEY (check_id, worker_group_id, created_at),
|
||||
CONSTRAINT fk_check_histories_check FOREIGN KEY (check_id) REFERENCES checks(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;
|
||||
|
||||
-- +migrate Down
|
||||
DROP TABLE oauth2_states;
|
||||
DROP TABLE monitor_worker_groups;
|
||||
DROP TABLE check_worker_groups;
|
||||
DROP TABLE worker_groups;
|
||||
DROP TABLE monitor_histories;
|
||||
DROP TABLE monitors;
|
||||
DROP TABLE check_histories;
|
||||
DROP TABLE checks;
|
||||
DROP TABLE triggers;
|
||||
DROP TABLE trigger_histories;
|
||||
|
|
|
@ -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;
|
|
@ -17,9 +17,9 @@ primary_region = 'waw'
|
|||
ROOT_URL = 'https://zdravko.mnts.dev'
|
||||
TEMPORAL_SERVER_HOST = 'server.process.zdravko.internal:7233'
|
||||
|
||||
TEMPORAL_DATABASE_PATH = '/data/temporal-9.db'
|
||||
SQLITE_DATABASE_PATH = '/data/zdravko-9.db'
|
||||
KEYVALUE_DATABASE_PATH = '/data/zdravko_kv.db'
|
||||
TEMPORAL_DATABASE_PATH = '/data/temporal-10.db'
|
||||
SQLITE_DATABASE_PATH = '/data/zdravko-10.db'
|
||||
KEYVALUE_DATABASE_PATH = '/data/zdravko_kv-10.db'
|
||||
|
||||
[processes]
|
||||
server = '--temporal --server'
|
||||
|
|
|
@ -18,12 +18,12 @@ type HealtcheckParam struct {
|
|||
Script string
|
||||
}
|
||||
|
||||
type MonitorResult struct {
|
||||
type CheckResult struct {
|
||||
Success bool
|
||||
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))
|
||||
|
||||
result, err := execution.Run(ctx)
|
||||
|
@ -31,23 +31,23 @@ func (a *Activities) Monitor(ctx context.Context, param HealtcheckParam) (*Monit
|
|||
return nil, err
|
||||
}
|
||||
|
||||
return &MonitorResult{Success: result.Success, Note: result.Note}, nil
|
||||
return &CheckResult{Success: result.Success, Note: result.Note}, nil
|
||||
}
|
||||
|
||||
type HealtcheckAddToHistoryParam struct {
|
||||
MonitorId string
|
||||
Status models.MonitorStatus
|
||||
CheckId string
|
||||
Status models.CheckStatus
|
||||
Note string
|
||||
WorkerGroupId string
|
||||
}
|
||||
|
||||
type MonitorAddToHistoryResult struct {
|
||||
type CheckAddToHistoryResult struct {
|
||||
}
|
||||
|
||||
func (a *Activities) MonitorAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*MonitorAddToHistoryResult, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/monitors/%s/history", a.config.ApiUrl, param.MonitorId)
|
||||
func (a *Activities) CheckAddToHistory(ctx context.Context, param HealtcheckAddToHistoryParam) (*CheckAddToHistoryResult, error) {
|
||||
url := fmt.Sprintf("%s/api/v1/checks/%s/history", a.config.ApiUrl, param.CheckId)
|
||||
|
||||
body := api.ApiV1MonitorsHistoryPOSTBody{
|
||||
body := api.ApiV1ChecksHistoryPOSTBody{
|
||||
Status: param.Status,
|
||||
Note: param.Note,
|
||||
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 &MonitorAddToHistoryResult{}, nil
|
||||
return &CheckAddToHistoryResult{}, nil
|
||||
}
|
|
@ -48,8 +48,8 @@ func NewServerConfig() *ServerConfig {
|
|||
// Set defaults
|
||||
v.SetDefault("port", GetEnvOrDefault("PORT", "8000"))
|
||||
v.SetDefault("rooturl", GetEnvOrDefault("ROOT_URL", "http://localhost:8000"))
|
||||
v.SetDefault("sqlitedatabasepath", GetEnvOrDefault("SQLITE_DATABASE_PATH", "zdravko.db"))
|
||||
v.SetDefault("keyvaluedatabasepath", GetEnvOrDefault("KEYVALUE_DATABASE_PATH", "zdravko_kv.db"))
|
||||
v.SetDefault("sqlitedatabasepath", GetEnvOrDefault("SQLITE_DATABASE_PATH", "store/zdravko.db"))
|
||||
v.SetDefault("keyvaluedatabasepath", GetEnvOrDefault("KEYVALUE_DATABASE_PATH", "store/zdravko_kv.db"))
|
||||
v.SetDefault("sessionsecret", os.Getenv("SESSION_SECRET"))
|
||||
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"))
|
||||
|
|
|
@ -23,7 +23,7 @@ func NewTemporalConfig() *TemporalConfig {
|
|||
v := newViper()
|
||||
|
||||
// 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("jwt.publickey", os.Getenv("JWT_PUBLIC_KEY"))
|
||||
|
||||
|
|
|
@ -40,26 +40,26 @@ func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
|
|||
// TODO: Can we instead get this from the Workflow outcome?
|
||||
//
|
||||
// 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()
|
||||
id := c.Param("id")
|
||||
|
||||
var body api.ApiV1MonitorsHistoryPOSTBody
|
||||
var body api.ApiV1ChecksHistoryPOSTBody
|
||||
err := (&echo.DefaultBinder{}).BindBody(c, &body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = services.GetMonitor(ctx, h.db, id)
|
||||
_, err = services.GetCheck(ctx, h.db, id)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Monitor not found")
|
||||
return echo.NewHTTPError(http.StatusNotFound, "Check not found")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.AddHistoryForMonitor(ctx, h.db, &models.MonitorHistory{
|
||||
MonitorId: id,
|
||||
err = services.AddHistoryForCheck(ctx, h.db, &models.CheckHistory{
|
||||
CheckId: id,
|
||||
WorkerGroupId: body.WorkerGroupId,
|
||||
Status: body.Status,
|
||||
Note: body.Note,
|
||||
|
|
|
@ -18,7 +18,7 @@ import (
|
|||
var examplesYaml embed.FS
|
||||
|
||||
type examples struct {
|
||||
Monitor string `yaml:"monitor"`
|
||||
Check string `yaml:"check"`
|
||||
Trigger string `yaml:"trigger"`
|
||||
}
|
||||
|
||||
|
@ -63,7 +63,7 @@ func NewBaseHandler(db *sqlx.DB, kvStore kv.KeyValueStore, temporal client.Clien
|
|||
panic(err)
|
||||
}
|
||||
|
||||
examples.Monitor = script.EscapeString(examples.Monitor)
|
||||
examples.Check = script.EscapeString(examples.Check)
|
||||
examples.Trigger = script.EscapeString(examples.Trigger)
|
||||
|
||||
return &BaseHandler{
|
||||
|
|
|
@ -13,21 +13,21 @@ import (
|
|||
|
||||
type IndexData struct {
|
||||
*components.Base
|
||||
Monitors map[string]MonitorsAndStatus
|
||||
MonitorsLength int
|
||||
Checks map[string]ChecksAndStatus
|
||||
ChecksLength int
|
||||
TimeRange string
|
||||
Status models.MonitorStatus
|
||||
Status models.CheckStatus
|
||||
}
|
||||
|
||||
type Monitor struct {
|
||||
type Check struct {
|
||||
Name string
|
||||
Group string
|
||||
Status models.MonitorStatus
|
||||
Status models.CheckStatus
|
||||
History *History
|
||||
}
|
||||
|
||||
type HistoryItem struct {
|
||||
Status models.MonitorStatus
|
||||
Status models.CheckStatus
|
||||
Date time.Time
|
||||
}
|
||||
|
||||
|
@ -36,23 +36,23 @@ type History struct {
|
|||
Uptime int
|
||||
}
|
||||
|
||||
type MonitorsAndStatus struct {
|
||||
Status models.MonitorStatus
|
||||
Monitors []*Monitor
|
||||
type ChecksAndStatus struct {
|
||||
Status models.CheckStatus
|
||||
Checks []*Check
|
||||
}
|
||||
|
||||
func getDateString(date time.Time) string {
|
||||
return date.UTC().Format("2006-01-02T15:04:05")
|
||||
}
|
||||
|
||||
func getHistory(history []*models.MonitorHistory, period time.Duration, buckets int) *History {
|
||||
historyMap := map[string]models.MonitorStatus{}
|
||||
func getHistory(history []*models.CheckHistory, period time.Duration, buckets int) *History {
|
||||
historyMap := map[string]models.CheckStatus{}
|
||||
numOfSuccess := 0
|
||||
numTotal := 0
|
||||
|
||||
for i := 0; i < buckets; i++ {
|
||||
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
||||
historyMap[dateString] = models.MonitorUnknown
|
||||
historyMap[dateString] = models.CheckUnknown
|
||||
}
|
||||
|
||||
for _, _history := range history {
|
||||
|
@ -64,12 +64,12 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
|
|||
}
|
||||
|
||||
numTotal++
|
||||
if _history.Status == models.MonitorSuccess {
|
||||
if _history.Status == models.CheckSuccess {
|
||||
numOfSuccess++
|
||||
}
|
||||
|
||||
// skip if it is already set to failure
|
||||
if historyMap[dateString] == models.MonitorFailure {
|
||||
if historyMap[dateString] == models.CheckFailure {
|
||||
continue
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ func getHistory(history []*models.MonitorHistory, period time.Duration, buckets
|
|||
|
||||
func (h *BaseHandler) Index(c echo.Context) error {
|
||||
ctx := context.Background()
|
||||
monitors, err := services.GetMonitors(ctx, h.db)
|
||||
checks, err := services.GetChecks(ctx, h.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -111,12 +111,12 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
|||
timeRange = "90days"
|
||||
}
|
||||
|
||||
overallStatus := models.MonitorUnknown
|
||||
statusByGroup := make(map[string]models.MonitorStatus)
|
||||
overallStatus := models.CheckUnknown
|
||||
statusByGroup := make(map[string]models.CheckStatus)
|
||||
|
||||
monitorsWithHistory := make([]*Monitor, len(monitors))
|
||||
for i, monitor := range monitors {
|
||||
history, err := services.GetMonitorHistoryForMonitor(ctx, h.db, monitor.Id)
|
||||
checksWithHistory := make([]*Check, len(checks))
|
||||
for i, check := range checks {
|
||||
history, err := services.GetCheckHistoryForCheck(ctx, h.db, check.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -131,37 +131,37 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
|||
historyResult = getHistory(history, time.Minute, 90)
|
||||
}
|
||||
|
||||
if statusByGroup[monitor.Group] == "" {
|
||||
statusByGroup[monitor.Group] = models.MonitorUnknown
|
||||
if statusByGroup[check.Group] == "" {
|
||||
statusByGroup[check.Group] = models.CheckUnknown
|
||||
}
|
||||
|
||||
status := historyResult.List[len(historyResult.List)-1]
|
||||
if status.Status == models.MonitorSuccess {
|
||||
if overallStatus == models.MonitorUnknown {
|
||||
if status.Status == models.CheckSuccess {
|
||||
if overallStatus == models.CheckUnknown {
|
||||
overallStatus = status.Status
|
||||
}
|
||||
if statusByGroup[monitor.Group] == models.MonitorUnknown {
|
||||
statusByGroup[monitor.Group] = status.Status
|
||||
if statusByGroup[check.Group] == models.CheckUnknown {
|
||||
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
|
||||
statusByGroup[monitor.Group] = status.Status
|
||||
statusByGroup[check.Group] = status.Status
|
||||
}
|
||||
|
||||
monitorsWithHistory[i] = &Monitor{
|
||||
Name: monitor.Name,
|
||||
Group: monitor.Group,
|
||||
checksWithHistory[i] = &Check{
|
||||
Name: check.Name,
|
||||
Group: check.Group,
|
||||
Status: status.Status,
|
||||
History: historyResult,
|
||||
}
|
||||
}
|
||||
|
||||
monitorsByGroup := map[string]MonitorsAndStatus{}
|
||||
for _, monitor := range monitorsWithHistory {
|
||||
monitorsByGroup[monitor.Group] = MonitorsAndStatus{
|
||||
Status: statusByGroup[monitor.Group],
|
||||
Monitors: append(monitorsByGroup[monitor.Group].Monitors, monitor),
|
||||
checksByGroup := map[string]ChecksAndStatus{}
|
||||
for _, check := range checksWithHistory {
|
||||
checksByGroup[check.Group] = ChecksAndStatus{
|
||||
Status: statusByGroup[check.Group],
|
||||
Checks: append(checksByGroup[check.Group].Checks, check),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,7 +172,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
|||
NavbarActive: GetPageByTitle(Pages, "Status"),
|
||||
Navbar: Pages,
|
||||
},
|
||||
Monitors: monitorsByGroup,
|
||||
Checks: checksByGroup,
|
||||
TimeRange: timeRange,
|
||||
Status: overallStatus,
|
||||
})
|
||||
|
|
|
@ -36,11 +36,13 @@ func NewSettings(user *AuthenticatedUser, page *components.Page, breadCrumbs []*
|
|||
|
||||
var SettingsPages = []*components.Page{
|
||||
{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/create", Title: "Targets Create", Breadcrumb: "Create"},
|
||||
{Path: "/settings/monitors", Title: "Checks", Breadcrumb: "Checks"},
|
||||
{Path: "/settings/monitors/create", Title: "Checks Create", Breadcrumb: "Create"},
|
||||
{Path: "/settings/hooks", Title: "Hooks", Breadcrumb: "Hooks"},
|
||||
{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/create", Title: "Worker Groups Create", Breadcrumb: "Create"},
|
||||
{Path: "/settings/notifications", Title: "Notifications", Breadcrumb: "Notifications"},
|
||||
|
@ -63,10 +65,11 @@ var SettingsSidebar = []SettingsSidebarGroup{
|
|||
Pages: []*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Targets"),
|
||||
GetPageByTitle(SettingsPages, "Checks"),
|
||||
GetPageByTitle(SettingsPages, "Hooks"),
|
||||
},
|
||||
},
|
||||
{
|
||||
Group: "Decide",
|
||||
Group: "Alert",
|
||||
Pages: []*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Triggers"),
|
||||
},
|
||||
|
@ -91,9 +94,9 @@ var SettingsSidebar = []SettingsSidebarGroup{
|
|||
type SettingsOverview struct {
|
||||
*Settings
|
||||
WorkerGroupsCount int
|
||||
MonitorsCount int
|
||||
ChecksCount int
|
||||
NotificationsCount int
|
||||
History []*services.MonitorHistoryWithMonitor
|
||||
History []*services.CheckHistoryWithCheck
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
|
||||
|
@ -105,12 +108,12 @@ func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
monitors, err := services.CountMonitors(ctx, h.db)
|
||||
checks, err := services.CountChecks(ctx, h.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
history, err := services.GetLastNMonitorHistory(ctx, h.db, 10)
|
||||
history, err := services.GetLastNCheckHistory(ctx, h.db, 10)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -122,7 +125,7 @@ func (h *BaseHandler) SettingsOverviewGET(c echo.Context) error {
|
|||
[]*components.Page{GetPageByTitle(SettingsPages, "Overview")},
|
||||
),
|
||||
WorkerGroupsCount: workerGroups,
|
||||
MonitorsCount: monitors,
|
||||
ChecksCount: checks,
|
||||
NotificationsCount: 42,
|
||||
History: history,
|
||||
})
|
||||
|
|
30
internal/handlers/settings_incidents.go
Normal file
30
internal/handlers/settings_incidents.go
Normal 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,
|
||||
})
|
||||
}
|
|
@ -14,13 +14,6 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type Incident struct{}
|
||||
|
||||
type SettingsIncidents struct {
|
||||
*Settings
|
||||
Incidents []*Incident
|
||||
}
|
||||
|
||||
type CreateTrigger struct {
|
||||
Name string `validate:"required"`
|
||||
Script string `validate:"required"`
|
||||
|
|
|
@ -17,7 +17,7 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type CreateMonitor struct {
|
||||
type CreateCheck struct {
|
||||
Name string `validate:"required"`
|
||||
Group string `validate:"required"`
|
||||
WorkerGroups string `validate:"required"`
|
||||
|
@ -25,96 +25,96 @@ type CreateMonitor struct {
|
|||
Script string `validate:"required"`
|
||||
}
|
||||
|
||||
type UpdateMonitor struct {
|
||||
type UpdateCheck struct {
|
||||
Group string `validate:"required"`
|
||||
WorkerGroups string `validate:"required"`
|
||||
Schedule string `validate:"required,cron"`
|
||||
Script string `validate:"required"`
|
||||
}
|
||||
|
||||
type MonitorWithWorkerGroupsAndStatus struct {
|
||||
*models.MonitorWithWorkerGroups
|
||||
Status services.MonitorStatus
|
||||
type CheckWithWorkerGroupsAndStatus struct {
|
||||
*models.CheckWithWorkerGroups
|
||||
Status services.CheckStatus
|
||||
}
|
||||
|
||||
type SettingsMonitors struct {
|
||||
type SettingsChecks struct {
|
||||
*Settings
|
||||
Monitors map[string][]*MonitorWithWorkerGroupsAndStatus
|
||||
MonitorGroups []string
|
||||
Checks map[string][]*CheckWithWorkerGroupsAndStatus
|
||||
CheckGroups []string
|
||||
}
|
||||
|
||||
type SettingsMonitor struct {
|
||||
type SettingsCheck struct {
|
||||
*Settings
|
||||
Monitor *MonitorWithWorkerGroupsAndStatus
|
||||
History []*models.MonitorHistory
|
||||
Check *CheckWithWorkerGroupsAndStatus
|
||||
History []*models.CheckHistory
|
||||
}
|
||||
|
||||
type SettingsMonitorCreate struct {
|
||||
type SettingsCheckCreate struct {
|
||||
*Settings
|
||||
Example string
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error {
|
||||
func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
monitors, err := services.GetMonitorsWithWorkerGroups(context.Background(), h.db)
|
||||
checks, err := services.GetChecksWithWorkerGroups(context.Background(), h.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
monitorsWithStatus := make([]*MonitorWithWorkerGroupsAndStatus, len(monitors))
|
||||
for i, monitor := range monitors {
|
||||
status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Id)
|
||||
checksWithStatus := make([]*CheckWithWorkerGroupsAndStatus, len(checks))
|
||||
for i, check := range checks {
|
||||
status, err := services.GetCheckStatus(context.Background(), h.temporal, check.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
monitorsWithStatus[i] = &MonitorWithWorkerGroupsAndStatus{
|
||||
MonitorWithWorkerGroups: monitor,
|
||||
checksWithStatus[i] = &CheckWithWorkerGroupsAndStatus{
|
||||
CheckWithWorkerGroups: check,
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
|
||||
monitorGroups := []string{}
|
||||
monitorsByGroup := map[string][]*MonitorWithWorkerGroupsAndStatus{}
|
||||
for _, monitor := range monitorsWithStatus {
|
||||
monitorsByGroup[monitor.Group] = append(monitorsByGroup[monitor.Group], monitor)
|
||||
if slices.Contains(monitorGroups, monitor.Group) == false {
|
||||
monitorGroups = append(monitorGroups, monitor.Group)
|
||||
checkGroups := []string{}
|
||||
checksByGroup := map[string][]*CheckWithWorkerGroupsAndStatus{}
|
||||
for _, check := range checksWithStatus {
|
||||
checksByGroup[check.Group] = append(checksByGroup[check.Group], check)
|
||||
if slices.Contains(checkGroups, check.Group) == false {
|
||||
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(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Checks"),
|
||||
[]*components.Page{GetPageByTitle(SettingsPages, "Checks")},
|
||||
),
|
||||
Monitors: monitorsByGroup,
|
||||
MonitorGroups: monitorGroups,
|
||||
Checks: checksByGroup,
|
||||
CheckGroups: checkGroups,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error {
|
||||
func (h *BaseHandler) SettingsChecksDescribeGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
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 {
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
monitorWithStatus := &MonitorWithWorkerGroupsAndStatus{
|
||||
MonitorWithWorkerGroups: monitor,
|
||||
checkWithStatus := &CheckWithWorkerGroupsAndStatus{
|
||||
CheckWithWorkerGroups: check,
|
||||
Status: status,
|
||||
}
|
||||
|
||||
history, err := services.GetMonitorHistoryForMonitor(context.Background(), h.db, slug)
|
||||
history, err := services.GetCheckHistoryForCheck(context.Background(), h.db, slug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -124,76 +124,76 @@ func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error {
|
|||
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(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Checks"),
|
||||
[]*components.Page{
|
||||
GetPageByTitle(SettingsPages, "Checks"),
|
||||
{
|
||||
Path: fmt.Sprintf("/settings/monitors/%s", slug),
|
||||
Path: fmt.Sprintf("/settings/checks/%s", slug),
|
||||
Title: "Describe",
|
||||
Breadcrumb: monitor.Name,
|
||||
Breadcrumb: check.Name,
|
||||
},
|
||||
}),
|
||||
Monitor: monitorWithStatus,
|
||||
Check: checkWithStatus,
|
||||
History: history[:maxElements],
|
||||
})
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsMonitorsDescribeDELETE(c echo.Context) error {
|
||||
func (h *BaseHandler) SettingsChecksDescribeDELETE(c echo.Context) error {
|
||||
slug := c.Param("id")
|
||||
|
||||
err := services.DeleteMonitor(context.Background(), h.db, slug)
|
||||
err := services.DeleteCheck(context.Background(), h.db, slug)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.DeleteMonitorSchedule(context.Background(), h.temporal, slug)
|
||||
err = services.DeleteCheckSchedule(context.Background(), h.temporal, slug)
|
||||
if err != nil {
|
||||
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")
|
||||
|
||||
monitor, err := services.GetMonitor(context.Background(), h.db, slug)
|
||||
check, err := services.GetCheck(context.Background(), h.db, slug)
|
||||
if err != nil {
|
||||
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 {
|
||||
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")
|
||||
|
||||
monitor, err := services.GetMonitor(context.Background(), h.db, slug)
|
||||
check, err := services.GetCheck(context.Background(), h.db, slug)
|
||||
if err != nil {
|
||||
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 {
|
||||
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()
|
||||
monitorId := c.Param("id")
|
||||
checkId := c.Param("id")
|
||||
|
||||
update := UpdateMonitor{
|
||||
update := UpdateCheck{
|
||||
Group: strings.ToLower(c.FormValue("group")),
|
||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||
Schedule: c.FormValue("schedule"),
|
||||
|
@ -204,18 +204,18 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
monitor, err := services.GetMonitor(ctx, h.db, monitorId)
|
||||
check, err := services.GetCheck(ctx, h.db, checkId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
monitor.Group = update.Group
|
||||
monitor.Schedule = update.Schedule
|
||||
monitor.Script = update.Script
|
||||
check.Group = update.Group
|
||||
check.Schedule = update.Schedule
|
||||
check.Script = update.Script
|
||||
|
||||
err = services.UpdateMonitor(
|
||||
err = services.UpdateCheck(
|
||||
ctx,
|
||||
h.db,
|
||||
monitor,
|
||||
check,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -241,23 +241,23 @@ func (h *BaseHandler) SettingsMonitorsDescribePOST(c echo.Context) error {
|
|||
workerGroups = append(workerGroups, workerGroup)
|
||||
}
|
||||
|
||||
err = services.UpdateMonitorWorkerGroups(ctx, h.db, monitor, workerGroups)
|
||||
err = services.UpdateCheckWorkerGroups(ctx, h.db, check, workerGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.CreateOrUpdateMonitorSchedule(ctx, h.temporal, monitor, workerGroups)
|
||||
err = services.CreateOrUpdateCheckSchedule(ctx, h.temporal, check, workerGroups)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
return c.Render(http.StatusOK, "settings_monitors_create.tmpl", &SettingsMonitorCreate{
|
||||
return c.Render(http.StatusOK, "settings_checks_create.tmpl", &SettingsCheckCreate{
|
||||
Settings: NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Checks"),
|
||||
|
@ -266,15 +266,15 @@ func (h *BaseHandler) SettingsMonitorsCreateGET(c echo.Context) error {
|
|||
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()
|
||||
monitorId := slug.Make(c.FormValue("name"))
|
||||
checkId := slug.Make(c.FormValue("name"))
|
||||
|
||||
create := CreateMonitor{
|
||||
create := CreateCheck{
|
||||
Name: c.FormValue("name"),
|
||||
Group: strings.ToLower(c.FormValue("group")),
|
||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||
|
@ -306,32 +306,32 @@ func (h *BaseHandler) SettingsMonitorsCreatePOST(c echo.Context) error {
|
|||
workerGroups = append(workerGroups, workerGroup)
|
||||
}
|
||||
|
||||
monitor := &models.Monitor{
|
||||
check := &models.Check{
|
||||
Name: create.Name,
|
||||
Group: create.Group,
|
||||
Id: monitorId,
|
||||
Id: checkId,
|
||||
Schedule: create.Schedule,
|
||||
Script: create.Script,
|
||||
}
|
||||
|
||||
err = services.CreateMonitor(
|
||||
err = services.CreateCheck(
|
||||
ctx,
|
||||
h.db,
|
||||
monitor,
|
||||
check,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.UpdateMonitorWorkerGroups(ctx, h.db, monitor, workerGroups)
|
||||
err = services.UpdateCheckWorkerGroups(ctx, h.db, check, workerGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = services.CreateOrUpdateMonitorSchedule(ctx, h.temporal, monitor, workerGroups)
|
||||
err = services.CreateOrUpdateCheckSchedule(ctx, h.temporal, check, workerGroups)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Redirect(http.StatusSeeOther, "/settings/monitors")
|
||||
return c.Redirect(http.StatusSeeOther, "/settings/checks")
|
||||
}
|
|
@ -22,7 +22,7 @@ type WorkerWithTokenAndActiveWorkers struct {
|
|||
}
|
||||
|
||||
type WorkerGroupWithActiveWorkers struct {
|
||||
*models.WorkerGroupWithMonitors
|
||||
*models.WorkerGroupWithChecks
|
||||
ActiveWorkers []string
|
||||
}
|
||||
|
||||
|
@ -39,7 +39,7 @@ type SettingsWorker struct {
|
|||
func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
|
||||
cc := c.(AuthenticatedContext)
|
||||
|
||||
workerGroups, err := services.GetWorkerGroupsWithMonitors(context.Background(), h.db)
|
||||
workerGroups, err := services.GetWorkerGroupsWithChecks(context.Background(), h.db)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -51,7 +51,7 @@ func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
workerGroupsWithActiveWorkers[i] = &WorkerGroupWithActiveWorkers{
|
||||
WorkerGroupWithMonitors: workerGroup,
|
||||
WorkerGroupWithChecks: workerGroup,
|
||||
ActiveWorkers: activeWorkers,
|
||||
}
|
||||
}
|
||||
|
|
316
internal/services/check.go
Normal file
316
internal/services/check.go
Normal 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
|
||||
}
|
67
internal/services/check_history.go
Normal file
67
internal/services/check_history.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -54,7 +54,7 @@ func GetWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.WorkerGroup, e
|
|||
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,
|
||||
`
|
||||
SELECT
|
||||
|
@ -62,10 +62,10 @@ SELECT
|
|||
worker_groups.name,
|
||||
worker_groups.created_at,
|
||||
worker_groups.updated_at,
|
||||
monitors.name as monitor_name
|
||||
checks.name as check_name
|
||||
FROM worker_groups
|
||||
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.id = monitor_worker_groups.worker_group_id
|
||||
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_id = monitors.id
|
||||
LEFT OUTER JOIN check_worker_groups ON worker_groups.id = check_worker_groups.worker_group_id
|
||||
LEFT OUTER JOIN checks ON check_worker_groups.check_id = checks.id
|
||||
ORDER BY worker_groups.name
|
||||
`)
|
||||
if err != nil {
|
||||
|
@ -73,29 +73,29 @@ ORDER BY worker_groups.name
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
workerGroups := map[string]*models.WorkerGroupWithMonitors{}
|
||||
workerGroups := map[string]*models.WorkerGroupWithChecks{}
|
||||
|
||||
for rows.Next() {
|
||||
workerGroup := &models.WorkerGroupWithMonitors{}
|
||||
workerGroup := &models.WorkerGroupWithChecks{}
|
||||
|
||||
var monitorName *string
|
||||
var checkName *string
|
||||
err = rows.Scan(
|
||||
&workerGroup.Id,
|
||||
&workerGroup.Name,
|
||||
&workerGroup.CreatedAt,
|
||||
&workerGroup.UpdatedAt,
|
||||
&monitorName,
|
||||
&checkName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if monitorName != nil {
|
||||
monitors := []string{}
|
||||
if checkName != nil {
|
||||
checks := []string{}
|
||||
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
|
||||
|
@ -122,7 +122,7 @@ func GetWorkerGroup(ctx context.Context, db *sqlx.DB, id string) (*models.Worker
|
|||
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,
|
||||
`
|
||||
SELECT
|
||||
|
@ -130,10 +130,10 @@ SELECT
|
|||
worker_groups.name,
|
||||
worker_groups.created_at,
|
||||
worker_groups.updated_at,
|
||||
monitors.name as monitor_name
|
||||
checks.name as check_name
|
||||
FROM worker_groups
|
||||
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.id = monitor_worker_groups.worker_group_id
|
||||
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_id = monitors.id
|
||||
LEFT OUTER JOIN check_worker_groups ON worker_groups.id = check_worker_groups.worker_group_id
|
||||
LEFT OUTER JOIN checks ON check_worker_groups.check_id = checks.id
|
||||
WHERE worker_groups.id=$1
|
||||
`,
|
||||
id,
|
||||
|
@ -143,22 +143,22 @@ WHERE worker_groups.id=$1
|
|||
}
|
||||
defer rows.Close()
|
||||
|
||||
workerGroup := &models.WorkerGroupWithMonitors{}
|
||||
workerGroup := &models.WorkerGroupWithChecks{}
|
||||
|
||||
for rows.Next() {
|
||||
var monitorName *string
|
||||
var checkName *string
|
||||
err = rows.Scan(
|
||||
&workerGroup.Id,
|
||||
&workerGroup.Name,
|
||||
&workerGroup.CreatedAt,
|
||||
&workerGroup.UpdatedAt,
|
||||
&monitorName,
|
||||
&checkName,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if monitorName != nil {
|
||||
workerGroup.Monitors = append(workerGroup.Monitors, *monitorName)
|
||||
if checkName != nil {
|
||||
workerGroup.Checks = append(workerGroup.Checks, *checkName)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
58
internal/workflows/check.go
Normal file
58
internal/workflows/check.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -2,8 +2,8 @@ package api
|
|||
|
||||
import "code.tjo.space/mentos1386/zdravko/database/models"
|
||||
|
||||
type ApiV1MonitorsHistoryPOSTBody struct {
|
||||
Status models.MonitorStatus `json:"status"`
|
||||
type ApiV1ChecksHistoryPOSTBody struct {
|
||||
Status models.CheckStatus `json:"status"`
|
||||
Note string `json:"note"`
|
||||
WorkerGroupId string `json:"worker_group"`
|
||||
}
|
|
@ -72,14 +72,16 @@ func Routes(
|
|||
//settings.GET("/targets/:id/disable", h.SettingsTargetsDisableGET)
|
||||
//settings.GET("/targets/:id/enable", h.SettingsTargetsEnableGET)
|
||||
|
||||
settings.GET("/monitors", h.SettingsMonitorsGET)
|
||||
settings.GET("/monitors/create", h.SettingsMonitorsCreateGET)
|
||||
settings.POST("/monitors/create", h.SettingsMonitorsCreatePOST)
|
||||
settings.GET("/monitors/:id", h.SettingsMonitorsDescribeGET)
|
||||
settings.POST("/monitors/:id", h.SettingsMonitorsDescribePOST)
|
||||
settings.GET("/monitors/:id/delete", h.SettingsMonitorsDescribeDELETE)
|
||||
settings.GET("/monitors/:id/disable", h.SettingsMonitorsDisableGET)
|
||||
settings.GET("/monitors/:id/enable", h.SettingsMonitorsEnableGET)
|
||||
settings.GET("/incidents", h.SettingsIncidentsGET)
|
||||
|
||||
settings.GET("/checks", h.SettingsChecksGET)
|
||||
settings.GET("/checks/create", h.SettingsChecksCreateGET)
|
||||
settings.POST("/checks/create", h.SettingsChecksCreatePOST)
|
||||
settings.GET("/checks/:id", h.SettingsChecksDescribeGET)
|
||||
settings.POST("/checks/:id", h.SettingsChecksDescribePOST)
|
||||
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)
|
||||
|
||||
|
@ -101,7 +103,6 @@ func Routes(
|
|||
apiv1 := e.Group("/api/v1")
|
||||
apiv1.Use(h.Authenticated)
|
||||
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
|
||||
apiv1.POST("/monitors/:id/history", h.ApiV1MonitorsHistoryPOST)
|
||||
|
||||
// Error handler
|
||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
|
|
|
@ -20,7 +20,7 @@ func NewWorker(temporalClient client.Client, cfg *config.ServerConfig) *Worker {
|
|||
workerWorkflows := workflows.NewWorkflows(workerActivities)
|
||||
|
||||
// Register Workflows
|
||||
w.RegisterWorkflow(workerWorkflows.MonitorWorkflowDefinition)
|
||||
w.RegisterWorkflow(workerWorkflows.CheckWorkflowDefinition)
|
||||
|
||||
return &Worker{
|
||||
worker: w,
|
||||
|
|
|
@ -92,11 +92,11 @@ func (w *Worker) Start() error {
|
|||
workerWorkflows := workflows.NewWorkflows(workerActivities)
|
||||
|
||||
// Register Workflows
|
||||
w.worker.RegisterWorkflow(workerWorkflows.MonitorWorkflowDefinition)
|
||||
w.worker.RegisterWorkflow(workerWorkflows.CheckWorkflowDefinition)
|
||||
|
||||
// Register Activities
|
||||
w.worker.RegisterActivity(workerActivities.Monitor)
|
||||
w.worker.RegisterActivity(workerActivities.MonitorAddToHistory)
|
||||
w.worker.RegisterActivity(workerActivities.Check)
|
||||
w.worker.RegisterActivity(workerActivities.CheckAddToHistory)
|
||||
|
||||
return w.worker.Run(worker.InterruptCh())
|
||||
}
|
||||
|
|
|
@ -51,9 +51,6 @@ code {
|
|||
@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 {
|
||||
@apply w-full block rounded-lg px-4 py-2 focus:outline-none focus:ring-2 focus:ring-blue-700 focus:text-blue-700;
|
||||
}
|
||||
|
|
|
@ -825,6 +825,10 @@ video {
|
|||
flex-direction: column;
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.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);
|
||||
}
|
||||
|
||||
.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 {
|
||||
display: block;
|
||||
width: 100%;
|
||||
|
@ -1856,10 +1838,22 @@ code {
|
|||
margin-top: 5rem;
|
||||
}
|
||||
|
||||
.lg\:w-48 {
|
||||
width: 12rem;
|
||||
}
|
||||
|
||||
.lg\:grid-cols-\[min-content_minmax\(0\2c 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 {
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
|
|
@ -10,15 +10,17 @@
|
|||
<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"
|
||||
>
|
||||
<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 }}
|
||||
<li>
|
||||
<li class="flex items-center gap-1 lg:flex-col lg:items-start">
|
||||
<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 }}
|
||||
</p>
|
||||
<ul>
|
||||
<ul class="flex flex-row flex-wrap gap-1 lg:flex-col">
|
||||
{{ range .Pages }}
|
||||
<li>
|
||||
<a
|
||||
|
@ -40,6 +42,9 @@
|
|||
{{ if eq .Title "Checks" }}
|
||||
<span class="text-slate-400">(3)</span>
|
||||
{{ end }}
|
||||
{{ if eq .Title "Hooks" }}
|
||||
<span class="text-slate-400">(3)</span>
|
||||
{{ end }}
|
||||
{{ if eq .Title "Triggers" }}
|
||||
<span class="text-slate-400">(3)</span>
|
||||
{{ end }}
|
||||
|
|
|
@ -1,26 +1,26 @@
|
|||
{{ define "main" }}
|
||||
<div class="container max-w-screen-md flex flex-col mt-20 gap-20">
|
||||
{{ $length := len .Monitors }}
|
||||
{{ $length := len .Checks }}
|
||||
{{ if eq $length 0 }}
|
||||
<section>
|
||||
<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 monitors yet.
|
||||
There are no checks yet.
|
||||
</h1>
|
||||
<p
|
||||
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.
|
||||
</p>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<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"
|
||||
>
|
||||
Create First Monitor
|
||||
Create First Check
|
||||
<svg class="feather ml-1 h-5 w-5 overflow-visible">
|
||||
<use href="/static/icons/feather-sprite.svg#plus" />
|
||||
</svg>
|
||||
|
@ -69,7 +69,7 @@
|
|||
</p>
|
||||
</div>
|
||||
{{ end }}
|
||||
<div class="monitors flex flex-col gap-4">
|
||||
<div class="checks flex flex-col gap-4">
|
||||
<div
|
||||
class="inline-flex gap-1 justify-center md:justify-end time-range"
|
||||
role="group"
|
||||
|
@ -93,7 +93,7 @@
|
|||
>90 Minutes</a
|
||||
>
|
||||
</div>
|
||||
{{ range $group, $monitorsAndStatus := .Monitors }}
|
||||
{{ range $group, $checksAndStatus := .Checks }}
|
||||
<details
|
||||
open
|
||||
class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:rotate-90"
|
||||
|
@ -101,11 +101,11 @@
|
|||
<summary
|
||||
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
|
||||
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
|
||||
></span>
|
||||
{{ else if eq $monitorsAndStatus.Status "FAILURE" }}
|
||||
{{ else if eq $checksAndStatus.Status "FAILURE" }}
|
||||
<span
|
||||
class="flex w-3 h-3 bg-red-400 rounded-full self-center"
|
||||
></span>
|
||||
|
@ -123,7 +123,7 @@
|
|||
<use href="/static/icons/feather-sprite.svg#chevron-right" />
|
||||
</svg>
|
||||
</summary>
|
||||
{{ range $monitorsAndStatus.Monitors }}
|
||||
{{ range $checksAndStatus.Checks }}
|
||||
<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"
|
||||
>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{{ 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 }}
|
||||
<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 monitors yet.
|
||||
There are no checks yet.
|
||||
</h1>
|
||||
<p
|
||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||
|
@ -16,10 +16,10 @@
|
|||
</p>
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||
<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"
|
||||
>
|
||||
Create First Monitor
|
||||
Create First Check
|
||||
<svg class="feather ml-1 h-5 w-5 overflow-visible">
|
||||
<use href="/static/icons/feather-sprite.svg#plus" />
|
||||
</svg>
|
||||
|
@ -30,13 +30,13 @@
|
|||
<section>
|
||||
<table>
|
||||
<caption>
|
||||
List of Monitors
|
||||
List of Checks
|
||||
<div class="mt-1 gap-4 grid grid-cols-1 md:grid-cols-[1fr,20%]">
|
||||
<p>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<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"
|
||||
>
|
||||
Create New
|
||||
|
@ -48,7 +48,7 @@
|
|||
</caption>
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
||||
<tr>
|
||||
<th scope="col">Monitor Group</th>
|
||||
<th scope="col">Check Group</th>
|
||||
<th scope="col">Name</th>
|
||||
<th scope="col">Visibility</th>
|
||||
<th scope="col">Worker Groups</th>
|
||||
|
@ -58,7 +58,7 @@
|
|||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{ range .MonitorGroups }}
|
||||
{{ range .CheckGroups }}
|
||||
{{ $currentGroup := . }}
|
||||
<tr class="row-special">
|
||||
<th scope="row">
|
||||
|
@ -71,9 +71,9 @@
|
|||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
{{ range $group, $monitors := $.Monitors }}
|
||||
{{ range $group, $checks := $.Checks }}
|
||||
{{ if eq $group $currentGroup }}
|
||||
{{ range $monitors }}
|
||||
{{ range $checks }}
|
||||
<tr>
|
||||
<th scope="row">└─</th>
|
||||
<th scope="row">
|
||||
|
@ -125,7 +125,7 @@
|
|||
{{ .Schedule }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/settings/monitors/{{ .Id }}" class="link"
|
||||
<a href="/settings/checks/{{ .Id }}" class="link"
|
||||
>Details</a
|
||||
>
|
||||
</td>
|
|
@ -1,10 +1,10 @@
|
|||
{{ define "settings" }}
|
||||
<section class="p-5">
|
||||
<form action="/settings/monitors/create" method="post">
|
||||
<form action="/settings/checks/create" method="post">
|
||||
<label for="name">Name</label>
|
||||
<input type="text" name="name" id="name" placeholder="Github.com" />
|
||||
<p>Name of the monitor can be anything.</p>
|
||||
<label list="existing-groups" for="group">Monitor Group</label>
|
||||
<p>Name of the check can be anything.</p>
|
||||
<label list="existing-groups" for="group">Check Group</label>
|
||||
<input
|
||||
type="text"
|
||||
name="group"
|
||||
|
@ -17,7 +17,7 @@
|
|||
<option value="default"></option>
|
||||
</datalist>
|
||||
<p>
|
||||
Group monitors together. This affects how they are presented on the
|
||||
Group checks together. This affects how they are presented on the
|
||||
homepage.
|
||||
</p>
|
||||
<label for="workergroups">Worker Groups</label>
|
||||
|
@ -29,7 +29,7 @@
|
|||
required
|
||||
/>
|
||||
<p>
|
||||
Worker groups are used to distribute the monitor to specific workers.
|
||||
Worker groups are used to distribute the check to specific workers.
|
||||
</p>
|
||||
<label for="schedule">Schedule</label>
|
||||
<input
|
||||
|
@ -41,7 +41,7 @@
|
|||
required
|
||||
/>
|
||||
<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.
|
||||
<br />
|
||||
You can also use <code>@every [interval]</code> where interval is a
|
|
@ -1,17 +1,17 @@
|
|||
{{ define "settings" }}
|
||||
<section class="p-5">
|
||||
<form action="/settings/monitors/{{ .Monitor.Id }}" method="post">
|
||||
<form action="/settings/checks/{{ .Check.Id }}" method="post">
|
||||
<h2>Configuration</h2>
|
||||
<label for="group">Monitor Group</label>
|
||||
<label for="group">Check Group</label>
|
||||
<input
|
||||
type="text"
|
||||
name="group"
|
||||
id="group"
|
||||
value="{{ .Monitor.Group }}"
|
||||
value="{{ .Check.Group }}"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Group monitors together. This affects how they are presented on the
|
||||
Group checks together. This affects how they are presented on the
|
||||
homepage.
|
||||
</p>
|
||||
<label for="workergroups">Worker Groups</label>
|
||||
|
@ -19,22 +19,22 @@
|
|||
type="text"
|
||||
name="workergroups"
|
||||
id="workergroups"
|
||||
value="{{ range .Monitor.WorkerGroups }}{{ . }}{{ end }}"
|
||||
value="{{ range .Check.WorkerGroups }}{{ . }}{{ end }}"
|
||||
required
|
||||
/>
|
||||
<p>
|
||||
Worker groups are used to distribute the monitor to specific workers.
|
||||
Worker groups are used to distribute the check to specific workers.
|
||||
</p>
|
||||
<label for="schedule">Schedule</label>
|
||||
<input
|
||||
type="text"
|
||||
name="schedule"
|
||||
id="schedule"
|
||||
value="{{ .Monitor.Schedule }}"
|
||||
value="{{ .Check.Schedule }}"
|
||||
required
|
||||
/>
|
||||
<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.
|
||||
<br />
|
||||
You can also use <code>@every [interval]</code> where interval is a
|
||||
|
@ -44,7 +44,7 @@
|
|||
</p>
|
||||
<label for="script">Script</label>
|
||||
<textarea required id="script" name="script" class="h-96">
|
||||
{{ ScriptUnescapeString .Monitor.Script }}</textarea
|
||||
{{ ScriptUnescapeString .Check.Script }}</textarea
|
||||
>
|
||||
<div
|
||||
id="editor"
|
||||
|
@ -65,13 +65,13 @@
|
|||
<section class="p-5 flex-1">
|
||||
<h2 class="mb-2 flex flex-row gap-2">
|
||||
Status
|
||||
{{ if eq .Monitor.Status "ACTIVE" }}
|
||||
{{ if eq .Check.Status "ACTIVE" }}
|
||||
<span
|
||||
class="self-center h-fit w-fit px-2 text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
|
||||
>
|
||||
ACTIVE
|
||||
</span>
|
||||
{{ else if eq .Monitor.Status "PAUSED" }}
|
||||
{{ else if eq .Check.Status "PAUSED" }}
|
||||
<span
|
||||
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 }}
|
||||
</h2>
|
||||
<p class="text-sm mb-2">
|
||||
Pausing the monitor will stop it from executing. This can be useful in
|
||||
cases of expected downtime. Or when the monitor is not needed anymore.
|
||||
Pausing the check will stop it from executing. This can be useful in
|
||||
cases of expected downtime. Or when the check is not needed anymore.
|
||||
</p>
|
||||
{{ if eq .Monitor.Status "ACTIVE" }}
|
||||
{{ if eq .Check.Status "ACTIVE" }}
|
||||
<a
|
||||
class="block text-center py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100"
|
||||
href="/settings/monitors/{{ .Monitor.Id }}/disable"
|
||||
href="/settings/checks/{{ .Check.Id }}/disable"
|
||||
>Pause</a
|
||||
>
|
||||
{{ else if eq .Monitor.Status "PAUSED" }}
|
||||
{{ else if eq .Check.Status "PAUSED" }}
|
||||
<a
|
||||
class="block text-center py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100"
|
||||
href="/settings/monitors/{{ .Monitor.Id }}/enable"
|
||||
href="/settings/checks/{{ .Check.Id }}/enable"
|
||||
>Resume</a
|
||||
>
|
||||
{{ end }}
|
||||
|
@ -100,10 +100,10 @@
|
|||
|
||||
<section class="p-2 flex-1 border-4 border-red-300">
|
||||
<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
|
||||
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
|
||||
>
|
||||
</section>
|
||||
|
@ -113,7 +113,7 @@
|
|||
<table>
|
||||
<caption>
|
||||
History
|
||||
<p>Last 10 executions of monitor script.</p>
|
||||
<p>Last 10 executions of check script.</p>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
|
@ -172,7 +172,7 @@
|
|||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
script = htmlDecode("{{ .Monitor.Script }}")
|
||||
script = htmlDecode("{{ .Check.Script }}")
|
||||
|
||||
require.config({ paths: { vs: '/static/monaco/vs' } });
|
||||
require(['vs/editor/editor.main'], function () {
|
84
web/templates/pages/settings_incidents.tmpl
Normal file
84
web/templates/pages/settings_incidents.tmpl
Normal 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 }}
|
|
@ -7,7 +7,7 @@
|
|||
</h1>
|
||||
<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,
|
||||
monitors, and notifications.
|
||||
checks, and notifications.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -16,15 +16,15 @@
|
|||
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>
|
||||
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p>
|
||||
<p class="text-3xl font-bold text-black">{{ .ChecksCount }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||
>
|
||||
<h3 class="text-sm leading-6 font-medium text-gray-400">
|
||||
Total Monitors
|
||||
Total Checks
|
||||
</h3>
|
||||
<p class="text-3xl font-bold text-black">{{ .MonitorsCount }}</p>
|
||||
<p class="text-3xl font-bold text-black">{{ .ChecksCount }}</p>
|
||||
</div>
|
||||
<div
|
||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||
|
@ -48,11 +48,11 @@
|
|||
<table>
|
||||
<caption>
|
||||
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>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Monitor</th>
|
||||
<th>Check</th>
|
||||
<th>Worker Group</th>
|
||||
<th>Status</th>
|
||||
<th>Executed At</th>
|
||||
|
@ -65,8 +65,8 @@
|
|||
<th>
|
||||
<a
|
||||
class="underline hover:text-blue-600"
|
||||
href="/settings/monitors/{{ .MonitorId }}"
|
||||
>{{ .MonitorName }}</a
|
||||
href="/settings/checks/{{ .CheckId }}"
|
||||
>{{ .CheckName }}</a
|
||||
>
|
||||
</th>
|
||||
<td>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{ 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 }}
|
||||
{{ if eq $length 0 }}
|
||||
|
|
|
@ -50,7 +50,7 @@
|
|||
<tr>
|
||||
<th>Name</th>
|
||||
<th>Workers</th>
|
||||
<th>Monitors</th>
|
||||
<th>Checks</th>
|
||||
<th>Action</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
@ -76,7 +76,7 @@
|
|||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
{{ len .Monitors }}
|
||||
{{ len .Checks }}
|
||||
</td>
|
||||
<td>
|
||||
<a href="/settings/worker-groups/{{ .Id }}" class="link"
|
||||
|
|
|
@ -51,13 +51,14 @@ func NewTemplates() *Templates {
|
|||
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
|
||||
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
|
||||
"settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"),
|
||||
"settings_incidents.tmpl": loadSettings("pages/settings_incidents.tmpl"),
|
||||
"settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"),
|
||||
"settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"),
|
||||
"settings_worker_groups_create.tmpl": loadSettings("pages/settings_worker_groups_create.tmpl"),
|
||||
"settings_worker_groups_describe.tmpl": loadSettings("pages/settings_worker_groups_describe.tmpl"),
|
||||
"settings_monitors.tmpl": loadSettings("pages/settings_monitors.tmpl"),
|
||||
"settings_monitors_create.tmpl": loadSettings("pages/settings_monitors_create.tmpl"),
|
||||
"settings_monitors_describe.tmpl": loadSettings("pages/settings_monitors_describe.tmpl"),
|
||||
"settings_checks.tmpl": loadSettings("pages/settings_checks.tmpl"),
|
||||
"settings_checks_create.tmpl": loadSettings("pages/settings_checks_create.tmpl"),
|
||||
"settings_checks_describe.tmpl": loadSettings("pages/settings_checks_describe.tmpl"),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue