diff --git a/internal/handlers/settingsmonitors.go b/internal/handlers/settingsmonitors.go index f1910c6..028c19a 100644 --- a/internal/handlers/settingsmonitors.go +++ b/internal/handlers/settingsmonitors.go @@ -28,15 +28,20 @@ type UpdateMonitor struct { Script string `validate:"required"` } +type MonitorWithWorkerGroupsAndStatus struct { + *models.MonitorWithWorkerGroups + Status services.MonitorStatus +} + type SettingsMonitors struct { *Settings - Monitors []*models.MonitorWithWorkerGroups + Monitors []*MonitorWithWorkerGroupsAndStatus MonitorsLength int } type SettingsMonitor struct { *Settings - Monitor *models.MonitorWithWorkerGroups + Monitor *MonitorWithWorkerGroupsAndStatus History []*models.MonitorHistory } @@ -48,14 +53,26 @@ func (h *BaseHandler) SettingsMonitorsGET(c echo.Context) error { return err } + monitorsWithStatus := make([]*MonitorWithWorkerGroupsAndStatus, len(monitors)) + for i, monitor := range monitors { + status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Slug) + if err != nil { + return err + } + monitorsWithStatus[i] = &MonitorWithWorkerGroupsAndStatus{ + MonitorWithWorkerGroups: monitor, + Status: status, + } + } + return c.Render(http.StatusOK, "settings_monitors.tmpl", &SettingsMonitors{ Settings: NewSettings( cc.Principal.User, GetPageByTitle(SettingsPages, "Monitors"), []*components.Page{GetPageByTitle(SettingsPages, "Monitors")}, ), - Monitors: monitors, - MonitorsLength: len(monitors), + Monitors: monitorsWithStatus, + MonitorsLength: len(monitorsWithStatus), }) } @@ -69,6 +86,16 @@ func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error { return err } + status, err := services.GetMonitorStatus(context.Background(), h.temporal, monitor.Slug) + if err != nil { + return err + } + + monitorWithStatus := &MonitorWithWorkerGroupsAndStatus{ + MonitorWithWorkerGroups: monitor, + Status: status, + } + history, err := services.GetMonitorHistoryForMonitor(context.Background(), h.db, slug) if err != nil { return err @@ -91,7 +118,7 @@ func (h *BaseHandler) SettingsMonitorsDescribeGET(c echo.Context) error { Breadcrumb: monitor.Name, }, }), - Monitor: monitor, + Monitor: monitorWithStatus, History: history[:maxElements], }) } diff --git a/internal/services/monitor.go b/internal/services/monitor.go index 4407aba..b17856c 100644 --- a/internal/services/monitor.go +++ b/internal/services/monitor.go @@ -13,8 +13,31 @@ import ( "golang.org/x/exp/maps" ) -func getScheduleId(monitor *models.Monitor) string { - return "monitor-" + monitor.Slug +type MonitorStatus string + +const ( + MonitorStatusUnknown MonitorStatus = "UNKNOWN" + MonitorStatusPaused MonitorStatus = "PAUSED" + MonitorStatusActive MonitorStatus = "ACTIVE" +) + +func getScheduleId(slug string) string { + return "monitor-" + slug +} + +func GetMonitorStatus(ctx context.Context, temporal client.Client, slug string) (MonitorStatus, error) { + schedule := temporal.ScheduleClient().GetHandle(ctx, getScheduleId(slug)) + + description, err := schedule.Describe(ctx) + if err != nil { + return MonitorStatusUnknown, err + } + + if description.Schedule.State.Paused { + return MonitorStatusPaused, nil + } + + return MonitorStatusActive, nil } func CreateMonitor(ctx context.Context, db *sqlx.DB, monitor *models.Monitor) error { @@ -202,13 +225,13 @@ func CreateOrUpdateMonitorSchedule( } options := client.ScheduleOptions{ - ID: getScheduleId(monitor), + ID: getScheduleId(monitor.Slug), Spec: client.ScheduleSpec{ CronExpressions: []string{monitor.Schedule}, Jitter: time.Second * 10, }, Action: &client.ScheduleWorkflowAction{ - ID: getScheduleId(monitor), + ID: getScheduleId(monitor.Slug), Workflow: workflows.NewWorkflows(nil).MonitorWorkflowDefinition, Args: args, TaskQueue: "default", @@ -218,7 +241,7 @@ func CreateOrUpdateMonitorSchedule( }, } - schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor)) + schedule := t.ScheduleClient().GetHandle(ctx, getScheduleId(monitor.Slug)) // If exists, we update _, err := schedule.Describe(ctx) diff --git a/web/static/css/main.css b/web/static/css/main.css index 5481903..5d1d201 100644 --- a/web/static/css/main.css +++ b/web/static/css/main.css @@ -80,14 +80,11 @@ code { @apply grid grid-cols-1 gap-5 grid-rows-[min-content] h-fit; } .settings section { - @apply relative overflow-x-auto shadow-md sm:rounded-lg text-gray-500 bg-white; + @apply relative overflow-x-auto shadow-md sm:rounded-lg text-gray-500 bg-white h-min; } .settings section h2 { - @apply flex flex-col text-lg font-semibold text-gray-900; -} -.settings section h2 span { - @apply text-sm font-medium text-gray-500; + @apply text-lg font-semibold text-gray-900; } .settings section form { diff --git a/web/static/css/tailwind.css b/web/static/css/tailwind.css index b9c5dad..52895c0 100644 --- a/web/static/css/tailwind.css +++ b/web/static/css/tailwind.css @@ -705,6 +705,16 @@ video { height: 24rem; } +.h-fit { + height: -moz-fit-content; + height: fit-content; +} + +.h-min { + height: -moz-min-content; + height: min-content; +} + .w-20 { width: 5rem; } @@ -721,6 +731,11 @@ video { width: 1.25rem; } +.w-fit { + width: -moz-fit-content; + width: fit-content; +} + .w-full { width: 100%; } @@ -737,6 +752,10 @@ video { max-width: 1280px; } +.flex-1 { + flex: 1 1 0%; +} + .flex-auto { flex: 1 1 auto; } @@ -753,6 +772,10 @@ video { grid-template-columns: auto min-content; } +.flex-row { + flex-direction: row; +} + .flex-col { flex-direction: column; } @@ -791,6 +814,10 @@ video { margin-bottom: calc(1rem * var(--tw-space-y-reverse)); } +.self-center { + align-self: center; +} + .justify-self-start { justify-self: start; } @@ -839,11 +866,25 @@ video { border-width: 1px; } +.border-4 { + border-width: 4px; +} + +.border-gray-200 { + --tw-border-opacity: 1; + border-color: rgb(229 231 235 / var(--tw-border-opacity)); +} + .border-gray-300 { --tw-border-opacity: 1; border-color: rgb(209 213 219 / var(--tw-border-opacity)); } +.border-red-300 { + --tw-border-opacity: 1; + border-color: rgb(252 165 165 / var(--tw-border-opacity)); +} + .bg-blue-100 { --tw-bg-opacity: 1; background-color: rgb(219 234 254 / var(--tw-bg-opacity)); @@ -899,6 +940,11 @@ video { background-color: rgb(248 113 113 / var(--tw-bg-opacity)); } +.bg-red-700 { + --tw-bg-opacity: 1; + background-color: rgb(185 28 28 / var(--tw-bg-opacity)); +} + .bg-white { --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -945,6 +991,16 @@ video { padding-bottom: 0.25rem; } +.py-2 { + padding-top: 0.5rem; + padding-bottom: 0.5rem; +} + +.py-2\.5 { + padding-top: 0.625rem; + padding-bottom: 0.625rem; +} + .py-3 { padding-top: 0.75rem; padding-bottom: 0.75rem; @@ -1342,6 +1398,8 @@ code { .settings section { position: relative; + height: -moz-min-content; + height: min-content; overflow-x: auto; --tw-bg-opacity: 1; background-color: rgb(255 255 255 / var(--tw-bg-opacity)); @@ -1359,8 +1417,6 @@ code { } .settings section h2 { - display: flex; - flex-direction: column; font-size: 1.125rem; line-height: 1.75rem; font-weight: 600; @@ -1368,14 +1424,6 @@ code { color: rgb(17 24 39 / var(--tw-text-opacity)); } -.settings section h2 span { - font-size: 0.875rem; - line-height: 1.25rem; - font-weight: 500; - --tw-text-opacity: 1; - color: rgb(107 114 128 / var(--tw-text-opacity)); -} - .settings section form { display: grid; grid-template-columns: repeat(1, minmax(0, 1fr)); @@ -1562,6 +1610,11 @@ code { background-color: rgb(30 64 175 / var(--tw-bg-opacity)); } +.hover\:bg-gray-100:hover { + --tw-bg-opacity: 1; + background-color: rgb(243 244 246 / var(--tw-bg-opacity)); +} + .hover\:bg-gray-500:hover { --tw-bg-opacity: 1; background-color: rgb(107 114 128 / var(--tw-bg-opacity)); @@ -1577,15 +1630,29 @@ code { background-color: rgb(239 68 68 / var(--tw-bg-opacity)); } +.hover\:bg-red-800:hover { + --tw-bg-opacity: 1; + background-color: rgb(153 27 27 / var(--tw-bg-opacity)); +} + .hover\:text-blue-600:hover { --tw-text-opacity: 1; color: rgb(37 99 235 / var(--tw-text-opacity)); } +.hover\:text-blue-700:hover { + --tw-text-opacity: 1; + color: rgb(29 78 216 / var(--tw-text-opacity)); +} + .hover\:underline:hover { text-decoration-line: underline; } +.focus\:z-10:focus { + z-index: 10; +} + .focus\:outline-none:focus { outline: 2px solid transparent; outline-offset: 2px; @@ -1602,6 +1669,16 @@ code { --tw-ring-color: rgb(147 197 253 / var(--tw-ring-opacity)); } +.focus\:ring-gray-100:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(243 244 246 / var(--tw-ring-opacity)); +} + +.focus\:ring-red-300:focus { + --tw-ring-opacity: 1; + --tw-ring-color: rgb(252 165 165 / var(--tw-ring-opacity)); +} + @media (min-width: 640px) { .sm\:ml-2 { margin-left: 0.5rem; @@ -1648,6 +1725,10 @@ code { } @media (min-width: 768px) { + .md\:flex-row { + flex-direction: row; + } + .md\:px-40 { padding-left: 10rem; padding-right: 10rem; diff --git a/web/templates/pages/settings_monitors.tmpl b/web/templates/pages/settings_monitors.tmpl index 50d836b..104ec05 100644 --- a/web/templates/pages/settings_monitors.tmpl +++ b/web/templates/pages/settings_monitors.tmpl @@ -65,12 +65,19 @@ {{end}}
Worker groups are used to distribute the monitor to specific workers.
- +Schedule is a cron expression that defines when the monitor should be executed. @@ -41,8 +41,6 @@ export const options = { // http errors should be less than 1% http_req_failed: ['rate<0.01'], }, - vus: 10, - duration: '5s', }; export default function () { diff --git a/web/templates/pages/settings_monitors_describe.tmpl b/web/templates/pages/settings_monitors_describe.tmpl index 93eeb27..4c5a826 100644 --- a/web/templates/pages/settings_monitors_describe.tmpl +++ b/web/templates/pages/settings_monitors_describe.tmpl @@ -26,6 +26,34 @@ +
Pausing the monitor will stop it from executing. This can be useful in cases of expected downtime.
+ Pause + Resume +Permanently delete this monitor.
+ Delete +Identity | -
---|
- {{ . }} - | -
No workers online for this worker group. |
---|
Identity | +
{{ . }} | +
Permanently delete this worker group. Workers will not be able to connect anymore.
+ Delete +