mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-21 23:33:34 +00:00
Merge pull request #5 from mentos1386/target-instead-of-checks
Target instead of checks
This commit is contained in:
commit
91fddaafaa
83 changed files with 2225 additions and 1057 deletions
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
Golang selfhosted Status/Healthcheck monitoring app.
|
Golang selfhosted Status/Healthcheck monitoring app.
|
||||||
|
|
||||||
Mostly just a project to test [temporal.io](https://temporal.io/).
|
|
||||||
|
|
||||||
### Roadmap
|
### Roadmap
|
||||||
- [x] SSO Support for authentication.
|
- [x] SSO Support for authentication.
|
||||||
- [x] SQLite for database.
|
- [x] SQLite for database.
|
||||||
|
@ -37,9 +35,12 @@ Demo is available at https://zdravko.mnts.dev.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Configure
|
# Configure
|
||||||
|
# You will need to configure an SSO provider
|
||||||
|
# This can be github for example.
|
||||||
cp example.env .env
|
cp example.env .env
|
||||||
|
|
||||||
# Generate JWT key
|
# Generate JWT key
|
||||||
|
# Copy the values to your .env
|
||||||
just generate-jwt-key
|
just generate-jwt-key
|
||||||
|
|
||||||
# Start development environment
|
# Start development environment
|
||||||
|
|
|
@ -8,10 +8,10 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/server"
|
"github.com/mentos1386/zdravko/pkg/server"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/temporal"
|
"github.com/mentos1386/zdravko/pkg/temporal"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/worker"
|
"github.com/mentos1386/zdravko/pkg/worker"
|
||||||
)
|
)
|
||||||
|
|
||||||
type StartableAndStoppable interface {
|
type StartableAndStoppable interface {
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package kv
|
package database
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
|
@ -1,4 +1,4 @@
|
||||||
package kv
|
package database
|
||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
|
@ -45,15 +45,6 @@ type OAuth2State struct {
|
||||||
ExpiresAt *Time `db:"expires_at"`
|
ExpiresAt *Time `db:"expires_at"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CheckStatusSuccess CheckStatus = "SUCCESS"
|
|
||||||
CheckStatusFailure CheckStatus = "FAILURE"
|
|
||||||
CheckStatusError CheckStatus = "ERROR"
|
|
||||||
CheckStatusUnknown CheckStatus = "UNKNOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
type CheckState string
|
type CheckState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -62,45 +53,24 @@ const (
|
||||||
CheckStateUnknown CheckState = "UNKNOWN"
|
CheckStateUnknown CheckState = "UNKNOWN"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CheckVisibility string
|
|
||||||
|
|
||||||
const (
|
|
||||||
CheckVisibilityPublic CheckVisibility = "PUBLIC"
|
|
||||||
CheckVisibilityPrivate CheckVisibility = "PRIVATE"
|
|
||||||
CheckVisibilityUnknown CheckVisibility = "UNKNOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Check struct {
|
type Check struct {
|
||||||
CreatedAt *Time `db:"created_at"`
|
CreatedAt *Time `db:"created_at"`
|
||||||
UpdatedAt *Time `db:"updated_at"`
|
UpdatedAt *Time `db:"updated_at"`
|
||||||
|
|
||||||
Id string `db:"id"`
|
Id string `db:"id"`
|
||||||
Name string `db:"name"`
|
Name string `db:"name"`
|
||||||
Group string `db:"group"`
|
|
||||||
Visibility CheckVisibility `db:"visibility"`
|
|
||||||
|
|
||||||
Schedule string `db:"schedule"`
|
Schedule string `db:"schedule"`
|
||||||
Script string `db:"script"`
|
Script string `db:"script"`
|
||||||
|
Filter string `db:"filter"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckWithWorkerGroups struct {
|
type CheckWithWorkerGroups struct {
|
||||||
Check
|
Check
|
||||||
|
|
||||||
// List of worker group names
|
// List of worker group names
|
||||||
WorkerGroups []string
|
WorkerGroups []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckHistory struct {
|
|
||||||
CreatedAt *Time `db:"created_at"`
|
|
||||||
|
|
||||||
CheckId string `db:"check_id"`
|
|
||||||
Status CheckStatus `db:"status"`
|
|
||||||
Note string `db:"note"`
|
|
||||||
|
|
||||||
WorkerGroupId string `db:"worker_group_id"`
|
|
||||||
WorkerGroupName string `db:"worker_group_name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type WorkerGroup struct {
|
type WorkerGroup struct {
|
||||||
CreatedAt *Time `db:"created_at"`
|
CreatedAt *Time `db:"created_at"`
|
||||||
UpdatedAt *Time `db:"updated_at"`
|
UpdatedAt *Time `db:"updated_at"`
|
||||||
|
@ -116,15 +86,6 @@ type WorkerGroupWithChecks struct {
|
||||||
Checks []string
|
Checks []string
|
||||||
}
|
}
|
||||||
|
|
||||||
type TriggerStatus string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TriggerStatusSuccess TriggerStatus = "SUCCESS"
|
|
||||||
TriggerStatusFailure TriggerStatus = "FAILURE"
|
|
||||||
TriggerStatusError TriggerStatus = "ERROR"
|
|
||||||
TriggerStatusUnknown TriggerStatus = "UNKNOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TriggerState string
|
type TriggerState string
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -133,14 +94,6 @@ const (
|
||||||
TriggerStateUnknown TriggerState = "UNKNOWN"
|
TriggerStateUnknown TriggerState = "UNKNOWN"
|
||||||
)
|
)
|
||||||
|
|
||||||
type TriggerVisibility string
|
|
||||||
|
|
||||||
const (
|
|
||||||
TriggerVisibilityPublic TriggerVisibility = "PUBLIC"
|
|
||||||
TriggerVisibilityPrivate TriggerVisibility = "PRIVATE"
|
|
||||||
TriggerVisibilityUnknown TriggerVisibility = "UNKNOWN"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Trigger struct {
|
type Trigger struct {
|
||||||
CreatedAt *Time `db:"created_at"`
|
CreatedAt *Time `db:"created_at"`
|
||||||
UpdatedAt *Time `db:"updated_at"`
|
UpdatedAt *Time `db:"updated_at"`
|
||||||
|
@ -150,10 +103,49 @@ type Trigger struct {
|
||||||
Script string `db:"script"`
|
Script string `db:"script"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TriggerHistory struct {
|
type TargetVisibility string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TargetVisibilityPublic TargetVisibility = "PUBLIC"
|
||||||
|
TargetVisibilityPrivate TargetVisibility = "PRIVATE"
|
||||||
|
TargetVisibilityUnknown TargetVisibility = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TargetState string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TargetStateActive TargetState = "ACTIVE"
|
||||||
|
TargetStatePaused TargetState = "PAUSED"
|
||||||
|
TargetStateUnknown TargetState = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Target struct {
|
||||||
|
CreatedAt *Time `db:"created_at"`
|
||||||
|
UpdatedAt *Time `db:"updated_at"`
|
||||||
|
|
||||||
|
Id string `db:"id"`
|
||||||
|
Name string `db:"name"`
|
||||||
|
Group string `db:"group"`
|
||||||
|
Visibility TargetVisibility `db:"visibility"`
|
||||||
|
State TargetState `db:"state"`
|
||||||
|
Metadata string `db:"metadata"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TargetStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
TargetStatusSuccess TargetStatus = "SUCCESS"
|
||||||
|
TargetStatusFailure TargetStatus = "FAILURE"
|
||||||
|
TargetStatusUnknown TargetStatus = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TargetHistory struct {
|
||||||
CreatedAt *Time `db:"created_at"`
|
CreatedAt *Time `db:"created_at"`
|
||||||
|
|
||||||
TriggerId string `db:"trigger_id"`
|
TargetId string `db:"target_id"`
|
||||||
Status TriggerStatus `db:"status"`
|
WorkerGroupId string `db:"worker_group_id"`
|
||||||
Note string `db:"note"`
|
CheckId string `db:"check_id"`
|
||||||
|
|
||||||
|
Status TargetStatus `db:"status"`
|
||||||
|
Note string `db:"note"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,11 +9,10 @@ CREATE TABLE oauth2_states (
|
||||||
CREATE TABLE checks (
|
CREATE TABLE checks (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
"group" TEXT NOT NULL,
|
|
||||||
schedule TEXT NOT NULL,
|
schedule TEXT NOT NULL,
|
||||||
script TEXT NOT NULL,
|
script TEXT NOT NULL,
|
||||||
|
|
||||||
visibility TEXT NOT NULL,
|
filter TEXT NOT NULL,
|
||||||
|
|
||||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||||
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||||
|
@ -21,8 +20,6 @@ CREATE TABLE checks (
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT unique_checks_name UNIQUE (name)
|
CONSTRAINT unique_checks_name UNIQUE (name)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
|
|
||||||
-- +migrate StatementBegin
|
-- +migrate StatementBegin
|
||||||
CREATE TRIGGER checks_updated_timestamp AFTER UPDATE ON checks BEGIN
|
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;
|
UPDATE checks SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = NEW.id;
|
||||||
|
@ -39,7 +36,6 @@ CREATE TABLE worker_groups (
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT unique_worker_groups_name UNIQUE (name)
|
CONSTRAINT unique_worker_groups_name UNIQUE (name)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- +migrate StatementBegin
|
-- +migrate StatementBegin
|
||||||
CREATE TRIGGER worker_groups_updated_timestamp AFTER UPDATE ON worker_groups BEGIN
|
CREATE TRIGGER worker_groups_updated_timestamp AFTER UPDATE ON worker_groups BEGIN
|
||||||
UPDATE worker_groups SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = NEW.id;
|
UPDATE worker_groups SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = NEW.id;
|
||||||
|
@ -55,19 +51,6 @@ CREATE TABLE check_worker_groups (
|
||||||
CONSTRAINT fk_check_worker_groups_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE
|
CONSTRAINT fk_check_worker_groups_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
CREATE TABLE check_histories (
|
|
||||||
check_id TEXT NOT NULL,
|
|
||||||
worker_group_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 (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 (
|
CREATE TABLE triggers (
|
||||||
id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
|
@ -80,24 +63,48 @@ CREATE TABLE triggers (
|
||||||
PRIMARY KEY (id),
|
PRIMARY KEY (id),
|
||||||
CONSTRAINT unique_triggers_name UNIQUE (name)
|
CONSTRAINT unique_triggers_name UNIQUE (name)
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
|
|
||||||
-- +migrate StatementBegin
|
-- +migrate StatementBegin
|
||||||
CREATE TRIGGER triggers_updated_timestamp AFTER UPDATE ON triggers BEGIN
|
CREATE TRIGGER triggers_updated_timestamp AFTER UPDATE ON triggers BEGIN
|
||||||
UPDATE triggers SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = NEW.id;
|
UPDATE triggers SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = NEW.id;
|
||||||
END;
|
END;
|
||||||
-- +migrate StatementEnd
|
-- +migrate StatementEnd
|
||||||
|
|
||||||
CREATE TABLE trigger_histories (
|
CREATE TABLE targets (
|
||||||
trigger_id TEXT NOT NULL,
|
id TEXT NOT NULL,
|
||||||
|
name TEXT NOT NULL,
|
||||||
|
"group" TEXT NOT NULL,
|
||||||
|
|
||||||
|
visibility TEXT NOT NULL,
|
||||||
|
state TEXT NOT NULL,
|
||||||
|
|
||||||
|
metadata 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_targets_name UNIQUE (name)
|
||||||
|
) STRICT;
|
||||||
|
-- +migrate StatementBegin
|
||||||
|
CREATE TRIGGER targets_updated_timestamp AFTER UPDATE ON targets BEGIN
|
||||||
|
UPDATE targets SET updated_at = strftime('%Y-%m-%dT%H:%M:%fZ') WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
-- +migrate StatementEnd
|
||||||
|
|
||||||
|
CREATE TABLE target_histories (
|
||||||
|
target_id TEXT NOT NULL,
|
||||||
|
worker_group_id TEXT NOT NULL,
|
||||||
|
check_id TEXT NOT NULL,
|
||||||
|
|
||||||
status TEXT NOT NULL,
|
status TEXT NOT NULL,
|
||||||
note TEXT NOT NULL,
|
note TEXT NOT NULL,
|
||||||
|
|
||||||
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ')),
|
||||||
|
|
||||||
PRIMARY KEY (trigger_id, created_at),
|
PRIMARY KEY (target_id, worker_group_id, check_id, created_at),
|
||||||
CONSTRAINT fk_trigger_histories_trigger FOREIGN KEY (trigger_id) REFERENCES triggers(id) ON DELETE CASCADE
|
CONSTRAINT fk_target_histories_target FOREIGN KEY (target_id) REFERENCES targets(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_target_histories_worker_group FOREIGN KEY (worker_group_id) REFERENCES worker_groups(id) ON DELETE CASCADE,
|
||||||
|
CONSTRAINT fk_target_histories_check FOREIGN KEY (check_id) REFERENCES checks(id) ON DELETE CASCADE
|
||||||
) STRICT;
|
) STRICT;
|
||||||
|
|
||||||
-- +migrate Down
|
-- +migrate Down
|
||||||
|
@ -105,9 +112,7 @@ DROP TABLE oauth2_states;
|
||||||
DROP TABLE check_worker_groups;
|
DROP TABLE check_worker_groups;
|
||||||
DROP TABLE worker_groups;
|
DROP TABLE worker_groups;
|
||||||
DROP TRIGGER worker_groups_updated_timestamp;
|
DROP TRIGGER worker_groups_updated_timestamp;
|
||||||
DROP TABLE check_histories;
|
|
||||||
DROP TABLE checks;
|
DROP TABLE checks;
|
||||||
DROP TRIGGER checks_updated_timestamp;
|
DROP TRIGGER checks_updated_timestamp;
|
||||||
DROP TABLE triggers;
|
DROP TABLE triggers;
|
||||||
DROP TABLE trigger_histories;
|
|
||||||
DROP TRIGGER triggers_updated_timestamp;
|
DROP TRIGGER triggers_updated_timestamp;
|
||||||
|
|
|
@ -17,9 +17,9 @@ primary_region = 'waw'
|
||||||
ROOT_URL = 'https://zdravko.mnts.dev'
|
ROOT_URL = 'https://zdravko.mnts.dev'
|
||||||
TEMPORAL_SERVER_HOST = 'server.process.zdravko.internal:7233'
|
TEMPORAL_SERVER_HOST = 'server.process.zdravko.internal:7233'
|
||||||
|
|
||||||
TEMPORAL_DATABASE_PATH = '/data/temporal-10.db'
|
TEMPORAL_DATABASE_PATH = '/data/temporal-11.db'
|
||||||
SQLITE_DATABASE_PATH = '/data/zdravko-10.db'
|
SQLITE_DATABASE_PATH = '/data/zdravko-11.db'
|
||||||
KEYVALUE_DATABASE_PATH = '/data/zdravko_kv-10.db'
|
KEYVALUE_DATABASE_PATH = '/data/zdravko_kv-11.db'
|
||||||
|
|
||||||
[processes]
|
[processes]
|
||||||
server = '--temporal --server'
|
server = '--temporal --server'
|
||||||
|
|
|
@ -7,8 +7,11 @@
|
||||||
SESSION_SECRET=your_secret
|
SESSION_SECRET=your_secret
|
||||||
|
|
||||||
# To generate keys, run "just generate-jwt-key"
|
# To generate keys, run "just generate-jwt-key"
|
||||||
JWT_PUBLIC_KEY=""
|
# When running `just run` or `just run-worker`
|
||||||
JWT_PRIVATE_KEY=""
|
# This doesn't have to be set, as it's read from the file
|
||||||
|
#
|
||||||
|
#JWT_PUBLIC_KEY=""
|
||||||
|
#JWT_PRIVATE_KEY=""
|
||||||
|
|
||||||
# To generate worker token, go to website and
|
# To generate worker token, go to website and
|
||||||
# create new worker. Then copy the token.
|
# create new worker. Then copy the token.
|
||||||
|
|
20
go.mod
20
go.mod
|
@ -1,9 +1,10 @@
|
||||||
module code.tjo.space/mentos1386/zdravko
|
module github.com/mentos1386/zdravko
|
||||||
|
|
||||||
go 1.21.6
|
go 1.21.6
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/dgraph-io/badger/v4 v4.2.0
|
github.com/dgraph-io/badger/v4 v4.2.0
|
||||||
|
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d
|
||||||
github.com/go-playground/validator/v10 v10.18.0
|
github.com/go-playground/validator/v10 v10.18.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.2.0
|
github.com/golang-jwt/jwt/v5 v5.2.0
|
||||||
github.com/gorilla/sessions v1.2.2
|
github.com/gorilla/sessions v1.2.2
|
||||||
|
@ -20,7 +21,7 @@ require (
|
||||||
go.temporal.io/api v1.27.0
|
go.temporal.io/api v1.27.0
|
||||||
go.temporal.io/sdk v1.26.0-rc.2
|
go.temporal.io/sdk v1.26.0-rc.2
|
||||||
go.temporal.io/server v1.22.4
|
go.temporal.io/server v1.22.4
|
||||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842
|
||||||
golang.org/x/oauth2 v0.17.0
|
golang.org/x/oauth2 v0.17.0
|
||||||
gopkg.in/yaml.v2 v2.4.0
|
gopkg.in/yaml.v2 v2.4.0
|
||||||
)
|
)
|
||||||
|
@ -56,7 +57,6 @@ require (
|
||||||
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
github.com/dgryski/go-farm v0.0.0-20200201041132-a6ae2369ad13 // indirect
|
||||||
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
|
||||||
github.com/dlclark/regexp2 v1.9.0 // indirect
|
github.com/dlclark/regexp2 v1.9.0 // indirect
|
||||||
github.com/dop251/goja v0.0.0-20231027120936-b396bb4c349d // indirect
|
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
github.com/facebookgo/clock v0.0.0-20150410010913-600d898af40a // indirect
|
||||||
|
@ -177,14 +177,14 @@ require (
|
||||||
go.uber.org/fx v1.20.0 // indirect
|
go.uber.org/fx v1.20.0 // indirect
|
||||||
go.uber.org/multierr v1.11.0 // indirect
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
go.uber.org/zap v1.26.0 // indirect
|
go.uber.org/zap v1.26.0 // indirect
|
||||||
golang.org/x/crypto v0.19.0 // indirect
|
golang.org/x/crypto v0.23.0 // indirect
|
||||||
golang.org/x/mod v0.15.0 // indirect
|
golang.org/x/mod v0.17.0 // indirect
|
||||||
golang.org/x/net v0.21.0 // indirect
|
golang.org/x/net v0.25.0 // indirect
|
||||||
golang.org/x/sync v0.6.0 // indirect
|
golang.org/x/sync v0.7.0 // indirect
|
||||||
golang.org/x/sys v0.17.0 // indirect
|
golang.org/x/sys v0.20.0 // indirect
|
||||||
golang.org/x/text v0.14.0 // indirect
|
golang.org/x/text v0.15.0 // indirect
|
||||||
golang.org/x/time v0.5.0 // indirect
|
golang.org/x/time v0.5.0 // indirect
|
||||||
golang.org/x/tools v0.17.0 // indirect
|
golang.org/x/tools v0.21.0 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
|
||||||
google.golang.org/api v0.155.0 // indirect
|
google.golang.org/api v0.155.0 // indirect
|
||||||
google.golang.org/appengine v1.6.8 // indirect
|
google.golang.org/appengine v1.6.8 // indirect
|
||||||
|
|
32
go.sum
32
go.sum
|
@ -569,16 +569,16 @@ golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8U
|
||||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo=
|
golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI=
|
||||||
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
|
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
|
||||||
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
|
||||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842 h1:vr/HnozRka3pE4EsMEg1lgkXJkTFJCVUX+S/ZT6wYzM=
|
||||||
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
|
golang.org/x/exp v0.0.0-20240506185415-9bf2ced13842/go.mod h1:XtvwrStGgqGPLc4cjQfWqZHG1YFdYs6swckp8vpsjnc=
|
||||||
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
@ -594,8 +594,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||||
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro=
|
||||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||||
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
|
||||||
golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8=
|
golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA=
|
||||||
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||||
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
@ -618,8 +618,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||||
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
|
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
|
||||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
|
@ -635,8 +635,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
|
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
|
||||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
@ -668,8 +668,8 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y=
|
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
|
||||||
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
|
@ -684,8 +684,8 @@ golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
|
||||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
|
golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk=
|
||||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||||
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk=
|
||||||
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
@ -706,8 +706,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f
|
||||||
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
golang.org/x/tools v0.1.8/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU=
|
||||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||||
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
|
||||||
golang.org/x/tools v0.17.0 h1:FvmRgNOcs3kOa+T20R1uhfP9F6HgG2mfxDv1vrx1Htc=
|
golang.org/x/tools v0.21.0 h1:qc0xYgIbsSDt9EyWz05J5wfa7LOVW0YTLOXrqdLAWIw=
|
||||||
golang.org/x/tools v0.17.0/go.mod h1:xsh6VxdV005rRVaS6SSAf9oiAqljS7UZUacMZ8Bnsps=
|
golang.org/x/tools v0.21.0/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
|
||||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
package activities
|
|
||||||
|
|
||||||
import "code.tjo.space/mentos1386/zdravko/internal/config"
|
|
||||||
|
|
||||||
type Activities struct {
|
|
||||||
config *config.WorkerConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewActivities(config *config.WorkerConfig) *Activities {
|
|
||||||
return &Activities{config: config}
|
|
||||||
}
|
|
|
@ -1,77 +0,0 @@
|
||||||
package activities
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bytes"
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/api"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/k6"
|
|
||||||
)
|
|
||||||
|
|
||||||
type HealtcheckParam struct {
|
|
||||||
Script string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckResult struct {
|
|
||||||
Success bool
|
|
||||||
Note string
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CheckResult{Success: result.Success, Note: result.Note}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type HealtcheckAddToHistoryParam struct {
|
|
||||||
CheckId string
|
|
||||||
Status models.CheckStatus
|
|
||||||
Note string
|
|
||||||
WorkerGroupId string
|
|
||||||
}
|
|
||||||
|
|
||||||
type CheckAddToHistoryResult struct {
|
|
||||||
}
|
|
||||||
|
|
||||||
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.ApiV1ChecksHistoryPOSTBody{
|
|
||||||
Status: param.Status,
|
|
||||||
Note: param.Note,
|
|
||||||
WorkerGroupId: param.WorkerGroupId,
|
|
||||||
}
|
|
||||||
|
|
||||||
jsonBody, err := json.Marshal(body)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
req, err := api.NewRequest(http.MethodPost, url, a.config.Token, bytes.NewReader(jsonBody))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
response, err := http.DefaultClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
defer response.Body.Close()
|
|
||||||
|
|
||||||
if response.StatusCode != http.StatusCreated {
|
|
||||||
return nil, fmt.Errorf("unexpected status code: %d", response.StatusCode)
|
|
||||||
}
|
|
||||||
|
|
||||||
return &CheckAddToHistoryResult{}, nil
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"errors"
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/api"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ApiV1WorkersConnectGETResponse struct {
|
|
||||||
Endpoint string `json:"endpoint"`
|
|
||||||
Group string `json:"group"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
cc := c.(AuthenticatedContext)
|
|
||||||
|
|
||||||
workerGroup, err := services.GetWorkerGroup(ctx, h.db, cc.Principal.Worker.Group)
|
|
||||||
if err != nil {
|
|
||||||
if errors.Is(err, sql.ErrNoRows) {
|
|
||||||
return echo.NewHTTPError(http.StatusUnauthorized, "Token invalid")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
response := ApiV1WorkersConnectGETResponse{
|
|
||||||
Endpoint: h.config.Temporal.ServerHost,
|
|
||||||
Group: workerGroup.Id,
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusOK, response)
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Can we instead get this from the Workflow outcome?
|
|
||||||
//
|
|
||||||
// To somehow listen for the outcomes and then store them automatically.
|
|
||||||
func (h *BaseHandler) ApiV1ChecksHistoryPOST(c echo.Context) error {
|
|
||||||
ctx := context.Background()
|
|
||||||
id := c.Param("id")
|
|
||||||
|
|
||||||
var body api.ApiV1ChecksHistoryPOSTBody
|
|
||||||
err := (&echo.DefaultBinder{}).BindBody(c, &body)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = services.GetCheck(ctx, h.db, id)
|
|
||||||
if err != nil {
|
|
||||||
if err == sql.ErrNoRows {
|
|
||||||
return echo.NewHTTPError(http.StatusNotFound, "Check not found")
|
|
||||||
}
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = services.AddHistoryForCheck(ctx, h.db, &models.CheckHistory{
|
|
||||||
CheckId: id,
|
|
||||||
WorkerGroupId: body.WorkerGroupId,
|
|
||||||
Status: body.Status,
|
|
||||||
Note: body.Note,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.JSON(http.StatusCreated, map[string]string{"status": "ok"})
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
package handlers
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/labstack/echo/v4"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Target struct{}
|
|
||||||
|
|
||||||
type SettingsTargets struct {
|
|
||||||
*Settings
|
|
||||||
Targets []*Target
|
|
||||||
}
|
|
||||||
|
|
||||||
func (h *BaseHandler) SettingsTargetsGET(c echo.Context) error {
|
|
||||||
cc := c.(AuthenticatedContext)
|
|
||||||
|
|
||||||
targets := make([]*Target, 0)
|
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "settings_targets.tmpl", &SettingsTargets{
|
|
||||||
Settings: NewSettings(
|
|
||||||
cc.Principal.User,
|
|
||||||
GetPageByTitle(SettingsPages, "Targets"),
|
|
||||||
[]*components.Page{GetPageByTitle(SettingsPages, "Targets")},
|
|
||||||
),
|
|
||||||
Targets: targets,
|
|
||||||
})
|
|
||||||
}
|
|
20
internal/server/activities/activities.go
Normal file
20
internal/server/activities/activities.go
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mentos1386/zdravko/database"
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Activities struct {
|
||||||
|
config *config.ServerConfig
|
||||||
|
db *sqlx.DB
|
||||||
|
kvStore database.KeyValueStore
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivities(config *config.ServerConfig, logger *slog.Logger, db *sqlx.DB, kvStore database.KeyValueStore) *Activities {
|
||||||
|
return &Activities{config: config, logger: logger, db: db, kvStore: kvStore}
|
||||||
|
}
|
30
internal/server/activities/add_target_history.go
Normal file
30
internal/server/activities/add_target_history.go
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Activities) AddTargetHistory(ctx context.Context, param temporal.ActivityAddTargetHistoryParam) (*temporal.ActivityAddTargetHistoryResult, error) {
|
||||||
|
|
||||||
|
status := models.TargetStatusUnknown
|
||||||
|
if param.Status == temporal.AddTargetHistoryStatusSuccess {
|
||||||
|
status = models.TargetStatusSuccess
|
||||||
|
}
|
||||||
|
if param.Status == temporal.AddTargetHistoryStatusFailure {
|
||||||
|
status = models.TargetStatusFailure
|
||||||
|
}
|
||||||
|
|
||||||
|
err := services.AddHistoryForTarget(ctx, a.db, &models.TargetHistory{
|
||||||
|
TargetId: param.Target.Id,
|
||||||
|
WorkerGroupId: param.WorkerGroupId,
|
||||||
|
CheckId: param.CheckId,
|
||||||
|
Status: status,
|
||||||
|
Note: param.Note,
|
||||||
|
})
|
||||||
|
|
||||||
|
return &temporal.ActivityAddTargetHistoryResult{}, err
|
||||||
|
}
|
73
internal/server/activities/targets_filter.go
Normal file
73
internal/server/activities/targets_filter.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/script"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Activities) TargetsFilter(ctx context.Context, param temporal.ActivityTargetsFilterParam) (*temporal.ActivityTargetsFilterResult, error) {
|
||||||
|
a.logger.Info("TargetsFilter", "filter", param.Filter)
|
||||||
|
|
||||||
|
allTargets, err := services.GetTargets(ctx, a.db)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
filteredTargets := make([]*temporal.Target, 0)
|
||||||
|
|
||||||
|
program, err := goja.Compile("filter", script.UnescapeString(param.Filter), false)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range allTargets {
|
||||||
|
vm := goja.New()
|
||||||
|
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||||
|
|
||||||
|
var metadata map[string]interface{}
|
||||||
|
err := yaml.Unmarshal([]byte(target.Metadata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a.logger.Info("TargetsFilter", "target", target)
|
||||||
|
|
||||||
|
targetWithMedatada := &struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
Group string
|
||||||
|
Metadata map[string]interface{}
|
||||||
|
}{
|
||||||
|
Id: target.Id,
|
||||||
|
Name: target.Name,
|
||||||
|
Group: target.Group,
|
||||||
|
Metadata: metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = vm.Set("target", targetWithMedatada)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
value, err := vm.RunProgram(program)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if value.Export().(bool) {
|
||||||
|
filteredTargets = append(filteredTargets, &temporal.Target{
|
||||||
|
Id: target.Id,
|
||||||
|
Name: target.Name,
|
||||||
|
Group: target.Group,
|
||||||
|
Metadata: target.Metadata,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &temporal.ActivityTargetsFilterResult{
|
||||||
|
Targets: filteredTargets,
|
||||||
|
}, nil
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
36
internal/server/handlers/api.go
Normal file
36
internal/server/handlers/api.go
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"database/sql"
|
||||||
|
"errors"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ApiV1WorkersConnectGETResponse struct {
|
||||||
|
Endpoint string `json:"endpoint"`
|
||||||
|
Group string `json:"group"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) ApiV1WorkersConnectGET(c echo.Context) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
cc := c.(AuthenticatedContext)
|
||||||
|
|
||||||
|
workerGroup, err := services.GetWorkerGroup(ctx, h.db, cc.Principal.Worker.Group)
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, sql.ErrNoRows) {
|
||||||
|
return echo.NewHTTPError(http.StatusUnauthorized, "Token invalid")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
response := ApiV1WorkersConnectGETResponse{
|
||||||
|
Endpoint: h.config.Temporal.ServerHost,
|
||||||
|
Group: workerGroup.Id,
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.JSON(http.StatusOK, response)
|
||||||
|
}
|
|
@ -7,11 +7,11 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
jwtInternal "code.tjo.space/mentos1386/zdravko/internal/jwt"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
jwtInternal "github.com/mentos1386/zdravko/pkg/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
const sessionName = "zdravko-hey"
|
const authenticationSessionName = "zdravko-hey"
|
||||||
|
|
||||||
type AuthenticatedPrincipal struct {
|
type AuthenticatedPrincipal struct {
|
||||||
User *AuthenticatedUser
|
User *AuthenticatedUser
|
||||||
|
@ -48,7 +48,7 @@ func GetUser(ctx context.Context) *AuthenticatedUser {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) AuthenticateRequestWithCookies(r *http.Request) (*AuthenticatedUser, error) {
|
func (h *BaseHandler) AuthenticateRequestWithCookies(r *http.Request) (*AuthenticatedUser, error) {
|
||||||
session, err := h.store.Get(r, sessionName)
|
session, err := h.store.Get(r, authenticationSessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ func (h *BaseHandler) AuthenticateRequestWithToken(r *http.Request) (*Authentica
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) SetAuthenticatedUserForRequest(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) error {
|
func (h *BaseHandler) SetAuthenticatedUserForRequest(w http.ResponseWriter, r *http.Request, user *AuthenticatedUser) error {
|
||||||
session, err := h.store.Get(r, sessionName)
|
session, err := h.store.Get(r, authenticationSessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -124,24 +124,16 @@ func (h *BaseHandler) SetAuthenticatedUserForRequest(w http.ResponseWriter, r *h
|
||||||
session.Values["oauth2_refresh_token"] = user.OAuth2RefreshToken
|
session.Values["oauth2_refresh_token"] = user.OAuth2RefreshToken
|
||||||
session.Values["oauth2_token_type"] = user.OAuth2TokenType
|
session.Values["oauth2_token_type"] = user.OAuth2TokenType
|
||||||
session.Values["oauth2_expiry"] = user.OAuth2Expiry.Format(time.RFC3339)
|
session.Values["oauth2_expiry"] = user.OAuth2Expiry.Format(time.RFC3339)
|
||||||
err = h.store.Save(r, w, session)
|
return h.store.Save(r, w, session)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) ClearAuthenticatedUserForRequest(w http.ResponseWriter, r *http.Request) error {
|
func (h *BaseHandler) ClearAuthenticatedUserForRequest(w http.ResponseWriter, r *http.Request) error {
|
||||||
session, err := h.store.Get(r, sessionName)
|
session, err := h.store.Get(r, authenticationSessionName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
session.Options.MaxAge = -1
|
session.Options.MaxAge = -1
|
||||||
err = h.store.Save(r, w, session)
|
return h.store.Save(r, w, session)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type AuthenticatedHandler func(http.ResponseWriter, *http.Request, *AuthenticatedPrincipal)
|
type AuthenticatedHandler func(http.ResponseWriter, *http.Request, *AuthenticatedPrincipal)
|
||||||
|
@ -159,7 +151,7 @@ func (h *BaseHandler) Authenticated(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
if user.OAuth2Expiry.Before(time.Now()) {
|
if user.OAuth2Expiry.Before(time.Now()) {
|
||||||
user, err = h.RefreshToken(c.Response(), c.Request(), user)
|
user, err = h.RefreshToken(c.Response(), c.Request(), user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, "/oauth2/login")
|
return c.Redirect(http.StatusTemporaryRedirect, "/oauth2/login?redirect="+c.Request().URL.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +165,6 @@ func (h *BaseHandler) Authenticated(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return next(cc)
|
return next(cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, "/oauth2/login")
|
return c.Redirect(http.StatusTemporaryRedirect, "/oauth2/login?redirect="+c.Request().URL.Path)
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -1,12 +1,8 @@
|
||||||
# Example trigger code
|
# Example trigger code
|
||||||
trigger: |
|
trigger: |
|
||||||
import kv from 'zdravko/kv';
|
import kv from 'k6/x/zdravko/kv';
|
||||||
import incidents, { severity } from 'zdravko/incidents';
|
import incidents, { severity } from 'k6/x/zdravko/incidents';
|
||||||
|
import { getTarget, getMonitor, getOutcome } from 'k6/x/zdravko';
|
||||||
// Only execute on this specific targets.
|
|
||||||
export function filter(target) {
|
|
||||||
return target.tags.kind === 'http';
|
|
||||||
}
|
|
||||||
|
|
||||||
const getMinute = (date) => {
|
const getMinute = (date) => {
|
||||||
return Math.floor(date.getTime() / 1000 / 60);
|
return Math.floor(date.getTime() / 1000 / 60);
|
||||||
|
@ -25,7 +21,11 @@ trigger: |
|
||||||
|
|
||||||
// This trigger will check if there were more than 5 issues in last
|
// This trigger will check if there were more than 5 issues in last
|
||||||
// 5 minutes, if so it will create a critical incident.
|
// 5 minutes, if so it will create a critical incident.
|
||||||
export default function (target, monitor, outcome) {
|
export default function () {
|
||||||
|
const target = getTarget();
|
||||||
|
const monitor = getMonitor();
|
||||||
|
const outcome = getOutcome();
|
||||||
|
|
||||||
// If the outcome is not failure, we close any potential incidents.
|
// If the outcome is not failure, we close any potential incidents.
|
||||||
if (outcome.status !== 'FAILURE') {
|
if (outcome.status !== 'FAILURE') {
|
||||||
incidents.close(target, monitor);
|
incidents.close(target, monitor);
|
||||||
|
@ -62,6 +62,7 @@ trigger: |
|
||||||
# Example monitor code
|
# Example monitor code
|
||||||
check: |
|
check: |
|
||||||
import http from 'k6/http';
|
import http from 'k6/http';
|
||||||
|
import { getTarget } from 'k6/x/zdravko';
|
||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
thresholds: {
|
thresholds: {
|
||||||
|
@ -70,12 +71,24 @@ check: |
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Filter out only HTTP targets.
|
|
||||||
export function filter(target) {
|
|
||||||
return target.tags.kind === "http";
|
|
||||||
};
|
|
||||||
|
|
||||||
// Execute the check on the targets.
|
// Execute the check on the targets.
|
||||||
export default function (target) {
|
export default function () {
|
||||||
http.get(target.url);
|
const { name, group, metadata } = getTarget();
|
||||||
|
|
||||||
|
console.log(`Running check for ${group}/${name}`)
|
||||||
|
|
||||||
|
http.get(metadata.spec.url);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
filter: |
|
||||||
|
target.metadata.kind == "Http" && target.metadata.spec.url != ""
|
||||||
|
|
||||||
|
target: |
|
||||||
|
kind: Http
|
||||||
|
labels:
|
||||||
|
production: "true"
|
||||||
|
spec:
|
||||||
|
url: "https://test.k6.io"
|
||||||
|
method: "GET"
|
||||||
|
headers:
|
||||||
|
User-Agent: "Zdravko"
|
|
@ -4,12 +4,12 @@ import (
|
||||||
"embed"
|
"embed"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/kv"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/gorilla/sessions"
|
"github.com/gorilla/sessions"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mentos1386/zdravko/database"
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/script"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
||||||
"gopkg.in/yaml.v2"
|
"gopkg.in/yaml.v2"
|
||||||
)
|
)
|
||||||
|
@ -18,8 +18,10 @@ import (
|
||||||
var examplesYaml embed.FS
|
var examplesYaml embed.FS
|
||||||
|
|
||||||
type examples struct {
|
type examples struct {
|
||||||
Check string `yaml:"check"`
|
Check string `yaml:"check"`
|
||||||
|
Filter string `yaml:"filter"`
|
||||||
Trigger string `yaml:"trigger"`
|
Trigger string `yaml:"trigger"`
|
||||||
|
Target string `yaml:"target"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var Pages = []*components.Page{
|
var Pages = []*components.Page{
|
||||||
|
@ -39,7 +41,7 @@ func GetPageByTitle(pages []*components.Page, title string) *components.Page {
|
||||||
|
|
||||||
type BaseHandler struct {
|
type BaseHandler struct {
|
||||||
db *sqlx.DB
|
db *sqlx.DB
|
||||||
kvStore kv.KeyValueStore
|
kvStore database.KeyValueStore
|
||||||
config *config.ServerConfig
|
config *config.ServerConfig
|
||||||
logger *slog.Logger
|
logger *slog.Logger
|
||||||
|
|
||||||
|
@ -50,7 +52,7 @@ type BaseHandler struct {
|
||||||
examples examples
|
examples examples
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBaseHandler(db *sqlx.DB, kvStore kv.KeyValueStore, temporal client.Client, config *config.ServerConfig, logger *slog.Logger) *BaseHandler {
|
func NewBaseHandler(db *sqlx.DB, kvStore database.KeyValueStore, temporal client.Client, config *config.ServerConfig, logger *slog.Logger) *BaseHandler {
|
||||||
store := sessions.NewCookieStore([]byte(config.SessionSecret))
|
store := sessions.NewCookieStore([]byte(config.SessionSecret))
|
||||||
|
|
||||||
examples := examples{}
|
examples := examples{}
|
||||||
|
@ -64,7 +66,9 @@ func NewBaseHandler(db *sqlx.DB, kvStore kv.KeyValueStore, temporal client.Clien
|
||||||
}
|
}
|
||||||
|
|
||||||
examples.Check = script.EscapeString(examples.Check)
|
examples.Check = script.EscapeString(examples.Check)
|
||||||
|
examples.Filter = script.EscapeString(examples.Filter)
|
||||||
examples.Trigger = script.EscapeString(examples.Trigger)
|
examples.Trigger = script.EscapeString(examples.Trigger)
|
||||||
|
examples.Target = script.EscapeString(examples.Target)
|
||||||
|
|
||||||
return &BaseHandler{
|
return &BaseHandler{
|
||||||
db: db,
|
db: db,
|
|
@ -3,7 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
|
@ -5,30 +5,30 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type IndexData struct {
|
type IndexData struct {
|
||||||
*components.Base
|
*components.Base
|
||||||
Checks map[string]ChecksAndStatus
|
Targets map[string]TargetsAndStatus
|
||||||
ChecksLength int
|
TargetsLength int
|
||||||
TimeRange string
|
TimeRange string
|
||||||
Status models.CheckStatus
|
Status models.TargetStatus
|
||||||
}
|
}
|
||||||
|
|
||||||
type Check struct {
|
type Target struct {
|
||||||
Name string
|
Name string
|
||||||
Visibility models.CheckVisibility
|
Visibility models.TargetVisibility
|
||||||
Group string
|
Group string
|
||||||
Status models.CheckStatus
|
Status models.TargetStatus
|
||||||
History *History
|
History *History
|
||||||
}
|
}
|
||||||
|
|
||||||
type HistoryItem struct {
|
type HistoryItem struct {
|
||||||
Status models.CheckStatus
|
Status models.TargetStatus
|
||||||
Date time.Time
|
Date time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,23 +37,23 @@ type History struct {
|
||||||
Uptime float64
|
Uptime float64
|
||||||
}
|
}
|
||||||
|
|
||||||
type ChecksAndStatus struct {
|
type TargetsAndStatus struct {
|
||||||
Status models.CheckStatus
|
Status models.TargetStatus
|
||||||
Checks []*Check
|
Targets []*Target
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDateString(date time.Time) string {
|
func getDateString(date time.Time) string {
|
||||||
return date.UTC().Format("2006-01-02T15:04:05")
|
return date.UTC().Format("2006-01-02T15:04:05")
|
||||||
}
|
}
|
||||||
|
|
||||||
func getHistory(history []*models.CheckHistory, period time.Duration, buckets int) *History {
|
func getHistory(history []*services.TargetHistory, period time.Duration, buckets int) *History {
|
||||||
historyMap := map[string]models.CheckStatus{}
|
historyMap := map[string]models.TargetStatus{}
|
||||||
numOfSuccess := 0.0
|
numOfSuccess := 0.0
|
||||||
numTotal := 0.0
|
numTotal := 0.0
|
||||||
|
|
||||||
for i := 0; i < buckets; i++ {
|
for i := 0; i < buckets; i++ {
|
||||||
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
dateString := getDateString(time.Now().Add(period * time.Duration(-i)).Truncate(period))
|
||||||
historyMap[dateString] = models.CheckStatusUnknown
|
historyMap[dateString] = models.TargetStatusUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, _history := range history {
|
for _, _history := range history {
|
||||||
|
@ -65,16 +65,16 @@ func getHistory(history []*models.CheckHistory, period time.Duration, buckets in
|
||||||
}
|
}
|
||||||
|
|
||||||
numTotal++
|
numTotal++
|
||||||
if _history.Status == models.CheckStatusSuccess {
|
if _history.Status == models.TargetStatusSuccess {
|
||||||
numOfSuccess++
|
numOfSuccess++
|
||||||
}
|
}
|
||||||
|
|
||||||
// skip if it is already set to failure
|
// skip if it is already set to failure
|
||||||
if historyMap[dateString] == models.CheckStatusFailure {
|
if historyMap[dateString] == models.TargetStatusFailure {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME: This is wrong! As we can have multiple checks in dateString.
|
// FIXME: This is wrong! As we can have multiple targets in dateString.
|
||||||
// We should look at only the newest one.
|
// We should look at only the newest one.
|
||||||
historyMap[dateString] = _history.Status
|
historyMap[dateString] = _history.Status
|
||||||
}
|
}
|
||||||
|
@ -102,7 +102,7 @@ func getHistory(history []*models.CheckHistory, period time.Duration, buckets in
|
||||||
|
|
||||||
func (h *BaseHandler) Index(c echo.Context) error {
|
func (h *BaseHandler) Index(c echo.Context) error {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
checks, err := services.GetChecks(ctx, h.db)
|
targets, err := services.GetTargets(ctx, h.db)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -112,12 +112,12 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
||||||
timeRange = "90days"
|
timeRange = "90days"
|
||||||
}
|
}
|
||||||
|
|
||||||
overallStatus := models.CheckStatusUnknown
|
overallStatus := models.TargetStatusUnknown
|
||||||
statusByGroup := make(map[string]models.CheckStatus)
|
statusByGroup := make(map[string]models.TargetStatus)
|
||||||
|
|
||||||
checksWithHistory := make([]*Check, len(checks))
|
targetsWithHistory := make([]*Target, len(targets))
|
||||||
for i, check := range checks {
|
for i, target := range targets {
|
||||||
history, err := services.GetCheckHistoryForCheck(ctx, h.db, check.Id)
|
history, err := services.GetTargetHistoryForTarget(ctx, h.db, target.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -132,38 +132,38 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
||||||
historyResult = getHistory(history, time.Minute, 90)
|
historyResult = getHistory(history, time.Minute, 90)
|
||||||
}
|
}
|
||||||
|
|
||||||
if statusByGroup[check.Group] == "" {
|
if statusByGroup[target.Group] == "" {
|
||||||
statusByGroup[check.Group] = models.CheckStatusUnknown
|
statusByGroup[target.Group] = models.TargetStatusUnknown
|
||||||
}
|
}
|
||||||
|
|
||||||
status := historyResult.List[len(historyResult.List)-1]
|
status := historyResult.List[len(historyResult.List)-1]
|
||||||
if status.Status == models.CheckStatusSuccess {
|
if status.Status == models.TargetStatusSuccess {
|
||||||
if overallStatus == models.CheckStatusUnknown {
|
if overallStatus == models.TargetStatusUnknown {
|
||||||
overallStatus = status.Status
|
overallStatus = status.Status
|
||||||
}
|
}
|
||||||
if statusByGroup[check.Group] == models.CheckStatusUnknown {
|
if statusByGroup[target.Group] == models.TargetStatusUnknown {
|
||||||
statusByGroup[check.Group] = status.Status
|
statusByGroup[target.Group] = status.Status
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if status.Status != models.CheckStatusSuccess && status.Status != models.CheckStatusUnknown {
|
if status.Status != models.TargetStatusSuccess && status.Status != models.TargetStatusUnknown {
|
||||||
overallStatus = status.Status
|
overallStatus = status.Status
|
||||||
statusByGroup[check.Group] = status.Status
|
statusByGroup[target.Group] = status.Status
|
||||||
}
|
}
|
||||||
|
|
||||||
checksWithHistory[i] = &Check{
|
targetsWithHistory[i] = &Target{
|
||||||
Name: check.Name,
|
Name: target.Name,
|
||||||
Visibility: check.Visibility,
|
Visibility: target.Visibility,
|
||||||
Group: check.Group,
|
Group: target.Group,
|
||||||
Status: status.Status,
|
Status: status.Status,
|
||||||
History: historyResult,
|
History: historyResult,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checksByGroup := map[string]ChecksAndStatus{}
|
targetsByGroup := map[string]TargetsAndStatus{}
|
||||||
for _, check := range checksWithHistory {
|
for _, target := range targetsWithHistory {
|
||||||
checksByGroup[check.Group] = ChecksAndStatus{
|
targetsByGroup[target.Group] = TargetsAndStatus{
|
||||||
Status: statusByGroup[check.Group],
|
Status: statusByGroup[target.Group],
|
||||||
Checks: append(checksByGroup[check.Group].Checks, check),
|
Targets: append(targetsByGroup[target.Group].Targets, target),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -174,7 +174,7 @@ func (h *BaseHandler) Index(c echo.Context) error {
|
||||||
NavbarActive: GetPageByTitle(Pages, "Status"),
|
NavbarActive: GetPageByTitle(Pages, "Status"),
|
||||||
Navbar: Pages,
|
Navbar: Pages,
|
||||||
},
|
},
|
||||||
Checks: checksByGroup,
|
Targets: targetsByGroup,
|
||||||
TimeRange: timeRange,
|
TimeRange: timeRange,
|
||||||
Status: overallStatus,
|
Status: overallStatus,
|
||||||
})
|
})
|
|
@ -12,13 +12,52 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
"golang.org/x/oauth2"
|
"golang.org/x/oauth2"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const oauth2RedirectSessionName = "zdravko-hey-oauth2"
|
||||||
|
|
||||||
|
func (h *BaseHandler) setOAuth2Redirect(c echo.Context, redirect string) error {
|
||||||
|
w := c.Response()
|
||||||
|
r := c.Request()
|
||||||
|
|
||||||
|
session, err := h.store.Get(r, oauth2RedirectSessionName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.Values["redirect"] = redirect
|
||||||
|
return h.store.Save(r, w, session)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) getOAuth2Redirect(c echo.Context) (string, error) {
|
||||||
|
r := c.Request()
|
||||||
|
|
||||||
|
session, err := h.store.Get(r, oauth2RedirectSessionName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if session.IsNew {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
return session.Values["redirect"].(string), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) clearOAuth2Redirect(c echo.Context) error {
|
||||||
|
w := c.Response()
|
||||||
|
r := c.Request()
|
||||||
|
|
||||||
|
session, err := h.store.Get(r, oauth2RedirectSessionName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
session.Options.MaxAge = -1
|
||||||
|
return h.store.Save(r, w, session)
|
||||||
|
}
|
||||||
|
|
||||||
type UserInfo struct {
|
type UserInfo struct {
|
||||||
Id int `json:"id"` // FIXME: This might not always be int?
|
Id int `json:"id"` // FIXME: This might not always be int?
|
||||||
Sub string `json:"sub"`
|
Sub string `json:"sub"`
|
||||||
|
@ -97,6 +136,14 @@ func (h *BaseHandler) OAuth2LoginGET(c echo.Context) error {
|
||||||
|
|
||||||
url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline)
|
url := conf.AuthCodeURL(state, oauth2.AccessTypeOffline)
|
||||||
|
|
||||||
|
redirect := c.QueryParam("redirect")
|
||||||
|
h.logger.Info("OAuth2LoginGET", "redirect", redirect)
|
||||||
|
|
||||||
|
err = h.setOAuth2Redirect(c, redirect)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, url)
|
return c.Redirect(http.StatusTemporaryRedirect, url)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -156,7 +203,21 @@ func (h *BaseHandler) OAuth2CallbackGET(c echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return c.Redirect(http.StatusTemporaryRedirect, "/settings")
|
redirect, err := h.getOAuth2Redirect(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
h.logger.Info("OAuth2CallbackGET", "redirect", redirect)
|
||||||
|
if redirect == "" {
|
||||||
|
redirect = "/settings"
|
||||||
|
}
|
||||||
|
|
||||||
|
err = h.clearOAuth2Redirect(c)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusTemporaryRedirect, redirect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) OAuth2LogoutGET(c echo.Context) error {
|
func (h *BaseHandler) OAuth2LogoutGET(c echo.Context) error {
|
|
@ -3,9 +3,9 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SettingsSidebarGroup struct {
|
type SettingsSidebarGroup struct {
|
||||||
|
@ -115,7 +115,7 @@ type SettingsHome struct {
|
||||||
WorkerGroupsCount int
|
WorkerGroupsCount int
|
||||||
ChecksCount int
|
ChecksCount int
|
||||||
NotificationsCount int
|
NotificationsCount int
|
||||||
History []*services.CheckHistoryWithCheck
|
History []*services.CheckHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) SettingsHomeGET(c echo.Context) error {
|
func (h *BaseHandler) SettingsHomeGET(c echo.Context) error {
|
||||||
|
@ -132,7 +132,7 @@ func (h *BaseHandler) SettingsHomeGET(c echo.Context) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
history, err := services.GetLastNCheckHistory(ctx, h.db, 10)
|
history, err := services.GetLastNCheckHistory(ctx, h.temporal, 10)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
|
@ -5,33 +5,31 @@ import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"slices"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/script"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateCheck struct {
|
type CreateCheck struct {
|
||||||
Name string `validate:"required"`
|
Name string `validate:"required"`
|
||||||
Group string `validate:"required"`
|
|
||||||
WorkerGroups string `validate:"required"`
|
WorkerGroups string `validate:"required"`
|
||||||
Schedule string `validate:"required,cron"`
|
Schedule string `validate:"required,cron"`
|
||||||
Script string `validate:"required"`
|
Script string `validate:"required"`
|
||||||
Visibility string `validate:"required,oneof=PUBLIC PRIVATE"`
|
Filter string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateCheck struct {
|
type UpdateCheck struct {
|
||||||
Group string `validate:"required"`
|
|
||||||
WorkerGroups string `validate:"required"`
|
WorkerGroups string `validate:"required"`
|
||||||
Schedule string `validate:"required,cron"`
|
Schedule string `validate:"required,cron"`
|
||||||
Script string `validate:"required"`
|
Script string `validate:"required"`
|
||||||
Visibility string `validate:"required,oneof=PUBLIC PRIVATE"`
|
Filter string `validate:"required"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CheckWithWorkerGroupsAndState struct {
|
type CheckWithWorkerGroupsAndState struct {
|
||||||
|
@ -41,19 +39,24 @@ type CheckWithWorkerGroupsAndState struct {
|
||||||
|
|
||||||
type SettingsChecks struct {
|
type SettingsChecks struct {
|
||||||
*Settings
|
*Settings
|
||||||
Checks map[string][]*CheckWithWorkerGroupsAndState
|
Checks []*CheckWithWorkerGroupsAndState
|
||||||
CheckGroups []string
|
History []struct {
|
||||||
|
CreatedAt time.Time
|
||||||
|
Status string
|
||||||
|
Note string
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsCheck struct {
|
type SettingsCheck struct {
|
||||||
*Settings
|
*Settings
|
||||||
Check *CheckWithWorkerGroupsAndState
|
Check *CheckWithWorkerGroupsAndState
|
||||||
History []*models.CheckHistory
|
History []*services.CheckHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsCheckCreate struct {
|
type SettingsCheckCreate struct {
|
||||||
*Settings
|
*Settings
|
||||||
Example string
|
ExampleScript string
|
||||||
|
ExampleFilter string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
|
func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
|
||||||
|
@ -68,7 +71,8 @@ func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
|
||||||
for i, check := range checks {
|
for i, check := range checks {
|
||||||
state, err := services.GetCheckState(context.Background(), h.temporal, check.Id)
|
state, err := services.GetCheckState(context.Background(), h.temporal, check.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
h.logger.Error("Failed to get check state", "error", err)
|
||||||
|
state = models.CheckStateUnknown
|
||||||
}
|
}
|
||||||
checksWithState[i] = &CheckWithWorkerGroupsAndState{
|
checksWithState[i] = &CheckWithWorkerGroupsAndState{
|
||||||
CheckWithWorkerGroups: check,
|
CheckWithWorkerGroups: check,
|
||||||
|
@ -76,23 +80,13 @@ func (h *BaseHandler) SettingsChecksGET(c echo.Context) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
checkGroups := []string{}
|
|
||||||
checksByGroup := map[string][]*CheckWithWorkerGroupsAndState{}
|
|
||||||
for _, check := range checksWithState {
|
|
||||||
checksByGroup[check.Group] = append(checksByGroup[check.Group], check)
|
|
||||||
if !slices.Contains(checkGroups, check.Group) {
|
|
||||||
checkGroups = append(checkGroups, check.Group)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.Render(http.StatusOK, "settings_checks.tmpl", &SettingsChecks{
|
return c.Render(http.StatusOK, "settings_checks.tmpl", &SettingsChecks{
|
||||||
Settings: NewSettings(
|
Settings: NewSettings(
|
||||||
cc.Principal.User,
|
cc.Principal.User,
|
||||||
GetPageByTitle(SettingsPages, "Checks"),
|
GetPageByTitle(SettingsPages, "Checks"),
|
||||||
[]*components.Page{GetPageByTitle(SettingsPages, "Checks")},
|
[]*components.Page{GetPageByTitle(SettingsPages, "Checks")},
|
||||||
),
|
),
|
||||||
Checks: checksByGroup,
|
Checks: checksWithState,
|
||||||
CheckGroups: checkGroups,
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +110,7 @@ func (h *BaseHandler) SettingsChecksDescribeGET(c echo.Context) error {
|
||||||
State: status,
|
State: status,
|
||||||
}
|
}
|
||||||
|
|
||||||
history, err := services.GetCheckHistoryForCheck(context.Background(), h.db, slug)
|
history, err := services.GetCheckHistoryForCheck(context.Background(), h.temporal, slug)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -196,11 +190,10 @@ func (h *BaseHandler) SettingsChecksDescribePOST(c echo.Context) error {
|
||||||
checkId := c.Param("id")
|
checkId := c.Param("id")
|
||||||
|
|
||||||
update := UpdateCheck{
|
update := UpdateCheck{
|
||||||
Group: strings.ToLower(c.FormValue("group")),
|
|
||||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||||
Schedule: c.FormValue("schedule"),
|
Schedule: c.FormValue("schedule"),
|
||||||
Script: script.EscapeString(c.FormValue("script")),
|
Script: script.EscapeString(c.FormValue("script")),
|
||||||
Visibility: c.FormValue("visibility"),
|
Filter: c.FormValue("filter"),
|
||||||
}
|
}
|
||||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(update)
|
err := validator.New(validator.WithRequiredStructEnabled()).Struct(update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -211,10 +204,9 @@ func (h *BaseHandler) SettingsChecksDescribePOST(c echo.Context) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
check.Group = update.Group
|
|
||||||
check.Schedule = update.Schedule
|
check.Schedule = update.Schedule
|
||||||
check.Script = update.Script
|
check.Script = update.Script
|
||||||
check.Visibility = models.CheckVisibility(update.Visibility)
|
check.Filter = update.Filter
|
||||||
|
|
||||||
err = services.UpdateCheck(
|
err = services.UpdateCheck(
|
||||||
ctx,
|
ctx,
|
||||||
|
@ -270,7 +262,8 @@ func (h *BaseHandler) SettingsChecksCreateGET(c echo.Context) error {
|
||||||
GetPageByTitle(SettingsPages, "Checks Create"),
|
GetPageByTitle(SettingsPages, "Checks Create"),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Example: h.examples.Check,
|
ExampleScript: h.examples.Check,
|
||||||
|
ExampleFilter: h.examples.Filter,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,11 +273,10 @@ func (h *BaseHandler) SettingsChecksCreatePOST(c echo.Context) error {
|
||||||
|
|
||||||
create := CreateCheck{
|
create := CreateCheck{
|
||||||
Name: c.FormValue("name"),
|
Name: c.FormValue("name"),
|
||||||
Group: strings.ToLower(c.FormValue("group")),
|
|
||||||
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
WorkerGroups: strings.ToLower(strings.TrimSpace(c.FormValue("workergroups"))),
|
||||||
Schedule: c.FormValue("schedule"),
|
Schedule: c.FormValue("schedule"),
|
||||||
Script: script.EscapeString(c.FormValue("script")),
|
Script: script.EscapeString(c.FormValue("script")),
|
||||||
Visibility: c.FormValue("visibility"),
|
Filter: c.FormValue("filter"),
|
||||||
}
|
}
|
||||||
err := validator.New(validator.WithRequiredStructEnabled()).Struct(create)
|
err := validator.New(validator.WithRequiredStructEnabled()).Struct(create)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -312,12 +304,11 @@ func (h *BaseHandler) SettingsChecksCreatePOST(c echo.Context) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
check := &models.Check{
|
check := &models.Check{
|
||||||
Name: create.Name,
|
Name: create.Name,
|
||||||
Group: create.Group,
|
Id: checkId,
|
||||||
Id: checkId,
|
Schedule: create.Schedule,
|
||||||
Schedule: create.Schedule,
|
Script: create.Script,
|
||||||
Script: create.Script,
|
Filter: create.Filter,
|
||||||
Visibility: models.CheckVisibility(create.Visibility),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = services.CreateCheck(
|
err = services.CreateCheck(
|
|
@ -3,7 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package handlers
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
240
internal/server/handlers/settings_targets.go
Normal file
240
internal/server/handlers/settings_targets.go
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
package handlers
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"slices"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/go-playground/validator/v10"
|
||||||
|
"github.com/gosimple/slug"
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CreateTarget struct {
|
||||||
|
Name string `validate:"required"`
|
||||||
|
Group string `validate:"required"`
|
||||||
|
Visibility string `validate:"required,oneof=PUBLIC PRIVATE"`
|
||||||
|
Metadata string `validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type UpdateTarget struct {
|
||||||
|
Group string `validate:"required"`
|
||||||
|
Visibility string `validate:"required,oneof=PUBLIC PRIVATE"`
|
||||||
|
Metadata string `validate:"required"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsTargets struct {
|
||||||
|
*Settings
|
||||||
|
Targets map[string][]*models.Target
|
||||||
|
TargetGroups []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsTarget struct {
|
||||||
|
*Settings
|
||||||
|
Target *models.Target
|
||||||
|
History []*services.TargetHistory
|
||||||
|
}
|
||||||
|
|
||||||
|
type SettingsTargetCreate struct {
|
||||||
|
*Settings
|
||||||
|
Example string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsGET(c echo.Context) error {
|
||||||
|
cc := c.(AuthenticatedContext)
|
||||||
|
|
||||||
|
targets, err := services.GetTargets(context.Background(), h.db)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
targetGroups := []string{}
|
||||||
|
targetsByGroup := map[string][]*models.Target{}
|
||||||
|
for _, target := range targets {
|
||||||
|
targetsByGroup[target.Group] = append(targetsByGroup[target.Group], target)
|
||||||
|
if !slices.Contains(targetGroups, target.Group) {
|
||||||
|
targetGroups = append(targetGroups, target.Group)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "settings_targets.tmpl", &SettingsTargets{
|
||||||
|
Settings: NewSettings(
|
||||||
|
cc.Principal.User,
|
||||||
|
GetPageByTitle(SettingsPages, "Targets"),
|
||||||
|
[]*components.Page{GetPageByTitle(SettingsPages, "Targets")},
|
||||||
|
),
|
||||||
|
Targets: targetsByGroup,
|
||||||
|
TargetGroups: targetGroups,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsDescribeGET(c echo.Context) error {
|
||||||
|
cc := c.(AuthenticatedContext)
|
||||||
|
|
||||||
|
slug := c.Param("id")
|
||||||
|
|
||||||
|
target, err := services.GetTarget(context.Background(), h.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
history, err := services.GetTargetHistoryForTarget(context.Background(), h.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
maxElements := 10
|
||||||
|
if len(history) < maxElements {
|
||||||
|
maxElements = len(history)
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "settings_targets_describe.tmpl", &SettingsTarget{
|
||||||
|
Settings: NewSettings(
|
||||||
|
cc.Principal.User,
|
||||||
|
GetPageByTitle(SettingsPages, "Targets"),
|
||||||
|
[]*components.Page{
|
||||||
|
GetPageByTitle(SettingsPages, "Targets"),
|
||||||
|
{
|
||||||
|
Path: fmt.Sprintf("/settings/targets/%s", slug),
|
||||||
|
Title: "Describe",
|
||||||
|
Breadcrumb: target.Name,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
Target: target,
|
||||||
|
History: history[:maxElements],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsDescribeDELETE(c echo.Context) error {
|
||||||
|
slug := c.Param("id")
|
||||||
|
|
||||||
|
err := services.DeleteTarget(context.Background(), h.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/settings/targets")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsDisableGET(c echo.Context) error {
|
||||||
|
slug := c.Param("id")
|
||||||
|
|
||||||
|
target, err := services.GetTarget(context.Background(), h.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = services.SetTargetState(context.Background(), h.db, target.Id, models.TargetStatePaused)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/targets/%s", slug))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsEnableGET(c echo.Context) error {
|
||||||
|
slug := c.Param("id")
|
||||||
|
|
||||||
|
target, err := services.GetTarget(context.Background(), h.db, slug)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = services.SetTargetState(context.Background(), h.db, target.Id, models.TargetStateActive)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/targets/%s", slug))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsDescribePOST(c echo.Context) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
targetId := c.Param("id")
|
||||||
|
|
||||||
|
update := UpdateTarget{
|
||||||
|
Group: strings.ToLower(c.FormValue("group")),
|
||||||
|
Visibility: c.FormValue("visibility"),
|
||||||
|
Metadata: c.FormValue("metadata"),
|
||||||
|
}
|
||||||
|
err := validator.New(validator.WithRequiredStructEnabled()).Struct(update)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
target, err := services.GetTarget(ctx, h.db, targetId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
target.Group = update.Group
|
||||||
|
target.Visibility = models.TargetVisibility(update.Visibility)
|
||||||
|
target.Metadata = update.Metadata
|
||||||
|
|
||||||
|
err = services.UpdateTarget(
|
||||||
|
ctx,
|
||||||
|
h.db,
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, fmt.Sprintf("/settings/targets/%s", targetId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsCreateGET(c echo.Context) error {
|
||||||
|
cc := c.(AuthenticatedContext)
|
||||||
|
|
||||||
|
return c.Render(http.StatusOK, "settings_targets_create.tmpl", &SettingsTargetCreate{
|
||||||
|
Settings: NewSettings(
|
||||||
|
cc.Principal.User,
|
||||||
|
GetPageByTitle(SettingsPages, "Targets"),
|
||||||
|
[]*components.Page{
|
||||||
|
GetPageByTitle(SettingsPages, "Targets"),
|
||||||
|
GetPageByTitle(SettingsPages, "Targets Create"),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Example: h.examples.Target,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *BaseHandler) SettingsTargetsCreatePOST(c echo.Context) error {
|
||||||
|
ctx := context.Background()
|
||||||
|
targetId := slug.Make(c.FormValue("name"))
|
||||||
|
|
||||||
|
create := CreateTarget{
|
||||||
|
Name: c.FormValue("name"),
|
||||||
|
Group: strings.ToLower(c.FormValue("group")),
|
||||||
|
Visibility: c.FormValue("visibility"),
|
||||||
|
Metadata: c.FormValue("metadata"),
|
||||||
|
}
|
||||||
|
err := validator.New(validator.WithRequiredStructEnabled()).Struct(create)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
target := &models.Target{
|
||||||
|
Name: create.Name,
|
||||||
|
Group: create.Group,
|
||||||
|
Id: targetId,
|
||||||
|
Visibility: models.TargetVisibility(create.Visibility),
|
||||||
|
State: models.TargetStateActive,
|
||||||
|
Metadata: create.Metadata,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = services.CreateTarget(
|
||||||
|
ctx,
|
||||||
|
h.db,
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.Redirect(http.StatusSeeOther, "/settings/targets")
|
||||||
|
}
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/script"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type CreateTrigger struct {
|
type CreateTrigger struct {
|
||||||
|
@ -36,7 +36,7 @@ type SettingsTriggers struct {
|
||||||
type SettingsTrigger struct {
|
type SettingsTrigger struct {
|
||||||
*Settings
|
*Settings
|
||||||
Trigger *TriggerWithState
|
Trigger *TriggerWithState
|
||||||
History []*models.TriggerHistory
|
History []*services.TriggerHistory
|
||||||
}
|
}
|
||||||
|
|
||||||
type SettingsTriggerCreate struct {
|
type SettingsTriggerCreate struct {
|
|
@ -6,13 +6,13 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/services"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates/components"
|
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
"github.com/gosimple/slug"
|
"github.com/gosimple/slug"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/services"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/jwt"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates/components"
|
||||||
)
|
)
|
||||||
|
|
||||||
type WorkerWithTokenAndActiveWorkers struct {
|
type WorkerWithTokenAndActiveWorkers struct {
|
||||||
|
@ -52,7 +52,7 @@ func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
|
||||||
}
|
}
|
||||||
workerGroupsWithActiveWorkers[i] = &WorkerGroupWithActiveWorkers{
|
workerGroupsWithActiveWorkers[i] = &WorkerGroupWithActiveWorkers{
|
||||||
WorkerGroupWithChecks: workerGroup,
|
WorkerGroupWithChecks: workerGroup,
|
||||||
ActiveWorkers: activeWorkers,
|
ActiveWorkers: activeWorkers,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,8 @@ import (
|
||||||
"net/http/httputil"
|
"net/http/httputil"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/jwt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *BaseHandler) Temporal(c echo.Context) error {
|
func (h *BaseHandler) Temporal(c echo.Context) error {
|
|
@ -6,9 +6,9 @@ import (
|
||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/workflows"
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
internaltemporal "github.com/mentos1386/zdravko/internal/temporal"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
||||||
"go.temporal.io/sdk/temporal"
|
"go.temporal.io/sdk/temporal"
|
||||||
"golang.org/x/exp/maps"
|
"golang.org/x/exp/maps"
|
||||||
|
@ -55,8 +55,8 @@ func SetCheckState(ctx context.Context, temporal client.Client, id string, state
|
||||||
|
|
||||||
func CreateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
|
func CreateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
|
||||||
_, err := db.NamedExecContext(ctx,
|
_, err := db.NamedExecContext(ctx,
|
||||||
`INSERT INTO checks (id, name, visibility, "group", script, schedule)
|
`INSERT INTO checks (id, name, script, schedule, filter)
|
||||||
VALUES (:id, :name, :visibility, :group, :script, :schedule)`,
|
VALUES (:id, :name, :script, :schedule, :filter)`,
|
||||||
check,
|
check,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
@ -64,7 +64,7 @@ func CreateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
|
||||||
|
|
||||||
func UpdateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
|
func UpdateCheck(ctx context.Context, db *sqlx.DB, check *models.Check) error {
|
||||||
_, err := db.NamedExecContext(ctx,
|
_, err := db.NamedExecContext(ctx,
|
||||||
`UPDATE checks SET visibility=:visibility, "group"=:group, script=:script, schedule=:schedule WHERE id=:id`,
|
`UPDATE checks SET script=:script, schedule=:schedule, filter=:filter WHERE id=:id`,
|
||||||
check,
|
check,
|
||||||
)
|
)
|
||||||
return err
|
return err
|
||||||
|
@ -120,12 +120,11 @@ func GetCheckWithWorkerGroups(ctx context.Context, db *sqlx.DB, id string) (*mod
|
||||||
SELECT
|
SELECT
|
||||||
checks.id,
|
checks.id,
|
||||||
checks.name,
|
checks.name,
|
||||||
checks.visibility,
|
|
||||||
checks."group",
|
|
||||||
checks.script,
|
checks.script,
|
||||||
checks.schedule,
|
checks.schedule,
|
||||||
checks.created_at,
|
checks.created_at,
|
||||||
checks.updated_at,
|
checks.updated_at,
|
||||||
|
checks.filter,
|
||||||
worker_groups.name as worker_group_name
|
worker_groups.name as worker_group_name
|
||||||
FROM checks
|
FROM checks
|
||||||
LEFT OUTER JOIN check_worker_groups ON checks.id = check_worker_groups.check_id
|
LEFT OUTER JOIN check_worker_groups ON checks.id = check_worker_groups.check_id
|
||||||
|
@ -147,12 +146,11 @@ ORDER BY checks.name
|
||||||
err = rows.Scan(
|
err = rows.Scan(
|
||||||
&check.Id,
|
&check.Id,
|
||||||
&check.Name,
|
&check.Name,
|
||||||
&check.Visibility,
|
|
||||||
&check.Group,
|
|
||||||
&check.Script,
|
&check.Script,
|
||||||
&check.Schedule,
|
&check.Schedule,
|
||||||
&check.CreatedAt,
|
&check.CreatedAt,
|
||||||
&check.UpdatedAt,
|
&check.UpdatedAt,
|
||||||
|
&check.Filter,
|
||||||
&workerGroupName,
|
&workerGroupName,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -180,12 +178,11 @@ func GetChecksWithWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.Chec
|
||||||
SELECT
|
SELECT
|
||||||
checks.id,
|
checks.id,
|
||||||
checks.name,
|
checks.name,
|
||||||
checks.visibility,
|
|
||||||
checks."group",
|
|
||||||
checks.script,
|
checks.script,
|
||||||
checks.schedule,
|
checks.schedule,
|
||||||
checks.created_at,
|
checks.created_at,
|
||||||
checks.updated_at,
|
checks.updated_at,
|
||||||
|
checks.filter,
|
||||||
worker_groups.name as worker_group_name
|
worker_groups.name as worker_group_name
|
||||||
FROM checks
|
FROM checks
|
||||||
LEFT OUTER JOIN check_worker_groups ON checks.id = check_worker_groups.check_id
|
LEFT OUTER JOIN check_worker_groups ON checks.id = check_worker_groups.check_id
|
||||||
|
@ -206,12 +203,11 @@ ORDER BY checks.name
|
||||||
err = rows.Scan(
|
err = rows.Scan(
|
||||||
&check.Id,
|
&check.Id,
|
||||||
&check.Name,
|
&check.Name,
|
||||||
&check.Visibility,
|
|
||||||
&check.Group,
|
|
||||||
&check.Script,
|
&check.Script,
|
||||||
&check.Schedule,
|
&check.Schedule,
|
||||||
&check.CreatedAt,
|
&check.CreatedAt,
|
||||||
&check.UpdatedAt,
|
&check.UpdatedAt,
|
||||||
|
&check.Filter,
|
||||||
&workerGroupName,
|
&workerGroupName,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -254,7 +250,8 @@ func CreateOrUpdateCheckSchedule(
|
||||||
}
|
}
|
||||||
|
|
||||||
args := make([]interface{}, 1)
|
args := make([]interface{}, 1)
|
||||||
args[0] = workflows.CheckWorkflowParam{
|
args[0] = internaltemporal.WorkflowCheckParam{
|
||||||
|
Filter: check.Filter,
|
||||||
Script: check.Script,
|
Script: check.Script,
|
||||||
CheckId: check.Id,
|
CheckId: check.Id,
|
||||||
WorkerGroupIds: workerGroupStrings,
|
WorkerGroupIds: workerGroupStrings,
|
||||||
|
@ -268,9 +265,9 @@ func CreateOrUpdateCheckSchedule(
|
||||||
},
|
},
|
||||||
Action: &client.ScheduleWorkflowAction{
|
Action: &client.ScheduleWorkflowAction{
|
||||||
ID: getScheduleId(check.Id),
|
ID: getScheduleId(check.Id),
|
||||||
Workflow: workflows.NewWorkflows(nil).CheckWorkflowDefinition,
|
Workflow: internaltemporal.WorkflowCheckName,
|
||||||
Args: args,
|
Args: args,
|
||||||
TaskQueue: "default",
|
TaskQueue: internaltemporal.TEMPORAL_SERVER_QUEUE,
|
||||||
RetryPolicy: &temporal.RetryPolicy{
|
RetryPolicy: &temporal.RetryPolicy{
|
||||||
MaximumAttempts: 3,
|
MaximumAttempts: 3,
|
||||||
},
|
},
|
105
internal/server/services/check_history.go
Normal file
105
internal/server/services/check_history.go
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
"go.temporal.io/api/enums/v1"
|
||||||
|
"go.temporal.io/api/workflowservice/v1"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type CheckHistory struct {
|
||||||
|
CheckId string
|
||||||
|
Status string
|
||||||
|
Duration time.Duration
|
||||||
|
StartTime time.Time
|
||||||
|
EndTime time.Time
|
||||||
|
WorkerGroupName string
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLastNCheckHistory(ctx context.Context, t client.Client, n int32) ([]*CheckHistory, error) {
|
||||||
|
var checkHistory []*CheckHistory
|
||||||
|
|
||||||
|
response, err := t.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||||
|
PageSize: n,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return checkHistory, err
|
||||||
|
}
|
||||||
|
|
||||||
|
executions := response.GetExecutions()
|
||||||
|
|
||||||
|
for _, execution := range executions {
|
||||||
|
scheduleId := string(execution.GetSearchAttributes().GetIndexedFields()["TemporalScheduledById"].Data)
|
||||||
|
|
||||||
|
// Remove the quotes around the checkId and the prefix.
|
||||||
|
checkId := scheduleId[len("\"check-") : len(scheduleId)-1]
|
||||||
|
|
||||||
|
var result temporal.WorkflowCheckResult
|
||||||
|
if execution.Status != enums.WORKFLOW_EXECUTION_STATUS_RUNNING {
|
||||||
|
workflowRun := t.GetWorkflow(ctx, execution.GetExecution().GetWorkflowId(), execution.GetExecution().GetRunId())
|
||||||
|
err := workflowRun.Get(ctx, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHistory = append(checkHistory, &CheckHistory{
|
||||||
|
CheckId: checkId,
|
||||||
|
Duration: execution.CloseTime.AsTime().Sub(execution.StartTime.AsTime()),
|
||||||
|
StartTime: execution.StartTime.AsTime(),
|
||||||
|
EndTime: execution.CloseTime.AsTime(),
|
||||||
|
Status: execution.Status.String(),
|
||||||
|
WorkerGroupName: execution.GetTaskQueue(),
|
||||||
|
Note: result.Note,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkHistory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetCheckHistoryForCheck(ctx context.Context, t client.Client, checkId string) ([]*CheckHistory, error) {
|
||||||
|
var checkHistory []*CheckHistory
|
||||||
|
|
||||||
|
response, err := t.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||||
|
PageSize: 10,
|
||||||
|
Query: fmt.Sprintf(`TemporalScheduledById = "%s"`, getScheduleId(checkId)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return checkHistory, err
|
||||||
|
}
|
||||||
|
|
||||||
|
executions := response.GetExecutions()
|
||||||
|
|
||||||
|
for _, execution := range executions {
|
||||||
|
scheduleId := string(execution.GetSearchAttributes().GetIndexedFields()["TemporalScheduledById"].Data)
|
||||||
|
|
||||||
|
// Remove the quotes around the checkId and the prefix.
|
||||||
|
checkId := scheduleId[len("\"check-") : len(scheduleId)-1]
|
||||||
|
|
||||||
|
var result temporal.WorkflowCheckResult
|
||||||
|
if execution.Status != enums.WORKFLOW_EXECUTION_STATUS_RUNNING {
|
||||||
|
workflowRun := t.GetWorkflow(ctx, execution.GetExecution().GetWorkflowId(), execution.GetExecution().GetRunId())
|
||||||
|
err := workflowRun.Get(ctx, &result)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
checkHistory = append(checkHistory, &CheckHistory{
|
||||||
|
CheckId: checkId,
|
||||||
|
Duration: execution.CloseTime.AsTime().Sub(execution.StartTime.AsTime()),
|
||||||
|
StartTime: execution.StartTime.AsTime(),
|
||||||
|
EndTime: execution.CloseTime.AsTime(),
|
||||||
|
Status: execution.Status.String(),
|
||||||
|
WorkerGroupName: execution.GetTaskQueue(),
|
||||||
|
Note: result.Note,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkHistory, nil
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package services
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
75
internal/server/services/targets.go
Normal file
75
internal/server/services/targets.go
Normal file
|
@ -0,0 +1,75 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func CountTargets(ctx context.Context, db *sqlx.DB) (int, error) {
|
||||||
|
var count int
|
||||||
|
err := db.GetContext(ctx, &count, "SELECT COUNT(*) FROM targets")
|
||||||
|
return count, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetTargetState(ctx context.Context, db *sqlx.DB, id string, state models.TargetState) error {
|
||||||
|
_, err := db.NamedExecContext(ctx,
|
||||||
|
`UPDATE targets SET state=:state WHERE id=:id`,
|
||||||
|
struct {
|
||||||
|
Id string
|
||||||
|
State models.TargetState
|
||||||
|
}{Id: id, State: state},
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateTarget(ctx context.Context, db *sqlx.DB, target *models.Target) error {
|
||||||
|
_, err := db.NamedExecContext(ctx,
|
||||||
|
`INSERT INTO targets (id, name, "group", visibility, state, metadata) VALUES (:id, :name, :group, :visibility, :state, :metadata)`,
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateTarget(ctx context.Context, db *sqlx.DB, target *models.Target) error {
|
||||||
|
_, err := db.NamedExecContext(ctx,
|
||||||
|
`UPDATE targets SET visibility=:visibility, "group"=:group, metadata=:metadata WHERE id=:id`,
|
||||||
|
target,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteTarget(ctx context.Context, db *sqlx.DB, id string) error {
|
||||||
|
_, err := db.ExecContext(ctx,
|
||||||
|
"DELETE FROM targets WHERE id=$1",
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTarget(ctx context.Context, db *sqlx.DB, id string) (*models.Target, error) {
|
||||||
|
target := &models.Target{}
|
||||||
|
err := db.GetContext(ctx, target,
|
||||||
|
"SELECT * FROM targets WHERE id=$1",
|
||||||
|
id,
|
||||||
|
)
|
||||||
|
return target, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTargets(ctx context.Context, db *sqlx.DB) ([]*models.Target, error) {
|
||||||
|
targets := []*models.Target{}
|
||||||
|
err := db.SelectContext(ctx, &targets,
|
||||||
|
"SELECT * FROM targets ORDER BY name",
|
||||||
|
)
|
||||||
|
return targets, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTargetsWithFilter(ctx context.Context, db *sqlx.DB, filter string) ([]*models.Target, error) {
|
||||||
|
targets := []*models.Target{}
|
||||||
|
err := db.SelectContext(ctx, &targets,
|
||||||
|
"SELECT * FROM targets WHERE name ILIKE $1 ORDER BY name",
|
||||||
|
"%"+filter+"%",
|
||||||
|
)
|
||||||
|
return targets, err
|
||||||
|
}
|
73
internal/server/services/targets_history.go
Normal file
73
internal/server/services/targets_history.go
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TargetHistory struct {
|
||||||
|
*models.TargetHistory
|
||||||
|
TargetName string `db:"target_name"`
|
||||||
|
WorkerGroupName string `db:"worker_group_name"`
|
||||||
|
CheckName string `db:"check_name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLastNTargetHistory(ctx context.Context, db *sqlx.DB, n int) ([]*TargetHistory, error) {
|
||||||
|
var targetHistory []*TargetHistory
|
||||||
|
err := db.SelectContext(ctx, &targetHistory, `
|
||||||
|
SELECT
|
||||||
|
th.*,
|
||||||
|
t.name AS target_name,
|
||||||
|
wg.name AS worker_group_name,
|
||||||
|
c.name AS check_name
|
||||||
|
FROM target_histories th
|
||||||
|
LEFT JOIN targets t ON th.target_id = t.id
|
||||||
|
LEFT JOIN worker_groups wg ON th.worker_group_id = wg.id
|
||||||
|
LEFT JOIN checks c ON th.check_id = c.id
|
||||||
|
WHERE th.target_id = $1
|
||||||
|
ORDER BY th.created_at DESC
|
||||||
|
LIMIT $1
|
||||||
|
`, n)
|
||||||
|
return targetHistory, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTargetHistoryForTarget(ctx context.Context, db *sqlx.DB, targetId string) ([]*TargetHistory, error) {
|
||||||
|
var targetHistory []*TargetHistory
|
||||||
|
err := db.SelectContext(ctx, &targetHistory, `
|
||||||
|
SELECT
|
||||||
|
th.*,
|
||||||
|
t.name AS target_name,
|
||||||
|
wg.name AS worker_group_name,
|
||||||
|
c.name AS check_name
|
||||||
|
FROM target_histories th
|
||||||
|
LEFT JOIN targets t ON th.target_id = t.id
|
||||||
|
LEFT JOIN worker_groups wg ON th.worker_group_id = wg.id
|
||||||
|
LEFT JOIN checks c ON th.check_id = c.id
|
||||||
|
WHERE th.target_id = $1
|
||||||
|
ORDER BY th.created_at DESC
|
||||||
|
`, targetId)
|
||||||
|
return targetHistory, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddHistoryForTarget(ctx context.Context, db *sqlx.DB, history *models.TargetHistory) error {
|
||||||
|
_, err := db.NamedExecContext(ctx,
|
||||||
|
`
|
||||||
|
INSERT INTO target_histories (
|
||||||
|
target_id,
|
||||||
|
worker_group_id,
|
||||||
|
check_id,
|
||||||
|
status,
|
||||||
|
note
|
||||||
|
) VALUES (
|
||||||
|
:target_id,
|
||||||
|
:worker_group_id,
|
||||||
|
:check_id,
|
||||||
|
:status,
|
||||||
|
:note
|
||||||
|
)`,
|
||||||
|
history,
|
||||||
|
)
|
||||||
|
return err
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package services
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
)
|
)
|
||||||
|
|
67
internal/server/services/trigger_history.go
Normal file
67
internal/server/services/trigger_history.go
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"go.temporal.io/api/workflowservice/v1"
|
||||||
|
"go.temporal.io/sdk/client"
|
||||||
|
)
|
||||||
|
|
||||||
|
type TriggerHistory struct {
|
||||||
|
TriggerId string
|
||||||
|
Status string
|
||||||
|
Duration time.Duration
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetLastNTriggerHistory(ctx context.Context, temporal client.Client, n int32) ([]*TriggerHistory, error) {
|
||||||
|
var checkHistory []*TriggerHistory
|
||||||
|
|
||||||
|
response, err := temporal.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||||
|
PageSize: n,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return checkHistory, err
|
||||||
|
}
|
||||||
|
|
||||||
|
executions := response.GetExecutions()
|
||||||
|
|
||||||
|
for _, execution := range executions {
|
||||||
|
scheduleId := string(execution.GetSearchAttributes().GetIndexedFields()["TemporalScheduledById"].Data)
|
||||||
|
checkId := scheduleId[len("trigger-"):]
|
||||||
|
checkHistory = append(checkHistory, &TriggerHistory{
|
||||||
|
TriggerId: checkId,
|
||||||
|
Duration: execution.CloseTime.AsTime().Sub(execution.StartTime.AsTime()),
|
||||||
|
Status: execution.Status.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkHistory, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetTriggerHistoryForTrigger(ctx context.Context, temporal client.Client, checkId string) ([]*TriggerHistory, error) {
|
||||||
|
var checkHistory []*TriggerHistory
|
||||||
|
|
||||||
|
response, err := temporal.ListWorkflow(ctx, &workflowservice.ListWorkflowExecutionsRequest{
|
||||||
|
PageSize: 10,
|
||||||
|
Query: fmt.Sprintf(`TemporalScheduledById = "%s"`, getScheduleId(checkId)),
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return checkHistory, err
|
||||||
|
}
|
||||||
|
|
||||||
|
executions := response.GetExecutions()
|
||||||
|
|
||||||
|
for _, execution := range executions {
|
||||||
|
scheduleId := string(execution.GetSearchAttributes().GetIndexedFields()["TemporalScheduledById"].Data)
|
||||||
|
checkId := scheduleId[len("check-"):]
|
||||||
|
checkHistory = append(checkHistory, &TriggerHistory{
|
||||||
|
TriggerId: checkId,
|
||||||
|
Duration: execution.CloseTime.AsTime().Sub(execution.StartTime.AsTime()),
|
||||||
|
Status: execution.Status.String(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkHistory, nil
|
||||||
|
}
|
|
@ -3,7 +3,7 @@ package services
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"go.temporal.io/api/enums/v1"
|
"go.temporal.io/api/enums/v1"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
77
internal/server/workflows/check.go
Normal file
77
internal/server/workflows/check.go
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
package workflows
|
||||||
|
|
||||||
|
import (
|
||||||
|
"sort"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
"go.temporal.io/sdk/workflow"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (w *Workflows) CheckWorkflowDefinition(ctx workflow.Context, param temporal.WorkflowCheckParam) (*temporal.WorkflowCheckResult, error) {
|
||||||
|
workerGroupIds := param.WorkerGroupIds
|
||||||
|
sort.Strings(workerGroupIds)
|
||||||
|
|
||||||
|
targetsFilterResult := temporal.ActivityTargetsFilterResult{}
|
||||||
|
err := workflow.ExecuteActivity(
|
||||||
|
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||||
|
StartToCloseTimeout: 60 * time.Second,
|
||||||
|
TaskQueue: temporal.TEMPORAL_SERVER_QUEUE,
|
||||||
|
}),
|
||||||
|
temporal.ActivityTargetsFilterName,
|
||||||
|
temporal.ActivityTargetsFilterParam{
|
||||||
|
Filter: param.Filter,
|
||||||
|
},
|
||||||
|
).Get(ctx, &targetsFilterResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, target := range targetsFilterResult.Targets {
|
||||||
|
for _, workerGroupId := range workerGroupIds {
|
||||||
|
var checkResult *temporal.ActivityCheckResult
|
||||||
|
err := workflow.ExecuteActivity(
|
||||||
|
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||||
|
StartToCloseTimeout: 60 * time.Second,
|
||||||
|
TaskQueue: workerGroupId,
|
||||||
|
}),
|
||||||
|
temporal.ActivityCheckName,
|
||||||
|
temporal.ActivityCheckParam{
|
||||||
|
Script: param.Script,
|
||||||
|
Target: target,
|
||||||
|
},
|
||||||
|
).Get(ctx, &checkResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
status := temporal.AddTargetHistoryStatusFailure
|
||||||
|
if checkResult.Success {
|
||||||
|
status = temporal.AddTargetHistoryStatusSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
var addTargetHistoryResult *temporal.ActivityAddTargetHistoryResult
|
||||||
|
err = workflow.ExecuteActivity(
|
||||||
|
workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||||
|
StartToCloseTimeout: 60 * time.Second,
|
||||||
|
TaskQueue: temporal.TEMPORAL_SERVER_QUEUE,
|
||||||
|
}),
|
||||||
|
temporal.ActivityAddTargetHistoryName,
|
||||||
|
&temporal.ActivityAddTargetHistoryParam{
|
||||||
|
Target: target,
|
||||||
|
WorkerGroupId: workerGroupId,
|
||||||
|
CheckId: param.CheckId,
|
||||||
|
Status: status,
|
||||||
|
Note: checkResult.Note,
|
||||||
|
},
|
||||||
|
).Get(ctx, &addTargetHistoryResult)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &temporal.WorkflowCheckResult{
|
||||||
|
Note: "Check workflow completed",
|
||||||
|
}, nil
|
||||||
|
}
|
9
internal/server/workflows/workflows.go
Normal file
9
internal/server/workflows/workflows.go
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
package workflows
|
||||||
|
|
||||||
|
|
||||||
|
type Workflows struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkflows() *Workflows {
|
||||||
|
return &Workflows{}
|
||||||
|
}
|
|
@ -1,67 +0,0 @@
|
||||||
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
|
|
||||||
}
|
|
22
internal/temporal/activity_add_target_history.go
Normal file
22
internal/temporal/activity_add_target_history.go
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
package temporal
|
||||||
|
|
||||||
|
type AddTargetHistoryStatus string
|
||||||
|
|
||||||
|
const (
|
||||||
|
AddTargetHistoryStatusSuccess AddTargetHistoryStatus = "SUCCESS"
|
||||||
|
AddTargetHistoryStatusFailure AddTargetHistoryStatus = "FAILURE"
|
||||||
|
AddTargetHistoryStatusUnknown AddTargetHistoryStatus = "UNKNOWN"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ActivityAddTargetHistoryParam struct {
|
||||||
|
Target *Target
|
||||||
|
WorkerGroupId string
|
||||||
|
CheckId string
|
||||||
|
Status AddTargetHistoryStatus
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityAddTargetHistoryResult struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActivityAddTargetHistoryName = "ADD_TARGET_HISTORY"
|
13
internal/temporal/activity_check.go
Normal file
13
internal/temporal/activity_check.go
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
package temporal
|
||||||
|
|
||||||
|
type ActivityCheckParam struct {
|
||||||
|
Script string
|
||||||
|
Target *Target
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityCheckResult struct {
|
||||||
|
Success bool
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActivityCheckName = "CHECK"
|
11
internal/temporal/activity_targets_filter.go
Normal file
11
internal/temporal/activity_targets_filter.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
package temporal
|
||||||
|
|
||||||
|
type ActivityTargetsFilterParam struct {
|
||||||
|
Filter string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ActivityTargetsFilterResult struct {
|
||||||
|
Targets []*Target
|
||||||
|
}
|
||||||
|
|
||||||
|
const ActivityTargetsFilterName = "TARGETS_FILTER"
|
|
@ -5,13 +5,24 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
"github.com/mentos1386/zdravko/pkg/jwt"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/retry"
|
"github.com/mentos1386/zdravko/pkg/retry"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Target struct {
|
||||||
|
Id string
|
||||||
|
Name string
|
||||||
|
Group string
|
||||||
|
Metadata string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Must be default, as we are also processing
|
||||||
|
// some temporal things.
|
||||||
|
const TEMPORAL_SERVER_QUEUE = "default"
|
||||||
|
|
||||||
type AuthHeadersProvider struct {
|
type AuthHeadersProvider struct {
|
||||||
Token string
|
Token string
|
||||||
}
|
}
|
||||||
|
|
14
internal/temporal/workflow_check.go
Normal file
14
internal/temporal/workflow_check.go
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
package temporal
|
||||||
|
|
||||||
|
type WorkflowCheckParam struct {
|
||||||
|
Script string
|
||||||
|
Filter string
|
||||||
|
CheckId string
|
||||||
|
WorkerGroupIds []string
|
||||||
|
}
|
||||||
|
|
||||||
|
type WorkflowCheckResult struct {
|
||||||
|
Note string
|
||||||
|
}
|
||||||
|
|
||||||
|
const WorkflowCheckName = "CHECK_WORKFLOW"
|
16
internal/worker/activities/activities.go
Normal file
16
internal/worker/activities/activities.go
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Activities struct {
|
||||||
|
config *config.WorkerConfig
|
||||||
|
logger *slog.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewActivities(config *config.WorkerConfig, logger *slog.Logger) *Activities {
|
||||||
|
return &Activities{config: config, logger: logger}
|
||||||
|
}
|
37
internal/worker/activities/check.go
Normal file
37
internal/worker/activities/check.go
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
package activities
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/k6"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/k6/zdravko"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/script"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (a *Activities) Check(ctx context.Context, param temporal.ActivityCheckParam) (*temporal.ActivityCheckResult, error) {
|
||||||
|
execution := k6.NewExecution(slog.Default(), script.UnescapeString(param.Script))
|
||||||
|
|
||||||
|
var metadata map[string]interface{}
|
||||||
|
err := yaml.Unmarshal([]byte(param.Target.Metadata), &metadata)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx = zdravko.WithZdravkoContext(ctx, zdravko.Context{
|
||||||
|
Target: zdravko.Target{
|
||||||
|
Name: param.Target.Name,
|
||||||
|
Group: param.Target.Group,
|
||||||
|
Metadata: metadata,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
result, err := execution.Run(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &temporal.ActivityCheckResult{Success: result.Success, Note: result.Note}, nil
|
||||||
|
}
|
8
internal/worker/workflows/workflows.go
Normal file
8
internal/worker/workflows/workflows.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package workflows
|
||||||
|
|
||||||
|
type Workflows struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewWorkflows() *Workflows {
|
||||||
|
return &Workflows{}
|
||||||
|
}
|
|
@ -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 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.CheckStatusUnknown, err
|
|
||||||
}
|
|
||||||
|
|
||||||
status := models.CheckStatusFailure
|
|
||||||
if checkResult.Success {
|
|
||||||
status = models.CheckStatusSuccess
|
|
||||||
}
|
|
||||||
|
|
||||||
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.CheckStatusUnknown, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return models.CheckStatusSuccess, nil
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
package workflows
|
|
||||||
|
|
||||||
import (
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/activities"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Workflows struct {
|
|
||||||
activities *activities.Activities
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewWorkflows(a *activities.Activities) *Workflows {
|
|
||||||
return &Workflows{activities: a}
|
|
||||||
}
|
|
5
justfile
5
justfile
|
@ -103,7 +103,7 @@ migration-new name:
|
||||||
echo "Created migration file: $FILENAME"
|
echo "Created migration file: $FILENAME"
|
||||||
|
|
||||||
# Generate and download all external dependencies.
|
# Generate and download all external dependencies.
|
||||||
generate:
|
generate: _tailwindcss-build _htmx-download _monaco-download _feather-icons-download
|
||||||
go generate ./...
|
go generate ./...
|
||||||
|
|
||||||
_tailwindcss-build:
|
_tailwindcss-build:
|
||||||
|
@ -119,11 +119,12 @@ _monaco-download:
|
||||||
mv node_modules/monaco-editor/min {{STATIC_DIR}}/monaco
|
mv node_modules/monaco-editor/min {{STATIC_DIR}}/monaco
|
||||||
rm -rf node_modules
|
rm -rf node_modules
|
||||||
|
|
||||||
# We onlt care about javascript language
|
# We only care about javascript language
|
||||||
find {{STATIC_DIR}}/monaco/vs/basic-languages/ \
|
find {{STATIC_DIR}}/monaco/vs/basic-languages/ \
|
||||||
-type d \
|
-type d \
|
||||||
-not -name 'javascript' \
|
-not -name 'javascript' \
|
||||||
-not -name 'typescript' \
|
-not -name 'typescript' \
|
||||||
|
-not -name 'yaml' \
|
||||||
-not -name 'basic-languages' \
|
-not -name 'basic-languages' \
|
||||||
-prune -exec rm -rf {} \;
|
-prune -exec rm -rf {} \;
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1 @@
|
||||||
package api
|
package api
|
||||||
|
|
||||||
import "code.tjo.space/mentos1386/zdravko/database/models"
|
|
||||||
|
|
||||||
type ApiV1ChecksHistoryPOSTBody struct {
|
|
||||||
Status models.CheckStatus `json:"status"`
|
|
||||||
Note string `json:"note"`
|
|
||||||
WorkerGroupId string `json:"worker_group"`
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
"github.com/mentos1386/zdravko/database/models"
|
||||||
"github.com/golang-jwt/jwt/v5"
|
"github.com/golang-jwt/jwt/v5"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
|
@ -5,6 +5,8 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/mentos1386/zdravko/pkg/k6/zdravko"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getLogger() *slog.Logger {
|
func getLogger() *slog.Logger {
|
||||||
|
@ -18,12 +20,12 @@ func getLogger() *slog.Logger {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestK6Success(t *testing.T) {
|
func TestK6Success(t *testing.T) {
|
||||||
ctx := context.Background()
|
|
||||||
logger := getLogger()
|
logger := getLogger()
|
||||||
|
|
||||||
script := `
|
script := `
|
||||||
import http from 'k6/http';
|
import http from 'k6/http';
|
||||||
import { sleep } from 'k6';
|
import { sleep } from 'k6';
|
||||||
|
import { getTarget } from 'k6/x/zdravko';
|
||||||
|
|
||||||
export const options = {
|
export const options = {
|
||||||
vus: 10,
|
vus: 10,
|
||||||
|
@ -31,6 +33,8 @@ export const options = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function () {
|
export default function () {
|
||||||
|
const target = getTarget();
|
||||||
|
console.log('Target:', target);
|
||||||
http.get('https://test.k6.io');
|
http.get('https://test.k6.io');
|
||||||
sleep(1);
|
sleep(1);
|
||||||
}
|
}
|
||||||
|
@ -38,6 +42,14 @@ export default function () {
|
||||||
|
|
||||||
execution := NewExecution(logger, script)
|
execution := NewExecution(logger, script)
|
||||||
|
|
||||||
|
ctx := zdravko.WithZdravkoContext(context.Background(), zdravko.Context{Target: zdravko.Target{
|
||||||
|
Name: "Test",
|
||||||
|
Group: "Test",
|
||||||
|
Metadata: map[string]interface{}{
|
||||||
|
"Kind": "Test",
|
||||||
|
},
|
||||||
|
}})
|
||||||
|
|
||||||
result, err := execution.Run(ctx)
|
result, err := execution.Run(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("Error starting execution: %v", err)
|
t.Errorf("Error starting execution: %v", err)
|
||||||
|
|
17
pkg/k6/zdravko/context.go
Normal file
17
pkg/k6/zdravko/context.go
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
package zdravko
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type zdravkoContextKey string
|
||||||
|
|
||||||
|
type Context struct {
|
||||||
|
Target Target
|
||||||
|
}
|
||||||
|
|
||||||
|
func WithZdravkoContext(ctx context.Context, zdravkoContext Context) context.Context {
|
||||||
|
return context.WithValue(ctx, zdravkoContextKey("zdravko-ctx"), zdravkoContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetZdravkoContext(ctx context.Context) Context {
|
||||||
|
return ctx.Value(zdravkoContextKey("zdravko-ctx")).(Context)
|
||||||
|
}
|
66
pkg/k6/zdravko/zdravko.go
Normal file
66
pkg/k6/zdravko/zdravko.go
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
package zdravko
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/dop251/goja"
|
||||||
|
"go.k6.io/k6/js/modules"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
modules.Register("k6/x/zdravko", New())
|
||||||
|
}
|
||||||
|
|
||||||
|
type (
|
||||||
|
// RootModule is the global module instance that will create module
|
||||||
|
// instances for each VU.
|
||||||
|
RootModule struct{}
|
||||||
|
|
||||||
|
// ModuleInstance represents an instance of the JS module.
|
||||||
|
ModuleInstance struct {
|
||||||
|
// vu provides methods for accessing internal k6 objects for a VU
|
||||||
|
vu modules.VU
|
||||||
|
// comparator is the exported type
|
||||||
|
zdravko *Zdravko
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Ensure the interfaces are implemented correctly.
|
||||||
|
var (
|
||||||
|
_ modules.Instance = &ModuleInstance{}
|
||||||
|
_ modules.Module = &RootModule{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// New returns a pointer to a new RootModule instance.
|
||||||
|
func New() *RootModule {
|
||||||
|
return &RootModule{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewModuleInstance implements the modules.Module interface returning a new instance for each VU.
|
||||||
|
func (*RootModule) NewModuleInstance(vu modules.VU) modules.Instance {
|
||||||
|
return &ModuleInstance{
|
||||||
|
vu: vu,
|
||||||
|
zdravko: &Zdravko{vu: vu},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Target struct {
|
||||||
|
Name string
|
||||||
|
Group string
|
||||||
|
Metadata map[string]interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Zdravko struct {
|
||||||
|
vu modules.VU
|
||||||
|
Targets []Target
|
||||||
|
}
|
||||||
|
|
||||||
|
func (z *Zdravko) GetTarget() goja.Value {
|
||||||
|
zdravkoContext := GetZdravkoContext(z.vu.Context())
|
||||||
|
return z.vu.Runtime().ToValue(zdravkoContext.Target)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exports implements the modules.Instance interface and returns the exported types for the JS module.
|
||||||
|
func (mi *ModuleInstance) Exports() modules.Exports {
|
||||||
|
return modules.Exports{
|
||||||
|
Default: mi.zdravko,
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,20 +4,20 @@ import (
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/handlers"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/kv"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/static"
|
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/mentos1386/zdravko/database"
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/handlers"
|
||||||
|
"github.com/mentos1386/zdravko/web/static"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Routes(
|
func Routes(
|
||||||
e *echo.Echo,
|
e *echo.Echo,
|
||||||
sqlDb *sqlx.DB,
|
sqlDb *sqlx.DB,
|
||||||
kvStore kv.KeyValueStore,
|
kvStore database.KeyValueStore,
|
||||||
temporalClient client.Client,
|
temporalClient client.Client,
|
||||||
cfg *config.ServerConfig,
|
cfg *config.ServerConfig,
|
||||||
logger *slog.Logger,
|
logger *slog.Logger,
|
||||||
|
@ -64,13 +64,13 @@ func Routes(
|
||||||
settings.GET("/triggers/:id/enable", h.SettingsTriggersEnableGET)
|
settings.GET("/triggers/:id/enable", h.SettingsTriggersEnableGET)
|
||||||
|
|
||||||
settings.GET("/targets", h.SettingsTargetsGET)
|
settings.GET("/targets", h.SettingsTargetsGET)
|
||||||
//settings.GET("/targets/create", h.SettingsTargetsCreateGET)
|
settings.GET("/targets/create", h.SettingsTargetsCreateGET)
|
||||||
//settings.POST("/targets/create", h.SettingsTargetsCreatePOST)
|
settings.POST("/targets/create", h.SettingsTargetsCreatePOST)
|
||||||
//settings.GET("/targets/:id", h.SettingsTargetsDescribeGET)
|
settings.GET("/targets/:id", h.SettingsTargetsDescribeGET)
|
||||||
//settings.POST("/targets/:id", h.SettingsTargetsDescribePOST)
|
settings.POST("/targets/:id", h.SettingsTargetsDescribePOST)
|
||||||
//settings.GET("/targets/:id/delete", h.SettingsTargetsDescribeDELETE)
|
settings.GET("/targets/:id/delete", h.SettingsTargetsDescribeDELETE)
|
||||||
//settings.GET("/targets/:id/disable", h.SettingsTargetsDisableGET)
|
settings.GET("/targets/:id/disable", h.SettingsTargetsDisableGET)
|
||||||
//settings.GET("/targets/:id/enable", h.SettingsTargetsEnableGET)
|
settings.GET("/targets/:id/enable", h.SettingsTargetsEnableGET)
|
||||||
|
|
||||||
settings.GET("/incidents", h.SettingsIncidentsGET)
|
settings.GET("/incidents", h.SettingsIncidentsGET)
|
||||||
|
|
||||||
|
@ -103,7 +103,6 @@ func Routes(
|
||||||
apiv1 := e.Group("/api/v1")
|
apiv1 := e.Group("/api/v1")
|
||||||
apiv1.Use(h.Authenticated)
|
apiv1.Use(h.Authenticated)
|
||||||
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
|
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
|
||||||
apiv1.POST("/checks/:id/history", h.ApiV1ChecksHistoryPOST)
|
|
||||||
|
|
||||||
// Error handler
|
// Error handler
|
||||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||||
|
|
|
@ -4,13 +4,12 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/database"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/kv"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/temporal"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/labstack/echo/v4/middleware"
|
"github.com/labstack/echo/v4/middleware"
|
||||||
|
"github.com/mentos1386/zdravko/database"
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
"github.com/mentos1386/zdravko/web/templates"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -45,12 +44,12 @@ func (s *Server) Start() error {
|
||||||
return errors.Wrap(err, "failed to connect to temporal")
|
return errors.Wrap(err, "failed to connect to temporal")
|
||||||
}
|
}
|
||||||
|
|
||||||
kvStore, err := kv.NewBadgerKeyValueStore(s.cfg.KeyValueDatabasePath)
|
kvStore, err := database.NewBadgerKeyValueStore(s.cfg.KeyValueDatabasePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to open kv store")
|
return errors.Wrap(err, "failed to open kv store")
|
||||||
}
|
}
|
||||||
|
|
||||||
s.worker = NewWorker(temporalClient, s.cfg)
|
s.worker = NewWorker(temporalClient, s.cfg, s.logger, sqliteDb, kvStore)
|
||||||
|
|
||||||
s.echo.Renderer = templates.NewTemplates()
|
s.echo.Renderer = templates.NewTemplates()
|
||||||
s.echo.Use(middleware.Logger())
|
s.echo.Use(middleware.Logger())
|
||||||
|
|
|
@ -1,34 +1,45 @@
|
||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/activities"
|
"log/slog"
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/workflows"
|
"github.com/jmoiron/sqlx"
|
||||||
|
"github.com/mentos1386/zdravko/database"
|
||||||
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/activities"
|
||||||
|
"github.com/mentos1386/zdravko/internal/server/workflows"
|
||||||
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
|
"go.temporal.io/sdk/activity"
|
||||||
"go.temporal.io/sdk/client"
|
"go.temporal.io/sdk/client"
|
||||||
"go.temporal.io/sdk/worker"
|
temporalWorker "go.temporal.io/sdk/worker"
|
||||||
|
"go.temporal.io/sdk/workflow"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Worker struct {
|
type Worker struct {
|
||||||
worker worker.Worker
|
worker temporalWorker.Worker
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorker(temporalClient client.Client, cfg *config.ServerConfig) *Worker {
|
func NewWorker(temporalClient client.Client, cfg *config.ServerConfig, logger *slog.Logger, db *sqlx.DB, kvStore database.KeyValueStore) *Worker {
|
||||||
w := worker.New(temporalClient, "default", worker.Options{})
|
worker := temporalWorker.New(temporalClient, temporal.TEMPORAL_SERVER_QUEUE, temporalWorker.Options{})
|
||||||
|
|
||||||
workerActivities := activities.NewActivities(&config.WorkerConfig{})
|
a := activities.NewActivities(cfg, logger, db, kvStore)
|
||||||
|
|
||||||
workerWorkflows := workflows.NewWorkflows(workerActivities)
|
w := workflows.NewWorkflows()
|
||||||
|
|
||||||
// Register Workflows
|
// Register Workflows
|
||||||
w.RegisterWorkflow(workerWorkflows.CheckWorkflowDefinition)
|
worker.RegisterWorkflowWithOptions(w.CheckWorkflowDefinition, workflow.RegisterOptions{Name: temporal.WorkflowCheckName})
|
||||||
|
|
||||||
|
// Register Activities
|
||||||
|
worker.RegisterActivityWithOptions(a.TargetsFilter, activity.RegisterOptions{Name: temporal.ActivityTargetsFilterName})
|
||||||
|
worker.RegisterActivityWithOptions(a.AddTargetHistory, activity.RegisterOptions{Name: temporal.ActivityAddTargetHistoryName})
|
||||||
|
|
||||||
return &Worker{
|
return &Worker{
|
||||||
worker: w,
|
worker: worker,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) Start() error {
|
func (w *Worker) Start() error {
|
||||||
return w.worker.Run(worker.InterruptCh())
|
return w.worker.Run(temporalWorker.InterruptCh())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Worker) Stop() {
|
func (w *Worker) Stop() {
|
||||||
|
|
|
@ -6,8 +6,8 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
internal "code.tjo.space/mentos1386/zdravko/internal/config"
|
internal "github.com/mentos1386/zdravko/internal/config"
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/jwt"
|
"github.com/mentos1386/zdravko/pkg/jwt"
|
||||||
"go.temporal.io/server/common/cluster"
|
"go.temporal.io/server/common/cluster"
|
||||||
"go.temporal.io/server/common/config"
|
"go.temporal.io/server/common/config"
|
||||||
"go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite"
|
"go.temporal.io/server/common/persistence/sql/sqlplugin/sqlite"
|
||||||
|
|
|
@ -16,7 +16,7 @@ import (
|
||||||
func NewServer(cfg *config.Config, tokenKeyProvider authorization.TokenKeyProvider) (t.Server, error) {
|
func NewServer(cfg *config.Config, tokenKeyProvider authorization.TokenKeyProvider) (t.Server, error) {
|
||||||
logger := log.NewZapLogger(log.BuildZapLogger(log.Config{
|
logger := log.NewZapLogger(log.BuildZapLogger(log.Config{
|
||||||
Stdout: true,
|
Stdout: true,
|
||||||
Level: "info",
|
Level: "warn",
|
||||||
OutputFile: "",
|
OutputFile: "",
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package temporal
|
package temporal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
"github.com/temporalio/ui-server/v2/server"
|
"github.com/temporalio/ui-server/v2/server"
|
||||||
t "go.temporal.io/server/temporal"
|
t "go.temporal.io/server/temporal"
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
package temporal
|
package temporal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
internal "code.tjo.space/mentos1386/zdravko/internal/config"
|
internal "github.com/mentos1386/zdravko/internal/config"
|
||||||
"github.com/temporalio/ui-server/v2/server"
|
"github.com/temporalio/ui-server/v2/server"
|
||||||
"github.com/temporalio/ui-server/v2/server/config"
|
"github.com/temporalio/ui-server/v2/server/config"
|
||||||
"github.com/temporalio/ui-server/v2/server/server_options"
|
"github.com/temporalio/ui-server/v2/server/server_options"
|
||||||
|
|
|
@ -3,17 +3,17 @@ package worker
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/activities"
|
"github.com/mentos1386/zdravko/internal/config"
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
"github.com/mentos1386/zdravko/internal/temporal"
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/temporal"
|
"github.com/mentos1386/zdravko/internal/worker/activities"
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/workflows"
|
"github.com/mentos1386/zdravko/pkg/api"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/api"
|
"github.com/mentos1386/zdravko/pkg/retry"
|
||||||
"code.tjo.space/mentos1386/zdravko/pkg/retry"
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
"go.temporal.io/sdk/activity"
|
||||||
"go.temporal.io/sdk/worker"
|
"go.temporal.io/sdk/worker"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -60,11 +60,13 @@ func getConnectionConfig(token string, apiUrl string) (*ConnectionConfig, error)
|
||||||
type Worker struct {
|
type Worker struct {
|
||||||
worker worker.Worker
|
worker worker.Worker
|
||||||
cfg *config.WorkerConfig
|
cfg *config.WorkerConfig
|
||||||
|
logger *slog.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewWorker(cfg *config.WorkerConfig) (*Worker, error) {
|
func NewWorker(cfg *config.WorkerConfig) (*Worker, error) {
|
||||||
return &Worker{
|
return &Worker{
|
||||||
cfg: cfg,
|
cfg: cfg,
|
||||||
|
logger: slog.Default().WithGroup("worker"),
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +80,7 @@ func (w *Worker) Start() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Println("Worker Group:", config.Group)
|
w.logger.Info("Worker Starting", "group", config.Group)
|
||||||
|
|
||||||
temporalClient, err := temporal.ConnectWorkerToTemporal(w.cfg.Token, config.Endpoint)
|
temporalClient, err := temporal.ConnectWorkerToTemporal(w.cfg.Token, config.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -88,15 +90,10 @@ func (w *Worker) Start() error {
|
||||||
// Create a new Worker
|
// Create a new Worker
|
||||||
w.worker = worker.New(temporalClient, config.Group, worker.Options{})
|
w.worker = worker.New(temporalClient, config.Group, worker.Options{})
|
||||||
|
|
||||||
workerActivities := activities.NewActivities(w.cfg)
|
workerActivities := activities.NewActivities(w.cfg, w.logger)
|
||||||
workerWorkflows := workflows.NewWorkflows(workerActivities)
|
|
||||||
|
|
||||||
// Register Workflows
|
|
||||||
w.worker.RegisterWorkflow(workerWorkflows.CheckWorkflowDefinition)
|
|
||||||
|
|
||||||
// Register Activities
|
// Register Activities
|
||||||
w.worker.RegisterActivity(workerActivities.Check)
|
w.worker.RegisterActivityWithOptions(workerActivities.Check, activity.RegisterOptions{Name: temporal.ActivityCheckName})
|
||||||
w.worker.RegisterActivity(workerActivities.CheckAddToHistory)
|
|
||||||
|
|
||||||
return w.worker.Run(worker.InterruptCh())
|
return w.worker.Run(worker.InterruptCh())
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,11 +64,11 @@ code {
|
||||||
@apply bg-blue-700 text-white;
|
@apply bg-blue-700 text-white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.checks .time-range > a {
|
.targets .time-range > a {
|
||||||
@apply font-medium text-sm px-2.5 py-1 rounded-lg;
|
@apply font-medium text-sm px-2.5 py-1 rounded-lg;
|
||||||
@apply text-black bg-gray-100 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400;
|
@apply text-black bg-gray-100 hover:bg-gray-300 focus:outline-none focus:ring-2 focus:ring-gray-400;
|
||||||
}
|
}
|
||||||
.checks .time-range > a.active {
|
.targets .time-range > a.active {
|
||||||
@apply bg-white hover:bg-gray-300 shadow;
|
@apply bg-white hover:bg-gray-300 shadow;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -722,6 +722,10 @@ video {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.h-12 {
|
||||||
|
height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
.h-20 {
|
.h-20 {
|
||||||
height: 5rem;
|
height: 5rem;
|
||||||
}
|
}
|
||||||
|
@ -987,6 +991,11 @@ video {
|
||||||
background-color: rgb(253 186 116 / var(--tw-bg-opacity));
|
background-color: rgb(253 186 116 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.bg-purple-100 {
|
||||||
|
--tw-bg-opacity: 1;
|
||||||
|
background-color: rgb(243 232 255 / var(--tw-bg-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.bg-red-100 {
|
.bg-red-100 {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
background-color: rgb(254 226 226 / var(--tw-bg-opacity));
|
||||||
|
@ -1221,6 +1230,11 @@ video {
|
||||||
color: rgb(22 101 52 / var(--tw-text-opacity));
|
color: rgb(22 101 52 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.text-purple-800 {
|
||||||
|
--tw-text-opacity: 1;
|
||||||
|
color: rgb(107 33 168 / var(--tw-text-opacity));
|
||||||
|
}
|
||||||
|
|
||||||
.text-red-600 {
|
.text-red-600 {
|
||||||
--tw-text-opacity: 1;
|
--tw-text-opacity: 1;
|
||||||
color: rgb(220 38 38 / var(--tw-text-opacity));
|
color: rgb(220 38 38 / var(--tw-text-opacity));
|
||||||
|
@ -1278,6 +1292,10 @@ video {
|
||||||
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.filter {
|
||||||
|
filter: var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow);
|
||||||
|
}
|
||||||
|
|
||||||
.transition-all {
|
.transition-all {
|
||||||
transition-property: all;
|
transition-property: all;
|
||||||
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1);
|
||||||
|
@ -1408,7 +1426,7 @@ code {
|
||||||
color: rgb(255 255 255 / var(--tw-text-opacity));
|
color: rgb(255 255 255 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.checks .time-range > a {
|
.targets .time-range > a {
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
padding-left: 0.625rem;
|
padding-left: 0.625rem;
|
||||||
padding-right: 0.625rem;
|
padding-right: 0.625rem;
|
||||||
|
@ -1423,12 +1441,12 @@ code {
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.checks .time-range > a:hover {
|
.targets .time-range > a:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.checks .time-range > a:focus {
|
.targets .time-range > a:focus {
|
||||||
outline: 2px solid transparent;
|
outline: 2px solid transparent;
|
||||||
outline-offset: 2px;
|
outline-offset: 2px;
|
||||||
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
|
||||||
|
@ -1438,7 +1456,7 @@ code {
|
||||||
--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity));
|
--tw-ring-color: rgb(156 163 175 / var(--tw-ring-opacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
.checks .time-range > a.active {
|
.targets .time-range > a.active {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||||
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
|
||||||
|
@ -1446,7 +1464,7 @@ code {
|
||||||
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
box-shadow: var(--tw-ring-offset-shadow, 0 0 #0000), var(--tw-ring-shadow, 0 0 #0000), var(--tw-shadow);
|
||||||
}
|
}
|
||||||
|
|
||||||
.checks .time-range > a.active:hover {
|
.targets .time-range > a.active:hover {
|
||||||
--tw-bg-opacity: 1;
|
--tw-bg-opacity: 1;
|
||||||
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
background-color: rgb(209 213 219 / var(--tw-bg-opacity));
|
||||||
}
|
}
|
||||||
|
@ -1790,6 +1808,10 @@ code {
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
@media (min-width: 640px) {
|
||||||
|
.sm\:col-span-2 {
|
||||||
|
grid-column: span 2 / span 2;
|
||||||
|
}
|
||||||
|
|
||||||
.sm\:w-auto {
|
.sm\:w-auto {
|
||||||
width: auto;
|
width: auto;
|
||||||
}
|
}
|
||||||
|
|
10
web/static/monaco/vs/basic-languages/yaml/yaml.js
Normal file
10
web/static/monaco/vs/basic-languages/yaml/yaml.js
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
/*!-----------------------------------------------------------------------------
|
||||||
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||||
|
* Version: 0.46.0(21007360cad28648bdf46282a2592cb47c3a7a6f)
|
||||||
|
* Released under the MIT license
|
||||||
|
* https://github.com/microsoft/monaco-editor/blob/main/LICENSE.txt
|
||||||
|
*-----------------------------------------------------------------------------*/
|
||||||
|
define("vs/basic-languages/yaml/yaml", ["require","require"],(require)=>{
|
||||||
|
"use strict";var moduleExports=(()=>{var m=Object.create;var l=Object.defineProperty;var b=Object.getOwnPropertyDescriptor;var p=Object.getOwnPropertyNames;var g=Object.getPrototypeOf,f=Object.prototype.hasOwnProperty;var w=(e=>typeof require<"u"?require:typeof Proxy<"u"?new Proxy(e,{get:(n,t)=>(typeof require<"u"?require:n)[t]}):e)(function(e){if(typeof require<"u")return require.apply(this,arguments);throw Error('Dynamic require of "'+e+'" is not supported')});var S=(e,n)=>()=>(n||e((n={exports:{}}).exports,n),n.exports),k=(e,n)=>{for(var t in n)l(e,t,{get:n[t],enumerable:!0})},a=(e,n,t,i)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of p(n))!f.call(e,r)&&r!==t&&l(e,r,{get:()=>n[r],enumerable:!(i=b(n,r))||i.enumerable});return e},c=(e,n,t)=>(a(e,n,"default"),t&&a(t,n,"default")),u=(e,n,t)=>(t=e!=null?m(g(e)):{},a(n||!e||!e.__esModule?l(t,"default",{value:e,enumerable:!0}):t,e)),y=e=>a(l({},"__esModule",{value:!0}),e);var d=S((C,s)=>{var h=u(w("vs/editor/editor.api"));s.exports=h});var $={};k($,{conf:()=>N,language:()=>x});var o={};c(o,u(d()));var N={comments:{lineComment:"#"},brackets:[["{","}"],["[","]"],["(",")"]],autoClosingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],surroundingPairs:[{open:"{",close:"}"},{open:"[",close:"]"},{open:"(",close:")"},{open:'"',close:'"'},{open:"'",close:"'"}],folding:{offSide:!0},onEnterRules:[{beforeText:/:\s*$/,action:{indentAction:o.languages.IndentAction.Indent}}]},x={tokenPostfix:".yaml",brackets:[{token:"delimiter.bracket",open:"{",close:"}"},{token:"delimiter.square",open:"[",close:"]"}],keywords:["true","True","TRUE","false","False","FALSE","null","Null","Null","~"],numberInteger:/(?:0|[+-]?[0-9]+)/,numberFloat:/(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/,numberOctal:/0o[0-7]+/,numberHex:/0x[0-9a-fA-F]+/,numberInfinity:/[+-]?\.(?:inf|Inf|INF)/,numberNaN:/\.(?:nan|Nan|NAN)/,numberDate:/\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/,escapes:/\\(?:[btnfr\\"']|[0-7][0-7]?|[0-3][0-7]{2})/,tokenizer:{root:[{include:"@whitespace"},{include:"@comment"},[/%[^ ]+.*$/,"meta.directive"],[/---/,"operators.directivesEnd"],[/\.{3}/,"operators.documentEnd"],[/[-?:](?= )/,"operators"],{include:"@anchor"},{include:"@tagHandle"},{include:"@flowCollections"},{include:"@blockStyle"},[/@numberInteger(?![ \t]*\S+)/,"number"],[/@numberFloat(?![ \t]*\S+)/,"number.float"],[/@numberOctal(?![ \t]*\S+)/,"number.octal"],[/@numberHex(?![ \t]*\S+)/,"number.hex"],[/@numberInfinity(?![ \t]*\S+)/,"number.infinity"],[/@numberNaN(?![ \t]*\S+)/,"number.nan"],[/@numberDate(?![ \t]*\S+)/,"number.date"],[/(".*?"|'.*?'|[^#'"]*?)([ \t]*)(:)( |$)/,["type","white","operators","white"]],{include:"@flowScalars"},[/.+?(?=(\s+#|$))/,{cases:{"@keywords":"keyword","@default":"string"}}]],object:[{include:"@whitespace"},{include:"@comment"},[/\}/,"@brackets","@pop"],[/,/,"delimiter.comma"],[/:(?= )/,"operators"],[/(?:".*?"|'.*?'|[^,\{\[]+?)(?=: )/,"type"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\},]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],array:[{include:"@whitespace"},{include:"@comment"},[/\]/,"@brackets","@pop"],[/,/,"delimiter.comma"],{include:"@flowCollections"},{include:"@flowScalars"},{include:"@tagHandle"},{include:"@anchor"},{include:"@flowNumber"},[/[^\],]+/,{cases:{"@keywords":"keyword","@default":"string"}}]],multiString:[[/^( +).+$/,"string","@multiStringContinued.$1"]],multiStringContinued:[[/^( *).+$/,{cases:{"$1==$S2":"string","@default":{token:"@rematch",next:"@popall"}}}]],whitespace:[[/[ \t\r\n]+/,"white"]],comment:[[/#.*$/,"comment"]],flowCollections:[[/\[/,"@brackets","@array"],[/\{/,"@brackets","@object"]],flowScalars:[[/"([^"\\]|\\.)*$/,"string.invalid"],[/'([^'\\]|\\.)*$/,"string.invalid"],[/'[^']*'/,"string"],[/"/,"string","@doubleQuotedString"]],doubleQuotedString:[[/[^\\"]+/,"string"],[/@escapes/,"string.escape"],[/\\./,"string.escape.invalid"],[/"/,"string","@pop"]],blockStyle:[[/[>|][0-9]*[+-]?$/,"operators","@multiString"]],flowNumber:[[/@numberInteger(?=[ \t]*[,\]\}])/,"number"],[/@numberFloat(?=[ \t]*[,\]\}])/,"number.float"],[/@numberOctal(?=[ \t]*[,\]\}])/,"number.octal"],[/@numberHex(?=[ \t]*[,\]\}])/,"number.hex"],[/@numberInfinity(?=[ \t]*[,\]\}])/,"number.infinity"],[/@numberNaN(?=[ \t]*[,\]\}])/,"number.nan"],[/@numberDate(?=[ \t]*[,\]\}])/,"number.date"]],tagHandle:[[/\![^ ]*/,"tag"]],anchor:[[/[&*][^ ]+/,"namespace"]]}};return y($);})();
|
||||||
|
return moduleExports;
|
||||||
|
});
|
|
@ -1,26 +1,26 @@
|
||||||
{{ define "main" }}
|
{{ define "main" }}
|
||||||
<div class="container max-w-screen-md flex flex-col mt-20 gap-20">
|
<div class="container max-w-screen-md flex flex-col mt-20 gap-20">
|
||||||
{{ $length := len .Checks }}
|
{{ $length := len .Targets }}
|
||||||
{{ if eq $length 0 }}
|
{{ if eq $length 0 }}
|
||||||
<section>
|
<section>
|
||||||
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
|
<div class="py-8 px-4 mx-auto max-w-screen-xl text-center lg:py-16">
|
||||||
<h1
|
<h1
|
||||||
class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl"
|
class="mb-4 text-2xl font-extrabold tracking-tight leading-none text-gray-900 md:text-3xl lg:text-4xl"
|
||||||
>
|
>
|
||||||
There are no checks yet.
|
There are no targets yet.
|
||||||
</h1>
|
</h1>
|
||||||
<p
|
<p
|
||||||
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
class="mb-8 text-l font-normal text-gray-700 lg:text-l sm:px-8 lg:px-40"
|
||||||
>
|
>
|
||||||
Create a check to check your services and get notified when they are
|
Create a target to target your services and get notified when they
|
||||||
down.
|
are down.
|
||||||
</p>
|
</p>
|
||||||
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
<div class="flex flex-col gap-4 sm:flex-row sm:justify-center">
|
||||||
<a
|
<a
|
||||||
href="/settings/checks/create"
|
href="/settings/targets/create"
|
||||||
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
class="inline-flex justify-center items-center py-3 px-5 text-base font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300"
|
||||||
>
|
>
|
||||||
Create First Check
|
Create First Target
|
||||||
<svg class="feather ml-1 h-5 w-5 overflow-visible">
|
<svg class="feather ml-1 h-5 w-5 overflow-visible">
|
||||||
<use href="/static/icons/feather-sprite.svg#plus" />
|
<use href="/static/icons/feather-sprite.svg#plus" />
|
||||||
</svg>
|
</svg>
|
||||||
|
@ -69,7 +69,7 @@
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
<div class="checks flex flex-col gap-4">
|
<div class="targets flex flex-col gap-4">
|
||||||
<div
|
<div
|
||||||
class="inline-flex gap-1 justify-center md:justify-end time-range"
|
class="inline-flex gap-1 justify-center md:justify-end time-range"
|
||||||
role="group"
|
role="group"
|
||||||
|
@ -93,7 +93,7 @@
|
||||||
>90 Minutes</a
|
>90 Minutes</a
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
{{ range $group, $checksAndStatus := .Checks }}
|
{{ range $group, $targetsAndStatus := .Targets }}
|
||||||
<details
|
<details
|
||||||
open
|
open
|
||||||
class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:rotate-90"
|
class="bg-white shadow-md rounded-lg p-6 py-4 gap-2 [&_svg]:open:rotate-90"
|
||||||
|
@ -101,11 +101,11 @@
|
||||||
<summary
|
<summary
|
||||||
class="flex flex-row gap-2 p-3 py-2 -mx-3 cursor-pointer hover:bg-blue-50 rounded-lg"
|
class="flex flex-row gap-2 p-3 py-2 -mx-3 cursor-pointer hover:bg-blue-50 rounded-lg"
|
||||||
>
|
>
|
||||||
{{ if eq $checksAndStatus.Status "SUCCESS" }}
|
{{ if eq $targetsAndStatus.Status "SUCCESS" }}
|
||||||
<span
|
<span
|
||||||
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
|
class="flex w-3 h-3 bg-green-400 rounded-full self-center"
|
||||||
></span>
|
></span>
|
||||||
{{ else if eq $checksAndStatus.Status "FAILURE" }}
|
{{ else if eq $targetsAndStatus.Status "FAILURE" }}
|
||||||
<span
|
<span
|
||||||
class="flex w-3 h-3 bg-red-400 rounded-full self-center"
|
class="flex w-3 h-3 bg-red-400 rounded-full self-center"
|
||||||
></span>
|
></span>
|
||||||
|
@ -123,7 +123,7 @@
|
||||||
<use href="/static/icons/feather-sprite.svg#chevron-right" />
|
<use href="/static/icons/feather-sprite.svg#chevron-right" />
|
||||||
</svg>
|
</svg>
|
||||||
</summary>
|
</summary>
|
||||||
{{ range $checksAndStatus.Checks }}
|
{{ range $targetsAndStatus.Targets }}
|
||||||
<div
|
<div
|
||||||
class="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-2 pb-2 border-b last-of-type:pb-0 last-of-type:border-0 border-gray-100"
|
class="grid grid-cols-1 sm:grid-cols-2 gap-2 mt-2 pb-2 border-b last-of-type:pb-0 last-of-type:border-0 border-gray-100"
|
||||||
>
|
>
|
||||||
|
|
|
@ -48,9 +48,8 @@
|
||||||
</caption>
|
</caption>
|
||||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th scope="col">Check Group</th>
|
|
||||||
<th scope="col">Name</th>
|
<th scope="col">Name</th>
|
||||||
<th scope="col">Visibility</th>
|
<th scope="col">Targets</th>
|
||||||
<th scope="col">Worker Groups</th>
|
<th scope="col">Worker Groups</th>
|
||||||
<th scope="col">State</th>
|
<th scope="col">State</th>
|
||||||
<th scope="col">Schedule</th>
|
<th scope="col">Schedule</th>
|
||||||
|
@ -58,90 +57,54 @@
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .CheckGroups }}
|
{{ range $checks := .Checks }}
|
||||||
{{ $currentGroup := . }}
|
<tr>
|
||||||
<tr class="row-special">
|
<th scope="row">
|
||||||
<th scope="rowgroup">
|
{{ .Name }}
|
||||||
{{ . }}
|
|
||||||
</th>
|
</th>
|
||||||
<td></td>
|
<td>
|
||||||
<td></td>
|
<span
|
||||||
<td></td>
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
|
||||||
<td></td>
|
>3</span
|
||||||
<td></td>
|
>
|
||||||
<td></td>
|
</td>
|
||||||
</tr>
|
<td>
|
||||||
{{ range $group, $checks := $.Checks }}
|
{{ range .WorkerGroups }}
|
||||||
{{ if eq $group $currentGroup }}
|
<span
|
||||||
{{ range $checks }}
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
<tr>
|
>
|
||||||
<th scope="row" aria-hidden="true">└─</th>
|
{{ . }}
|
||||||
<th scope="row">
|
</span>
|
||||||
{{ .Name }}
|
|
||||||
</th>
|
|
||||||
<td>
|
|
||||||
{{ if eq .Visibility "PUBLIC" }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
|
||||||
>
|
|
||||||
Public
|
|
||||||
</span>
|
|
||||||
{{ else if eq .Visibility "PRIVATE" }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-fuchsia-100 text-fuchsia-800"
|
|
||||||
>
|
|
||||||
Private
|
|
||||||
</span>
|
|
||||||
{{ else }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
|
|
||||||
>
|
|
||||||
Unknown
|
|
||||||
</span>
|
|
||||||
{{ end }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ range .WorkerGroups }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
|
||||||
>
|
|
||||||
{{ . }}
|
|
||||||
</span>
|
|
||||||
{{ end }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ if eq .State "ACTIVE" }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
|
|
||||||
>
|
|
||||||
ACTIVE
|
|
||||||
</span>
|
|
||||||
{{ else if eq .State "PAUSED" }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800"
|
|
||||||
>
|
|
||||||
PAUSED
|
|
||||||
</span>
|
|
||||||
{{ else }}
|
|
||||||
<span
|
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
|
|
||||||
>
|
|
||||||
UNKNOWN
|
|
||||||
</span>
|
|
||||||
{{ end }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
{{ .Schedule }}
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="/settings/checks/{{ .Id }}" class="link"
|
|
||||||
>Details</a
|
|
||||||
>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
{{ end }}
|
{{ end }}
|
||||||
{{ end }}
|
</td>
|
||||||
{{ end }}
|
<td>
|
||||||
|
{{ if eq .State "ACTIVE" }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
|
||||||
|
>
|
||||||
|
ACTIVE
|
||||||
|
</span>
|
||||||
|
{{ else if eq .State "PAUSED" }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800"
|
||||||
|
>
|
||||||
|
PAUSED
|
||||||
|
</span>
|
||||||
|
{{ else }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
|
||||||
|
>
|
||||||
|
UNKNOWN
|
||||||
|
</span>
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ .Schedule }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/settings/checks/{{ .Id }}" class="link">Details</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -2,40 +2,20 @@
|
||||||
<section class="p-5">
|
<section class="p-5">
|
||||||
<form action="/settings/checks/create" method="post">
|
<form action="/settings/checks/create" method="post">
|
||||||
<label for="name">Name</label>
|
<label for="name">Name</label>
|
||||||
<input type="text" name="name" id="name" placeholder="Github.com" />
|
<input type="text" name="name" id="name" placeholder="HTTP GET Request" />
|
||||||
<p>Name of the check can be anything.</p>
|
<p>Name of the check can be anything.</p>
|
||||||
<label for="visibility">Visibility</label>
|
|
||||||
<select name="visibility" id="visibility" required>
|
|
||||||
<option value="PUBLIC">Public</option>
|
|
||||||
<option value="PRIVATE">Private</option>
|
|
||||||
</select>
|
|
||||||
<p>
|
|
||||||
Visibility determines who can see the check. If set to
|
|
||||||
<code>public</code>, it will be visible to everyone on the homepage.
|
|
||||||
Otherwise it will be only visible to signed in users.
|
|
||||||
</p>
|
|
||||||
<label for="group">Check Group</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="group"
|
|
||||||
id="group"
|
|
||||||
placeholder="default"
|
|
||||||
value="default"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p>
|
|
||||||
Group checks together. This affects how they are presented on the
|
|
||||||
homepage.
|
|
||||||
</p>
|
|
||||||
<label for="workergroups">Worker Groups</label>
|
<label for="workergroups">Worker Groups</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
name="workergroups"
|
name="workergroups"
|
||||||
id="workergroups"
|
id="workergroups"
|
||||||
placeholder="NA EU"
|
placeholder="europe asia"
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<p>Worker groups are used to distribute the check to specific workers.</p>
|
<p>
|
||||||
|
Worker groups are used to distribute the check to specific workers.
|
||||||
|
Space is a separator between groups.
|
||||||
|
</p>
|
||||||
<label for="schedule">Schedule</label>
|
<label for="schedule">Schedule</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -54,15 +34,27 @@
|
||||||
<code>@daily</code>, <code>@weekly</code>, <code>@monthly</code>,
|
<code>@daily</code>, <code>@weekly</code>, <code>@monthly</code>,
|
||||||
<code>@yearly</code>.
|
<code>@yearly</code>.
|
||||||
</p>
|
</p>
|
||||||
<label for="script">Script</label>
|
<label for="filter">Filter</label>
|
||||||
<textarea required id="script" name="script" class="h-96">
|
<textarea required id="filter" name="filter" class="sm:col-span-2 h-12">
|
||||||
{{ ScriptUnescapeString .Example }}</textarea
|
{{ ScriptUnescapeString .ExampleFilter }}</textarea
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="editor"
|
id="editor-filter"
|
||||||
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
class="hidden sm:col-span-2 block w-full h-12 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
></div>
|
></div>
|
||||||
<p>
|
<p class="sm:col-span-2">
|
||||||
|
With filter we specify what targets the check will run on. The must be a
|
||||||
|
javascript expression that returns a boolean.
|
||||||
|
</p>
|
||||||
|
<label for="script">Script</label>
|
||||||
|
<textarea required id="script" name="script" class="sm:col-span-2 h-96">
|
||||||
|
{{ ScriptUnescapeString .ExampleScript }}</textarea
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="editor-script"
|
||||||
|
class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
|
></div>
|
||||||
|
<p class="sm:col-span-2">
|
||||||
Script is what determines the status of a service. You can read more
|
Script is what determines the status of a service. You can read more
|
||||||
about it on
|
about it on
|
||||||
<a target="_blank" href="https://k6.io/docs/using-k6/http-requests/"
|
<a target="_blank" href="https://k6.io/docs/using-k6/http-requests/"
|
||||||
|
@ -75,36 +67,50 @@
|
||||||
|
|
||||||
<script src="/static/monaco/vs/loader.js"></script>
|
<script src="/static/monaco/vs/loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function htmlDecode(input) {
|
const items = [
|
||||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
{
|
||||||
return doc.documentElement.textContent;
|
name: "filter",
|
||||||
}
|
language: "javascript",
|
||||||
script = htmlDecode("{{ .Example }}");
|
options: {
|
||||||
|
quickSuggestions: false,
|
||||||
document.getElementById("editor").classList.remove("hidden");
|
},
|
||||||
document.getElementById("script").hidden = true;
|
},
|
||||||
|
{ name: "script", language: "javascript" },
|
||||||
|
];
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
const script = window.editor.getValue();
|
for (const { name } of items) {
|
||||||
document.getElementById("script").value = script;
|
const elem = window.editors[name].getValue();
|
||||||
|
document.getElementById(name).value = elem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require.config({ paths: { vs: "/static/monaco/vs" } });
|
window.editors = {};
|
||||||
require(["vs/editor/editor.main"], function () {
|
for (const { name, language, options = {} } of items) {
|
||||||
window.editor = monaco.editor.create(document.getElementById("editor"), {
|
const textarea = document.getElementById(name);
|
||||||
value: script,
|
const editor = document.getElementById("editor-" + name);
|
||||||
language: "javascript",
|
|
||||||
minimap: { enabled: false },
|
|
||||||
codeLens: false,
|
|
||||||
contextmenu: false,
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const divElem = document.getElementById("editor");
|
editor.classList.remove("hidden");
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
textarea.hidden = true;
|
||||||
window.editor.layout();
|
|
||||||
|
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||||
|
require(["vs/editor/editor.main"], function () {
|
||||||
|
window.editors[name] = monaco.editor.create(editor, {
|
||||||
|
value: textarea.value,
|
||||||
|
language: language,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
codeLens: false,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
wordWrap: "on",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
window.editors[name].layout();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(editor);
|
||||||
});
|
});
|
||||||
resizeObserver.observe(divElem);
|
}
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -2,38 +2,6 @@
|
||||||
<section class="p-5">
|
<section class="p-5">
|
||||||
<form action="/settings/checks/{{ .Check.Id }}" method="post">
|
<form action="/settings/checks/{{ .Check.Id }}" method="post">
|
||||||
<h2>Configuration</h2>
|
<h2>Configuration</h2>
|
||||||
<label for="visibility">Visibility</label>
|
|
||||||
<select name="visibility" id="visibility" required>
|
|
||||||
<option
|
|
||||||
{{ if eq .Check.Visibility "PUBLIC" }}selected="selected"{{ end }}
|
|
||||||
value="PUBLIC"
|
|
||||||
>
|
|
||||||
Public
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
{{ if eq .Check.Visibility "PRIVATE" }}selected="selected"{{ end }}
|
|
||||||
value="PRIVATE"
|
|
||||||
>
|
|
||||||
Private
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
<p>
|
|
||||||
Visibility determines who can see the check. If set to
|
|
||||||
<code>public</code>, it will be visible to everyone on the homepage.
|
|
||||||
Otherwise it will be only visible to signed in users.
|
|
||||||
</p>
|
|
||||||
<label for="group">Check Group</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
name="group"
|
|
||||||
id="group"
|
|
||||||
value="{{ .Check.Group }}"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<p>
|
|
||||||
Group checks together. This affects how they are presented on the
|
|
||||||
homepage.
|
|
||||||
</p>
|
|
||||||
<label for="workergroups">Worker Groups</label>
|
<label for="workergroups">Worker Groups</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -60,15 +28,27 @@
|
||||||
<code>@daily</code>, <code>@weekly</code>, <code>@monthly</code>,
|
<code>@daily</code>, <code>@weekly</code>, <code>@monthly</code>,
|
||||||
<code>@yearly</code>.
|
<code>@yearly</code>.
|
||||||
</p>
|
</p>
|
||||||
|
<label for="filter">Filter</label>
|
||||||
|
<textarea required id="filter" name="filter" class="sm:col-span-2 h-12">
|
||||||
|
{{ ScriptUnescapeString .Check.Filter }}</textarea
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="editor-filter"
|
||||||
|
class="hidden sm:col-span-2 block w-full h-12 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
|
></div>
|
||||||
|
<p class="sm:col-span-2">
|
||||||
|
With filter we specify what targets the check will run on. The must be a
|
||||||
|
javascript expression that returns a boolean.
|
||||||
|
</p>
|
||||||
<label for="script">Script</label>
|
<label for="script">Script</label>
|
||||||
<textarea required id="script" name="script" class="h-96">
|
<textarea required id="script" name="script" class="sm:col-span-2 h-96">
|
||||||
{{ ScriptUnescapeString .Check.Script }}</textarea
|
{{ ScriptUnescapeString .Check.Script }}</textarea
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="editor"
|
id="editor-script"
|
||||||
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden hidden"
|
class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
></div>
|
></div>
|
||||||
<p>
|
<p class="sm:col-span-2">
|
||||||
Script is what determines the status of a service. You can read more
|
Script is what determines the status of a service. You can read more
|
||||||
about it on
|
about it on
|
||||||
<a target="_blank" href="https://k6.io/docs/using-k6/http-requests/"
|
<a target="_blank" href="https://k6.io/docs/using-k6/http-requests/"
|
||||||
|
@ -135,79 +115,119 @@
|
||||||
</caption>
|
</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Check ID</th>
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Worker Group</th>
|
<th>Worker Group</th>
|
||||||
<th>Created At</th>
|
<th>Started At</th>
|
||||||
|
<th>Ended At</th>
|
||||||
<th>Duration</th>
|
<th>Duration</th>
|
||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .History }}
|
{{ range .History }}
|
||||||
<tr>
|
{{ if eq .Status "Running" }}
|
||||||
<td>
|
<tr>
|
||||||
<span
|
<td>{{ .CheckId }}</td>
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
|
<td>
|
||||||
bg-green-100 text-green-800
|
<span
|
||||||
{{ else }}
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
bg-red-100 text-red-800
|
>
|
||||||
{{ end }}"
|
{{ .Status }}...
|
||||||
>
|
</span>
|
||||||
{{ .Status }}
|
</td>
|
||||||
</span>
|
<td>
|
||||||
</td>
|
<span
|
||||||
<td>
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
<span
|
>
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
{{ .WorkerGroupName }}
|
||||||
>
|
</span>
|
||||||
{{ .WorkerGroupName }}
|
</td>
|
||||||
</span>
|
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||||
</td>
|
<td></td>
|
||||||
<td>
|
<td></td>
|
||||||
{{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }}
|
<td class="whitespace-normal"></td>
|
||||||
</td>
|
</tr>
|
||||||
<td>{ .Duration }</td>
|
{{ else }}
|
||||||
<td class="whitespace-normal">
|
<tr>
|
||||||
{{ .Note }}
|
<td>{{ .CheckId }}</td>
|
||||||
</td>
|
<td>
|
||||||
</tr>
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||||
|
{{ if eq .Status "Completed" }}
|
||||||
|
bg-purple-100 text-purple-800
|
||||||
|
{{ else }}
|
||||||
|
bg-red-100 text-red-800
|
||||||
|
{{ end }}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ .Status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
|
>
|
||||||
|
{{ .WorkerGroupName }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
<td>{{ .EndTime.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
<td>{{ DurationRoundMillisecond .Duration }}</td>
|
||||||
|
<td class="whitespace-normal">{{ .Note }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script src="/static/monaco/vs/loader.js"></script>
|
<script src="/static/monaco/vs/loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("editor").classList.remove("hidden");
|
const items = [
|
||||||
document.getElementById("script").hidden = true;
|
{
|
||||||
|
name: "filter",
|
||||||
|
language: "javascript",
|
||||||
|
options: {
|
||||||
|
quickSuggestions: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{ name: "script", language: "javascript" },
|
||||||
|
];
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
const script = window.editor.getValue();
|
for (const { name } of items) {
|
||||||
document.getElementById('script').value = script;
|
const elem = window.editors[name].getValue();
|
||||||
}
|
document.getElementById(name).value = elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function htmlDecode(input) {
|
window.editors = {};
|
||||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
for (const { name, language, options = {} } of items) {
|
||||||
return doc.documentElement.textContent;
|
const textarea = document.getElementById(name);
|
||||||
}
|
const editor = document.getElementById("editor-" + name);
|
||||||
script = htmlDecode("{{ .Check.Script }}")
|
|
||||||
|
|
||||||
require.config({ paths: { vs: '/static/monaco/vs' } });
|
editor.classList.remove("hidden");
|
||||||
require(['vs/editor/editor.main'], function () {
|
textarea.hidden = true;
|
||||||
window.editor = monaco.editor.create(document.getElementById('editor'), {
|
|
||||||
value: script,
|
|
||||||
language: 'javascript',
|
|
||||||
minimap: { enabled: false },
|
|
||||||
codeLens: false,
|
|
||||||
contextmenu: false,
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const divElem = document.getElementById('editor');
|
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||||
const resizeObserver = new ResizeObserver(entries => {
|
require(["vs/editor/editor.main"], function () {
|
||||||
window.editor.layout();
|
window.editors[name] = monaco.editor.create(editor, {
|
||||||
});
|
value: textarea.value,
|
||||||
resizeObserver.observe(divElem);
|
language: language,
|
||||||
});
|
minimap: { enabled: false },
|
||||||
</script>
|
codeLens: false,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
wordWrap: "on",
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
window.editors[name].layout();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -21,9 +21,7 @@
|
||||||
<div
|
<div
|
||||||
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
class="inline-block bg-white rounded-lg shadow p-5 text-center sm:text-left"
|
||||||
>
|
>
|
||||||
<h3 class="text-sm leading-6 font-medium text-gray-400">
|
<h3 class="text-sm leading-6 font-medium text-gray-400">Total Checks</h3>
|
||||||
Total Checks
|
|
||||||
</h3>
|
|
||||||
<p class="text-3xl font-bold text-black">{{ .ChecksCount }}</p>
|
<p class="text-3xl font-bold text-black">{{ .ChecksCount }}</p>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
@ -44,56 +42,76 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<section class="mt-4">
|
<section>
|
||||||
<table>
|
<table>
|
||||||
<caption>
|
<caption>
|
||||||
Execution History
|
History
|
||||||
<p>Last 10 executions for all checks and worker groups.</p>
|
<p>Last 10 executions.</p>
|
||||||
</caption>
|
</caption>
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Check</th>
|
<th>Check ID</th>
|
||||||
<th>Worker Group</th>
|
|
||||||
<th>Status</th>
|
<th>Status</th>
|
||||||
<th>Executed At</th>
|
<th>Worker Group</th>
|
||||||
|
<th>Started At</th>
|
||||||
|
<th>Ended At</th>
|
||||||
|
<th>Duration</th>
|
||||||
<th>Note</th>
|
<th>Note</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{{ range .History }}
|
{{ range .History }}
|
||||||
<tr>
|
{{ if eq .Status "Running" }}
|
||||||
<th>
|
<tr>
|
||||||
<a
|
<td>{{ .CheckId }}</td>
|
||||||
class="underline hover:text-blue-600"
|
<td>
|
||||||
href="/settings/checks/{{ .CheckId }}"
|
<span
|
||||||
>{{ .CheckName }}</a
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
>
|
>
|
||||||
</th>
|
{{ .Status }}...
|
||||||
<td>
|
</span>
|
||||||
<span
|
</td>
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
<td>
|
||||||
>
|
<span
|
||||||
{{ .WorkerGroupName }}
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
</span>
|
>
|
||||||
</td>
|
{{ .WorkerGroupName }}
|
||||||
<td>
|
</span>
|
||||||
<span
|
</td>
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
|
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||||
bg-green-100 text-green-800
|
<td></td>
|
||||||
{{ else }}
|
<td></td>
|
||||||
bg-red-100 text-red-800
|
<td class="whitespace-normal"></td>
|
||||||
{{ end }}"
|
</tr>
|
||||||
>
|
{{ else }}
|
||||||
{{ .Status }}
|
<tr>
|
||||||
</span>
|
<td>{{ .CheckId }}</td>
|
||||||
</td>
|
<td>
|
||||||
<td>
|
<span
|
||||||
{{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }}
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full
|
||||||
</td>
|
{{ if eq .Status "Completed" }}
|
||||||
<td class="whitespace-normal">
|
bg-purple-100 text-purple-800
|
||||||
{{ .Note }}
|
{{ else }}
|
||||||
</td>
|
bg-red-100 text-red-800
|
||||||
</tr>
|
{{ end }}
|
||||||
|
"
|
||||||
|
>
|
||||||
|
{{ .Status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
|
>
|
||||||
|
{{ .WorkerGroupName }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>{{ .StartTime.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
<td>{{ .EndTime.Format "2006-01-02 15:04:05" }}</td>
|
||||||
|
<td>{{ DurationRoundMillisecond .Duration }}</td>
|
||||||
|
<td class="whitespace-normal">{{ .Note }}</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
{{ end }}
|
{{ end }}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
|
@ -55,32 +55,88 @@
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</caption>
|
</caption>
|
||||||
<thead>
|
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
||||||
<tr>
|
<tr>
|
||||||
<th>Name</th>
|
<th scope="col">Target Group</th>
|
||||||
<th>Type</th>
|
<th scope="col">Name</th>
|
||||||
<th>Action</th>
|
<th scope="col">Visibility</th>
|
||||||
|
<th scope="col">State</th>
|
||||||
|
<th scope="col">Action</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
{{ range .Targets }}
|
<tbody>
|
||||||
<tbody>
|
{{ range .TargetGroups }}
|
||||||
<tr>
|
{{ $currentGroup := . }}
|
||||||
<th scope="row">
|
<tr class="row-special">
|
||||||
{{ .Name }}
|
<th scope="rowgroup">
|
||||||
|
{{ . }}
|
||||||
</th>
|
</th>
|
||||||
<td>
|
<td></td>
|
||||||
<span
|
<td></td>
|
||||||
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
<td></td>
|
||||||
>
|
<td></td>
|
||||||
{{ .Type }}
|
|
||||||
</span>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<a href="/settings/targets/{{ .Id }}" class="link">Details</a>
|
|
||||||
</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
{{ range $group, $targets := $.Targets }}
|
||||||
{{ end }}
|
{{ if eq $group $currentGroup }}
|
||||||
|
{{ range $targets }}
|
||||||
|
<tr>
|
||||||
|
<th scope="row" aria-hidden="true">└─</th>
|
||||||
|
<th scope="row">
|
||||||
|
{{ .Name }}
|
||||||
|
</th>
|
||||||
|
<td>
|
||||||
|
{{ if eq .Visibility "PUBLIC" }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
|
>
|
||||||
|
Public
|
||||||
|
</span>
|
||||||
|
{{ else if eq .Visibility "PRIVATE" }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-fuchsia-100 text-fuchsia-800"
|
||||||
|
>
|
||||||
|
Private
|
||||||
|
</span>
|
||||||
|
{{ else }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
|
||||||
|
>
|
||||||
|
Unknown
|
||||||
|
</span>
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ if eq .State "ACTIVE" }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800"
|
||||||
|
>
|
||||||
|
ACTIVE
|
||||||
|
</span>
|
||||||
|
{{ else if eq .State "PAUSED" }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-yellow-100 text-yellow-800"
|
||||||
|
>
|
||||||
|
PAUSED
|
||||||
|
</span>
|
||||||
|
{{ else }}
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800"
|
||||||
|
>
|
||||||
|
UNKNOWN
|
||||||
|
</span>
|
||||||
|
{{ end }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a href="/settings/targets/{{ .Id }}" class="link"
|
||||||
|
>Details</a
|
||||||
|
>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
92
web/templates/pages/settings_targets_create.tmpl
Normal file
92
web/templates/pages/settings_targets_create.tmpl
Normal file
|
@ -0,0 +1,92 @@
|
||||||
|
{{ define "settings" }}
|
||||||
|
<section class="p-5">
|
||||||
|
<form action="/settings/targets/create" method="post">
|
||||||
|
<label for="name">Name</label>
|
||||||
|
<input type="text" name="name" id="name" placeholder="Github.com" />
|
||||||
|
<p>Name of the target can be anything.</p>
|
||||||
|
<label for="visibility">Visibility</label>
|
||||||
|
<select name="visibility" id="visibility" required>
|
||||||
|
<option value="PUBLIC">Public</option>
|
||||||
|
<option value="PRIVATE">Private</option>
|
||||||
|
</select>
|
||||||
|
<p>
|
||||||
|
Visibility determines who can see the target. If set to
|
||||||
|
<code>public</code>, it will be visible to everyone on the homepage.
|
||||||
|
Otherwise it will be only visible to signed in users.
|
||||||
|
</p>
|
||||||
|
<label for="group">Target Group</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="group"
|
||||||
|
id="group"
|
||||||
|
placeholder="default"
|
||||||
|
value="default"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
Group targets together. This affects how they are presented on the
|
||||||
|
homepage.
|
||||||
|
</p>
|
||||||
|
<label for="metadata">Metadata</label>
|
||||||
|
<textarea
|
||||||
|
required
|
||||||
|
id="metadata"
|
||||||
|
name="metadata"
|
||||||
|
class="sm:col-span-2 h-96"
|
||||||
|
>
|
||||||
|
{{ ScriptUnescapeString .Example }}</textarea
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="editor-metadata"
|
||||||
|
class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
|
></div>
|
||||||
|
<p class="sm:col-span-2">
|
||||||
|
Metadata is a YAML object that contains the configuration for the
|
||||||
|
target. This configuration can be then used for <code>Checks</code> to
|
||||||
|
filter the targets to act on as well as by using
|
||||||
|
<code>getTarget()</code>
|
||||||
|
function to fetch target metadata.
|
||||||
|
</p>
|
||||||
|
<button type="submit" onclick="save()">Create</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="/static/monaco/vs/loader.js"></script>
|
||||||
|
<script>
|
||||||
|
const items = [{ name: "metadata", language: "yaml" }];
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
for (const { name } of items) {
|
||||||
|
const elem = window.editors[name].getValue();
|
||||||
|
document.getElementById(name).value = elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.editors = {};
|
||||||
|
for (const { name, language, options = {} } of items) {
|
||||||
|
const textarea = document.getElementById(name);
|
||||||
|
const editor = document.getElementById("editor-" + name);
|
||||||
|
|
||||||
|
editor.classList.remove("hidden");
|
||||||
|
textarea.hidden = true;
|
||||||
|
|
||||||
|
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||||
|
require(["vs/editor/editor.main"], function () {
|
||||||
|
window.editors[name] = monaco.editor.create(editor, {
|
||||||
|
value: textarea.value,
|
||||||
|
language: language,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
codeLens: false,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
window.editors[name].layout();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
203
web/templates/pages/settings_targets_describe.tmpl
Normal file
203
web/templates/pages/settings_targets_describe.tmpl
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
{{ define "settings" }}
|
||||||
|
<section class="p-5">
|
||||||
|
<form action="/settings/targets/{{ .Target.Id }}" method="post">
|
||||||
|
<h2>Configuration</h2>
|
||||||
|
<label for="visibility">Visibility</label>
|
||||||
|
<select name="visibility" id="visibility" required>
|
||||||
|
<option
|
||||||
|
{{ if eq .Target.Visibility "PUBLIC" }}selected="selected"{{ end }}
|
||||||
|
value="PUBLIC"
|
||||||
|
>
|
||||||
|
Public
|
||||||
|
</option>
|
||||||
|
<option
|
||||||
|
{{ if eq .Target.Visibility "PRIVATE" }}selected="selected"{{ end }}
|
||||||
|
value="PRIVATE"
|
||||||
|
>
|
||||||
|
Private
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<p>
|
||||||
|
Visibility determines who can see the target. If set to
|
||||||
|
<code>public</code>, it will be visible to everyone on the homepage.
|
||||||
|
Otherwise it will be only visible to signed in users.
|
||||||
|
</p>
|
||||||
|
<label for="group">Target Group</label>
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
name="group"
|
||||||
|
id="group"
|
||||||
|
value="{{ .Target.Group }}"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
<p>
|
||||||
|
Group targets together. This affects how they are presented on the
|
||||||
|
homepage.
|
||||||
|
</p>
|
||||||
|
<label for="metadata">Metadata</label>
|
||||||
|
<textarea
|
||||||
|
required
|
||||||
|
id="metadata"
|
||||||
|
name="metadata"
|
||||||
|
class="sm:col-span-2 h-96"
|
||||||
|
>
|
||||||
|
{{ ScriptUnescapeString .Target.Metadata }}</textarea
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
id="editor-metadata"
|
||||||
|
class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
|
></div>
|
||||||
|
<p class="sm:col-span-2">
|
||||||
|
Metadata is a YAML object that contains the configuration for the
|
||||||
|
target. This configuration can be then used for <code>Targets</code> to
|
||||||
|
filter the targets to act on as well as by using
|
||||||
|
<code>getTarget()</code>
|
||||||
|
function to fetch target metadata.
|
||||||
|
</p>
|
||||||
|
<button type="submit" onclick="save()">Save</button>
|
||||||
|
</form>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div class="flex md:flex-row flex-col gap-4 h-min">
|
||||||
|
<section class="p-5 flex-1">
|
||||||
|
<h2 class="mb-2 flex flex-row gap-2">
|
||||||
|
State
|
||||||
|
{{ if eq .Target.State "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 .Target.State "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"
|
||||||
|
>
|
||||||
|
PAUSED
|
||||||
|
</span>
|
||||||
|
{{ end }}
|
||||||
|
</h2>
|
||||||
|
<p class="text-sm mb-2">
|
||||||
|
Pausing the target will stop it from executing. This can be useful in
|
||||||
|
cases of expected downtime. Or when the target is not needed anymore.
|
||||||
|
</p>
|
||||||
|
{{ if eq .Target.State "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/targets/{{ .Target.Id }}/disable"
|
||||||
|
>Pause</a
|
||||||
|
>
|
||||||
|
{{ else if eq .Target.State "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/targets/{{ .Target.Id }}/enable"
|
||||||
|
>Resume</a
|
||||||
|
>
|
||||||
|
{{ end }}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<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 target.</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/targets/{{ .Target.Id }}/delete"
|
||||||
|
>Delete</a
|
||||||
|
>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<table>
|
||||||
|
<caption>
|
||||||
|
History
|
||||||
|
<p>Last 10 executions of the targets.</p>
|
||||||
|
</caption>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Worker Group</th>
|
||||||
|
<th>Check</th>
|
||||||
|
<th>Created At</th>
|
||||||
|
<th>Duration</th>
|
||||||
|
<th>Note</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{{ range .History }}
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full {{ if eq .Status "SUCCESS" }}
|
||||||
|
bg-green-100 text-green-800
|
||||||
|
{{ else }}
|
||||||
|
bg-red-100 text-red-800
|
||||||
|
{{ end }}"
|
||||||
|
>
|
||||||
|
{{ .Status }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
|
>
|
||||||
|
{{ .WorkerGroupName }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<span
|
||||||
|
class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-blue-100 text-blue-800"
|
||||||
|
>
|
||||||
|
{{ .CheckName }}
|
||||||
|
</span>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{{ .CreatedAt.Time.Format "2006-01-02 15:04:05" }}
|
||||||
|
</td>
|
||||||
|
<td>{ .Duration }</td>
|
||||||
|
<td class="whitespace-normal">
|
||||||
|
{{ .Note }}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{{ end }}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<script src="/static/monaco/vs/loader.js"></script>
|
||||||
|
<script>
|
||||||
|
const items = [{ name: "metadata", language: "yaml" }];
|
||||||
|
|
||||||
|
function save() {
|
||||||
|
for (const { name } of items) {
|
||||||
|
const elem = window.editors[name].getValue();
|
||||||
|
document.getElementById(name).value = elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.editors = {};
|
||||||
|
for (const { name, language } of items) {
|
||||||
|
const textarea = document.getElementById(name);
|
||||||
|
const editor = document.getElementById("editor-" + name);
|
||||||
|
|
||||||
|
editor.classList.remove("hidden");
|
||||||
|
textarea.hidden = true;
|
||||||
|
|
||||||
|
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||||
|
require(["vs/editor/editor.main"], function () {
|
||||||
|
window.editors[name] = monaco.editor.create(editor, {
|
||||||
|
value: textarea.value,
|
||||||
|
language: language,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
codeLens: false,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
window.editors[name].layout();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
{{ end }}
|
|
@ -10,14 +10,14 @@
|
||||||
/>
|
/>
|
||||||
<p>Name of the trigger can be anything.</p>
|
<p>Name of the trigger can be anything.</p>
|
||||||
<label for="script">Script</label>
|
<label for="script">Script</label>
|
||||||
<textarea required id="script" name="script" class="h-96">
|
<textarea required id="script" name="script" class="sm:col-span-2 h-96">
|
||||||
{{ ScriptUnescapeString .Example }}</textarea
|
{{ ScriptUnescapeString .Example }}</textarea
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="editor"
|
id="editor-script"
|
||||||
class="hidden block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
></div>
|
></div>
|
||||||
<p>
|
<p class="sm:col-span-2">
|
||||||
The trigger script executes for every matching <code>target</code>'s
|
The trigger script executes for every matching <code>target</code>'s
|
||||||
execution of <code>trigger</code>. The outcome of that
|
execution of <code>trigger</code>. The outcome of that
|
||||||
<code>trigger</code> is passed to the script as a
|
<code>trigger</code> is passed to the script as a
|
||||||
|
@ -30,36 +30,40 @@
|
||||||
|
|
||||||
<script src="/static/monaco/vs/loader.js"></script>
|
<script src="/static/monaco/vs/loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
function htmlDecode(input) {
|
const items = [{ name: "script", language: "javascript" }];
|
||||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
|
||||||
return doc.documentElement.textContent;
|
|
||||||
}
|
|
||||||
script = htmlDecode("{{ .Example }}");
|
|
||||||
|
|
||||||
document.getElementById("editor").classList.remove("hidden");
|
|
||||||
document.getElementById("script").hidden = true;
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
const script = window.editor.getValue();
|
for (const { name } of items) {
|
||||||
document.getElementById("script").value = script;
|
const elem = window.editors[name].getValue();
|
||||||
|
document.getElementById(name).value = elem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
require.config({ paths: { vs: "/static/monaco/vs" } });
|
window.editors = {};
|
||||||
require(["vs/editor/editor.main"], function () {
|
for (const { name, language, options = {} } of items) {
|
||||||
window.editor = monaco.editor.create(document.getElementById("editor"), {
|
const textarea = document.getElementById(name);
|
||||||
value: script,
|
const editor = document.getElementById("editor-" + name);
|
||||||
language: "javascript",
|
|
||||||
minimap: { enabled: false },
|
|
||||||
codeLens: false,
|
|
||||||
contextmenu: false,
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const divElem = document.getElementById("editor");
|
editor.classList.remove("hidden");
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
textarea.hidden = true;
|
||||||
window.editor.layout();
|
|
||||||
|
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||||
|
require(["vs/editor/editor.main"], function () {
|
||||||
|
window.editors[name] = monaco.editor.create(editor, {
|
||||||
|
value: textarea.value,
|
||||||
|
language: language,
|
||||||
|
minimap: { enabled: false },
|
||||||
|
codeLens: false,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
window.editors[name].layout();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(editor);
|
||||||
});
|
});
|
||||||
resizeObserver.observe(divElem);
|
}
|
||||||
});
|
|
||||||
</script>
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -3,14 +3,14 @@
|
||||||
<form action="/settings/triggers/{{ .Trigger.Id }}" method="post">
|
<form action="/settings/triggers/{{ .Trigger.Id }}" method="post">
|
||||||
<h2>Configuration</h2>
|
<h2>Configuration</h2>
|
||||||
<label for="script">Script</label>
|
<label for="script">Script</label>
|
||||||
<textarea required id="script" name="script" class="h-96">
|
<textarea required id="script" name="script" class="sm:col-span-2 h-96">
|
||||||
{{ ScriptUnescapeString .Trigger.Script }}</textarea
|
{{ ScriptUnescapeString .Trigger.Script }}</textarea
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
id="editor"
|
id="editor-script"
|
||||||
class="block w-full h-96 rounded-lg border border-gray-300 overflow-hidden hidden"
|
class="hidden sm:col-span-2 block w-full h-96 rounded-lg border border-gray-300 overflow-hidden"
|
||||||
></div>
|
></div>
|
||||||
<p>
|
<p class="sm:col-span-2">
|
||||||
The trigger script executes for every matching <code>target</code>'s
|
The trigger script executes for every matching <code>target</code>'s
|
||||||
execution of <code>trigger</code>. The outcome of that
|
execution of <code>trigger</code>. The outcome of that
|
||||||
<code>trigger</code> is passed to the script as a
|
<code>trigger</code> is passed to the script as a
|
||||||
|
@ -111,37 +111,41 @@
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<script src="/static/monaco/vs/loader.js"></script>
|
<script src="/static/monaco/vs/loader.js"></script>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("editor").classList.remove("hidden");
|
const items = [{ name: "script", language: "javascript" }];
|
||||||
document.getElementById("script").hidden = true;
|
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
const script = window.editor.getValue();
|
for (const { name } of items) {
|
||||||
document.getElementById('script').value = script;
|
const elem = window.editors[name].getValue();
|
||||||
}
|
document.getElementById(name).value = elem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function htmlDecode(input) {
|
window.editors = {};
|
||||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
for (const { name, language, options = {} } of items) {
|
||||||
return doc.documentElement.textContent;
|
const textarea = document.getElementById(name);
|
||||||
}
|
const editor = document.getElementById("editor-" + name);
|
||||||
script = htmlDecode("{{ .Trigger.Script }}")
|
|
||||||
|
|
||||||
require.config({ paths: { vs: '/static/monaco/vs' } });
|
editor.classList.remove("hidden");
|
||||||
require(['vs/editor/editor.main'], function () {
|
textarea.hidden = true;
|
||||||
window.editor = monaco.editor.create(document.getElementById('editor'), {
|
|
||||||
value: script,
|
|
||||||
language: 'javascript',
|
|
||||||
minimap: { enabled: false },
|
|
||||||
codeLens: false,
|
|
||||||
contextmenu: false,
|
|
||||||
scrollBeyondLastLine: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
const divElem = document.getElementById('editor');
|
require.config({ paths: { vs: "/static/monaco/vs" } });
|
||||||
const resizeObserver = new ResizeObserver(entries => {
|
require(["vs/editor/editor.main"], function () {
|
||||||
window.editor.layout();
|
window.editors[name] = monaco.editor.create(editor, {
|
||||||
});
|
value: textarea.value,
|
||||||
resizeObserver.observe(divElem);
|
language: language,
|
||||||
});
|
minimap: { enabled: false },
|
||||||
</script>
|
codeLens: false,
|
||||||
|
contextmenu: false,
|
||||||
|
scrollBeyondLastLine: false,
|
||||||
|
...options,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resizeObserver = new ResizeObserver((entries) => {
|
||||||
|
window.editors[name].layout();
|
||||||
|
});
|
||||||
|
resizeObserver.observe(editor);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
</script>
|
||||||
{{ end }}
|
{{ end }}
|
||||||
|
|
|
@ -8,8 +8,8 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"code.tjo.space/mentos1386/zdravko/internal/script"
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mentos1386/zdravko/pkg/script"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed *
|
//go:embed *
|
||||||
|
@ -26,6 +26,12 @@ func load(files ...string) *template.Template {
|
||||||
|
|
||||||
t := template.New("default").Funcs(
|
t := template.New("default").Funcs(
|
||||||
template.FuncMap{
|
template.FuncMap{
|
||||||
|
"DurationRoundSecond": func(d time.Duration) time.Duration {
|
||||||
|
return d.Round(time.Second)
|
||||||
|
},
|
||||||
|
"DurationRoundMillisecond": func(d time.Duration) time.Duration {
|
||||||
|
return d.Round(time.Millisecond)
|
||||||
|
},
|
||||||
"StringsJoin": strings.Join,
|
"StringsJoin": strings.Join,
|
||||||
"Now": time.Now,
|
"Now": time.Now,
|
||||||
"ScriptUnescapeString": script.UnescapeString,
|
"ScriptUnescapeString": script.UnescapeString,
|
||||||
|
@ -51,6 +57,8 @@ func NewTemplates() *Templates {
|
||||||
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
|
"settings_triggers_create.tmpl": loadSettings("pages/settings_triggers_create.tmpl"),
|
||||||
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
|
"settings_triggers_describe.tmpl": loadSettings("pages/settings_triggers_describe.tmpl"),
|
||||||
"settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"),
|
"settings_targets.tmpl": loadSettings("pages/settings_targets.tmpl"),
|
||||||
|
"settings_targets_create.tmpl": loadSettings("pages/settings_targets_create.tmpl"),
|
||||||
|
"settings_targets_describe.tmpl": loadSettings("pages/settings_targets_describe.tmpl"),
|
||||||
"settings_incidents.tmpl": loadSettings("pages/settings_incidents.tmpl"),
|
"settings_incidents.tmpl": loadSettings("pages/settings_incidents.tmpl"),
|
||||||
"settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"),
|
"settings_notifications.tmpl": loadSettings("pages/settings_notifications.tmpl"),
|
||||||
"settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"),
|
"settings_worker_groups.tmpl": loadSettings("pages/settings_worker_groups.tmpl"),
|
||||||
|
|
Loading…
Reference in a new issue