feat(monitors,workergroups): design for deletion and stauts

This commit is contained in:
Tine 2024-02-28 12:06:24 +01:00
parent 306f583418
commit d9dafd766a
Signed by: mentos1386
SSH key fingerprint: SHA256:MNtTsLbihYaWF8j1fkOHfkKNlnN1JQfxEU/rBU8nCGw
8 changed files with 236 additions and 52 deletions

View file

@ -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],
})
}

View file

@ -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)

View file

@ -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 {

View file

@ -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;

View file

@ -65,12 +65,19 @@
{{end}}
</td>
<td>
{{ if eq .Status "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 .Status "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}}

View file

@ -8,7 +8,7 @@
<input type="text" name="workergroups" id="workergroups" placeholder="NA EU"/>
<p>Worker groups are used to distribute the monitor to specific workers.</p>
<label for="schedule">Schedule</label>
<input type="text" name="schedule" id="schedule" placeholder="@every 1m"/>
<input type="text" name="schedule" id="schedule" placeholder="@every 1m" value="@every 1m"/>
<p>
Schedule is a cron expression that defines when the monitor should be executed.
</br>
@ -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 () {

View file

@ -26,6 +26,34 @@
</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">
Status
{{ if eq .Monitor.Status "ACTIVE" }}
<span class="self-center h-fit w-fit px-2 text-xs leading-5 font-semibold rounded-full bg-green-100 text-green-800">
ACTIVE
</span>
{{ else if eq .Monitor.Status "PAUSED" }}
<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 monitor will stop it from executing. This can be useful in cases of expected downtime.</p>
<a class="block text-center py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100" href="/settings/monitors/{{ .Monitor.Slug }}/enable">Pause</a>
<a class="block text-center py-2.5 px-5 me-2 mb-2 text-sm font-medium text-gray-900 focus:outline-none bg-white rounded-lg border border-gray-200 hover:bg-gray-100 hover:text-blue-700 focus:z-10 focus:ring-4 focus:ring-gray-100" href="/settings/monitors/{{ .Monitor.Slug }}/disable">Resume</a>
</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 monitor.</p>
<a class="block text-center focus:outline-none text-white bg-red-700 hover:bg-red-800 focus:ring-4 focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 me-2 mb-2" href="/settings/monitors/{{ .Monitor.Slug }}/delete">Delete</a>
</section>
</div>
<section>
<table>
<caption>

View file

@ -16,30 +16,53 @@
</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>
<div class="flex md:flex-row flex-col gap-4 h-min">
<section class="flex-1">
<table>
<caption>
<span>
Active Workers
{{ if eq ( len .Worker.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 .Worker.ActiveWorkers }} ONLINE
</span>
{{ end }}
</span>
<p>
Current workers that were online in last minutes.
</p>
</caption>
{{ if eq ( len .Worker.ActiveWorkers) 0 }}
<thead><tr><th>No workers online for this worker group.</th></tr></thead>
{{ else }}
<thead>
<tr>
<th>Identity</th>
</tr>
</thead>
<tbody>
{{range .Worker.ActiveWorkers }}
<tr>
<th>{{ . }}</th>
</tr>
{{end}}
</tbody>
{{end}}
</table>
</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 worker group. Workers will not be able to connect anymore.</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/worker-groups/{{ .Worker.Slug }}/delete">Delete</a>
</section>
</div>
<script>
const copyTokenButton = document.getElementById('copy-token');