mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-21 23:33:34 +00:00
feat: single schedule for all worker groups, simplify tables, show active workers
This commit is contained in:
parent
7035afe008
commit
306f583418
15 changed files with 400 additions and 229 deletions
|
@ -33,9 +33,10 @@ func (a *Activities) Monitor(ctx context.Context, param HealtcheckParam) (*Monit
|
|||
}
|
||||
|
||||
type HealtcheckAddToHistoryParam struct {
|
||||
Slug string
|
||||
Status string
|
||||
Note string
|
||||
Slug string
|
||||
Status string
|
||||
Note string
|
||||
WorkerGroup string
|
||||
}
|
||||
|
||||
type MonitorAddToHistoryResult struct {
|
||||
|
@ -45,8 +46,9 @@ func (a *Activities) MonitorAddToHistory(ctx context.Context, param HealtcheckAd
|
|||
url := fmt.Sprintf("%s/api/v1/monitors/%s/history", a.config.ApiUrl, param.Slug)
|
||||
|
||||
body := api.ApiV1MonitorsHistoryPOSTBody{
|
||||
Status: param.Status,
|
||||
Note: param.Note,
|
||||
Status: param.Status,
|
||||
Note: param.Note,
|
||||
WorkerGroup: param.WorkerGroup,
|
||||
}
|
||||
|
||||
jsonBody, err := json.Marshal(body)
|
||||
|
|
|
@ -14,20 +14,26 @@ import (
|
|||
"github.com/labstack/echo/v4"
|
||||
)
|
||||
|
||||
type WorkerWithToken struct {
|
||||
type WorkerWithTokenAndActiveWorkers struct {
|
||||
*models.WorkerGroup
|
||||
Token string
|
||||
Token string
|
||||
ActiveWorkers []string
|
||||
}
|
||||
|
||||
type WorkerGroupWithActiveWorkers struct {
|
||||
*models.WorkerGroupWithMonitors
|
||||
ActiveWorkers []string
|
||||
}
|
||||
|
||||
type SettingsWorkerGroups struct {
|
||||
*Settings
|
||||
WorkerGroups []*models.WorkerGroupWithMonitors
|
||||
WorkerGroups []*WorkerGroupWithActiveWorkers
|
||||
WorkerGroupsLength int
|
||||
}
|
||||
|
||||
type SettingsWorker struct {
|
||||
*Settings
|
||||
Worker *WorkerWithToken
|
||||
Worker *WorkerWithTokenAndActiveWorkers
|
||||
}
|
||||
|
||||
func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
|
||||
|
@ -38,14 +44,26 @@ func (h *BaseHandler) SettingsWorkerGroupsGET(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
workerGroupsWithActiveWorkers := make([]*WorkerGroupWithActiveWorkers, len(workerGroups))
|
||||
for i, workerGroup := range workerGroups {
|
||||
activeWorkers, err := services.GetActiveWorkers(context.Background(), workerGroup.Slug, h.temporal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
workerGroupsWithActiveWorkers[i] = &WorkerGroupWithActiveWorkers{
|
||||
WorkerGroupWithMonitors: workerGroup,
|
||||
ActiveWorkers: activeWorkers,
|
||||
}
|
||||
}
|
||||
|
||||
return c.Render(http.StatusOK, "settings_worker_groups.tmpl", &SettingsWorkerGroups{
|
||||
Settings: NewSettings(
|
||||
cc.Principal.User,
|
||||
GetPageByTitle(SettingsPages, "Worker Groups"),
|
||||
[]*components.Page{GetPageByTitle(SettingsPages, "Worker Groups")},
|
||||
),
|
||||
WorkerGroups: workerGroups,
|
||||
WorkerGroupsLength: len(workerGroups),
|
||||
WorkerGroups: workerGroupsWithActiveWorkers,
|
||||
WorkerGroupsLength: len(workerGroupsWithActiveWorkers),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -65,6 +83,11 @@ func (h *BaseHandler) SettingsWorkerGroupsDescribeGET(c echo.Context) error {
|
|||
return err
|
||||
}
|
||||
|
||||
activeWorkers, err := services.GetActiveWorkers(context.Background(), worker.Slug, h.temporal)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.Render(http.StatusOK, "settings_worker_groups_describe.tmpl", &SettingsWorker{
|
||||
Settings: NewSettings(
|
||||
cc.Principal.User,
|
||||
|
@ -77,9 +100,10 @@ func (h *BaseHandler) SettingsWorkerGroupsDescribeGET(c echo.Context) error {
|
|||
Breadcrumb: worker.Name,
|
||||
},
|
||||
}),
|
||||
Worker: &WorkerWithToken{
|
||||
WorkerGroup: worker,
|
||||
Token: token,
|
||||
Worker: &WorkerWithTokenAndActiveWorkers{
|
||||
WorkerGroup: worker,
|
||||
Token: token,
|
||||
ActiveWorkers: activeWorkers,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
|
@ -13,8 +13,8 @@ import (
|
|||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func getScheduleId(monitor *models.Monitor, group string) string {
|
||||
return "monitor-" + monitor.Slug + "-" + group
|
||||
func getScheduleId(monitor *models.Monitor) string {
|
||||
return "monitor-" + monitor.Slug
|
||||
}
|
||||
|
||||
func CreateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error {
|
||||
|
@ -121,7 +121,7 @@ WHERE monitors.slug=$1 AND monitors.deleted_at IS NULL
|
|||
func GetMonitors(ctx context.Context, db *sqlx.DB) ([]*models.Monitor, error) {
|
||||
monitors := []*models.Monitor{}
|
||||
err := db.SelectContext(ctx, &monitors,
|
||||
"SELECT * FROM monitors WHERE deleted_at IS NULL",
|
||||
"SELECT * FROM monitors WHERE deleted_at IS NULL ORDER BY name",
|
||||
)
|
||||
return monitors, err
|
||||
}
|
||||
|
@ -142,6 +142,7 @@ FROM monitors
|
|||
LEFT OUTER JOIN monitor_worker_groups ON monitors.slug = monitor_worker_groups.monitor_slug
|
||||
LEFT OUTER JOIN worker_groups ON monitor_worker_groups.worker_group_slug = worker_groups.slug
|
||||
WHERE monitors.deleted_at IS NULL
|
||||
ORDER BY monitors.name
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -188,59 +189,66 @@ func CreateOrUpdateMonitorSchedule(
|
|||
) error {
|
||||
log.Println("Creating or Updating Monitor Schedule")
|
||||
|
||||
args := make([]interface{}, 0)
|
||||
args = append(args, workflows.MonitorWorkflowParam{Script: monitor.Script, Slug: monitor.Slug})
|
||||
workerGroupStrings := make([]string, len(workerGroups))
|
||||
for i, group := range workerGroups {
|
||||
workerGroupStrings[i] = group.Slug
|
||||
}
|
||||
|
||||
for _, group := range workerGroups {
|
||||
options := client.ScheduleOptions{
|
||||
ID: getScheduleId(monitor, group.Slug),
|
||||
Spec: client.ScheduleSpec{
|
||||
CronExpressions: []string{monitor.Schedule},
|
||||
Jitter: time.Second * 10,
|
||||
args := make([]interface{}, 1)
|
||||
args[0] = workflows.MonitorWorkflowParam{
|
||||
Script: monitor.Script,
|
||||
Slug: monitor.Slug,
|
||||
WorkerGroups: workerGroupStrings,
|
||||
}
|
||||
|
||||
options := client.ScheduleOptions{
|
||||
ID: getScheduleId(monitor),
|
||||
Spec: client.ScheduleSpec{
|
||||
CronExpressions: []string{monitor.Schedule},
|
||||
Jitter: time.Second * 10,
|
||||
},
|
||||
Action: &client.ScheduleWorkflowAction{
|
||||
ID: getScheduleId(monitor),
|
||||
Workflow: workflows.NewWorkflows(nil).MonitorWorkflowDefinition,
|
||||
Args: args,
|
||||
TaskQueue: "default",
|
||||
RetryPolicy: &temporal.RetryPolicy{
|
||||
MaximumAttempts: 3,
|
||||
},
|
||||
Action: &client.ScheduleWorkflowAction{
|
||||
ID: getScheduleId(monitor, group.Slug),
|
||||
Workflow: workflows.NewWorkflows(nil).MonitorWorkflowDefinition,
|
||||
Args: args,
|
||||
TaskQueue: group.Slug,
|
||||
RetryPolicy: &temporal.RetryPolicy{
|
||||
MaximumAttempts: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor))
|
||||
|
||||
// If exists, we update
|
||||
_, err := schedule.Describe(ctx)
|
||||
if err == nil {
|
||||
err = schedule.Update(ctx, client.ScheduleUpdateOptions{
|
||||
DoUpdate: func(input client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
|
||||
return &client.ScheduleUpdate{
|
||||
Schedule: &client.Schedule{
|
||||
Spec: &options.Spec,
|
||||
Action: options.Action,
|
||||
Policy: input.Description.Schedule.Policy,
|
||||
State: input.Description.Schedule.State,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor, group.Slug))
|
||||
|
||||
// If exists, we update
|
||||
_, err := schedule.Describe(ctx)
|
||||
if err == nil {
|
||||
err = schedule.Update(ctx, client.ScheduleUpdateOptions{
|
||||
DoUpdate: func(input client.ScheduleUpdateInput) (*client.ScheduleUpdate, error) {
|
||||
return &client.ScheduleUpdate{
|
||||
Schedule: &client.Schedule{
|
||||
Spec: &options.Spec,
|
||||
Action: options.Action,
|
||||
Policy: input.Description.Schedule.Policy,
|
||||
State: input.Description.Schedule.State,
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
schedule, err = t.ScheduleClient().Create(ctx, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = schedule.Trigger(ctx, client.ScheduleTriggerOptions{})
|
||||
} else {
|
||||
schedule, err = t.ScheduleClient().Create(ctx, options)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
err = schedule.Trigger(ctx, client.ScheduleTriggerOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -5,9 +5,25 @@ import (
|
|||
|
||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"go.temporal.io/api/enums/v1"
|
||||
"go.temporal.io/sdk/client"
|
||||
"golang.org/x/exp/maps"
|
||||
)
|
||||
|
||||
func GetActiveWorkers(ctx context.Context, workerGroupSlug string, temporal client.Client) ([]string, error) {
|
||||
response, err := temporal.DescribeTaskQueue(ctx, workerGroupSlug, enums.TASK_QUEUE_TYPE_ACTIVITY)
|
||||
if err != nil {
|
||||
return make([]string, 0), err
|
||||
}
|
||||
|
||||
workers := make([]string, len(response.Pollers))
|
||||
for i, poller := range response.Pollers {
|
||||
workers[i] = poller.Identity
|
||||
}
|
||||
|
||||
return workers, nil
|
||||
}
|
||||
|
||||
func CreateWorkerGroup(ctx context.Context, db *sqlx.DB, workerGroup *models.WorkerGroup) error {
|
||||
_, err := db.NamedExecContext(ctx,
|
||||
"INSERT INTO worker_groups (slug, name) VALUES (:slug, :name)",
|
||||
|
@ -19,7 +35,7 @@ func CreateWorkerGroup(ctx context.Context, db *sqlx.DB, workerGroup *models.Wor
|
|||
func GetWorkerGroups(ctx context.Context, db *sqlx.DB) ([]*models.WorkerGroup, error) {
|
||||
var workerGroups []*models.WorkerGroup
|
||||
err := db.SelectContext(ctx, &workerGroups,
|
||||
"SELECT * FROM worker_groups WHERE deleted_at IS NULL",
|
||||
"SELECT * FROM worker_groups WHERE deleted_at IS NULL ORDER BY name",
|
||||
)
|
||||
return workerGroups, err
|
||||
}
|
||||
|
@ -38,6 +54,7 @@ FROM worker_groups
|
|||
LEFT OUTER JOIN monitor_worker_groups ON worker_groups.slug = monitor_worker_groups.worker_group_slug
|
||||
LEFT OUTER JOIN monitors ON monitor_worker_groups.monitor_slug = monitors.slug
|
||||
WHERE worker_groups.deleted_at IS NULL AND monitors.deleted_at IS NULL
|
||||
ORDER BY worker_groups.name
|
||||
`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package workflows
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/database/models"
|
||||
|
@ -9,41 +10,48 @@ import (
|
|||
)
|
||||
|
||||
type MonitorWorkflowParam struct {
|
||||
Script string
|
||||
Slug string
|
||||
Script string
|
||||
Slug string
|
||||
WorkerGroups []string
|
||||
}
|
||||
|
||||
func (w *Workflows) MonitorWorkflowDefinition(ctx workflow.Context, param MonitorWorkflowParam) error {
|
||||
options := workflow.ActivityOptions{
|
||||
StartToCloseTimeout: 10 * time.Second,
|
||||
}
|
||||
ctx = workflow.WithActivityOptions(ctx, options)
|
||||
workerGroups := param.WorkerGroups
|
||||
sort.Strings(workerGroups)
|
||||
|
||||
heatlcheckParam := activities.HealtcheckParam{
|
||||
Script: param.Script,
|
||||
}
|
||||
for _, workerGroup := range workerGroups {
|
||||
ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{
|
||||
StartToCloseTimeout: 60 * time.Second,
|
||||
TaskQueue: workerGroup,
|
||||
})
|
||||
|
||||
var monitorResult *activities.MonitorResult
|
||||
err := workflow.ExecuteActivity(ctx, w.activities.Monitor, heatlcheckParam).Get(ctx, &monitorResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
heatlcheckParam := activities.HealtcheckParam{
|
||||
Script: param.Script,
|
||||
}
|
||||
|
||||
status := models.MonitorFailure
|
||||
if monitorResult.Success {
|
||||
status = models.MonitorSuccess
|
||||
}
|
||||
var monitorResult *activities.MonitorResult
|
||||
err := workflow.ExecuteActivity(ctx, w.activities.Monitor, heatlcheckParam).Get(ctx, &monitorResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
historyParam := activities.HealtcheckAddToHistoryParam{
|
||||
Slug: param.Slug,
|
||||
Status: status,
|
||||
Note: monitorResult.Note,
|
||||
}
|
||||
status := models.MonitorFailure
|
||||
if monitorResult.Success {
|
||||
status = models.MonitorSuccess
|
||||
}
|
||||
|
||||
var historyResult *activities.MonitorAddToHistoryResult
|
||||
err = workflow.ExecuteActivity(ctx, w.activities.MonitorAddToHistory, historyParam).Get(ctx, &historyResult)
|
||||
if err != nil {
|
||||
return err
|
||||
historyParam := activities.HealtcheckAddToHistoryParam{
|
||||
Slug: param.Slug,
|
||||
Status: status,
|
||||
Note: monitorResult.Note,
|
||||
WorkerGroup: workerGroup,
|
||||
}
|
||||
|
||||
var historyResult *activities.MonitorAddToHistoryResult
|
||||
err = workflow.ExecuteActivity(ctx, w.activities.MonitorAddToHistory, historyParam).Get(ctx, &historyResult)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package api
|
||||
|
||||
type ApiV1MonitorsHistoryPOSTBody struct {
|
||||
Status string `json:"status"`
|
||||
Note string `json:"note"`
|
||||
Status string `json:"status"`
|
||||
Note string `json:"note"`
|
||||
WorkerGroup string `json:"worker_group"`
|
||||
}
|
||||
|
|
84
pkg/server/routes.go
Normal file
84
pkg/server/routes.go
Normal file
|
@ -0,0 +1,84 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/handlers"
|
||||
"code.tjo.space/mentos1386/zdravko/web/static"
|
||||
"github.com/jmoiron/sqlx"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
"go.temporal.io/sdk/client"
|
||||
)
|
||||
|
||||
func Routes(
|
||||
e *echo.Echo,
|
||||
db *sqlx.DB,
|
||||
temporalClient client.Client,
|
||||
cfg *config.ServerConfig,
|
||||
logger *slog.Logger,
|
||||
) {
|
||||
h := handlers.NewBaseHandler(db, temporalClient, cfg, logger)
|
||||
|
||||
// Health
|
||||
e.GET("/health", func(c echo.Context) error {
|
||||
err := db.Ping()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||
})
|
||||
|
||||
// Server static files
|
||||
stat := e.Group("/static")
|
||||
stat.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||
Filesystem: http.FS(static.Static),
|
||||
}))
|
||||
|
||||
// Public
|
||||
e.GET("", h.Index)
|
||||
e.GET("/incidents", h.Incidents)
|
||||
|
||||
// Settings
|
||||
settings := e.Group("/settings")
|
||||
settings.Use(h.Authenticated)
|
||||
settings.GET("", h.SettingsOverviewGET)
|
||||
settings.GET("/monitors", h.SettingsMonitorsGET)
|
||||
settings.GET("/monitors/create", h.SettingsMonitorsCreateGET)
|
||||
settings.POST("/monitors/create", h.SettingsMonitorsCreatePOST)
|
||||
settings.GET("/monitors/:slug", h.SettingsMonitorsDescribeGET)
|
||||
settings.POST("/monitors/:slug", h.SettingsMonitorsDescribePOST)
|
||||
settings.GET("/worker-groups", h.SettingsWorkerGroupsGET)
|
||||
settings.GET("/worker-groups/create", h.SettingsWorkerGroupsCreateGET)
|
||||
settings.POST("/worker-groups/create", h.SettingsWorkerGroupsCreatePOST)
|
||||
settings.GET("/worker-groups/:slug", h.SettingsWorkerGroupsDescribeGET)
|
||||
settings.Match([]string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"}, "/temporal*", h.Temporal)
|
||||
|
||||
// OAuth2
|
||||
oauth2 := e.Group("/oauth2")
|
||||
oauth2.GET("/login", h.OAuth2LoginGET)
|
||||
oauth2.GET("/callback", h.OAuth2CallbackGET)
|
||||
oauth2.GET("/logout", h.OAuth2LogoutGET, h.Authenticated)
|
||||
|
||||
// API
|
||||
apiv1 := e.Group("/api/v1")
|
||||
apiv1.Use(h.Authenticated)
|
||||
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
|
||||
apiv1.POST("/monitors/:slug/history", h.ApiV1MonitorsHistoryPOST)
|
||||
|
||||
// Error handler
|
||||
e.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
code := http.StatusInternalServerError
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
code = he.Code
|
||||
}
|
||||
|
||||
if code == http.StatusNotFound {
|
||||
_ = h.Error404(c)
|
||||
return
|
||||
}
|
||||
_ = c.String(code, err.Error())
|
||||
}
|
||||
}
|
|
@ -3,13 +3,10 @@ package server
|
|||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
|
||||
"code.tjo.space/mentos1386/zdravko/database"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/handlers"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/temporal"
|
||||
"code.tjo.space/mentos1386/zdravko/web/static"
|
||||
"code.tjo.space/mentos1386/zdravko/web/templates"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/labstack/echo/v4/middleware"
|
||||
|
@ -19,6 +16,8 @@ type Server struct {
|
|||
echo *echo.Echo
|
||||
cfg *config.ServerConfig
|
||||
logger *slog.Logger
|
||||
|
||||
worker *Worker
|
||||
}
|
||||
|
||||
func NewServer(cfg *config.ServerConfig) (*Server, error) {
|
||||
|
@ -34,10 +33,6 @@ func (s *Server) Name() string {
|
|||
}
|
||||
|
||||
func (s *Server) Start() error {
|
||||
s.echo.Renderer = templates.NewTemplates()
|
||||
//s.echo.Use(middleware.Logger())
|
||||
s.echo.Use(middleware.Recover())
|
||||
|
||||
db, err := database.ConnectToDatabase(s.logger, s.cfg.DatabasePath)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -48,72 +43,25 @@ func (s *Server) Start() error {
|
|||
return err
|
||||
}
|
||||
|
||||
h := handlers.NewBaseHandler(db, temporalClient, s.cfg, s.logger)
|
||||
s.worker = NewWorker(temporalClient, s.cfg)
|
||||
|
||||
// Health
|
||||
s.echo.GET("/health", func(c echo.Context) error {
|
||||
err = db.Ping()
|
||||
if err != nil {
|
||||
return err
|
||||
s.echo.Renderer = templates.NewTemplates()
|
||||
//s.echo.Use(middleware.Logger())
|
||||
s.echo.Use(middleware.Recover())
|
||||
Routes(s.echo, db, temporalClient, s.cfg, s.logger)
|
||||
|
||||
go func() {
|
||||
if err := s.worker.Start(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return c.JSON(http.StatusOK, map[string]string{"status": "ok"})
|
||||
})
|
||||
|
||||
// Server static files
|
||||
stat := s.echo.Group("/static")
|
||||
stat.Use(middleware.StaticWithConfig(middleware.StaticConfig{
|
||||
Filesystem: http.FS(static.Static),
|
||||
}))
|
||||
|
||||
// Public
|
||||
s.echo.GET("", h.Index)
|
||||
s.echo.GET("/incidents", h.Incidents)
|
||||
|
||||
// Settings
|
||||
settings := s.echo.Group("/settings")
|
||||
settings.Use(h.Authenticated)
|
||||
settings.GET("", h.SettingsOverviewGET)
|
||||
settings.GET("/monitors", h.SettingsMonitorsGET)
|
||||
settings.GET("/monitors/create", h.SettingsMonitorsCreateGET)
|
||||
settings.POST("/monitors/create", h.SettingsMonitorsCreatePOST)
|
||||
settings.GET("/monitors/:slug", h.SettingsMonitorsDescribeGET)
|
||||
settings.POST("/monitors/:slug", h.SettingsMonitorsDescribePOST)
|
||||
settings.GET("/worker-groups", h.SettingsWorkerGroupsGET)
|
||||
settings.GET("/worker-groups/create", h.SettingsWorkerGroupsCreateGET)
|
||||
settings.POST("/worker-groups/create", h.SettingsWorkerGroupsCreatePOST)
|
||||
settings.GET("/worker-groups/:slug", h.SettingsWorkerGroupsDescribeGET)
|
||||
settings.Match([]string{"GET", "HEAD", "POST", "PUT", "PATCH", "DELETE"}, "/temporal*", h.Temporal)
|
||||
|
||||
// OAuth2
|
||||
oauth2 := s.echo.Group("/oauth2")
|
||||
oauth2.GET("/login", h.OAuth2LoginGET)
|
||||
oauth2.GET("/callback", h.OAuth2CallbackGET)
|
||||
oauth2.GET("/logout", h.OAuth2LogoutGET, h.Authenticated)
|
||||
|
||||
// API
|
||||
apiv1 := s.echo.Group("/api/v1")
|
||||
apiv1.Use(h.Authenticated)
|
||||
apiv1.GET("/workers/connect", h.ApiV1WorkersConnectGET)
|
||||
apiv1.POST("/monitors/:slug/history", h.ApiV1MonitorsHistoryPOST)
|
||||
|
||||
// Error handler
|
||||
s.echo.HTTPErrorHandler = func(err error, c echo.Context) {
|
||||
code := http.StatusInternalServerError
|
||||
if he, ok := err.(*echo.HTTPError); ok {
|
||||
code = he.Code
|
||||
}
|
||||
|
||||
if code == http.StatusNotFound {
|
||||
_ = h.Error404(c)
|
||||
return
|
||||
}
|
||||
_ = c.String(code, err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
return s.echo.Start(":" + s.cfg.Port)
|
||||
}
|
||||
|
||||
func (s *Server) Stop() error {
|
||||
s.worker.Stop()
|
||||
|
||||
ctx := context.Background()
|
||||
return s.echo.Shutdown(ctx)
|
||||
}
|
||||
|
|
36
pkg/server/worker.go
Normal file
36
pkg/server/worker.go
Normal file
|
@ -0,0 +1,36 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"code.tjo.space/mentos1386/zdravko/internal/activities"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/config"
|
||||
"code.tjo.space/mentos1386/zdravko/internal/workflows"
|
||||
"go.temporal.io/sdk/client"
|
||||
"go.temporal.io/sdk/worker"
|
||||
)
|
||||
|
||||
type Worker struct {
|
||||
worker worker.Worker
|
||||
}
|
||||
|
||||
func NewWorker(temporalClient client.Client, cfg *config.ServerConfig) *Worker {
|
||||
w := worker.New(temporalClient, "default", worker.Options{})
|
||||
|
||||
workerActivities := activities.NewActivities(&config.WorkerConfig{})
|
||||
|
||||
workerWorkflows := workflows.NewWorkflows(workerActivities)
|
||||
|
||||
// Register Workflows
|
||||
w.RegisterWorkflow(workerWorkflows.MonitorWorkflowDefinition)
|
||||
|
||||
return &Worker{
|
||||
worker: w,
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Worker) Start() error {
|
||||
return w.worker.Run(worker.InterruptCh())
|
||||
}
|
||||
|
||||
func (w *Worker) Stop() {
|
||||
w.worker.Stop()
|
||||
}
|
|
@ -107,15 +107,24 @@ code {
|
|||
@apply sm:col-span-2;
|
||||
}
|
||||
|
||||
.settings section table {
|
||||
@apply w-full text-sm text-left rtl:text-right text-gray-500;
|
||||
}
|
||||
.settings section table caption {
|
||||
@apply p-5 text-lg font-semibold text-left rtl:text-right text-gray-900 bg-white;
|
||||
}
|
||||
.settings section table caption p {
|
||||
@apply mt-1 text-sm font-normal text-gray-500;
|
||||
}
|
||||
.settings section table thead {
|
||||
@apply text-xs text-gray-700 uppercase bg-gray-50;
|
||||
}
|
||||
.settings section table thead th {
|
||||
@apply px-6 py-3 text-center;
|
||||
@apply px-6 py-4 text-center text-xs font-semibold text-gray-600 uppercase tracking-wider;
|
||||
}
|
||||
.settings section table tbody th {
|
||||
.settings section table tbody tr th {
|
||||
@apply px-6 py-4 font-medium text-gray-900 whitespace-nowrap text-center;
|
||||
}
|
||||
.settings section table tbody tr {
|
||||
@apply px-6 py-4 text-center;
|
||||
.settings section table tbody tr td {
|
||||
@apply px-6 py-4 text-center whitespace-nowrap;
|
||||
}
|
||||
|
|
|
@ -725,10 +725,6 @@ video {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.min-w-full {
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.max-w-screen-lg {
|
||||
max-width: 1024px;
|
||||
}
|
||||
|
@ -819,8 +815,8 @@ video {
|
|||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.whitespace-nowrap {
|
||||
white-space: nowrap;
|
||||
.whitespace-normal {
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.rounded {
|
||||
|
@ -843,10 +839,6 @@ video {
|
|||
border-width: 1px;
|
||||
}
|
||||
|
||||
.border-b-2 {
|
||||
border-bottom-width: 2px;
|
||||
}
|
||||
|
||||
.border-gray-300 {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgb(209 213 219 / var(--tw-border-opacity));
|
||||
|
@ -948,11 +940,6 @@ video {
|
|||
padding-right: 1.25rem;
|
||||
}
|
||||
|
||||
.px-6 {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
}
|
||||
|
||||
.py-1 {
|
||||
padding-top: 0.25rem;
|
||||
padding-bottom: 0.25rem;
|
||||
|
@ -963,11 +950,6 @@ video {
|
|||
padding-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.py-4 {
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.py-8 {
|
||||
padding-top: 2rem;
|
||||
padding-bottom: 2rem;
|
||||
|
@ -977,10 +959,6 @@ video {
|
|||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
.text-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
|
@ -1000,11 +978,6 @@ video {
|
|||
line-height: 1.5rem;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
|
@ -1055,10 +1028,6 @@ video {
|
|||
letter-spacing: -0.025em;
|
||||
}
|
||||
|
||||
.tracking-wider {
|
||||
letter-spacing: 0.05em;
|
||||
}
|
||||
|
||||
.text-black {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
||||
|
@ -1084,11 +1053,6 @@ video {
|
|||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-600 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.text-gray-700 {
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(55 65 81 / var(--tw-text-opacity));
|
||||
|
@ -1509,6 +1473,44 @@ code {
|
|||
}
|
||||
}
|
||||
|
||||
.settings section table {
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.settings section table:where([dir="rtl"], [dir="rtl"] *) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.settings section table caption {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(255 255 255 / var(--tw-bg-opacity));
|
||||
padding: 1.25rem;
|
||||
text-align: left;
|
||||
font-size: 1.125rem;
|
||||
line-height: 1.75rem;
|
||||
font-weight: 600;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.settings section table caption:where([dir="rtl"], [dir="rtl"] *) {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.settings section table caption p {
|
||||
margin-top: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
line-height: 1.25rem;
|
||||
font-weight: 400;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(107 114 128 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.settings section table thead {
|
||||
--tw-bg-opacity: 1;
|
||||
background-color: rgb(249 250 251 / var(--tw-bg-opacity));
|
||||
|
@ -1522,12 +1524,19 @@ code {
|
|||
.settings section table thead th {
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
padding-top: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
text-align: center;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
--tw-text-opacity: 1;
|
||||
color: rgb(75 85 99 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.settings section table tbody th {
|
||||
.settings section table tbody tr th {
|
||||
white-space: nowrap;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
|
@ -1539,7 +1548,8 @@ code {
|
|||
color: rgb(17 24 39 / var(--tw-text-opacity));
|
||||
}
|
||||
|
||||
.settings section table tbody tr {
|
||||
.settings section table tbody tr td {
|
||||
white-space: nowrap;
|
||||
padding-left: 1.5rem;
|
||||
padding-right: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
|
@ -1686,7 +1696,3 @@ code {
|
|||
line-height: 2.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
.rtl\:text-right:where([dir="rtl"], [dir="rtl"] *) {
|
||||
text-align: right;
|
||||
}
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
</div>
|
||||
{{ else }}
|
||||
<section>
|
||||
<table class="w-full text-sm text-left rtl:text-right text-gray-500">
|
||||
<caption class="p-5 text-lg font-semibold text-left rtl:text-right text-gray-900 bg-white">
|
||||
<table>
|
||||
<caption>
|
||||
List of Monitors
|
||||
<div class="mt-1 gap-4 flex justify-between">
|
||||
<p class="mt-1 text-sm font-normal text-gray-500">
|
||||
<p>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<a href="/settings/monitors/create" class="inline-flex justify-center items-center py-1 px-2 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
||||
|
|
|
@ -27,36 +27,36 @@
|
|||
</section>
|
||||
|
||||
<section>
|
||||
<table class="min-w-full">
|
||||
<caption class="p-5 text-lg font-semibold text-left rtl:text-right text-gray-900 bg-white">
|
||||
<table>
|
||||
<caption>
|
||||
History
|
||||
<p class="mt-1 text-sm font-normal text-gray-500">
|
||||
<p>
|
||||
Last 10 executions of monitor script.
|
||||
</p>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Status</th>
|
||||
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Created At</th>
|
||||
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Duration</th>
|
||||
<th class="px-6 py-3 border-b-2 border-gray-300 text-left text-xs font-semibold text-gray-600 uppercase tracking-wider">Note</th>
|
||||
<th>Status</th>
|
||||
<th>Created At</th>
|
||||
<th>Duration</th>
|
||||
<th>Note</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .History}}
|
||||
<tr>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<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 class="px-6 py-4 whitespace-nowrap">
|
||||
<td>
|
||||
{{ .CreatedAt.Format "2006-01-02 15:04:05" }}
|
||||
</td>
|
||||
<td class="px-6 py-4 whitespace-nowrap">
|
||||
<td>
|
||||
{ .Duration }
|
||||
</td>
|
||||
<td class="px-6 py-4">
|
||||
<td class="whitespace-normal">
|
||||
{{ .Note }}
|
||||
</td>
|
||||
</tr>
|
||||
|
|
|
@ -19,11 +19,11 @@
|
|||
</div>
|
||||
{{ else }}
|
||||
<section>
|
||||
<table class="w-full text-sm text-left rtl:text-right text-gray-500">
|
||||
<caption class="p-5 text-lg font-semibold text-left rtl:text-right text-gray-900 bg-white">
|
||||
<table>
|
||||
<caption>
|
||||
List of Worker Groups
|
||||
<div class="mt-1 gap-4 flex justify-between">
|
||||
<p class="mt-1 text-sm font-normal text-gray-500">
|
||||
<p>
|
||||
{{ $description }}
|
||||
</p>
|
||||
<a href="/settings/worker-groups/create" class="inline-flex justify-center items-center py-1 px-2 text-sm font-medium text-center text-white rounded-lg bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:ring-blue-300">
|
||||
|
@ -32,7 +32,7 @@
|
|||
</a>
|
||||
</div>
|
||||
</caption>
|
||||
<thead class="text-xs text-gray-700 uppercase bg-gray-50">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
Name
|
||||
|
@ -55,12 +55,15 @@
|
|||
{{.Name}}
|
||||
</th>
|
||||
<td>
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
10 ONLINE
|
||||
</span>
|
||||
{{ if eq ( len .ActiveWorkers) 0 }}
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-gray-100 text-gray-800">
|
||||
NONE
|
||||
</span>
|
||||
{{ else }}
|
||||
<span class="px-2 inline-flex text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
|
||||
{{ len .ActiveWorkers }} ONLINE
|
||||
</span>
|
||||
{{ end }}
|
||||
</td>
|
||||
<td>
|
||||
{{ len .Monitors }}
|
||||
|
|
|
@ -16,6 +16,31 @@
|
|||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<table>
|
||||
<caption>
|
||||
Active Workers
|
||||
<p >
|
||||
Current workers that were online in last minutes.
|
||||
</p>
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Identity</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{{range .Worker.ActiveWorkers }}
|
||||
<tr>
|
||||
<td>
|
||||
{{ . }}
|
||||
</td>
|
||||
</tr>
|
||||
{{end}}
|
||||
</tbody>
|
||||
</table>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
const copyTokenButton = document.getElementById('copy-token');
|
||||
|
||||
|
|
Loading…
Reference in a new issue