mirror of
https://github.com/mentos1386/zdravko.git
synced 2024-11-25 00:58:07 +00:00
feat(monitors,workergroups): design for deletion and stauts
This commit is contained in:
parent
306f583418
commit
d9dafd766a
8 changed files with 236 additions and 52 deletions
|
@ -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],
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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}}
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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');
|
||||
|
|
Loading…
Reference in a new issue